From f9480ddc246342f4223816201807e418942b7896 Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 20 Aug 2024 14:45:43 -0400 Subject: [PATCH] Add loop and break --- dust-lang/src/analyzer.rs | 5 ++ dust-lang/src/ast/expression.rs | 38 +++++++----- dust-lang/src/ast/mod.rs | 2 +- dust-lang/src/ast/statement.rs | 7 +-- dust-lang/src/dust_error.rs | 2 +- dust-lang/src/lexer.rs | 2 + dust-lang/src/parser.rs | 57 +++++++++++++++++- dust-lang/src/token.rs | 41 ++++++++----- dust-lang/src/vm.rs | 100 +++++++++++++++++++++++++------- 9 files changed, 197 insertions(+), 57 deletions(-) diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index c217ec1..a2e8b71 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -153,6 +153,11 @@ impl<'recovered, 'a: 'recovered> Analyzer<'a> { fn analyze_expression(&self, expression: &Expression) -> Result<(), AnalysisError> { match expression { Expression::Block(block_expression) => self.analyze_block(&block_expression.inner)?, + Expression::Break(break_node) => { + if let Some(expression) = &break_node.inner { + self.analyze_expression(expression)?; + } + } Expression::Call(call_expression) => { let CallExpression { invoker, arguments } = call_expression.inner.as_ref(); diff --git a/dust-lang/src/ast/expression.rs b/dust-lang/src/ast/expression.rs index bb04842..60c65d4 100644 --- a/dust-lang/src/ast/expression.rs +++ b/dust-lang/src/ast/expression.rs @@ -2,20 +2,19 @@ use std::{ cmp::Ordering, collections::HashMap, fmt::{self, Display, Formatter}, + rc::Weak, }; use serde::{Deserialize, Serialize}; -use crate::{ - BuiltInFunction, Context, ContextError, FunctionType, Identifier, RangeableType, StructType, - Type, -}; +use crate::{BuiltInFunction, Context, FunctionType, Identifier, RangeableType, StructType, Type}; use super::{AstError, Node, Span, Statement}; #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] pub enum Expression { Block(Node>), + Break(Node>>), Call(Node>), FieldAccess(Node>), Grouped(Node>), @@ -33,6 +32,10 @@ pub enum Expression { } impl Expression { + pub fn r#break(expression: Option, position: Span) -> Self { + Self::Break(Node::new(expression.map(Box::new), position)) + } + pub fn map, Expression)>>>(pairs: T, position: Span) -> Self { Self::Map(Node::new( Box::new(MapExpression { @@ -266,13 +269,15 @@ impl Expression { } } - pub fn return_type<'recovered>( - &self, - context: &'recovered Context, - ) -> Result, AstError> { + pub fn return_type(&self, context: &Context) -> Result, AstError> { let return_type = match self { - Expression::Block(block_expression) => { - return block_expression.inner.return_type(context) + Expression::Block(block_expression) => block_expression.inner.return_type(context)?, + Expression::Break(expression_node) => { + if let Some(expression) = expression_node.inner.as_ref() { + expression.return_type(context)? + } else { + None + } } Expression::Call(call_expression) => { let CallExpression { invoker, .. } = call_expression.inner.as_ref(); @@ -479,6 +484,7 @@ impl Expression { pub fn position(&self) -> Span { match self { Expression::Block(block) => block.position, + Expression::Break(expression_node) => expression_node.position, Expression::Call(call) => call.position, Expression::FieldAccess(field_access) => field_access.position, Expression::Grouped(grouped) => grouped.position, @@ -501,6 +507,13 @@ impl Display for Expression { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Expression::Block(block) => write!(f, "{}", block.inner), + Expression::Break(break_node) => { + if let Some(expression_node) = &break_node.inner { + write!(f, "break {};", expression_node) + } else { + write!(f, "break;") + } + } Expression::Call(call) => write!(f, "{}", call.inner), Expression::FieldAccess(field_access) => write!(f, "{}", field_access.inner), Expression::Grouped(grouped) => write!(f, "({})", grouped.inner), @@ -1027,10 +1040,7 @@ pub enum BlockExpression { } impl BlockExpression { - fn return_type<'recovered>( - &self, - context: &'recovered Context, - ) -> Result, AstError> { + fn return_type(&self, context: &Context) -> Result, AstError> { match self { BlockExpression::Async(statements) | BlockExpression::Sync(statements) => { if let Some(statement) = statements.last() { diff --git a/dust-lang/src/ast/mod.rs b/dust-lang/src/ast/mod.rs index ed1ba1b..89cae04 100644 --- a/dust-lang/src/ast/mod.rs +++ b/dust-lang/src/ast/mod.rs @@ -12,7 +12,7 @@ use std::{ use serde::{Deserialize, Serialize}; -use crate::{ContextError, Type}; +use crate::ContextError; pub type Span = (usize, usize); diff --git a/dust-lang/src/ast/statement.rs b/dust-lang/src/ast/statement.rs index ed0e360..cf57d25 100644 --- a/dust-lang/src/ast/statement.rs +++ b/dust-lang/src/ast/statement.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter}; use serde::{Deserialize, Serialize}; -use crate::{Context, ContextError, Identifier, Type}; +use crate::{Context, Identifier, Type}; use super::{AstError, Expression, Node, Span}; @@ -28,10 +28,7 @@ impl Statement { } } - pub fn return_type<'recovered>( - &self, - context: &'recovered Context, - ) -> Result, AstError> { + pub fn return_type(&self, context: &Context) -> Result, AstError> { match self { Statement::Expression(expression) => expression.return_type(context), Statement::ExpressionNullified(_) => Ok(None), diff --git a/dust-lang/src/dust_error.rs b/dust-lang/src/dust_error.rs index 6ecd46e..c5b89a2 100644 --- a/dust-lang/src/dust_error.rs +++ b/dust-lang/src/dust_error.rs @@ -1,5 +1,5 @@ //! Top-level error handling for the Dust language. -use annotate_snippets::{Level, Message, Renderer, Snippet}; +use annotate_snippets::{Level, Renderer, Snippet}; use std::fmt::Display; use crate::{ast::Span, AnalysisError, LexError, ParseError, RuntimeError}; diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs index 930ef0c..9bc87c7 100644 --- a/dust-lang/src/lexer.rs +++ b/dust-lang/src/lexer.rs @@ -424,12 +424,14 @@ impl<'src> Lexer<'src> { "NaN" => Token::Float("NaN"), "async" => Token::Async, "bool" => Token::Bool, + "break" => Token::Break, "else" => Token::Else, "false" => Token::Boolean("false"), "float" => Token::FloatKeyword, "if" => Token::If, "int" => Token::Int, "let" => Token::Let, + "loop" => Token::Loop, "map" => Token::Map, "mut" => Token::Mut, "struct" => Token::Struct, diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index 40cfddf..af637a1 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -413,9 +413,33 @@ impl<'src> Parser<'src> { error, position: start_position, })?; - let statement = Expression::literal(boolean, start_position); + let expression = Expression::literal(boolean, start_position); - Ok(statement) + Ok(expression) + } + Token::Break => { + let break_end = self.current_position.1; + + self.next_token()?; + + let (expression_option, end) = if let Token::Semicolon = self.current_token { + // Do not consume the semicolon, allowing it to nullify the expression + + (None, break_end) + } else if let Ok(expression) = self.parse_expression(0) { + let end = expression.position().1; + + (Some(expression), end) + } else { + return Err(ParseError::ExpectedToken { + expected: TokenKind::Semicolon, + actual: self.current_token.to_owned(), + position: self.current_position, + }); + }; + let position = (start_position.0, end); + + Ok(Expression::r#break(expression_option, position)) } Token::Float(text) => { self.next_token()?; @@ -586,6 +610,14 @@ impl<'src> Parser<'src> { expressions.push(expression); } } + Token::Loop => { + self.next_token()?; + + let block = self.parse_block()?; + let position = (start_position.0, block.position.1); + + Ok(Expression::infinite_loop(block, position)) + } Token::Map => { self.next_token()?; @@ -1145,6 +1177,27 @@ mod tests { use super::*; + #[test] + fn break_loop() { + let source = "loop { break; }"; + + assert_eq!( + parse(source), + Ok(AbstractSyntaxTree::with_statements([ + Statement::Expression(Expression::infinite_loop( + Node::new( + BlockExpression::Sync(vec![Statement::ExpressionNullified(Node::new( + Expression::r#break(None, (7, 12)), + (7, 13) + ))]), + (5, 15) + ), + (0, 15) + )) + ])) + ); + } + #[test] fn built_in_function() { let source = "42.to_string()"; diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index 1979c30..9e613fe 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -3,10 +3,6 @@ use std::fmt::{self, Display, Formatter}; use serde::{Deserialize, Serialize}; -pub struct Raw<'src> { - data: &'src str, -} - /// Source code token. #[derive(Debug, Serialize, Deserialize, PartialEq)] pub enum Token<'src> { @@ -23,11 +19,13 @@ pub enum Token<'src> { // Keywords Async, Bool, + Break, Else, FloatKeyword, If, Int, Let, + Loop, Map, Mut, Str, @@ -73,6 +71,7 @@ impl<'src> Token<'src> { Token::Bang => TokenOwned::Bang, Token::Bool => TokenOwned::Bool, Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()), + Token::Break => TokenOwned::Break, Token::Colon => TokenOwned::Colon, Token::Comma => TokenOwned::Comma, Token::Dot => TokenOwned::Dot, @@ -97,6 +96,7 @@ impl<'src> Token<'src> { Token::Let => TokenOwned::Let, Token::Less => TokenOwned::Less, Token::LessEqual => TokenOwned::LessOrEqual, + Token::Loop => TokenOwned::Loop, Token::Map => TokenOwned::Map, Token::Minus => TokenOwned::Minus, Token::MinusEqual => TokenOwned::MinusEqual, @@ -129,6 +129,7 @@ impl<'src> Token<'src> { Token::BangEqual => "!=", Token::Bang => "!", Token::Bool => "bool", + Token::Break => "break", Token::Colon => ":", Token::Comma => ",", Token::Dot => ".", @@ -150,6 +151,7 @@ impl<'src> Token<'src> { Token::Let => "let", Token::Less => "<", Token::LessEqual => "<=", + Token::Loop => "loop", Token::Map => "map", Token::Minus => "-", Token::MinusEqual => "-=", @@ -176,6 +178,7 @@ impl<'src> Token<'src> { Token::Bang => TokenKind::Bang, Token::Bool => TokenKind::Bool, Token::Boolean(_) => TokenKind::Boolean, + Token::Break => TokenKind::Break, Token::Colon => TokenKind::Colon, Token::Comma => TokenKind::Comma, Token::Dot => TokenKind::Dot, @@ -200,6 +203,7 @@ impl<'src> Token<'src> { Token::Let => TokenKind::Let, Token::Less => TokenKind::Less, Token::LessEqual => TokenKind::LessOrEqual, + Token::Loop => TokenKind::Loop, Token::Map => TokenKind::Map, Token::Minus => TokenKind::Minus, Token::MinusEqual => TokenKind::MinusEqual, @@ -296,11 +300,13 @@ pub enum TokenOwned { // Keywords Bool, + Break, Else, FloatKeyword, If, Int, Let, + Loop, Map, Mut, Str, @@ -347,6 +353,7 @@ impl Display for TokenOwned { TokenOwned::BangEqual => Token::BangEqual.fmt(f), TokenOwned::Bool => Token::Bool.fmt(f), TokenOwned::Boolean(boolean) => Token::Boolean(boolean).fmt(f), + TokenOwned::Break => Token::Break.fmt(f), TokenOwned::Colon => Token::Colon.fmt(f), TokenOwned::Comma => Token::Comma.fmt(f), TokenOwned::Dot => Token::Dot.fmt(f), @@ -371,6 +378,7 @@ impl Display for TokenOwned { TokenOwned::Let => Token::Let.fmt(f), TokenOwned::Less => Token::Less.fmt(f), TokenOwned::LessOrEqual => Token::LessEqual.fmt(f), + TokenOwned::Loop => Token::Loop.fmt(f), TokenOwned::Map => Token::Map.fmt(f), TokenOwned::Minus => Token::Minus.fmt(f), TokenOwned::MinusEqual => Token::MinusEqual.fmt(f), @@ -385,7 +393,7 @@ impl Display for TokenOwned { TokenOwned::Star => Token::Star.fmt(f), TokenOwned::Slash => Token::Slash.fmt(f), TokenOwned::Str => Token::Str.fmt(f), - TokenOwned::String(string) => write!(f, "{string}"), + TokenOwned::String(string) => Token::String(string).fmt(f), TokenOwned::Struct => Token::Struct.fmt(f), TokenOwned::While => Token::While.fmt(f), } @@ -408,11 +416,13 @@ pub enum TokenKind { // Keywords Async, Bool, + Break, Else, FloatKeyword, If, Int, Let, + Loop, Map, Str, While, @@ -458,6 +468,7 @@ impl Display for TokenKind { TokenKind::BangEqual => Token::BangEqual.fmt(f), TokenKind::Bool => Token::Bool.fmt(f), TokenKind::Boolean => write!(f, "boolean value"), + TokenKind::Break => Token::Break.fmt(f), TokenKind::Colon => Token::Colon.fmt(f), TokenKind::Comma => Token::Comma.fmt(f), TokenKind::Dot => Token::Dot.fmt(f), @@ -482,6 +493,7 @@ impl Display for TokenKind { TokenKind::Let => Token::Let.fmt(f), TokenKind::Less => Token::Less.fmt(f), TokenKind::LessOrEqual => Token::LessEqual.fmt(f), + TokenKind::Loop => Token::Loop.fmt(f), TokenKind::Map => Token::Map.fmt(f), TokenKind::Minus => Token::Minus.fmt(f), TokenKind::MinusEqual => Token::MinusEqual.fmt(f), @@ -494,7 +506,7 @@ impl Display for TokenKind { TokenKind::RightSquareBrace => Token::RightSquareBrace.fmt(f), TokenKind::Semicolon => Token::Semicolon.fmt(f), TokenKind::Star => Token::Star.fmt(f), - TokenKind::Str => write!(f, "str"), + TokenKind::Str => Token::Str.fmt(f), TokenKind::Slash => Token::Slash.fmt(f), TokenKind::String => write!(f, "string value"), TokenKind::Struct => Token::Struct.fmt(f), @@ -507,17 +519,13 @@ impl Display for TokenKind { pub(crate) mod tests { use super::*; - pub fn all_tokens<'src>() -> [Token<'src>; 46] { + pub fn all_tokens<'src>() -> [Token<'src>; 47] { [ - Token::Identifier("foobar"), - Token::Boolean("true"), - Token::Float("1.0"), - Token::Integer("1"), - Token::String("string"), Token::Async, Token::Bang, Token::BangEqual, Token::Bool, + Token::Break, Token::Colon, Token::Comma, Token::Dot, @@ -536,9 +544,9 @@ pub(crate) mod tests { Token::LeftCurlyBrace, Token::LeftParenthesis, Token::LeftSquareBrace, + Token::Let, Token::Less, Token::LessEqual, - Token::Let, Token::Map, Token::Minus, Token::MinusEqual, @@ -550,9 +558,14 @@ pub(crate) mod tests { Token::RightParenthesis, Token::RightSquareBrace, Token::Semicolon, - Token::Slash, Token::Star, Token::Str, + Token::Slash, + Token::Boolean("true"), + Token::Float("0.0"), + Token::Integer("0"), + Token::String("string"), + Token::Identifier("foobar"), Token::Struct, Token::While, ] diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 96a24ce..3492d59 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -95,31 +95,39 @@ impl Vm { } pub fn run(&mut self) -> Result, RuntimeError> { - let mut previous_value = None; + let mut previous_evaluation = None; while let Some(statement) = self.abstract_tree.statements.pop_front() { - previous_value = self.run_statement(statement, true)?; + previous_evaluation = self.run_statement(statement, true)?; } - Ok(previous_value) + match previous_evaluation { + Some(Evaluation::Break(value_option)) => Ok(value_option), + Some(Evaluation::Return(value_option)) => Ok(value_option), + _ => Ok(None), + } } fn run_statement( &self, statement: Statement, collect_garbage: bool, - ) -> Result, RuntimeError> { + ) -> Result, RuntimeError> { log::debug!("Running statement: {}", statement); let position = statement.position(); let result = match statement { - Statement::Expression(expression) => self - .run_expression(expression, collect_garbage) - .map(|evaluation| evaluation.value()), + Statement::Expression(expression) => { + Ok(Some(self.run_expression(expression, collect_garbage)?)) + } Statement::ExpressionNullified(expression) => { - self.run_expression(expression.inner, collect_garbage)?; + let evaluation = self.run_expression(expression.inner, collect_garbage)?; - Ok(None) + if let Evaluation::Break(_) = evaluation { + Ok(Some(evaluation)) + } else { + Ok(None) + } } Statement::Let(let_statement) => { self.run_let_statement(let_statement.inner, collect_garbage)?; @@ -218,6 +226,23 @@ impl Vm { let position = expression.position(); let evaluation_result = match expression { Expression::Block(Node { inner, .. }) => self.run_block(*inner, collect_garbage), + Expression::Break(Node { inner, .. }) => { + let break_expression = if let Some(expression) = inner { + *expression + } else { + return Ok(Evaluation::Break(None)); + }; + let run_break = self.run_expression(break_expression, collect_garbage)?; + let evaluation = match run_break { + Evaluation::Break(value_option) => Evaluation::Break(value_option), + Evaluation::Return(value_option) => Evaluation::Break(value_option), + Evaluation::Constructor(_) => { + return Err(RuntimeError::ExpectedValue { position }) + } + }; + + Ok(evaluation) + } Expression::Call(call) => self.run_call(*call.inner, collect_garbage), Expression::FieldAccess(field_access) => { self.run_field_access(*field_access.inner, collect_garbage) @@ -585,8 +610,19 @@ impl Vm { fn run_loop(&self, loop_expression: LoopExpression) -> Result { match loop_expression { - LoopExpression::Infinite { block } => loop { - self.run_block(block.inner.clone(), false)?; + LoopExpression::Infinite { + block: Node { inner, .. }, + } => match inner { + BlockExpression::Sync(statements) => 'outer: loop { + for statement in statements.clone() { + let evaluation = self.run_statement(statement, false)?; + + if let Some(Evaluation::Break(value_option)) = evaluation { + break 'outer Ok(Evaluation::Return(value_option)); + } + } + }, + BlockExpression::Async(_) => todo!(), }, LoopExpression::While { condition, block } => { while self @@ -849,11 +885,11 @@ impl Vm { let evaluation_result = self.run_statement(statement, false); match evaluation_result { - Ok(evaluation) => { + Ok(evaluation_option) => { if i == statements_length - 1 { let mut final_result = final_result.lock().unwrap(); - *final_result = evaluation; + *final_result = evaluation_option; } None @@ -864,18 +900,27 @@ impl Vm { if let Some(error) = error_option { Err(error) + } else if let Some(evaluation) = final_result.lock().unwrap().take() { + Ok(evaluation) } else { - Ok(Evaluation::Return(final_result.lock().unwrap().clone())) + Ok(Evaluation::Return(None)) } } BlockExpression::Sync(statements) => { - let mut previous_value = None; + let mut previous_evaluation = None; for statement in statements { - previous_value = self.run_statement(statement, collect_garbage)?; + previous_evaluation = self.run_statement(statement, collect_garbage)?; + + if let Some(Evaluation::Break(value_option)) = previous_evaluation { + return Ok(Evaluation::Break(value_option)); + } } - Ok(Evaluation::Return(previous_value)) + match previous_evaluation { + Some(evaluation) => Ok(evaluation), + None => Ok(Evaluation::Return(None)), + } } } } @@ -898,7 +943,11 @@ impl Vm { .ok_or(RuntimeError::ExpectedBoolean { position })?; if boolean { - self.run_block(if_block.inner, collect_garbage)?; + let evaluation = self.run_block(if_block.inner, collect_garbage)?; + + if let Evaluation::Break(_) = evaluation { + return Ok(evaluation); + } } Ok(Evaluation::Return(None)) @@ -916,7 +965,11 @@ impl Vm { .ok_or(RuntimeError::ExpectedBoolean { position })?; if boolean { - self.run_block(if_block.inner, collect_garbage)?; + let evaluation = self.run_block(if_block.inner, collect_garbage)?; + + if let Evaluation::Break(_) = evaluation { + return Ok(evaluation); + } } match r#else { @@ -931,7 +984,7 @@ impl Vm { } enum Evaluation { - Break, + Break(Option), Constructor(Constructor), Return(Option), } @@ -1262,6 +1315,13 @@ mod tests { use super::*; + #[test] + fn break_loop() { + let input = "let mut x = 0; loop { x += 1; if x == 10 { break; } } x"; + + assert_eq!(run(input), Ok(Some(Value::mutable(Value::Integer(10))))); + } + #[test] fn string_index() { let input = "'foo'[0]";