From 0fb0b63a976ce488bd54c7851514180bd9877c4e Mon Sep 17 00:00:00 2001 From: Jeff Date: Mon, 12 Aug 2024 15:02:04 -0400 Subject: [PATCH] Fix dot notations precedence; Add some miscellaneous expansions --- dust-lang/src/abstract_tree.rs | 36 +++++++-- dust-lang/src/analyzer.rs | 73 +++++++++--------- dust-lang/src/lexer.rs | 9 +++ dust-lang/src/parser.rs | 118 ++++++++++++++++-------------- dust-lang/src/token.rs | 130 +++++++++++++++++++++++++++++++-- dust-lang/src/type.rs | 2 + dust-lang/src/value.rs | 65 +++++++++++++++++ dust-lang/src/vm.rs | 103 +++++++++++++------------- 8 files changed, 384 insertions(+), 152 deletions(-) diff --git a/dust-lang/src/abstract_tree.rs b/dust-lang/src/abstract_tree.rs index dc27996..85b5e96 100644 --- a/dust-lang/src/abstract_tree.rs +++ b/dust-lang/src/abstract_tree.rs @@ -62,10 +62,6 @@ pub enum Statement { value_arguments: Option>>, }, - // Property access expression - // TODO: This should be a binary operation - PropertyAccess(Box>, Box>), - // Loops While { condition: Box>, @@ -113,7 +109,11 @@ impl Statement { pub fn expected_type(&self, context: &Context) -> Option { match self { Statement::Block(nodes) => nodes.last().unwrap().inner.expected_type(context), - Statement::BinaryOperation { left, operator, .. } => match operator.inner { + Statement::BinaryOperation { + left, + operator, + right, + } => match operator.inner { BinaryOperator::Add | BinaryOperator::Divide | BinaryOperator::Modulo @@ -129,6 +129,21 @@ impl Statement { | BinaryOperator::Or => Some(Type::Boolean), BinaryOperator::Assign | BinaryOperator::AddAssign => None, + + BinaryOperator::FieldAccess => { + let left_type = left.inner.expected_type(context)?; + + if let Type::Map(properties) = left_type { + let key = match &right.inner { + Statement::Identifier(identifier) => identifier, + _ => return None, + }; + + properties.get(key).cloned() + } else { + None + } + } }, Statement::BuiltInFunctionCall { function, .. } => function.expected_return_type(), Statement::Constant(value) => Some(value.r#type(context)), @@ -157,7 +172,6 @@ impl Statement { Some(Type::Map(types)) } Statement::Nil(_) => None, - Statement::PropertyAccess(_, _) => None, Statement::UnaryOperation { operator, operand } => match operator.inner { UnaryOperator::Negate => Some(operand.inner.expected_type(context)?), UnaryOperator::Not => Some(Type::Boolean), @@ -202,7 +216,11 @@ impl Display for Statement { operator, right, } => { - write!(f, "{left} {operator} {right}") + if let BinaryOperator::FieldAccess = operator.inner { + write!(f, "{left}{operator}{right}") + } else { + write!(f, "{left} {operator} {right}") + } } Statement::BuiltInFunctionCall { function, @@ -340,7 +358,6 @@ impl Display for Statement { write!(f, "}}") } Statement::Nil(node) => write!(f, "{node};"), - Statement::PropertyAccess(left, right) => write!(f, "{left}.{right}"), Statement::UnaryOperation { operator, operand } => { write!(f, "{operator}{operand}") } @@ -353,6 +370,8 @@ impl Display for Statement { #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] pub enum BinaryOperator { + FieldAccess, + // Math Add, Divide, @@ -385,6 +404,7 @@ impl Display for BinaryOperator { BinaryOperator::And => write!(f, "&&"), BinaryOperator::Divide => write!(f, "/"), BinaryOperator::Equal => write!(f, "=="), + BinaryOperator::FieldAccess => write!(f, "."), BinaryOperator::Greater => write!(f, ">"), BinaryOperator::GreaterOrEqual => write!(f, ">="), BinaryOperator::Less => write!(f, "<"), diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index 7771b83..e3f0343 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -96,6 +96,44 @@ impl<'a> Analyzer<'a> { } } + if let BinaryOperator::FieldAccess = operator.inner { + self.analyze_statement(left)?; + + if let Statement::Identifier(_) = right.inner { + // Do not expect a value for property accessors + } else { + self.analyze_statement(right)?; + } + + if let Some(Type::List { .. }) = left.inner.expected_type(self.context) { + let right_type = right.inner.expected_type(self.context); + + if let Some(Type::Integer) = right_type { + // Allow indexing lists with integers + } else if let Some(Type::Range) = right_type { + // Allow indexing lists with ranges + } else { + return Err(AnalyzerError::ExpectedIntegerOrRange { + actual: right.as_ref().clone(), + }); + } + } + + if let Some(Type::Map { .. }) = left.inner.expected_type(self.context) { + if let Some(Type::String) = right.inner.expected_type(self.context) { + // Allow indexing maps with strings + } else if let Statement::Identifier(_) = right.inner { + // Allow indexing maps with identifiers + } else { + return Err(AnalyzerError::ExpectedIdentifierOrString { + actual: right.as_ref().clone(), + }); + } + } + + return Ok(()); + } + self.analyze_statement(left)?; self.analyze_statement(right)?; @@ -324,41 +362,6 @@ impl<'a> Analyzer<'a> { Statement::Nil(node) => { self.analyze_statement(node)?; } - Statement::PropertyAccess(left, right) => { - self.analyze_statement(left)?; - - if let Statement::Identifier(_) = right.inner { - // Do not expect a value for property accessors - } else { - self.analyze_statement(right)?; - } - - if let Some(Type::List { .. }) = left.inner.expected_type(self.context) { - let right_type = right.inner.expected_type(self.context); - - if let Some(Type::Integer) = right_type { - // Allow indexing lists with integers - } else if let Some(Type::Range) = right_type { - // Allow indexing lists with ranges - } else { - return Err(AnalyzerError::ExpectedIntegerOrRange { - actual: right.as_ref().clone(), - }); - } - } - - if let Some(Type::Map { .. }) = left.inner.expected_type(self.context) { - if let Some(Type::String) = right.inner.expected_type(self.context) { - // Allow indexing maps with strings - } else if let Statement::Identifier(_) = right.inner { - // Allow indexing maps with identifiers - } else { - return Err(AnalyzerError::ExpectedIdentifierOrString { - actual: right.as_ref().clone(), - }); - } - } - } Statement::UnaryOperation { operator, operand } => { self.analyze_statement(operand)?; diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs index f7c2cfb..efe1079 100644 --- a/dust-lang/src/lexer.rs +++ b/dust-lang/src/lexer.rs @@ -264,6 +264,11 @@ impl Lexer { (Token::Bang, (self.position - 1, self.position)) } + ':' => { + self.position += 1; + + (Token::Colon, (self.position - 1, self.position)) + } _ => { self.position += 1; @@ -408,13 +413,17 @@ impl Lexer { let token = match string { "Infinity" => Token::Float("Infinity"), "NaN" => Token::Float("NaN"), + "bool" => Token::Bool, "else" => Token::Else, "false" => Token::Boolean("false"), + "float" => Token::FloatKeyword, "if" => Token::If, + "int" => Token::Int, "is_even" => Token::IsEven, "is_odd" => Token::IsOdd, "length" => Token::Length, "read_line" => Token::ReadLine, + "struct" => Token::Struct, "to_string" => Token::ToString, "true" => Token::Boolean("true"), "while" => Token::While, diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index ba06653..fd9d2b9 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -604,6 +604,8 @@ impl<'src> Parser<'src> { }; if let Token::Dot = &self.current.0 { + let operator_position = self.current.1; + self.next_token()?; let right = self.parse_statement(operator_precedence)?; @@ -658,7 +660,11 @@ impl<'src> Parser<'src> { } return Ok(Node::new( - Statement::PropertyAccess(Box::new(left), Box::new(right)), + Statement::BinaryOperation { + left: Box::new(left), + operator: Node::new(BinaryOperator::FieldAccess, operator_position), + right: Box::new(right), + }, (left_start, right_end), )); } @@ -852,47 +858,48 @@ mod tests { assert_eq!( parse(input), Ok(AbstractSyntaxTree { - nodes: [Node { - inner: Statement::PropertyAccess( - Box::new(Node { - inner: Statement::Map(vec![( - Node { - inner: Statement::Identifier(Identifier::new("x")), - position: (2, 3) - }, - Node { - inner: Statement::Map(vec![( - Node { - inner: Statement::Identifier(Identifier::new("y")), - position: (8, 9) - }, - Node { - inner: Statement::Constant(Value::integer(42)), - position: (12, 14) - } + nodes: [Node::new( + Statement::BinaryOperation { + left: Box::new(Node::new( + Statement::BinaryOperation { + left: Box::new(Node::new( + Statement::Map(vec![( + Node::new( + Statement::Identifier(Identifier::new("x")), + (2, 3) + ), + Node::new( + Statement::Map(vec![( + Node::new( + Statement::Identifier(Identifier::new("y")), + (8, 9) + ), + Node::new( + Statement::Constant(Value::integer(42)), + (12, 14) + ) + )]), + (6, 16) + ) )]), - position: (6, 16) - } - )]), - position: (0, 18) - }), - Box::new(Node { - inner: Statement::PropertyAccess( - Box::new(Node { - inner: Statement::Identifier(Identifier::new("x")), - position: (19, 20) - }), - Box::new(Node { - inner: Statement::Identifier(Identifier::new("y")), - - position: (21, 22) - }) - ), - position: (19, 22) - }) - ), - position: (0, 22) - }] + (0, 18) + )), + operator: Node::new(BinaryOperator::FieldAccess, (18, 19)), + right: Box::new(Node::new( + Statement::Identifier(Identifier::new("x")), + (19, 20) + )) + }, + (0, 20) + )), + operator: Node::new(BinaryOperator::FieldAccess, (20, 21)), + right: Box::new(Node::new( + Statement::Identifier(Identifier::new("y")), + (21, 22) + )) + }, + (0, 22) + )] .into() }) ) @@ -1444,7 +1451,7 @@ mod tests { } #[test] - fn map_with_two_properties() { + fn map_with_two_fields() { let input = "{ x = 42, y = 'foobar' }"; assert_eq!( @@ -1469,8 +1476,8 @@ mod tests { } #[test] - fn map_with_one_property() { - let input = "{ x = 42, }"; + fn map_with_one_field() { + let input = "{ x = 42 }"; assert_eq!( parse(input), @@ -1480,7 +1487,7 @@ mod tests { Node::new(Statement::Identifier(Identifier::new("x")), (2, 3)), Node::new(Statement::Constant(Value::integer(42)), (6, 8)) )]), - (0, 11) + (0, 10) )] .into() }) @@ -1732,8 +1739,8 @@ mod tests { parse(input), Ok(AbstractSyntaxTree { nodes: [Node::new( - Statement::PropertyAccess( - Box::new(Node::new( + Statement::BinaryOperation { + left: Box::new(Node::new( Statement::List(vec![ Node::new(Statement::Constant(Value::integer(1)), (1, 2)), Node::new(Statement::Constant(Value::integer(2)), (4, 5)), @@ -1741,8 +1748,12 @@ mod tests { ]), (0, 9) )), - Box::new(Node::new(Statement::Constant(Value::integer(0)), (10, 11))), - ), + operator: Node::new(BinaryOperator::FieldAccess, (9, 10)), + right: Box::new(Node::new( + Statement::Constant(Value::integer(0)), + (10, 11) + )), + }, (0, 11), )] .into() @@ -1758,16 +1769,17 @@ mod tests { parse(input), Ok(AbstractSyntaxTree { nodes: [Node::new( - Statement::PropertyAccess( - Box::new(Node::new( + Statement::BinaryOperation { + left: Box::new(Node::new( Statement::Identifier(Identifier::new("a")), (0, 1) )), - Box::new(Node::new( + operator: Node::new(BinaryOperator::FieldAccess, (1, 2)), + right: Box::new(Node::new( Statement::Identifier(Identifier::new("b")), (2, 3) )), - ), + }, (0, 3), )] .into() diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index 1cde745..4ad04d3 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -17,18 +17,24 @@ pub enum Token<'src> { String(&'src str), // Keywords + Bool, Else, + FloatKeyword, If, + Int, IsEven, IsOdd, Length, ReadLine, + Str, + Struct, ToString, While, WriteLine, // Symbols Bang, + Colon, Comma, Dot, DoubleAmpersand, @@ -59,7 +65,9 @@ impl<'src> Token<'src> { pub fn to_owned(&self) -> TokenOwned { match self { Token::Bang => TokenOwned::Bang, + Token::Bool => TokenOwned::Bool, Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()), + Token::Colon => TokenOwned::Colon, Token::Comma => TokenOwned::Comma, Token::Dot => TokenOwned::Dot, Token::DoubleAmpersand => TokenOwned::DoubleAmpersand, @@ -70,10 +78,12 @@ impl<'src> Token<'src> { Token::Eof => TokenOwned::Eof, Token::Equal => TokenOwned::Equal, Token::Float(float) => TokenOwned::Float(float.to_string()), + Token::FloatKeyword => TokenOwned::FloatKeyword, Token::Greater => TokenOwned::Greater, Token::GreaterEqual => TokenOwned::GreaterOrEqual, Token::Identifier(text) => TokenOwned::Identifier(text.to_string()), Token::If => TokenOwned::If, + Token::Int => TokenOwned::Int, Token::Integer(integer) => TokenOwned::Integer(integer.to_string()), Token::IsEven => TokenOwned::IsEven, Token::IsOdd => TokenOwned::IsOdd, @@ -95,6 +105,8 @@ impl<'src> Token<'src> { Token::Star => TokenOwned::Star, Token::Slash => TokenOwned::Slash, Token::String(text) => TokenOwned::String(text.to_string()), + Token::Str => TokenOwned::Str, + Token::Struct => TokenOwned::Struct, Token::ToString => TokenOwned::ToString, Token::While => TokenOwned::While, Token::WriteLine => TokenOwned::WriteLine, @@ -104,11 +116,14 @@ impl<'src> Token<'src> { pub fn as_str(&self) -> &str { match self { Token::Boolean(boolean_text) => boolean_text, + Token::Float(float_text) => float_text, Token::Identifier(text) => text, Token::Integer(integer_text) => integer_text, Token::String(text) => text, Token::Bang => "!", + Token::Bool => "bool", + Token::Colon => ":", Token::Comma => ",", Token::Dot => ".", Token::DoubleAmpersand => "&&", @@ -118,10 +133,11 @@ impl<'src> Token<'src> { Token::Else => "else", Token::Eof => "EOF", Token::Equal => "=", - Token::Float(_) => "float", + Token::FloatKeyword => "float", Token::Greater => ">", Token::GreaterEqual => ">=", Token::If => "if", + Token::Int => "int", Token::IsEven => "is_even", Token::IsOdd => "is_odd", Token::LeftCurlyBrace => "{", @@ -141,6 +157,8 @@ impl<'src> Token<'src> { Token::Semicolon => ";", Token::Star => "*", Token::Slash => "/", + Token::Str => "str", + Token::Struct => "struct", Token::ToString => "to_string", Token::While => "while", Token::WriteLine => "write_line", @@ -150,7 +168,9 @@ impl<'src> Token<'src> { pub fn kind(&self) -> TokenKind { match self { Token::Bang => TokenKind::Bang, + Token::Bool => TokenKind::Bool, Token::Boolean(_) => TokenKind::Boolean, + Token::Colon => TokenKind::Colon, Token::Comma => TokenKind::Comma, Token::Dot => TokenKind::Dot, Token::DoubleAmpersand => TokenKind::DoubleAmpersand, @@ -161,10 +181,12 @@ impl<'src> Token<'src> { Token::Eof => TokenKind::Eof, Token::Equal => TokenKind::Equal, Token::Float(_) => TokenKind::Float, + Token::FloatKeyword => TokenKind::FloatKeyword, Token::Greater => TokenKind::Greater, Token::GreaterEqual => TokenKind::GreaterOrEqual, Token::Identifier(_) => TokenKind::Identifier, Token::If => TokenKind::If, + Token::Int => TokenKind::Int, Token::Integer(_) => TokenKind::Integer, Token::IsEven => TokenKind::IsEven, Token::IsOdd => TokenKind::IsOdd, @@ -185,7 +207,9 @@ impl<'src> Token<'src> { Token::Semicolon => TokenKind::Semicolon, Token::Star => TokenKind::Star, Token::Slash => TokenKind::Slash, + Token::Str => TokenKind::Str, Token::String(_) => TokenKind::String, + Token::Struct => TokenKind::Struct, Token::ToString => TokenKind::ToString, Token::While => TokenKind::While, Token::WriteLine => TokenKind::WriteLine, @@ -301,18 +325,23 @@ pub enum TokenOwned { String(String), // Keywords + Bool, Else, + FloatKeyword, If, + Int, IsEven, IsOdd, Length, ReadLine, + Str, ToString, While, WriteLine, // Symbols Bang, + Colon, Comma, Dot, DoubleAmpersand, @@ -336,6 +365,7 @@ pub enum TokenOwned { RightSquareBrace, Semicolon, Star, + Struct, Slash, } @@ -343,7 +373,9 @@ impl Display for TokenOwned { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { TokenOwned::Bang => Token::Bang.fmt(f), + TokenOwned::Bool => write!(f, "bool"), TokenOwned::Boolean(boolean) => write!(f, "{boolean}"), + TokenOwned::Colon => Token::Colon.fmt(f), TokenOwned::Comma => Token::Comma.fmt(f), TokenOwned::Dot => Token::Dot.fmt(f), TokenOwned::DoubleAmpersand => Token::DoubleAmpersand.fmt(f), @@ -354,10 +386,12 @@ impl Display for TokenOwned { TokenOwned::Eof => Token::Eof.fmt(f), TokenOwned::Equal => Token::Equal.fmt(f), TokenOwned::Float(float) => write!(f, "{float}"), + TokenOwned::FloatKeyword => write!(f, "float"), TokenOwned::Greater => Token::Greater.fmt(f), TokenOwned::GreaterOrEqual => Token::GreaterEqual.fmt(f), TokenOwned::Identifier(text) => write!(f, "{text}"), TokenOwned::If => Token::If.fmt(f), + TokenOwned::Int => write!(f, "int"), TokenOwned::Integer(integer) => write!(f, "{integer}"), TokenOwned::IsEven => Token::IsEven.fmt(f), TokenOwned::IsOdd => Token::IsOdd.fmt(f), @@ -378,7 +412,9 @@ impl Display for TokenOwned { TokenOwned::Semicolon => Token::Semicolon.fmt(f), TokenOwned::Star => Token::Star.fmt(f), TokenOwned::Slash => Token::Slash.fmt(f), + TokenOwned::Str => write!(f, "str"), TokenOwned::String(string) => write!(f, "{string}"), + TokenOwned::Struct => Token::Struct.fmt(f), TokenOwned::ToString => Token::ToString.fmt(f), TokenOwned::While => Token::While.fmt(f), TokenOwned::WriteLine => Token::WriteLine.fmt(f), @@ -400,18 +436,23 @@ pub enum TokenKind { String, // Keywords + Bool, Else, + FloatKeyword, If, + Int, IsEven, IsOdd, Length, ReadLine, + Str, ToString, While, WriteLine, // Symbols Bang, + Colon, Comma, Dot, DoubleAmpersand, @@ -435,6 +476,7 @@ pub enum TokenKind { RightSquareBrace, Semicolon, Star, + Struct, Slash, } @@ -442,7 +484,9 @@ impl Display for TokenKind { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { TokenKind::Bang => Token::Bang.fmt(f), - TokenKind::Boolean => write!(f, "boolean"), + TokenKind::Bool => Token::Bool.fmt(f), + TokenKind::Boolean => write!(f, "boolean value"), + TokenKind::Colon => Token::Colon.fmt(f), TokenKind::Comma => Token::Comma.fmt(f), TokenKind::Dot => Token::Dot.fmt(f), TokenKind::DoubleAmpersand => Token::DoubleAmpersand.fmt(f), @@ -452,12 +496,14 @@ impl Display for TokenKind { TokenKind::Else => Token::Else.fmt(f), TokenKind::Eof => Token::Eof.fmt(f), TokenKind::Equal => Token::Equal.fmt(f), - TokenKind::Float => write!(f, "float"), + TokenKind::Float => write!(f, "float value"), + TokenKind::FloatKeyword => Token::FloatKeyword.fmt(f), TokenKind::Greater => Token::Greater.fmt(f), TokenKind::GreaterOrEqual => Token::GreaterEqual.fmt(f), TokenKind::Identifier => write!(f, "identifier"), TokenKind::If => Token::If.fmt(f), - TokenKind::Integer => write!(f, "integer"), + TokenKind::Int => Token::Int.fmt(f), + TokenKind::Integer => write!(f, "integer value"), TokenKind::IsEven => Token::IsEven.fmt(f), TokenKind::IsOdd => Token::IsOdd.fmt(f), TokenKind::LeftCurlyBrace => Token::LeftCurlyBrace.fmt(f), @@ -476,11 +522,85 @@ 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::Slash => Token::Slash.fmt(f), - TokenKind::String => write!(f, "string"), + TokenKind::String => write!(f, "string value"), + TokenKind::Struct => Token::Struct.fmt(f), TokenKind::ToString => Token::ToString.fmt(f), TokenKind::While => Token::While.fmt(f), TokenKind::WriteLine => Token::WriteLine.fmt(f), } } } + +#[cfg(test)] +mod tests { + use super::*; + + fn all_tokens<'src>() -> [Token<'src>; 42] { + [ + Token::Bang, + Token::Boolean("true"), + Token::Colon, + Token::Comma, + Token::Dot, + Token::DoubleAmpersand, + Token::DoubleDot, + Token::DoubleEqual, + Token::DoublePipe, + Token::Else, + Token::Eof, + Token::Equal, + Token::Float("42.0"), + Token::Greater, + Token::GreaterEqual, + Token::Identifier("foobar"), + Token::If, + Token::Integer("42"), + Token::IsEven, + Token::IsOdd, + Token::LeftCurlyBrace, + Token::LeftParenthesis, + Token::LeftSquareBrace, + Token::Length, + Token::Less, + Token::LessEqual, + Token::Minus, + Token::Percent, + Token::Plus, + Token::PlusEqual, + Token::ReadLine, + Token::RightCurlyBrace, + Token::RightParenthesis, + Token::RightSquareBrace, + Token::Semicolon, + Token::Star, + Token::Slash, + Token::String("foobar"), + Token::Struct, + Token::ToString, + Token::While, + Token::WriteLine, + ] + } + + #[test] + fn token_displays() { + for token in all_tokens().iter() { + let display = token.to_string(); + + assert_eq!(display, token.to_owned().to_string()); + + if let Token::Boolean(_) + | Token::Float(_) + | Token::Identifier(_) + | Token::Integer(_) + | Token::String(_) = token + { + continue; + } else { + assert_eq!(display, token.kind().to_string()); + } + } + } +} diff --git a/dust-lang/src/type.rs b/dust-lang/src/type.rs index 33201fe..278cc97 100644 --- a/dust-lang/src/type.rs +++ b/dust-lang/src/type.rs @@ -31,6 +31,7 @@ pub struct TypeConflict { pub enum Type { Any, Boolean, + Defined(Identifier), Enum { name: Identifier, type_parameters: Option>, @@ -216,6 +217,7 @@ impl Display for Type { match self { Type::Any => write!(f, "any"), Type::Boolean => write!(f, "bool"), + Type::Defined(identifier) => write!(f, "{identifier}"), Type::Enum { variants, .. } => { write!(f, "enum ")?; diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index 4a86733..e1296bd 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -348,6 +348,7 @@ impl Display for Value { } ValueInner::Range(range) => write!(f, "{}..{}", range.start, range.end), ValueInner::String(string) => write!(f, "{string}"), + ValueInner::Struct(r#struct) => write!(f, "{struct}"), } } } @@ -403,6 +404,7 @@ impl Serialize for Value { tuple_ser.end() } ValueInner::String(string) => serializer.serialize_str(string), + ValueInner::Struct(r#struct) => r#struct.serialize(serializer), } } } @@ -659,6 +661,7 @@ pub enum ValueInner { Map(BTreeMap), Range(Range), String(String), + Struct(Struct), } impl ValueInner { @@ -692,6 +695,7 @@ impl ValueInner { } ValueInner::Range(_) => Type::Range, ValueInner::String(_) => Type::String, + ValueInner::Struct(r#struct) => Type::Defined(r#struct.name().clone()), } } @@ -747,6 +751,8 @@ impl Ord for ValueInner { (Range(_), _) => Ordering::Greater, (String(left), String(right)) => left.cmp(right), (String(_), _) => Ordering::Greater, + (Struct(left), Struct(right)) => left.cmp(right), + (Struct(_), _) => Ordering::Greater, } } } @@ -832,6 +838,65 @@ impl Display for Function { } } +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] +pub enum Struct { + Unit { + name: Identifier, + }, + Tuple { + name: Identifier, + fields: Vec, + }, + Fields { + name: Identifier, + fields: Vec<(Identifier, Value)>, + }, +} + +impl Struct { + pub fn name(&self) -> &Identifier { + match self { + Struct::Unit { name } => name, + Struct::Tuple { name, .. } => name, + Struct::Fields { name, .. } => name, + } + } +} + +impl Display for Struct { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Struct::Unit { name } => write!(f, "{}", name), + Struct::Tuple { name, fields } => { + write!(f, "{}(", name)?; + + for (index, field) in fields.iter().enumerate() { + if index > 0 { + write!(f, ", ")?; + } + + write!(f, "{}", field)?; + } + + write!(f, ")") + } + Struct::Fields { name, fields } => { + write!(f, "{} {{", name)?; + + for (index, (identifier, r#type)) in fields.iter().enumerate() { + if index > 0 { + write!(f, ", ")?; + } + + write!(f, "{}: {}", identifier, r#type)?; + } + + write!(f, "}}") + } + } + } +} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum ValueError { CannotAdd(Value, Value), diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index a61143d..673b8be 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -137,6 +137,58 @@ impl Vm { return Ok(None); } + if let BinaryOperator::FieldAccess = operator.inner { + let left_span = left.position; + let left_value = if let Some(value) = self.run_statement(*left)? { + value + } else { + return Err(VmError::ExpectedValue { + position: left_span, + }); + }; + let right_span = right.position; + + if let (Some(list), Statement::Constant(value)) = + (left_value.as_list(), &right.inner) + { + if let Some(index) = value.as_integer() { + let value = list.get(index as usize).cloned(); + + return Ok(value); + } + + if let Some(range) = value.as_range() { + let range = range.start as usize..range.end as usize; + + if let Some(items) = list.get(range) { + return Ok(Some(Value::list(items.to_vec()))); + } + } + } + + if let Some(map) = left_value.as_map() { + if let Statement::Identifier(identifier) = right.inner { + let value = map.get(&identifier).cloned(); + + return Ok(value); + } + + if let Some(value) = self.run_statement(*right)? { + if let Some(string) = value.as_string() { + let identifier = Identifier::new(string); + + let value = map.get(&identifier).cloned(); + + return Ok(value); + } + } + } + + return Err(VmError::ExpectedIdentifierIntegerOrRange { + position: right_span, + }); + } + let left_position = left.position; let left_value = if let Some(value) = self.run_statement(*left)? { value @@ -466,57 +518,6 @@ impl Vm { Ok(None) } - Statement::PropertyAccess(left, right) => { - let left_span = left.position; - let left_value = if let Some(value) = self.run_statement(*left)? { - value - } else { - return Err(VmError::ExpectedValue { - position: left_span, - }); - }; - let right_span = right.position; - - if let (Some(list), Statement::Constant(value)) = - (left_value.as_list(), &right.inner) - { - if let Some(index) = value.as_integer() { - let value = list.get(index as usize).cloned(); - - return Ok(value); - } - - if let Some(range) = value.as_range() { - let range = range.start as usize..range.end as usize; - - if let Some(items) = list.get(range) { - return Ok(Some(Value::list(items.to_vec()))); - } - } - } - - if let Some(map) = left_value.as_map() { - if let Statement::Identifier(identifier) = right.inner { - let value = map.get(&identifier).cloned(); - - return Ok(value); - } - - if let Some(value) = self.run_statement(*right)? { - if let Some(string) = value.as_string() { - let identifier = Identifier::new(string); - - let value = map.get(&identifier).cloned(); - - return Ok(value); - } - } - } - - Err(VmError::ExpectedIdentifierIntegerOrRange { - position: right_span, - }) - } Statement::UnaryOperation { operator, operand } => { let position = operand.position; let value = if let Some(value) = self.run_statement(*operand)? {