From 5c8e72a6f7742f3bd374febae581ed4b972f1ee0 Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 13 Aug 2024 19:41:36 -0400 Subject: [PATCH] Implement fields struct instantiation --- dust-lang/src/abstract_tree.rs | 64 ++++++------------- dust-lang/src/analyzer.rs | 41 +++++++++---- dust-lang/src/lib.rs | 2 +- dust-lang/src/parser.rs | 108 ++++++++++++++++++++++++++++++--- dust-lang/src/vm.rs | 61 ++++++++++++------- 5 files changed, 188 insertions(+), 88 deletions(-) diff --git a/dust-lang/src/abstract_tree.rs b/dust-lang/src/abstract_tree.rs index fcad545..b27fa5d 100644 --- a/dust-lang/src/abstract_tree.rs +++ b/dust-lang/src/abstract_tree.rs @@ -6,7 +6,7 @@ use std::{ use serde::{Deserialize, Serialize}; -use crate::{BuiltInFunction, Context, Identifier, Span, StructType, Type, Value}; +use crate::{BuiltInFunction, Context, Identifier, Span, Type, Value}; /// In-memory representation of a Dust program. #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] @@ -71,7 +71,10 @@ pub enum Statement { type_arguments: Option>>, value_arguments: Option>>, }, - StructInstantiation(StructInstantiation), + FieldsStructInstantiation { + name: Node, + fields: Vec<(Node, Node)>, + }, // Loops While { @@ -169,6 +172,7 @@ impl Statement { }, Statement::BuiltInFunctionCall { function, .. } => function.expected_return_type(), Statement::Constant(value) => Some(value.r#type()), + Statement::FieldsStructInstantiation { name, .. } => context.get_type(&name.inner), Statement::Invokation { invokee: function, .. } => function.inner.expected_type(context), @@ -200,17 +204,6 @@ impl Statement { UnaryOperator::Not => Some(Type::Boolean), }, Statement::StructDefinition(_) => None, - Statement::StructInstantiation(struct_instantiation) => match struct_instantiation { - StructInstantiation::Tuple { name, fields } => { - Some(Type::Struct(StructType::Tuple { - name: name.inner.clone(), - fields: fields - .iter() - .map(|field| field.inner.expected_type(context)) - .collect::>>()?, - })) - } - }, Statement::While { .. } => None, } } @@ -313,6 +306,19 @@ impl Display for Statement { write!(f, ")") } Statement::Constant(value) => write!(f, "{value}"), + Statement::FieldsStructInstantiation { name, fields } => { + write!(f, "{name} {{ ")?; + + for (i, (identifier, value)) in fields.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + + write!(f, "{identifier}: {value}")?; + } + + write!(f, " }}") + } Statement::Invokation { invokee: function, type_arguments: type_parameters, @@ -424,9 +430,6 @@ impl Display for Statement { Statement::StructDefinition(struct_definition) => { write!(f, "{struct_definition}") } - Statement::StructInstantiation(struct_instantiation) => { - write!(f, "{struct_instantiation}") - } Statement::While { condition, body } => { write!(f, "while {condition} {body}") } @@ -535,32 +538,3 @@ impl Display for StructDefinition { } } } - -#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] -pub enum StructInstantiation { - // The Unit variant is absent because unit structs are instantiated without any fields - Tuple { - name: Node, - fields: Vec>, - }, -} - -impl Display for StructInstantiation { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - StructInstantiation::Tuple { name, fields } => { - write!(f, "{name}(")?; - - for (i, field) in fields.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - - write!(f, "{field}")?; - } - - write!(f, ")") - } - } - } -} diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index ff71613..0fdbf54 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -10,7 +10,7 @@ use std::{ }; use crate::{ - abstract_tree::{BinaryOperator, StructInstantiation, UnaryOperator}, + abstract_tree::{BinaryOperator, UnaryOperator}, parse, AbstractSyntaxTree, Context, DustError, Identifier, Node, Span, Statement, StructDefinition, StructType, Type, }; @@ -291,6 +291,34 @@ impl<'a> Analyzer<'a> { } } Statement::Constant(_) => {} + Statement::FieldsStructInstantiation { + name, + fields: field_arguments, + } => { + let expected_type = self.context.get_type(&name.inner); + + if let Some(Type::Struct(StructType::Fields { fields, .. })) = expected_type { + for ((_, expected_type), (_, argument)) in + fields.iter().zip(field_arguments.iter()) + { + let actual_type = argument.inner.expected_type(self.context); + + if let Some(actual_type) = actual_type { + expected_type.check(&actual_type).map_err(|conflict| { + AnalyzerError::TypeConflict { + actual_statement: argument.clone(), + actual_type: conflict.actual, + expected: conflict.expected, + } + })?; + } else { + return Err(AnalyzerError::ExpectedValue { + actual: argument.clone(), + }); + } + } + } + } Statement::Invokation { invokee, value_arguments, @@ -488,17 +516,6 @@ impl<'a> Analyzer<'a> { self.context.set_type(name, r#type, node.position); } - Statement::StructInstantiation(struct_instantiation) => { - let name = match struct_instantiation { - StructInstantiation::Tuple { name, .. } => name, - }; - - if self.context.get_type(&name.inner).is_none() { - return Err(AnalyzerError::UndefinedType { - identifier: name.clone(), - }); - } - } Statement::UnaryOperation { operator, operand } => { self.analyze_statement(operand)?; diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs index f8bd0d7..240b5b8 100644 --- a/dust-lang/src/lib.rs +++ b/dust-lang/src/lib.rs @@ -30,7 +30,7 @@ pub mod vm; pub use abstract_tree::{ AbstractSyntaxTree, AssignmentOperator, BinaryOperator, Node, Statement, StructDefinition, - StructInstantiation, UnaryOperator, + UnaryOperator, }; pub use analyzer::{analyze, Analyzer, AnalyzerError}; pub use built_in_function::{BuiltInFunction, BuiltInFunctionError}; diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index 58f6ebd..d08224c 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -100,6 +100,7 @@ pub struct Parser<'src> { source: &'src str, lexer: Lexer, current: (Token<'src>, Span), + context: ParserContext, } impl<'src> Parser<'src> { @@ -111,6 +112,7 @@ impl<'src> Parser<'src> { source, lexer, current, + context: ParserContext::None, } } @@ -218,6 +220,63 @@ impl<'src> Parser<'src> { position, )) } + (Token::Identifier(text), position) => { + self.next_token()?; + + if let ParserContext::IfElseStatement = self.context { + return Ok(Node::new( + Statement::Identifier(Identifier::new(text)), + position, + )); + } + + if let Token::LeftCurlyBrace = self.current.0 { + self.next_token()?; + + let mut fields = 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::FieldsStructInstantiation { + name: Node::new(Identifier::new(text), position), + fields, + }, + (position.0, right_end), + )); + } + + let field_name = self.parse_identifier()?; + + if let Token::Equal = self.current.0 { + self.next_token()?; + } else { + return Err(ParseError::ExpectedToken { + expected: TokenKind::Equal, + actual: self.current.0.to_owned(), + position: self.current.1, + }); + } + + let field_value = self.parse_statement(0)?; + + fields.push((field_name, field_value)); + + if let Token::Comma = self.current.0 { + self.next_token()?; + } + } + } + + Ok(Node::new( + Statement::Identifier(Identifier::new(text)), + position, + )) + } (Token::Integer(text), position) => { self.next_token()?; @@ -253,17 +312,11 @@ impl<'src> Parser<'src> { )) } } - (Token::Identifier(text), position) => { - self.next_token()?; - - Ok(Node::new( - Statement::Identifier(Identifier::new(text)), - position, - )) - } (Token::If, position) => { self.next_token()?; + self.context = ParserContext::IfElseStatement; + let condition = Box::new(self.parse_statement(0)?); let if_body = Box::new(self.parse_block()?); @@ -300,6 +353,8 @@ impl<'src> Parser<'src> { let else_body = Box::new(self.parse_block()?); let else_end = else_body.position.1; + self.context = ParserContext::None; + return Ok(Node::new( Statement::IfElseIfElse { condition, @@ -315,6 +370,8 @@ impl<'src> Parser<'src> { let else_body = Box::new(self.parse_block()?); let else_end = else_body.position.1; + self.context = ParserContext::None; + Ok(Node::new( Statement::IfElse { condition, @@ -327,6 +384,8 @@ impl<'src> Parser<'src> { } else { let if_end = if_body.position.1; + self.context = ParserContext::None; + Ok(Node::new( Statement::If { condition, @@ -972,6 +1031,11 @@ impl<'src> Parser<'src> { } } +enum ParserContext { + IfElseStatement, + None, +} + #[derive(Debug, PartialEq, Clone)] pub enum ParseError { BooleanError { @@ -1083,6 +1147,34 @@ mod tests { use super::*; + #[test] + fn fields_struct_instantiation() { + let input = "Foo { a = 42, b = 4.0 }"; + + assert_eq!( + parse(input), + Ok(AbstractSyntaxTree { + nodes: [Node::new( + Statement::FieldsStructInstantiation { + name: Node::new(Identifier::new("Foo"), (0, 3)), + fields: vec![ + ( + Node::new(Identifier::new("a"), (6, 7)), + Node::new(Statement::Constant(Value::integer(42)), (10, 12)) + ), + ( + Node::new(Identifier::new("b"), (14, 15)), + Node::new(Statement::Constant(Value::float(4.0)), (18, 21)) + ) + ] + }, + (0, 23) + )] + .into() + }) + ); + } + #[test] fn fields_struct() { let input = "struct Foo { a: int, b: float }"; diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index f30e754..8475ba2 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -12,8 +12,7 @@ use std::{ use crate::{ parse, value::ValueInner, AbstractSyntaxTree, Analyzer, AssignmentOperator, BinaryOperator, BuiltInFunctionError, Context, DustError, Identifier, Node, ParseError, Span, Statement, - Struct, StructDefinition, StructInstantiation, StructType, Type, UnaryOperator, Value, - ValueError, + Struct, StructType, Type, UnaryOperator, Value, ValueError, }; /// Run the source code and return the result. @@ -65,8 +64,9 @@ pub fn run_with_context(source: &str, context: Context) -> Result, /// Dust virtual machine. /// -/// **Warning**: Do not run an AbstractSyntaxTree that has not been analyzed. Use the `run` or -/// `run_with_context` functions to make sure the program is analyzed before running it. +/// **Warning**: Do not run an AbstractSyntaxTree that has not been analyzed *with the same +/// context*. Use the `run` or `run_with_context` functions to make sure the program is analyzed +/// before running it. /// /// See the `run_with_context` function for an example of how to use the Analyzer and the VM. pub struct Vm { @@ -325,6 +325,25 @@ impl Vm { Ok(function_call_return) } Statement::Constant(value) => Ok(Some(value.clone())), + Statement::FieldsStructInstantiation { name, fields } => { + let mut values = Vec::new(); + + for (identifier, value_node) in fields { + let position = value_node.position; + let value = if let Some(value) = self.run_statement(value_node)? { + value + } else { + return Err(VmError::ExpectedValue { position }); + }; + + values.push((identifier.inner, value)); + } + + Ok(Some(Value::r#struct(Struct::Fields { + name: name.inner, + fields: values, + }))) + } Statement::Invokation { invokee, type_arguments: _, @@ -600,24 +619,6 @@ impl Vm { Ok(None) } Statement::StructDefinition(_) => Ok(None), - Statement::StructInstantiation(struct_instantiation) => match struct_instantiation { - StructInstantiation::Tuple { name, fields } => { - Ok(Some(Value::r#struct(Struct::Tuple { - name: name.inner, - fields: fields - .into_iter() - .map(|node| { - let position = node.position; - if let Some(value) = self.run_statement(node)? { - Ok(value) - } else { - Err(VmError::ExpectedValue { position }) - } - }) - .collect::, VmError>>()?, - }))) - } - }, Statement::UnaryOperation { operator, operand } => { let position = operand.position; let value = if let Some(value) = self.run_statement(*operand)? { @@ -832,6 +833,22 @@ mod tests { use super::*; + #[test] + fn define_and_instantiate_fields_struct() { + let input = "struct Foo { bar: int, baz: float } Foo { bar = 42, baz = 4.0 }"; + + assert_eq!( + run(input), + Ok(Some(Value::r#struct(Struct::Fields { + name: Identifier::new("Foo"), + fields: vec![ + (Identifier::new("bar"), Value::integer(42)), + (Identifier::new("baz"), Value::float(4.0)) + ] + }))) + ); + } + #[test] fn assign_tuple_struct_variable() { let input = "