diff --git a/dust-lang/src/abstract_tree.rs b/dust-lang/src/abstract_tree.rs index c2343d0..325f55b 100644 --- a/dust-lang/src/abstract_tree.rs +++ b/dust-lang/src/abstract_tree.rs @@ -55,7 +55,8 @@ pub enum Statement { value: Box>, }, - // A sequence of statements + // Statement blocks, delimited by curly braces + AsyncBlock(Vec>), Block(Vec>), // Logic, math and comparison expressions with two operands @@ -139,8 +140,9 @@ pub enum Statement { impl Statement { pub fn expected_type(&self, context: &Context) -> Option { match self { + Statement::AsyncBlock(_) => None, Statement::Assignment { .. } => None, - Statement::Block(nodes) => nodes.last().unwrap().inner.expected_type(context), + Statement::Block(statements) => statements.last().unwrap().inner.expected_type(context), Statement::BinaryOperation { left, operator, @@ -247,6 +249,19 @@ impl Display for Statement { } => { write!(f, "{identifier} {operator} {value}") } + Statement::AsyncBlock(statements) => { + write!(f, "async {{ ")?; + + for (i, statement) in statements.iter().enumerate() { + if i > 0 { + write!(f, " ")?; + } + + write!(f, "{statement}")?; + } + + write!(f, " }}") + } Statement::Block(statements) => { write!(f, "{{ ")?; diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index 0fdbf54..5bc1a02 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -93,6 +93,13 @@ impl<'a> Analyzer<'a> { return Ok(()); } + Statement::AsyncBlock(statements) => { + for statement in statements { + self.analyze_statement(statement)?; + } + + return Ok(()); + } Statement::BinaryOperation { left, operator, diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs index bb83dfc..6694e20 100644 --- a/dust-lang/src/lexer.rs +++ b/dust-lang/src/lexer.rs @@ -419,6 +419,7 @@ impl Lexer { let token = match string { "Infinity" => Token::Float("Infinity"), "NaN" => Token::Float("NaN"), + "async" => Token::Async, "bool" => Token::Bool, "else" => Token::Else, "false" => Token::Boolean("false"), @@ -507,27 +508,28 @@ mod tests { #[test] fn all_keywords() { - let input = "bool else false float if int is_even is_odd length read_line struct to_string true while write_line"; + let input = "async bool else false float if int is_even is_odd length read_line struct to_string true while write_line"; assert_eq!( lex(input), Ok(vec![ - (Token::Bool, (0, 4)), - (Token::Else, (5, 9)), - (Token::Boolean("false"), (10, 15)), - (Token::FloatKeyword, (16, 21)), - (Token::If, (22, 24)), - (Token::Int, (25, 28)), - (Token::IsEven, (29, 36)), - (Token::IsOdd, (37, 43)), - (Token::Length, (44, 50)), - (Token::ReadLine, (51, 60)), - (Token::Struct, (61, 67)), - (Token::ToString, (68, 77)), - (Token::Boolean("true"), (78, 82)), - (Token::While, (83, 88)), - (Token::WriteLine, (89, 99)), - (Token::Eof, (99, 99)), + (Token::Async, (0, 5)), + (Token::Bool, (6, 10)), + (Token::Else, (11, 15)), + (Token::Boolean("false"), (16, 21)), + (Token::FloatKeyword, (22, 27)), + (Token::If, (28, 30)), + (Token::Int, (31, 34)), + (Token::IsEven, (35, 42)), + (Token::IsOdd, (43, 49)), + (Token::Length, (50, 56)), + (Token::ReadLine, (57, 66)), + (Token::Struct, (67, 73)), + (Token::ToString, (74, 83)), + (Token::Boolean("true"), (84, 88)), + (Token::While, (89, 94)), + (Token::WriteLine, (95, 105)), + (Token::Eof, (105, 105)), ]) ); } diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index f96b615..bbb683f 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -229,6 +229,37 @@ impl<'src> Parser<'src> { fn parse_primary(&mut self) -> Result, ParseError> { match self.current { + (Token::Async, position) => { + self.next_token()?; + + if let Token::LeftCurlyBrace = self.current.0 { + self.next_token()?; + } else { + return Err(ParseError::UnexpectedToken { + actual: self.current.0.to_owned(), + position: self.current.1, + }); + } + + let mut statements = Vec::new(); + + loop { + if let Token::RightCurlyBrace = self.current.0 { + let right_end = self.current.1 .1; + + self.next_token()?; + + return Ok(Node::new( + Statement::AsyncBlock(statements), + (position.0, right_end), + )); + } + + let statement = self.parse_statement(0)?; + + statements.push(statement); + } + } (Token::Boolean(text), position) => { self.next_token()?; @@ -1180,6 +1211,48 @@ mod tests { use super::*; + #[test] + fn async_block() { + let input = "async { x = 42; y = 4.0 }"; + + assert_eq!( + parse(input), + Ok(AbstractSyntaxTree { + nodes: [Node::new( + Statement::AsyncBlock(vec![ + Node::new( + Statement::Nil(Box::new(Node::new( + Statement::Assignment { + identifier: Node::new(Identifier::new("x"), (8, 9)), + operator: Node::new(AssignmentOperator::Assign, (10, 11)), + value: Box::new(Node::new( + Statement::Constant(Value::integer(42)), + (12, 14) + )), + }, + (8, 14) + ))), + (8, 15) + ), + Node::new( + Statement::Assignment { + identifier: Node::new(Identifier::new("y"), (16, 17)), + operator: Node::new(AssignmentOperator::Assign, (18, 19)), + value: Box::new(Node::new( + Statement::Constant(Value::float(4.0)), + (20, 23) + )), + }, + (16, 23) + ) + ]), + (0, 25) + )] + .into() + }) + ); + } + #[test] fn tuple_struct_access() { let input = "Foo(42, 'bar').0"; diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index f916219..b0b2bf8 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -17,6 +17,7 @@ pub enum Token<'src> { String(&'src str), // Keywords + Async, Bool, Else, FloatKeyword, @@ -65,6 +66,7 @@ pub enum Token<'src> { impl<'src> Token<'src> { pub fn to_owned(&self) -> TokenOwned { match self { + Token::Async => TokenOwned::Async, Token::Bang => TokenOwned::Bang, Token::Bool => TokenOwned::Bool, Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()), @@ -123,6 +125,7 @@ impl<'src> Token<'src> { Token::Integer(integer_text) => integer_text, Token::String(text) => text, + Token::Async => "async", Token::Bang => "!", Token::Bool => "bool", Token::Colon => ":", @@ -170,6 +173,7 @@ impl<'src> Token<'src> { pub fn kind(&self) -> TokenKind { match self { + Token::Async => TokenKind::Async, Token::Bang => TokenKind::Bang, Token::Bool => TokenKind::Bool, Token::Boolean(_) => TokenKind::Boolean, @@ -309,6 +313,7 @@ pub enum TokenOwned { WriteLine, // Symbols + Async, Bang, Colon, Comma, @@ -342,6 +347,7 @@ pub enum TokenOwned { impl Display for TokenOwned { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { + TokenOwned::Async => Token::Async.fmt(f), TokenOwned::Bang => Token::Bang.fmt(f), TokenOwned::Bool => write!(f, "bool"), TokenOwned::Boolean(boolean) => write!(f, "{boolean}"), @@ -407,6 +413,7 @@ pub enum TokenKind { String, // Keywords + Async, Bool, Else, FloatKeyword, @@ -455,6 +462,7 @@ pub enum TokenKind { impl Display for TokenKind { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { + TokenKind::Async => Token::Async.fmt(f), TokenKind::Bang => Token::Bang.fmt(f), TokenKind::Bool => Token::Bool.fmt(f), TokenKind::Boolean => write!(f, "boolean value"), @@ -510,8 +518,9 @@ 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::Async, Token::Bang, Token::Bool, Token::Boolean("true"), diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 8f246b2..efd573f 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -9,6 +9,8 @@ use std::{ fmt::{self, Display, Formatter}, }; +use rayon::iter::{IntoParallelIterator, ParallelIterator}; + use crate::{ parse, value::ValueInner, AbstractSyntaxTree, Analyzer, AssignmentOperator, BinaryOperator, BuiltInFunctionError, Context, DustError, Identifier, Node, ParseError, Span, Statement, @@ -173,6 +175,17 @@ impl Vm { Ok(None) } }, + Statement::AsyncBlock(statements) => { + let error_option = statements + .into_par_iter() + .find_map_any(|statement| self.run_statement(statement).err()); + + if let Some(error) = error_option { + return Err(error); + } + + Ok(None) + } Statement::BinaryOperation { left, operator, @@ -856,6 +869,13 @@ mod tests { use super::*; + #[test] + fn async_block() { + let input = "x = 1; async { x += 1; x -= 1; } x"; + + assert!(run(input).unwrap().unwrap().as_integer().is_some()); + } + #[test] fn define_and_instantiate_fields_struct() { let input = "struct Foo { bar: int, baz: float } Foo { bar = 42, baz = 4.0 }";