diff --git a/src/emit/mod.rs b/src/emit/mod.rs index a3c503c..aa3e6c0 100644 --- a/src/emit/mod.rs +++ b/src/emit/mod.rs @@ -3,6 +3,7 @@ use std::collections::BTreeMap; use crate::ast::TemplateAstExpr; use crate::input::NomoInput; use crate::parser::TemplateToken; +use crate::parser::TokenOperator; use crate::value::NomoValue; pub struct EmitMachine { @@ -95,6 +96,12 @@ pub enum Instruction { value: NomoValue, slot: VariableSlot, }, + MathOperate { + op: TokenOperator, + left_slot: VariableSlot, + right_slot: VariableSlot, + result_slot: VariableSlot, + }, } #[derive(Debug, Clone)] @@ -416,7 +423,7 @@ fn emit_ast_expr( } fn emit_expr_load( - _machine: &mut EmitMachine, + machine: &mut EmitMachine, eval: &mut Vec, emit_slot: VariableSlot, expression: &TemplateAstExpr<'_>, @@ -435,6 +442,19 @@ fn emit_expr_load( slot: emit_slot, }); } + TemplateAstExpr::Operation { op, lhs, rhs } => { + let left_slot = machine.reserve_slot(); + emit_expr_load(machine, eval, left_slot, lhs); + let right_slot = machine.reserve_slot(); + emit_expr_load(machine, eval, right_slot, rhs); + + eval.push(Instruction::MathOperate { + op: *op, + left_slot, + right_slot, + result_slot: emit_slot, + }); + } TemplateAstExpr::Invalid { .. } => eval.push(Instruction::Abort), TemplateAstExpr::StaticContent { .. } | TemplateAstExpr::Interpolation { .. } => { unreachable!("Invalid AST here") @@ -446,7 +466,6 @@ fn emit_expr_load( TemplateAstExpr::ForChain { .. } => todo!(), TemplateAstExpr::For { .. } => todo!(), TemplateAstExpr::ForElse => todo!(), - TemplateAstExpr::Operation { .. } => todo!(), TemplateAstExpr::IfConditional { .. } => todo!(), TemplateAstExpr::ConditionalContent { .. } => todo!(), diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 5080224..590edd8 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -96,8 +96,9 @@ pub fn execute(vm: &VMInstructions, global_context: &Context) -> Result { - let value = scopes.get(slot).as_str().unwrap(); - output.push_str(value); + let value = scopes.get(slot).try_to_string().unwrap(); + + output.push_str(&value); } Instruction::PushScope { inherit_parent: _ } => { scopes.push_scope(); @@ -186,6 +187,26 @@ pub fn execute(vm: &VMInstructions, global_context: &Context) -> Result { scopes.insert_into_slot(*slot, value.clone()); } + Instruction::MathOperate { + op, + left_slot, + right_slot, + result_slot, + } => { + let left_value = scopes.get(left_slot); + let right_value = scopes.get(right_slot); + + let result = match op { + crate::parser::TokenOperator::Plus => left_value.try_add(right_value), + crate::parser::TokenOperator::Minus => left_value.try_sub(right_value), + crate::parser::TokenOperator::Times => left_value.try_mul(right_value), + crate::parser::TokenOperator::Divide => left_value.try_div(right_value), + crate::parser::TokenOperator::And => left_value.try_and(right_value), + crate::parser::TokenOperator::Or => left_value.try_or(right_value), + }; + + scopes.insert_into_slot(*result_slot, result.unwrap()); + } } ip += 1; diff --git a/src/value.rs b/src/value.rs index 1dc86e0..7aea85a 100644 --- a/src/value.rs +++ b/src/value.rs @@ -97,6 +97,147 @@ impl NomoValue { None } } + + pub(crate) fn try_add(&self, right_value: &NomoValue) -> Option { + match (self, right_value) { + (NomoValue::Integer { value: lval }, NomoValue::Integer { value: rval }) => { + Some(NomoValue::Integer { + value: *lval + *rval, + }) + } + ( + NomoValue::SignedInteger { value: lval }, + NomoValue::SignedInteger { value: rval }, + ) => Some(NomoValue::SignedInteger { + value: *lval + *rval, + }), + (NomoValue::Integer { value: val }, NomoValue::SignedInteger { value: sval }) + | (NomoValue::SignedInteger { value: sval }, NomoValue::Integer { value: val }) => { + Some(NomoValue::SignedInteger { + value: (i64::try_from(*val).ok()?) + *sval, + }) + } + (NomoValue::String { value: rstr }, NomoValue::String { value: lstr }) => { + Some(NomoValue::String { + value: Cow::Owned(format!("{rstr}{lstr}")), + }) + } + _ => None, + } + } + + pub(crate) fn try_sub(&self, right_value: &NomoValue) -> Option { + macro_rules! op { + ($left:expr, $right:expr) => {{ ($left) - ($right) }}; + } + match (self, right_value) { + (NomoValue::Integer { value: lval }, NomoValue::Integer { value: rval }) => { + Some(NomoValue::Integer { + value: op!(*lval, *rval), + }) + } + ( + NomoValue::SignedInteger { value: lval }, + NomoValue::SignedInteger { value: rval }, + ) => Some(NomoValue::SignedInteger { + value: op!(*lval, *rval), + }), + (NomoValue::Integer { value: val }, NomoValue::SignedInteger { value: sval }) + | (NomoValue::SignedInteger { value: sval }, NomoValue::Integer { value: val }) => { + Some(NomoValue::SignedInteger { + value: op!(i64::try_from(*val).ok()?, *sval), + }) + } + _ => None, + } + } + + pub(crate) fn try_mul(&self, right_value: &NomoValue) -> Option { + macro_rules! op { + ($left:expr, $right:expr) => {{ ($left) * ($right) }}; + } + match (self, right_value) { + (NomoValue::Integer { value: lval }, NomoValue::Integer { value: rval }) => { + Some(NomoValue::Integer { + value: op!(*lval, *rval), + }) + } + ( + NomoValue::SignedInteger { value: lval }, + NomoValue::SignedInteger { value: rval }, + ) => Some(NomoValue::SignedInteger { + value: op!(*lval, *rval), + }), + (NomoValue::Integer { value: val }, NomoValue::SignedInteger { value: sval }) + | (NomoValue::SignedInteger { value: sval }, NomoValue::Integer { value: val }) => { + Some(NomoValue::SignedInteger { + value: op!(i64::try_from(*val).ok()?, *sval), + }) + } + _ => None, + } + } + + pub(crate) fn try_div(&self, right_value: &NomoValue) -> Option { + macro_rules! op { + ($left:expr, $right:expr) => {{ ($left) / ($right) }}; + } + match (self, right_value) { + (NomoValue::Integer { value: lval }, NomoValue::Integer { value: rval }) => { + Some(NomoValue::Integer { + value: op!(*lval, *rval), + }) + } + ( + NomoValue::SignedInteger { value: lval }, + NomoValue::SignedInteger { value: rval }, + ) => Some(NomoValue::SignedInteger { + value: op!(*lval, *rval), + }), + (NomoValue::Integer { value: val }, NomoValue::SignedInteger { value: sval }) + | (NomoValue::SignedInteger { value: sval }, NomoValue::Integer { value: val }) => { + Some(NomoValue::SignedInteger { + value: op!(i64::try_from(*val).ok()?, *sval), + }) + } + _ => None, + } + } + + pub(crate) fn try_and(&self, right_value: &NomoValue) -> Option { + match (self, right_value) { + (NomoValue::Bool { value: lval }, NomoValue::Bool { value: rval }) => { + Some(NomoValue::Bool { + value: *lval && *rval, + }) + } + _ => None, + } + } + + pub(crate) fn try_or(&self, right_value: &NomoValue) -> Option { + match (self, right_value) { + (NomoValue::Bool { value: lval }, NomoValue::Bool { value: rval }) => { + Some(NomoValue::Bool { + value: *lval || *rval, + }) + } + _ => None, + } + } + + pub(crate) fn try_to_string(&self) -> Option { + match self { + NomoValue::String { value } => Some(value.to_string()), + NomoValue::Array { .. } => None, + NomoValue::Bool { value } => Some(value.to_string()), + NomoValue::Object { .. } => None, + NomoValue::Integer { value } => Some(value.to_string()), + NomoValue::SignedInteger { value } => Some(value.to_string()), + NomoValue::Float { value } => Some(value.to_string()), + NomoValue::Iterator { .. } => None, + } + } } pub trait CloneIterator: Iterator { diff --git a/tests/cases/1-parsed@maths.snap b/tests/cases/1-parsed@maths.snap new file mode 100644 index 0000000..837a801 --- /dev/null +++ b/tests/cases/1-parsed@maths.snap @@ -0,0 +1,56 @@ +--- +source: tests/file_tests.rs +expression: parsed +info: + input: "{{= 5 * 3 }}\n{{= 2 * 3 + 4 * 3 }}\n{{= 3 / 3 + 3 }}" + context: {} +input_file: tests/cases/maths.nomo +--- +ParsedTemplate { + tokens: [ + [LeftDelim]"{{" (0..2), + [WantsOutput]"=" (2..3), + [Whitespace]" " (3..4), + [Literal(Integer(5))]"5" (4..5), + [Whitespace]" " (5..6), + [Operator(Times)]"*" (6..7), + [Whitespace]" " (7..8), + [Literal(Integer(3))]"3" (8..9), + [Whitespace]" " (9..10), + [RightDelim]"}}" (10..12), + [Whitespace]"\n" (12..13), + [LeftDelim]"{{" (13..15), + [WantsOutput]"=" (15..16), + [Whitespace]" " (16..17), + [Literal(Integer(2))]"2" (17..18), + [Whitespace]" " (18..19), + [Operator(Times)]"*" (19..20), + [Whitespace]" " (20..21), + [Literal(Integer(3))]"3" (21..22), + [Whitespace]" " (22..23), + [Operator(Plus)]"+" (23..24), + [Whitespace]" " (24..25), + [Literal(Integer(4))]"4" (25..26), + [Whitespace]" " (26..27), + [Operator(Times)]"*" (27..28), + [Whitespace]" " (28..29), + [Literal(Integer(3))]"3" (29..30), + [Whitespace]" " (30..31), + [RightDelim]"}}" (31..33), + [Whitespace]"\n" (33..34), + [LeftDelim]"{{" (34..36), + [WantsOutput]"=" (36..37), + [Whitespace]" " (37..38), + [Literal(Integer(3))]"3" (38..39), + [Whitespace]" " (39..40), + [Operator(Divide)]"/" (40..41), + [Whitespace]" " (41..42), + [Literal(Integer(3))]"3" (42..43), + [Whitespace]" " (43..44), + [Operator(Plus)]"+" (44..45), + [Whitespace]" " (45..46), + [Literal(Integer(3))]"3" (46..47), + [Whitespace]" " (47..48), + [RightDelim]"}}" (48..50), + ], +} diff --git a/tests/cases/2-ast@maths.snap b/tests/cases/2-ast@maths.snap new file mode 100644 index 0000000..6aa9130 --- /dev/null +++ b/tests/cases/2-ast@maths.snap @@ -0,0 +1,100 @@ +--- +source: tests/file_tests.rs +expression: ast +info: + input: "{{= 5 * 3 }}\n{{= 2 * 3 + 4 * 3 }}\n{{= 3 / 3 + 3 }}" + context: {} +input_file: tests/cases/maths.nomo +--- +TemplateAst { + root: [ + Interpolation { + prev_whitespace_content: None, + expression: Operation { + op: Times, + lhs: Literal { + source: [Literal(Integer(5))]"5" (4..5), + value: Integer { + value: 5, + }, + }, + rhs: Literal { + source: [Literal(Integer(3))]"3" (8..9), + value: Integer { + value: 3, + }, + }, + }, + post_whitespace_content: Some( + [Whitespace]"\n" (12..13), + ), + }, + Interpolation { + prev_whitespace_content: None, + expression: Operation { + op: Plus, + lhs: Operation { + op: Times, + lhs: Literal { + source: [Literal(Integer(2))]"2" (17..18), + value: Integer { + value: 2, + }, + }, + rhs: Literal { + source: [Literal(Integer(3))]"3" (21..22), + value: Integer { + value: 3, + }, + }, + }, + rhs: Operation { + op: Times, + lhs: Literal { + source: [Literal(Integer(4))]"4" (25..26), + value: Integer { + value: 4, + }, + }, + rhs: Literal { + source: [Literal(Integer(3))]"3" (29..30), + value: Integer { + value: 3, + }, + }, + }, + }, + post_whitespace_content: Some( + [Whitespace]"\n" (33..34), + ), + }, + Interpolation { + prev_whitespace_content: None, + expression: Operation { + op: Plus, + lhs: Operation { + op: Divide, + lhs: Literal { + source: [Literal(Integer(3))]"3" (38..39), + value: Integer { + value: 3, + }, + }, + rhs: Literal { + source: [Literal(Integer(3))]"3" (42..43), + value: Integer { + value: 3, + }, + }, + }, + rhs: Literal { + source: [Literal(Integer(3))]"3" (46..47), + value: Integer { + value: 3, + }, + }, + }, + post_whitespace_content: None, + }, + ], +} diff --git a/tests/cases/3-instructions@maths.snap b/tests/cases/3-instructions@maths.snap new file mode 100644 index 0000000..d6a4920 --- /dev/null +++ b/tests/cases/3-instructions@maths.snap @@ -0,0 +1,187 @@ +--- +source: tests/file_tests.rs +expression: emit +info: + input: "{{= 5 * 3 }}\n{{= 2 * 3 + 4 * 3 }}\n{{= 3 / 3 + 3 }}" + context: {} +input_file: tests/cases/maths.nomo +--- +VMInstructions { + labels: {}, + instructions: [ + LoadLiteralToSlot { + source: [Literal(Integer(5))]"5" (4..5), + value: Integer { + value: 5, + }, + slot: VariableSlot { + index: 1, + }, + }, + LoadLiteralToSlot { + source: [Literal(Integer(3))]"3" (8..9), + value: Integer { + value: 3, + }, + slot: VariableSlot { + index: 2, + }, + }, + MathOperate { + op: Times, + left_slot: VariableSlot { + index: 1, + }, + right_slot: VariableSlot { + index: 2, + }, + result_slot: VariableSlot { + index: 0, + }, + }, + EmitFromSlot { + slot: VariableSlot { + index: 0, + }, + }, + AppendContent { + content: "\n" (12..13), + }, + LoadLiteralToSlot { + source: [Literal(Integer(2))]"2" (17..18), + value: Integer { + value: 2, + }, + slot: VariableSlot { + index: 5, + }, + }, + LoadLiteralToSlot { + source: [Literal(Integer(3))]"3" (21..22), + value: Integer { + value: 3, + }, + slot: VariableSlot { + index: 6, + }, + }, + MathOperate { + op: Times, + left_slot: VariableSlot { + index: 5, + }, + right_slot: VariableSlot { + index: 6, + }, + result_slot: VariableSlot { + index: 4, + }, + }, + LoadLiteralToSlot { + source: [Literal(Integer(4))]"4" (25..26), + value: Integer { + value: 4, + }, + slot: VariableSlot { + index: 8, + }, + }, + LoadLiteralToSlot { + source: [Literal(Integer(3))]"3" (29..30), + value: Integer { + value: 3, + }, + slot: VariableSlot { + index: 9, + }, + }, + MathOperate { + op: Times, + left_slot: VariableSlot { + index: 8, + }, + right_slot: VariableSlot { + index: 9, + }, + result_slot: VariableSlot { + index: 7, + }, + }, + MathOperate { + op: Plus, + left_slot: VariableSlot { + index: 4, + }, + right_slot: VariableSlot { + index: 7, + }, + result_slot: VariableSlot { + index: 3, + }, + }, + EmitFromSlot { + slot: VariableSlot { + index: 3, + }, + }, + AppendContent { + content: "\n" (33..34), + }, + LoadLiteralToSlot { + source: [Literal(Integer(3))]"3" (38..39), + value: Integer { + value: 3, + }, + slot: VariableSlot { + index: 12, + }, + }, + LoadLiteralToSlot { + source: [Literal(Integer(3))]"3" (42..43), + value: Integer { + value: 3, + }, + slot: VariableSlot { + index: 13, + }, + }, + MathOperate { + op: Divide, + left_slot: VariableSlot { + index: 12, + }, + right_slot: VariableSlot { + index: 13, + }, + result_slot: VariableSlot { + index: 11, + }, + }, + LoadLiteralToSlot { + source: [Literal(Integer(3))]"3" (46..47), + value: Integer { + value: 3, + }, + slot: VariableSlot { + index: 14, + }, + }, + MathOperate { + op: Plus, + left_slot: VariableSlot { + index: 11, + }, + right_slot: VariableSlot { + index: 14, + }, + result_slot: VariableSlot { + index: 10, + }, + }, + EmitFromSlot { + slot: VariableSlot { + index: 10, + }, + }, + ], +} diff --git a/tests/cases/4-output@maths.snap b/tests/cases/4-output@maths.snap new file mode 100644 index 0000000..ab89da7 --- /dev/null +++ b/tests/cases/4-output@maths.snap @@ -0,0 +1,9 @@ +--- +source: tests/file_tests.rs +expression: output +info: + input: "{{= 5 * 3 }}\n{{= 2 * 3 + 4 * 3 }}\n{{= 3 / 3 + 3 }}" + context: {} +input_file: tests/cases/maths.nomo +--- +"15\n18\n4" diff --git a/tests/cases/maths.nomo b/tests/cases/maths.nomo new file mode 100644 index 0000000..4ba36c3 --- /dev/null +++ b/tests/cases/maths.nomo @@ -0,0 +1,6 @@ +{ +} +--- +{{= 5 * 3 }} +{{= 2 * 3 + 4 * 3 }} +{{= 3 / 3 + 3 }} \ No newline at end of file