diff --git a/ndc_lib/src/ast/expression.rs b/ndc_lib/src/ast/expression.rs index 0c0fcbe8..407bb726 100644 --- a/ndc_lib/src/ast/expression.rs +++ b/ndc_lib/src/ast/expression.rs @@ -89,6 +89,7 @@ pub enum Expression { value: Box, }, Break, + Continue, RangeInclusive { start: Option>, end: Option>, @@ -374,6 +375,7 @@ impl fmt::Debug for ExpressionLocation { .finish(), Expression::Return { value } => f.debug_struct("Return").field("value", value).finish(), Expression::Break => f.debug_struct("Break").finish(), + Expression::Continue => f.debug_struct("Continue").finish(), Expression::RangeInclusive { start, end } => f .debug_struct("RangeInclusive") .field("start", start) diff --git a/ndc_lib/src/ast/parser.rs b/ndc_lib/src/ast/parser.rs index cdf2a950..0114fda4 100644 --- a/ndc_lib/src/ast/parser.rs +++ b/ndc_lib/src/ast/parser.rs @@ -895,6 +895,9 @@ impl Parser { } else if let Some(token_location) = self.consume_token_if(&[Token::Break]) { let expression = Expression::Break; return Ok(expression.to_location(token_location.span)); + } else if let Some(token_location) = self.consume_token_if(&[Token::Continue]) { + let expression = Expression::Continue; + return Ok(expression.to_location(token_location.span)); } // matches curly bracketed block expression `{ }` else if self.match_token(&[Token::LeftCurlyBracket]).is_some() { diff --git a/ndc_lib/src/interpreter/evaluate/mod.rs b/ndc_lib/src/interpreter/evaluate/mod.rs index f94788aa..c84fd451 100644 --- a/ndc_lib/src/interpreter/evaluate/mod.rs +++ b/ndc_lib/src/interpreter/evaluate/mod.rs @@ -224,6 +224,7 @@ pub(crate) fn evaluate_expression( let result = evaluate_expression(loop_body, environment); match result { Err(FunctionCarrier::Break(value)) => return Ok(value), + Err(FunctionCarrier::Continue) => {} Err(err) => return Err(err), Ok(_) => {} } @@ -365,6 +366,7 @@ pub(crate) fn evaluate_expression( match result { Err(FunctionCarrier::Break(break_value)) => return Ok(break_value), + Err(FunctionCarrier::Continue) => unreachable!(), Err(err) => return Err(err), Ok(_) => {} } @@ -399,6 +401,7 @@ pub(crate) fn evaluate_expression( } // TODO: for now we just put unit in here so we can improve break functionality later Expression::Break => return Err(FunctionCarrier::Break(Value::unit())), + Expression::Continue => return Err(FunctionCarrier::Continue), Expression::Index { value: lhs_expr, index: index_expr, @@ -906,7 +909,8 @@ fn call_function( FunctionCarrier::EvaluationError(_) | FunctionCarrier::FunctionNotFound // TODO: for now we just pass the break from inside the function to outside the function. This would allow some pretty funky code and might introduce weird bugs? - | FunctionCarrier::Break(_), + | FunctionCarrier::Break(_) + | FunctionCarrier::Continue ) => e, Err(carrier @ FunctionCarrier::IntoEvaluationError(_)) => Err(carrier.lift_if(span)), } @@ -971,7 +975,11 @@ fn execute_for_iterations( declare_or_assign_variable(l_value, r_value, true, &mut scope, span)?; if tail.is_empty() { - execute_body(body, &mut scope, out_values)?; + match execute_body(body, &mut scope, out_values) { + Err(FunctionCarrier::Continue) => {}, + Err(error) => return Err(error), + Ok(_value) => {} + } } else { execute_for_iterations(tail, body, out_values, &mut scope, span)?; } diff --git a/ndc_lib/src/interpreter/function.rs b/ndc_lib/src/interpreter/function.rs index 90db1718..443005db 100644 --- a/ndc_lib/src/interpreter/function.rs +++ b/ndc_lib/src/interpreter/function.rs @@ -503,6 +503,8 @@ pub enum FunctionCarrier { Return(Value), #[error("not an error")] Break(Value), + #[error("not an error")] + Continue, #[error("evaluation error {0}")] EvaluationError(#[from] EvaluationError), #[error("function does not exist")] diff --git a/ndc_lib/src/interpreter/mod.rs b/ndc_lib/src/interpreter/mod.rs index ba27f827..953cab30 100644 --- a/ndc_lib/src/interpreter/mod.rs +++ b/ndc_lib/src/interpreter/mod.rs @@ -89,6 +89,12 @@ impl Interpreter { expr.span, ))?; } + Err(FunctionCarrier::Continue) => { + Err(EvaluationError::syntax_error( + "unexpected continue statement outside of loop body".to_string(), + expr.span, + ))?; + } Err(FunctionCarrier::EvaluationError(e)) => return Err(InterpreterError::from(e)), _ => { panic!( diff --git a/ndc_lib/src/lexer/token.rs b/ndc_lib/src/lexer/token.rs index ab776fb4..da3c93b6 100644 --- a/ndc_lib/src/lexer/token.rs +++ b/ndc_lib/src/lexer/token.rs @@ -62,6 +62,7 @@ pub enum Token { Else, Return, Break, + Continue, For, In, While, @@ -140,6 +141,7 @@ impl fmt::Display for Token { Self::Else => "else", Self::Return => "return", Self::Break => "break", + Self::Continue => "continue", Self::For => "for", Self::In => "in", Self::While => "while", @@ -320,6 +322,7 @@ impl From for Token { "false" => Self::False, "return" => Self::Return, "break" => Self::Break, + "continue" => Self::Continue, "pure" => Self::Pure, _ => Self::Identifier(value), } diff --git a/tests/programs/004_basic/035_continue.ndct b/tests/programs/004_basic/035_continue.ndct new file mode 100644 index 00000000..36073197 --- /dev/null +++ b/tests/programs/004_basic/035_continue.ndct @@ -0,0 +1,24 @@ +--PROGRAM-- +let counter = 0; +for i in 1..100 { + if i == 23 { + continue; + } + counter += 1; +} +assert_eq(counter, 98); + +// Nested break +let counter = 0; +for i in 0..50 { + for j in 1..5 { + if j == 2 { continue; } + counter += 1; + } +} + +assert_eq(counter, 150); + +print("ok"); +--EXPECT-- +ok diff --git a/tests/programs/004_basic/036_continue_outside_loop.ndct b/tests/programs/004_basic/036_continue_outside_loop.ndct new file mode 100644 index 00000000..314e7caf --- /dev/null +++ b/tests/programs/004_basic/036_continue_outside_loop.ndct @@ -0,0 +1,4 @@ +--PROGRAM-- +continue; +--EXPECT-ERROR-- +unexpected continue statement outside of loop diff --git a/tests/programs/004_basic/037_continue_while.ndct b/tests/programs/004_basic/037_continue_while.ndct new file mode 100644 index 00000000..21f2b0bb --- /dev/null +++ b/tests/programs/004_basic/037_continue_while.ndct @@ -0,0 +1,13 @@ +--PROGRAM-- +let i = 0; +let sum = 0; +while i < 100 { + i += 1; + if i == 30 { continue; } + sum += i; +} + +assert_eq(sum, 5020); +print("ok"); +--EXPECT-- +ok diff --git a/tests/programs/004_basic/035_string_concat_operator.ndct b/tests/programs/004_basic/038_string_concat_operator.ndct similarity index 100% rename from tests/programs/004_basic/035_string_concat_operator.ndct rename to tests/programs/004_basic/038_string_concat_operator.ndct diff --git a/tests/programs/004_basic/036_binary_literal.ndct b/tests/programs/004_basic/039_binary_literal.ndct similarity index 100% rename from tests/programs/004_basic/036_binary_literal.ndct rename to tests/programs/004_basic/039_binary_literal.ndct diff --git a/tests/programs/004_basic/037_hex_literal.ndct b/tests/programs/004_basic/040_hex_literal.ndct similarity index 100% rename from tests/programs/004_basic/037_hex_literal.ndct rename to tests/programs/004_basic/040_hex_literal.ndct diff --git a/tests/programs/004_basic/038_custom_radix_literal.ndct b/tests/programs/004_basic/041_custom_radix_literal.ndct similarity index 100% rename from tests/programs/004_basic/038_custom_radix_literal.ndct rename to tests/programs/004_basic/041_custom_radix_literal.ndct diff --git a/tests/programs/004_basic/039_octal_literal.ndct b/tests/programs/004_basic/042_octal_literal.ndct similarity index 100% rename from tests/programs/004_basic/039_octal_literal.ndct rename to tests/programs/004_basic/042_octal_literal.ndct diff --git a/tests/programs/004_basic/040_assignment_chaining.ndct b/tests/programs/004_basic/043_assignment_chaining.ndct similarity index 100% rename from tests/programs/004_basic/040_assignment_chaining.ndct rename to tests/programs/004_basic/043_assignment_chaining.ndct