From de30f241a8694197ce928c3d23426b79541409f2 Mon Sep 17 00:00:00 2001 From: Jeff Date: Sun, 11 Aug 2024 21:37:44 -0400 Subject: [PATCH] Refactor function call dot notation; Add better analysis of function calls and property access --- dust-lang/src/analyzer.rs | 65 +++++++++++++++++++++++++++---------- dust-lang/src/parser.rs | 67 ++++++++++++++++++++++++++++++++------- dust-lang/src/value.rs | 20 +++++++++++- dust-lang/src/vm.rs | 17 +++++++++- 4 files changed, 139 insertions(+), 30 deletions(-) diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index 109aceb..e9485b1 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -190,14 +190,17 @@ impl<'a> Analyzer<'a> { } } Statement::Constant(_) => {} - Statement::FunctionCall { function, .. } => { - if let Statement::Identifier(_) = &function.inner { - // Function is in the correct position - } else { - return Err(AnalyzerError::ExpectedIdentifier { - actual: function.as_ref().clone(), - position: function.position, - }); + Statement::FunctionCall { + function, + value_arguments, + .. + } => { + self.analyze_node(function)?; + + if let Some(arguments) = value_arguments { + for argument in arguments { + self.analyze_node(argument)?; + } } } Statement::Identifier(identifier) => { @@ -325,14 +328,27 @@ impl<'a> Analyzer<'a> { self.analyze_node(node)?; } Statement::PropertyAccess(left, right) => { - if let Statement::Identifier(_) | Statement::Constant(_) | Statement::List(_) = - &left.inner - { - // Left side is valid - } else { - return Err(AnalyzerError::ExpectedValue { - actual: left.as_ref().clone(), - }); + if let Some(Type::List { .. }) = left.inner.expected_type(self.context) { + if let Some(Type::Integer) = right.inner.expected_type(self.context) { + // Allow indexing lists with integers + } else { + return Err(AnalyzerError::ExpectedInteger { + actual: right.as_ref().clone(), + position: right.position, + }); + } + } + + 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(), + }); + } } self.analyze_node(left)?; @@ -358,11 +374,18 @@ impl<'a> Analyzer<'a> { #[derive(Clone, Debug, PartialEq)] pub enum AnalyzerError { + ExpectedBoolean { + actual: Node, + position: Span, + }, ExpectedIdentifier { actual: Node, position: Span, }, - ExpectedBoolean { + ExpectedIdentifierOrString { + actual: Node, + }, + ExpectedInteger { actual: Node, position: Span, }, @@ -395,6 +418,8 @@ impl AnalyzerError { match self { AnalyzerError::ExpectedBoolean { position, .. } => *position, AnalyzerError::ExpectedIdentifier { position, .. } => *position, + AnalyzerError::ExpectedIdentifierOrString { actual } => actual.position, + AnalyzerError::ExpectedInteger { position, .. } => *position, AnalyzerError::ExpectedValue { actual } => actual.position, AnalyzerError::ExpectedValueArgumentCount { position, .. } => *position, AnalyzerError::TypeConflict { @@ -418,6 +443,12 @@ impl Display for AnalyzerError { AnalyzerError::ExpectedIdentifier { actual, .. } => { write!(f, "Expected identifier, found {}", actual) } + AnalyzerError::ExpectedIdentifierOrString { actual } => { + write!(f, "Expected identifier or string, found {}", actual) + } + AnalyzerError::ExpectedInteger { actual, .. } => { + write!(f, "Expected integer, found {}", actual) + } AnalyzerError::ExpectedValue { actual, .. } => { write!(f, "Expected value, found {}", actual) } diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index 032b0eb..8e1e8c9 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -561,6 +561,54 @@ impl<'src> Parser<'src> { let right = self.parse_statement(Token::Dot.precedence() + 1)?; let right_end = right.position.1; + if let Statement::BuiltInFunctionCall { + function, + type_arguments, + value_arguments, + } = right.inner + { + let value_arguments = if let Some(mut arguments) = value_arguments { + arguments.insert(0, left); + + Some(arguments) + } else { + Some(vec![left]) + }; + + return Ok(Node::new( + Statement::BuiltInFunctionCall { + function, + type_arguments, + value_arguments, + }, + (left_start, right_end), + )); + } + + if let Statement::FunctionCall { + function, + type_arguments, + value_arguments, + } = right.inner + { + let value_arguments = if let Some(mut arguments) = value_arguments { + arguments.insert(0, left); + + Some(arguments) + } else { + Some(vec![left]) + }; + + return Ok(Node::new( + Statement::FunctionCall { + function, + type_arguments, + value_arguments, + }, + (left_start, right_end), + )); + } + return Ok(Node::new( Statement::PropertyAccess(Box::new(left), Box::new(right)), (left_start, right_end), @@ -1408,17 +1456,14 @@ mod tests { parse(input), Ok(AbstractSyntaxTree { nodes: [Node::new( - Statement::PropertyAccess( - Box::new(Node::new(Statement::Constant(Value::integer(42)), (0, 2))), - Box::new(Node::new( - Statement::BuiltInFunctionCall { - function: BuiltInFunction::IsEven, - type_arguments: None, - value_arguments: None - }, - (3, 10) - )), - ), + Statement::BuiltInFunctionCall { + function: BuiltInFunction::IsEven, + type_arguments: None, + value_arguments: Some(vec![Node::new( + Statement::Constant(Value::integer(42)), + (0, 2) + )]) + }, (0, 10), )] .into() diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index f1c9c17..fe5a607 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -93,6 +93,10 @@ impl Value { self.0.r#type(context) } + pub fn get_property(&self, property: &Identifier) -> Option { + self.0.get_property(property) + } + pub fn as_boolean(&self) -> Option { if let ValueInner::Boolean(boolean) = self.0.as_ref() { Some(*boolean) @@ -626,7 +630,7 @@ pub enum ValueInner { } impl ValueInner { - pub fn r#type(&self, context: &Context) -> Type { + fn r#type(&self, context: &Context) -> Type { match self { ValueInner::Boolean(_) => Type::Boolean, ValueInner::Float(_) => Type::Float, @@ -658,6 +662,20 @@ impl ValueInner { ValueInner::String(_) => Type::String, } } + + fn get_property(&self, property: &Identifier) -> Option { + match self { + ValueInner::List(list) => { + if property.as_str() == "length" { + Some(Value::integer(list.len() as i64)) + } else { + None + } + } + ValueInner::Map(value_map) => value_map.get(property).cloned(), + _ => None, + } + } } impl Eq for ValueInner {} diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 570a070..1e5154f 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -6,7 +6,8 @@ use std::{ use crate::{ abstract_tree::BinaryOperator, parse, value::ValueInner, AbstractSyntaxTree, Analyzer, - BuiltInFunctionError, Context, DustError, Node, ParseError, Span, Statement, Value, ValueError, + BuiltInFunctionError, Context, DustError, Identifier, Node, ParseError, Span, Statement, Value, + ValueError, }; pub fn run<'src>( @@ -536,6 +537,12 @@ pub enum VmError { UndefinedVariable { identifier: Node, }, + UndefinedProperty { + value: Value, + value_position: Span, + property: Identifier, + property_position: Span, + }, } impl VmError { @@ -552,6 +559,9 @@ impl VmError { Self::ExpectedList { position } => *position, Self::ExpectedValue { position } => *position, Self::UndefinedVariable { identifier } => identifier.position, + Self::UndefinedProperty { + property_position, .. + } => *property_position, } } } @@ -602,6 +612,11 @@ impl Display for VmError { Self::UndefinedVariable { identifier } => { write!(f, "Undefined identifier: {}", identifier) } + Self::UndefinedProperty { + value, property, .. + } => { + write!(f, "Value {} does not have the property {}", value, property) + } } } }