diff --git a/benches/main.rs b/benches/main.rs index aeb54a7..3880ad2 100644 --- a/benches/main.rs +++ b/benches/main.rs @@ -14,7 +14,8 @@ fn bench_increment_value(criterion: &mut Criterion) { group.bench_function(format!("n = {number}"), |bencher| { bencher.iter(|| { - black_box(Evaluator::from(Environment::default()).eval(&ast)).unwrap(); + black_box(Evaluator::from(Environment::default()).evaluate(&ast)) + .unwrap(); }); }); } @@ -70,7 +71,8 @@ fn bench_prime_count(criterion: &mut Criterion) { group.bench_function(format!("n = {number}"), |bencher| { bencher.iter(|| { - black_box(Evaluator::from(Environment::default()).eval(&ast)).unwrap(); + black_box(Evaluator::from(Environment::default()).evaluate(&ast)) + .unwrap(); }); }); } @@ -90,7 +92,8 @@ fn bench_recursive_factorial(criterion: &mut Criterion) { group.bench_function(format!("n = {number}"), |bencher| { bencher.iter(|| { - black_box(Evaluator::from(Environment::default()).eval(&ast)).unwrap(); + black_box(Evaluator::from(Environment::default()).evaluate(&ast)) + .unwrap(); }); }); } diff --git a/crates/val-wasm/src/ast_node.rs b/crates/val-wasm/src/ast_node.rs index 6280e06..508460d 100644 --- a/crates/val-wasm/src/ast_node.rs +++ b/crates/val-wasm/src/ast_node.rs @@ -162,6 +162,34 @@ impl From<(&Statement<'_>, &Span)> for AstNode { } } +impl From<(&AssignmentTarget<'_>, &Span)> for AstNode { + fn from(value: (&AssignmentTarget<'_>, &Span)) -> Self { + let (target, span) = value; + + let range = Range::from(span); + + let mut children = Vec::new(); + + match target { + AssignmentTarget::Identifier(_) => Self { + kind: target.kind(), + range, + children, + }, + AssignmentTarget::ListAccess(list, index) => { + children.push(Self::from((&list.0, &list.1))); + children.push(Self::from((&index.0, &index.1))); + + Self { + kind: target.kind(), + range, + children, + } + } + } + } +} + impl From<(&Expression<'_>, &Span)> for AstNode { fn from(value: (&Expression<'_>, &Span)) -> Self { let (expression, span) = value; diff --git a/crates/val-wasm/src/lib.rs b/crates/val-wasm/src/lib.rs index 3f7754c..0aa4975 100644 --- a/crates/val-wasm/src/lib.rs +++ b/crates/val-wasm/src/lib.rs @@ -7,7 +7,8 @@ use { serde::Serialize, serde_wasm_bindgen::to_value, val::{ - Environment, Evaluator, Expression, Program, RoundingMode, Span, Statement, + AssignmentTarget, Environment, Evaluator, Expression, Program, + RoundingMode, Span, Statement, }, wasm_bindgen::prelude::*, }; @@ -50,7 +51,7 @@ pub fn evaluate(input: &str) -> Result { rounding_mode: RoundingMode::FromZero.into(), })); - match evaluator.eval(&ast) { + match evaluator.evaluate(&ast) { Ok(value) => Ok(to_value(&value.to_string()).unwrap()), Err(error) => Err( to_value(&[ValError { diff --git a/justfile b/justfile index 77b2d2f..02b3b24 100644 --- a/justfile +++ b/justfile @@ -34,7 +34,7 @@ check: [group: 'check'] ci: test clippy forbid - cargo +nightly fmt --all -- --check + cargo fmt --all -- --check cargo update --locked --package val [group: 'check'] @@ -43,11 +43,11 @@ clippy: [group: 'format'] fmt: - cargo +nightly fmt + cargo fmt [group: 'format'] fmt-check: - cargo +nightly fmt --all -- --check + cargo fmt --all -- --check [group: 'format'] fmt-web: diff --git a/rustfmt.toml b/rustfmt.toml index 8f09c6b..4f292ee 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,4 @@ edition = "2018" -imports_granularity = "One" max_width = 80 newline_style = "Unix" tab_spaces = 2 diff --git a/src/arguments.rs b/src/arguments.rs index e29c48d..d1680ed 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -72,7 +72,7 @@ impl Arguments { })); match parse(&content) { - Ok(ast) => match evaluator.eval(&ast) { + Ok(ast) => match evaluator.evaluate(&ast) { Ok(_) => Ok(()), Err(error) => { error @@ -94,14 +94,14 @@ impl Arguments { } } - fn eval_expression(&self, value: String) -> Result { + fn evaluate_expression(&self, value: String) -> Result { let mut evaluator = Evaluator::from(Environment::new(Config { precision: self.precision, rounding_mode: self.rounding_mode.into(), })); match parse(&value) { - Ok(ast) => match evaluator.eval(&ast) { + Ok(ast) => match evaluator.evaluate(&ast) { Ok(value) => { if let Value::Null = value { return Ok(()); @@ -162,7 +162,7 @@ impl Arguments { let filename = filename.to_string_lossy().to_string(); match parse(content) { - Ok(ast) => match evaluator.eval(&ast) { + Ok(ast) => match evaluator.evaluate(&ast) { Ok(_) => {} Err(error) => { error @@ -194,7 +194,7 @@ impl Arguments { let line: &'static str = Box::leak(line.into_boxed_str()); match parse(line) { - Ok(ast) => match evaluator.eval(&ast) { + Ok(ast) => match evaluator.evaluate(&ast) { Ok(value) if !matches!(value, Value::Null) => println!("{value}"), Ok(_) => {} Err(error) => error @@ -215,7 +215,7 @@ impl Arguments { pub fn run(self) -> Result { match (&self.filename, &self.expression) { (Some(filename), _) => self.eval(filename), - (_, Some(expression)) => self.eval_expression(expression.clone()), + (_, Some(expression)) => self.evaluate_expression(expression.clone()), _ => { #[cfg(not(target_family = "wasm"))] { diff --git a/src/ast.rs b/src/ast.rs index cc37f6a..dc44b80 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -34,7 +34,7 @@ impl Program<'_> { #[derive(Debug, Clone)] pub enum Statement<'a> { - Assignment(Spanned>, Spanned>), + Assignment(Spanned>, Spanned>), Block(Vec>>), Break, Continue, @@ -179,6 +179,55 @@ impl Statement<'_> { } } +#[derive(Debug, Clone)] +pub enum AssignmentTarget<'a> { + Identifier(&'a str), + ListAccess(Box>, Box>>), +} + +impl Display for AssignmentTarget<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + AssignmentTarget::Identifier(identifier) => { + write!(f, "identifier({identifier})") + } + AssignmentTarget::ListAccess(list, index) => { + write!(f, "list_access({}, {})", list.0, index.0) + } + } + } +} + +impl AssignmentTarget<'_> { + #[must_use] + pub fn kind(&self) -> String { + String::from(match self { + AssignmentTarget::Identifier(_) => "identifier", + AssignmentTarget::ListAccess(_, _) => "list_access", + }) + } +} + +impl<'a> AssignmentTarget<'a> { + pub(crate) fn indices(&self) -> Vec<&Spanned>> { + match self { + AssignmentTarget::Identifier(_) => Vec::new(), + AssignmentTarget::ListAccess(base, index) => { + let mut indices = base.0.indices(); + indices.push(index); + indices + } + } + } + + pub(crate) fn root(&self, span: Span) -> (&'a str, Span) { + match self { + AssignmentTarget::Identifier(name) => (name, span), + AssignmentTarget::ListAccess(base, _) => base.0.root(base.1), + } + } +} + #[derive(Debug, Clone)] pub enum UnaryOp { Negate, diff --git a/src/completion.rs b/src/completion.rs index 69e5c05..8191b11 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -10,20 +10,8 @@ pub enum Completion<'a> { impl<'a> Completion<'a> { pub(crate) fn unwrap(&self) -> Value<'a> { match self { - Completion::Value(v) | Completion::Return(v) => v.clone(), + Completion::Return(value) | Completion::Value(value) => value.clone(), Completion::Break | Completion::Continue => Value::Null, } } - - pub(crate) fn is_return(&self) -> bool { - matches!(self, Completion::Return(_)) - } - - pub(crate) fn is_break(&self) -> bool { - matches!(self, Completion::Break) - } - - pub(crate) fn is_continue(&self) -> bool { - matches!(self, Completion::Continue) - } } diff --git a/src/evaluator.rs b/src/evaluator.rs index 4ae5d72..be51933 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -1,8 +1,8 @@ use {super::*, crate::context::Context}; pub struct Evaluator<'a> { - context: Context, - pub environment: Environment<'a>, + pub(crate) context: Context, + pub(crate) environment: Environment<'a>, } impl<'a> From> for Evaluator<'a> { @@ -14,16 +14,94 @@ impl<'a> From> for Evaluator<'a> { } } -fn finite_non_negative_usize(number: f64) -> Option { - if number.is_finite() && number >= 0.0 { - let number = number.trunc(); - format!("{number:.0}").parse().ok() - } else { - None +impl<'a> Evaluator<'a> { + fn assign( + &mut self, + target: &Spanned>, + value: Value<'a>, + ) -> Result<(), Error> { + match &target.0 { + AssignmentTarget::Identifier(name) => { + self.environment.add_symbol(name, value); + Ok(()) + } + AssignmentTarget::ListAccess(_, _) => { + let (name, name_span) = target.0.root(target.1); + + let indices = target.0.indices(); + + let Some(root) = self.environment.resolve_symbol(name) else { + return Err(Error::new( + name_span, + format!("Undefined variable `{name}`"), + )); + }; + + let root = + self.assign_indices(name, root, &indices, value, target.1)?; + + self.environment.add_symbol(name, root); + + Ok(()) + } + } + } + + fn assign_indices( + &mut self, + name: &'a str, + value: Value<'a>, + indices: &[&Spanned>], + assigned: Value<'a>, + span: Span, + ) -> Result, Error> { + let Some((index, rest)) = indices.split_first() else { + return Ok(assigned); + }; + + let mut list = match value { + Value::List(items) => items, + other => { + return Err(Error::new( + index.1, + format!("'{}' is not a list (found {})", name, other.type_name()), + )); + } + }; + + let index = self + .evaluate_expression(index)? + .number(index.1)? + .to_f64(self.environment.config.rounding_mode) + .and_then(|number| { + if number.is_finite() && number >= 0.0 { + let number = number.trunc(); + format!("{number:.0}").parse::().ok() + } else { + None + } + }) + .ok_or_else(|| { + Error::new(index.1, "List index must be a non-negative finite number") + })?; + + if index >= list.len() { + return Err(Error::new( + span, + format!( + "Index {} out of bounds for list of length {}", + index, + list.len() + ), + )); + } + + list[index] = + self.assign_indices(name, list[index].clone(), rest, assigned, span)?; + + Ok(Value::List(list)) } -} -impl<'a> Evaluator<'a> { pub(crate) fn enter_function( &mut self, f: impl FnOnce(&mut Self) -> Result, @@ -47,7 +125,7 @@ impl<'a> Evaluator<'a> { /// # Errors /// /// Returns an evaluation error when a statement or expression is invalid. - pub fn eval( + pub fn evaluate( &mut self, ast: &Spanned>, ) -> Result, Error> { @@ -58,14 +136,14 @@ impl<'a> Evaluator<'a> { let mut result = Value::Null; for statement in statements { - let completion = self.eval_statement(statement)?; + let completion = self.evaluate_statement(statement)?; result = completion.unwrap(); - if completion.is_return() - || completion.is_break() - || completion.is_continue() - { + if matches!( + &completion, + Completion::Return(_) | Completion::Break | Completion::Continue + ) { break; } } @@ -75,260 +153,7 @@ impl<'a> Evaluator<'a> { } } - pub(crate) fn eval_statement( - &mut self, - statement: &Spanned>, - ) -> Result, Error> { - let (node, span) = statement; - - match node { - Statement::Assignment(lhs, rhs) => { - let value = self.eval_expression(rhs)?; - - match &lhs.0 { - Expression::Identifier(name) => { - self.environment.add_symbol(name, value.clone()); - } - Expression::ListAccess(base_box, index_box) => { - let (list_name, list_span) = match &base_box.0 { - Expression::Identifier(name) => (*name, base_box.1), - _ => { - return Err(Error::new( - base_box.1, - "left‑hand side must be a variable or list element", - )); - } - }; - - let mut list = match self.environment.resolve_symbol(list_name) { - Some(Value::List(items)) => items, - Some(other) => { - return Err(Error::new( - list_span, - format!( - "'{}' is not a list (found {})", - list_name, - other.type_name() - ), - )); - } - None => { - return Err(Error::new( - list_span, - format!("Undefined variable `{list_name}`"), - )); - } - }; - - let Some(index) = self - .eval_expression(index_box)? - .number(index_box.1)? - .to_f64(self.environment.config.rounding_mode) - .and_then(finite_non_negative_usize) - else { - return Err(Error::new( - index_box.1, - "List index must be a non-negative finite number", - )); - }; - - if index >= list.len() { - return Err(Error::new( - lhs.1, - format!( - "Index {} out of bounds for list of length {}", - index, - list.len() - ), - )); - } - - list[index] = value.clone(); - - self.environment.add_symbol(list_name, Value::List(list)); - } - - _ => { - return Err(Error::new( - lhs.1, - "left‑hand side must be a variable or list element", - )); - } - } - - Ok(Completion::Value(value)) - } - Statement::Block(statements) => { - let mut result = Value::Null; - - for statement in statements { - let completion = self.eval_statement(statement)?; - - result = completion.unwrap(); - - if completion.is_return() - || completion.is_break() - || completion.is_continue() - { - return Ok(completion); - } - } - - Ok(Completion::Value(result)) - } - Statement::Break => { - if !self.context.inside_loop() { - return Err(Error::new( - *span, - "Cannot use 'break' outside of a loop", - )); - } - Ok(Completion::Break) - } - Statement::Continue => { - if !self.context.inside_loop() { - return Err(Error::new( - *span, - "Cannot use 'continue' outside of a loop", - )); - } - - Ok(Completion::Continue) - } - Statement::Expression(expression) => { - Ok(Completion::Value(self.eval_expression(expression)?)) - } - Statement::For(name, iterable, body) => { - let list = self.eval_expression(iterable)?.list(iterable.1)?; - let mut result = Value::Null; - - self.enter_loop(|evaluator| { - for item in list { - evaluator.environment.add_symbol(name, item); - - for statement in body { - let completion = evaluator.eval_statement(statement)?; - - result = completion.unwrap(); - - if completion.is_return() { - return Ok(Completion::Return(result)); - } else if completion.is_break() { - return Ok(Completion::Value(result)); - } else if completion.is_continue() { - break; - } - } - } - - Ok(Completion::Value(result)) - }) - } - Statement::Function(name, params, body) => { - let function = Function::UserDefined { - body: body.clone(), - environment: self.environment.clone(), - name, - parameters: params.clone(), - }; - - self.environment.add_function(name, function.clone()); - - Ok(Completion::Value(Value::Function(function))) - } - Statement::If(condition, then_branch, else_branch) => { - if self.eval_expression(condition)?.boolean(condition.1)? { - let mut result = Value::Null; - - for statement in then_branch { - let completion = self.eval_statement(statement)?; - - result = completion.unwrap(); - - if completion.is_return() - || completion.is_break() - || completion.is_continue() - { - return Ok(completion); - } - } - - Ok(Completion::Value(result)) - } else if let Some(else_statements) = else_branch { - let mut result = Value::Null; - - for statement in else_statements { - let completion = self.eval_statement(statement)?; - - result = completion.unwrap(); - - if completion.is_return() - || completion.is_break() - || completion.is_continue() - { - return Ok(completion); - } - } - - Ok(Completion::Value(result)) - } else { - Ok(Completion::Value(Value::Null)) - } - } - Statement::Loop(body) => self.enter_loop(|evaluator| { - loop { - for statement in body { - let completion = evaluator.eval_statement(statement)?; - - let result = completion.unwrap(); - - if completion.is_return() { - return Ok(Completion::Return(result)); - } else if completion.is_break() { - return Ok(Completion::Value(result)); - } else if completion.is_continue() { - break; - } - } - } - }), - Statement::Return(expr) => { - if !self.context.inside_function() { - return Err(Error::new(*span, "Cannot return outside of a function")); - } - - Ok(Completion::Return(match expr { - Some(expr) => self.eval_expression(expr)?, - None => Value::Null, - })) - } - Statement::While(condition, body) => { - let mut result = Value::Null; - - self.enter_loop(|evaluator| { - while evaluator.eval_expression(condition)?.boolean(condition.1)? { - for statement in body { - let completion = evaluator.eval_statement(statement)?; - - result = completion.unwrap(); - - if completion.is_return() { - return Ok(Completion::Return(result)); - } else if completion.is_break() { - return Ok(Completion::Value(result)); - } else if completion.is_continue() { - break; - } - } - } - - Ok(Completion::Value(result)) - }) - } - } - } - - fn eval_expression( + fn evaluate_expression( &mut self, ast: &Spanned>, ) -> Result, Error> { @@ -336,8 +161,10 @@ impl<'a> Evaluator<'a> { match node { Expression::BinaryOp(BinaryOp::Add, lhs, rhs) => { - let (lhs_val, rhs_val) = - (self.eval_expression(lhs)?, self.eval_expression(rhs)?); + let (lhs_val, rhs_val) = ( + self.evaluate_expression(lhs)?, + self.evaluate_expression(rhs)?, + ); match (&lhs_val, &rhs_val) { (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a.add( @@ -367,8 +194,10 @@ impl<'a> Evaluator<'a> { } } Expression::BinaryOp(BinaryOp::Divide, lhs, rhs) => { - let (lhs_val, rhs_val) = - (self.eval_expression(lhs)?, self.eval_expression(rhs)?); + let (lhs_val, rhs_val) = ( + self.evaluate_expression(lhs)?, + self.evaluate_expression(rhs)?, + ); let (lhs_num, rhs_num) = (lhs_val.number(lhs.1)?, rhs_val.number(rhs.1)?); @@ -384,7 +213,7 @@ impl<'a> Evaluator<'a> { ))) } Expression::BinaryOp(BinaryOp::Equal, lhs, rhs) => Ok(Value::Boolean( - self.eval_expression(lhs)? == self.eval_expression(rhs)?, + self.evaluate_expression(lhs)? == self.evaluate_expression(rhs)?, )), Expression::BinaryOp( op @ (BinaryOp::LessThan @@ -394,8 +223,10 @@ impl<'a> Evaluator<'a> { lhs, rhs, ) => { - let (lhs_val, rhs_val) = - (self.eval_expression(lhs)?, self.eval_expression(rhs)?); + let (lhs_val, rhs_val) = ( + self.evaluate_expression(lhs)?, + self.evaluate_expression(rhs)?, + ); match (&lhs_val, &rhs_val) { (Value::Number(a), Value::Number(b)) => { @@ -429,19 +260,21 @@ impl<'a> Evaluator<'a> { } Expression::BinaryOp(BinaryOp::LogicalAnd, lhs, rhs) => { Ok(Value::Boolean( - self.eval_expression(lhs)?.boolean(lhs.1)? - && self.eval_expression(rhs)?.boolean(rhs.1)?, + self.evaluate_expression(lhs)?.boolean(lhs.1)? + && self.evaluate_expression(rhs)?.boolean(rhs.1)?, )) } Expression::BinaryOp(BinaryOp::LogicalOr, lhs, rhs) => { Ok(Value::Boolean( - self.eval_expression(lhs)?.boolean(lhs.1)? - || self.eval_expression(rhs)?.boolean(rhs.1)?, + self.evaluate_expression(lhs)?.boolean(lhs.1)? + || self.evaluate_expression(rhs)?.boolean(rhs.1)?, )) } Expression::BinaryOp(BinaryOp::Modulo, lhs, rhs) => { - let (lhs_val, rhs_val) = - (self.eval_expression(lhs)?, self.eval_expression(rhs)?); + let (lhs_val, rhs_val) = ( + self.evaluate_expression(lhs)?, + self.evaluate_expression(rhs)?, + ); let (lhs_num, rhs_num) = (lhs_val.number(lhs.1)?, rhs_val.number(rhs.1)?); @@ -473,18 +306,20 @@ impl<'a> Evaluator<'a> { Ok(Value::Number(remainder)) } Expression::BinaryOp(BinaryOp::Multiply, lhs, rhs) => Ok(Value::Number( - self.eval_expression(lhs)?.number(lhs.1)?.mul( - &self.eval_expression(rhs)?.number(rhs.1)?, + self.evaluate_expression(lhs)?.number(lhs.1)?.mul( + &self.evaluate_expression(rhs)?.number(rhs.1)?, self.environment.config.precision, self.environment.config.rounding_mode, ), )), Expression::BinaryOp(BinaryOp::NotEqual, lhs, rhs) => Ok(Value::Boolean( - self.eval_expression(lhs)? != self.eval_expression(rhs)?, + self.evaluate_expression(lhs)? != self.evaluate_expression(rhs)?, )), Expression::BinaryOp(BinaryOp::Power, lhs, rhs) => { - let (lhs_val, rhs_val) = - (self.eval_expression(lhs)?, self.eval_expression(rhs)?); + let (lhs_val, rhs_val) = ( + self.evaluate_expression(lhs)?, + self.evaluate_expression(rhs)?, + ); let (lhs_num, rhs_num) = (lhs_val.number(lhs.1)?, rhs_val.number(rhs.1)?); @@ -501,8 +336,8 @@ impl<'a> Evaluator<'a> { Ok(Value::Number(result)) } Expression::BinaryOp(BinaryOp::Subtract, lhs, rhs) => Ok(Value::Number( - self.eval_expression(lhs)?.number(lhs.1)?.sub( - &self.eval_expression(rhs)?.number(rhs.1)?, + self.evaluate_expression(lhs)?.number(lhs.1)?.sub( + &self.evaluate_expression(rhs)?.number(rhs.1)?, self.environment.config.precision, self.environment.config.rounding_mode, ), @@ -512,7 +347,7 @@ impl<'a> Evaluator<'a> { let mut evaluated_arguments = Vec::with_capacity(arguments.len()); for argument in arguments { - evaluated_arguments.push(self.eval_expression(argument)?); + evaluated_arguments.push(self.evaluate_expression(argument)?); } self @@ -531,13 +366,13 @@ impl<'a> Evaluator<'a> { let mut evaluated_list = Vec::with_capacity(list.len()); for item in list { - evaluated_list.push(self.eval_expression(item)?); + evaluated_list.push(self.evaluate_expression(item)?); } Ok(Value::List(evaluated_list)) } Expression::ListAccess(list, index) => { - let list_value = self.eval_expression(list)?; + let list_value = self.evaluate_expression(list)?; let list = match &list_value { Value::List(items) => items.clone(), @@ -550,10 +385,17 @@ impl<'a> Evaluator<'a> { }; let Some(index) = self - .eval_expression(index)? + .evaluate_expression(index)? .number(index.1)? .to_f64(self.environment.config.rounding_mode) - .and_then(finite_non_negative_usize) + .and_then(|number| { + if number.is_finite() && number >= 0.0 { + let number = number.trunc(); + format!("{number:.0}").parse::().ok() + } else { + None + } + }) else { return Err(Error::new( index.1, @@ -577,11 +419,197 @@ impl<'a> Evaluator<'a> { Expression::Null => Ok(Value::Null), Expression::Number(number) => Ok(Value::Number(number.clone())), Expression::String(string) => Ok(Value::String(string)), - Expression::UnaryOp(UnaryOp::Negate, rhs) => { - Ok(Value::Number(-self.eval_expression(rhs)?.number(rhs.1)?)) + Expression::UnaryOp(UnaryOp::Negate, rhs) => Ok(Value::Number( + -self.evaluate_expression(rhs)?.number(rhs.1)?, + )), + Expression::UnaryOp(UnaryOp::Not, rhs) => Ok(Value::Boolean( + !self.evaluate_expression(rhs)?.boolean(rhs.1)?, + )), + } + } + + pub(crate) fn evaluate_statement( + &mut self, + statement: &Spanned>, + ) -> Result, Error> { + let (node, span) = statement; + + match node { + Statement::Assignment(lhs, rhs) => { + let value = self.evaluate_expression(rhs)?; + + self.assign(lhs, value.clone())?; + + Ok(Completion::Value(value)) + } + Statement::Block(statements) => { + let mut result = Value::Null; + + for statement in statements { + let completion = self.evaluate_statement(statement)?; + + result = completion.unwrap(); + + if matches!( + &completion, + Completion::Return(_) | Completion::Break | Completion::Continue + ) { + return Ok(completion); + } + } + + Ok(Completion::Value(result)) } - Expression::UnaryOp(UnaryOp::Not, rhs) => { - Ok(Value::Boolean(!self.eval_expression(rhs)?.boolean(rhs.1)?)) + Statement::Break => { + if !self.context.inside_loop() { + return Err(Error::new( + *span, + "Cannot use 'break' outside of a loop", + )); + } + + Ok(Completion::Break) + } + Statement::Continue => { + if !self.context.inside_loop() { + return Err(Error::new( + *span, + "Cannot use 'continue' outside of a loop", + )); + } + + Ok(Completion::Continue) + } + Statement::Expression(expression) => { + Ok(Completion::Value(self.evaluate_expression(expression)?)) + } + Statement::For(name, iterable, body) => { + let list = self.evaluate_expression(iterable)?.list(iterable.1)?; + + let mut result = Value::Null; + + self.enter_loop(|evaluator| { + for item in list { + evaluator.environment.add_symbol(name, item); + + for statement in body { + let completion = evaluator.evaluate_statement(statement)?; + + result = completion.unwrap(); + + match completion { + Completion::Break => return Ok(Completion::Value(result)), + Completion::Continue => break, + Completion::Return(_) => return Ok(Completion::Return(result)), + Completion::Value(_) => {} + } + } + } + + Ok(Completion::Value(result)) + }) + } + Statement::Function(name, params, body) => { + let function = Function::UserDefined { + body: body.clone(), + environment: self.environment.clone(), + name, + parameters: params.clone(), + }; + + self.environment.add_function(name, function.clone()); + + Ok(Completion::Value(Value::Function(function))) + } + Statement::If(condition, then_branch, else_branch) => { + if self.evaluate_expression(condition)?.boolean(condition.1)? { + let mut result = Value::Null; + + for statement in then_branch { + let completion = self.evaluate_statement(statement)?; + + result = completion.unwrap(); + + if matches!( + &completion, + Completion::Return(_) | Completion::Break | Completion::Continue + ) { + return Ok(completion); + } + } + + Ok(Completion::Value(result)) + } else if let Some(else_statements) = else_branch { + let mut result = Value::Null; + + for statement in else_statements { + let completion = self.evaluate_statement(statement)?; + + result = completion.unwrap(); + + if matches!( + &completion, + Completion::Return(_) | Completion::Break | Completion::Continue + ) { + return Ok(completion); + } + } + + Ok(Completion::Value(result)) + } else { + Ok(Completion::Value(Value::Null)) + } + } + Statement::Loop(body) => self.enter_loop(|evaluator| { + loop { + for statement in body { + let completion = evaluator.evaluate_statement(statement)?; + + let result = completion.unwrap(); + + match completion { + Completion::Break => return Ok(Completion::Value(result)), + Completion::Continue => break, + Completion::Return(_) => return Ok(Completion::Return(result)), + Completion::Value(_) => {} + } + } + } + }), + Statement::Return(expression) => { + if !self.context.inside_function() { + return Err(Error::new(*span, "Cannot return outside of a function")); + } + + Ok(Completion::Return(match expression { + Some(expression) => self.evaluate_expression(expression)?, + None => Value::Null, + })) + } + Statement::While(condition, body) => { + let mut result = Value::Null; + + self.enter_loop(|evaluator| { + while evaluator + .evaluate_expression(condition)? + .boolean(condition.1)? + { + for statement in body { + let completion = evaluator.evaluate_statement(statement)?; + + result = completion.unwrap(); + + match completion { + Completion::Break => return Ok(Completion::Value(result)), + Completion::Continue => break, + Completion::Return(_) => return Ok(Completion::Return(result)), + Completion::Value(_) => {} + } + } + } + + Ok(Completion::Value(result)) + }) } } } diff --git a/src/function.rs b/src/function.rs index e39579a..1d2c8f3 100644 --- a/src/function.rs +++ b/src/function.rs @@ -60,9 +60,9 @@ impl<'src> Function<'src> { } for statement in body { - let result = evaluator.eval_statement(statement)?; + let result = evaluator.evaluate_statement(statement)?; - if result.is_return() { + if matches!(&result, Completion::Return(_)) { return Ok(result.unwrap()); } } diff --git a/src/highlighter.rs b/src/highlighter.rs index 29ad5d1..fa58ce6 100644 --- a/src/highlighter.rs +++ b/src/highlighter.rs @@ -94,7 +94,7 @@ impl<'src> TreeHighlighter<'src> { match node { Statement::Assignment(lhs, rhs) => { - self.collect_expression_spans(lhs, spans); + self.collect_assignment_target_spans(lhs, spans); if let Some(eq_pos) = self.content[start..end].find('=') { spans.push((start + eq_pos, start + eq_pos + 1, COLOR_OPERATOR)); @@ -309,6 +309,42 @@ impl<'src> TreeHighlighter<'src> { } } + fn collect_assignment_target_spans( + &self, + target: &Spanned>, + spans: &mut Vec<(usize, usize, &'static str)>, + ) { + let (node, span) = target; + + match node { + AssignmentTarget::Identifier(identifier) => { + spans.push(( + span.start, + span.start + identifier.len(), + COLOR_IDENTIFIER, + )); + } + AssignmentTarget::ListAccess(list, index) => { + self.collect_assignment_target_spans(list, spans); + self.collect_expression_spans(index, spans); + + if let Some(open_bracket) = + self.content[list.1.end..index.1.start].find('[') + { + let start = list.1.end + open_bracket; + spans.push((start, start + 1, COLOR_OPERATOR)); + } + + if let Some(close_bracket) = + self.content[index.1.end..span.end].find(']') + { + let start = index.1.end + close_bracket; + spans.push((start, start + 1, COLOR_OPERATOR)); + } + } + } + } + fn collect_expression_spans( &self, expression: &Spanned>, diff --git a/src/lib.rs b/src/lib.rs index 6bea7a0..f8c7b78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -mod consts; - pub(crate) use { crate::{builtins::BUILTINS, consts::with_consts}, ariadne::{Color, Label, Report, ReportKind, Source}, @@ -34,38 +32,15 @@ pub(crate) use { std::borrow::Cow::{self, Owned}, }; -pub use crate::{ - arguments::Arguments, - ast::{BinaryOp, Expression, Program, Statement, UnaryOp}, - builtin::{Builtin, BuiltinFunction, BuiltinFunctionPayload}, - completion::Completion, - config::Config, - environment::Environment, - error::Error, - evaluator::Evaluator, - float_ext::FloatExt, - function::Function, - parser::parse, - rounding_mode::RoundingMode, - value::Value, -}; - -pub type Span = SimpleSpan; - type Result = std::result::Result; type Spanned = (T, Span); -#[doc(hidden)] -pub mod arguments; - -#[cfg(not(target_family = "wasm"))] -mod highlighter; - mod ast; mod builtin; mod builtins; mod completion; mod config; +mod consts; mod context; mod environment; mod error; @@ -75,3 +50,27 @@ mod function; mod parser; mod rounding_mode; mod value; + +#[doc(hidden)] +pub mod arguments; + +#[cfg(not(target_family = "wasm"))] +mod highlighter; + +pub use crate::{ + arguments::Arguments, + ast::{AssignmentTarget, BinaryOp, Expression, Program, Statement, UnaryOp}, + builtin::{Builtin, BuiltinFunction, BuiltinFunctionPayload}, + completion::Completion, + config::Config, + environment::Environment, + error::Error, + evaluator::Evaluator, + float_ext::FloatExt, + function::Function, + parser::parse, + rounding_mode::RoundingMode, + value::Value, +}; + +pub type Span = SimpleSpan; diff --git a/src/parser.rs b/src/parser.rs index ed492e6..0f064d0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -31,6 +31,24 @@ fn program_parser<'a>() .map_with(|ast, e| (ast, e.span())) } +fn index_parser<'a, P>( + expression: P, +) -> impl Parser< + 'a, + &'a str, + (Spanned>, SimpleSpan), + extra::Err>, +> + Clone +where + P: Parser<'a, &'a str, Spanned>, extra::Err>> + + Clone, +{ + expression + .delimited_by(just('['), just(']')) + .padded() + .map_with(|expression, e| (expression, e.span())) +} + fn statement_parser<'a>() -> impl Parser<'a, &'a str, Spanned>, extra::Err>> + Clone { @@ -47,27 +65,22 @@ fn statement_parser<'a>() let simple_ident = text::ident().padded().map_with(|name, e| { let span = e.span(); - (Expression::Identifier(name), span) + (AssignmentTarget::Identifier(name), span) }); - let indexed_ident = simple_ident.foldl( - expression - .clone() - .delimited_by(just('['), just(']')) - .padded() - .map_with(|expression, e| (expression, e.span())) - .repeated(), + let assignment_target = simple_ident.foldl( + index_parser(expression.clone()).repeated(), |base, (index, span)| { let span = (base.1.start..span.end).into(); - let expression = - Expression::ListAccess(Box::new(base), Box::new(index)); + let target = + AssignmentTarget::ListAccess(Box::new(base), Box::new(index)); - (expression, span) + (target, span) }, ); - let assignment_statement = indexed_ident + let assignment_statement = assignment_target .then_ignore(just('=').padded()) .then(expression.clone()) .map(|(lhs, rhs)| Statement::Assignment(lhs, rhs)) @@ -246,12 +259,7 @@ fn expression_parser<'a>() .padded(); let list_access = atom.clone().foldl( - expression - .clone() - .delimited_by(just('['), just(']')) - .padded() - .map_with(|expression, e| (expression, e.span())) - .repeated(), + index_parser(expression.clone()).repeated(), |list: Spanned>, (index, span): (Spanned>, SimpleSpan)| { let span = (list.1.start..span.end).into(); @@ -468,6 +476,11 @@ mod tests { .program("x = 5") .ast("statements(assignment(identifier(x), number(5)))") .run(); + + Test::new() + .program("foo[0][1] = bar") + .ast("statements(assignment(list_access(list_access(identifier(foo), number(0)), number(1)), identifier(bar)))") + .run(); } #[test] diff --git a/tests/integration.rs b/tests/integration.rs index 9fd4d4b..9b88bd3 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1964,6 +1964,21 @@ fn list_element_assignment_updates_value() -> Result { .run() } +#[test] +fn nested_list_element_assignment_updates_value() -> Result { + Test::new()? + .program(indoc! { + " + nums = [[1, 2], [3, 4]] + nums[1][0] = 5 + println(nums) + " + }) + .expected_status(0) + .expected_stdout(Exact("[[1, 2], [5, 4]]\n")) + .run() +} + #[test] fn list_literals() -> Result { Test::new()?