From cf9a9837c8443791de4551590919c232606b1ace Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 20 Aug 2024 07:20:44 -0400 Subject: [PATCH] Add a parser test; Pass VM test --- dust-lang/src/ast/expression.rs | 14 ++- dust-lang/src/built_in_function.rs | 75 ++++++----- dust-lang/src/dust_error.rs | 28 +++-- dust-lang/src/lexer.rs | 1 - dust-lang/src/parser.rs | 22 +++- dust-lang/src/token.rs | 11 +- dust-lang/src/type.rs | 2 +- dust-lang/src/value.rs | 191 +++++++++++------------------ dust-lang/src/vm.rs | 17 +-- 9 files changed, 173 insertions(+), 188 deletions(-) diff --git a/dust-lang/src/ast/expression.rs b/dust-lang/src/ast/expression.rs index 08279c3..1353f08 100644 --- a/dust-lang/src/ast/expression.rs +++ b/dust-lang/src/ast/expression.rs @@ -6,7 +6,7 @@ use std::{ use serde::{Deserialize, Serialize}; -use crate::{Context, FunctionType, Identifier, RangeableType, StructType, Type}; +use crate::{BuiltInFunction, Context, FunctionType, Identifier, RangeableType, StructType, Type}; use super::{Node, Span, Statement}; @@ -338,6 +338,9 @@ impl Expression { } } Expression::Literal(literal_expression) => match literal_expression.inner.as_ref() { + LiteralExpression::BuiltInFunction(built_in_function) => { + built_in_function.return_type() + } LiteralExpression::Primitive(primitive_value) => match primitive_value { PrimitiveValueExpression::Boolean(_) => Some(Type::Boolean), PrimitiveValueExpression::Character(_) => Some(Type::Character), @@ -682,6 +685,7 @@ impl From for LiteralExpression { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum LiteralExpression { + BuiltInFunction(BuiltInFunction), Primitive(PrimitiveValueExpression), String(String), } @@ -689,6 +693,9 @@ pub enum LiteralExpression { impl Display for LiteralExpression { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { + LiteralExpression::BuiltInFunction(built_in_function) => { + write!(f, "{built_in_function}") + } LiteralExpression::Primitive(primitive) => { write!(f, "{primitive}") } @@ -708,6 +715,11 @@ impl PartialOrd for LiteralExpression { impl Ord for LiteralExpression { fn cmp(&self, other: &Self) -> Ordering { match (self, other) { + ( + LiteralExpression::BuiltInFunction(left), + LiteralExpression::BuiltInFunction(right), + ) => left.cmp(right), + (LiteralExpression::BuiltInFunction(_), _) => Ordering::Greater, (LiteralExpression::Primitive(left), LiteralExpression::Primitive(right)) => { left.cmp(right) } diff --git a/dust-lang/src/built_in_function.rs b/dust-lang/src/built_in_function.rs index 57a3ac4..d1779ba 100644 --- a/dust-lang/src/built_in_function.rs +++ b/dust-lang/src/built_in_function.rs @@ -13,7 +13,7 @@ use crate::{Identifier, Type, Value}; #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] pub enum BuiltInFunction { // String tools - ToString, + ToString { argument: Box }, // Integer and float tools IsEven, @@ -34,26 +34,46 @@ impl BuiltInFunction { BuiltInFunction::IsOdd => "is_odd", BuiltInFunction::Length => "length", BuiltInFunction::ReadLine => "read_line", - BuiltInFunction::ToString => "to_string", + BuiltInFunction::ToString { .. } => "to_string", BuiltInFunction::WriteLine => "write_line", } } - pub fn value_parameters(&self) -> Vec<(Identifier, Type)> { + pub fn type_parameters(&self) -> Option> { match self { - BuiltInFunction::ToString => vec![("value".into(), Type::Any)], - BuiltInFunction::IsEven => vec![("value".into(), Type::Number)], - BuiltInFunction::IsOdd => vec![("value".into(), Type::Number)], - BuiltInFunction::Length => { - vec![( - "value".into(), - Type::ListOf { - item_type: Box::new(Type::Any), - }, - )] - } - BuiltInFunction::ReadLine => vec![], - BuiltInFunction::WriteLine => vec![("output".into(), Type::Any)], + BuiltInFunction::ToString { .. } => None, + BuiltInFunction::IsEven => None, + BuiltInFunction::IsOdd => None, + BuiltInFunction::Length => None, + BuiltInFunction::ReadLine => None, + BuiltInFunction::WriteLine => None, + } + } + + pub fn value_parameters(&self) -> Option> { + match self { + BuiltInFunction::ToString { .. } => Some(vec![("value".into(), Type::Any)]), + BuiltInFunction::IsEven => Some(vec![("value".into(), Type::Number)]), + BuiltInFunction::IsOdd => Some(vec![("value".into(), Type::Number)]), + BuiltInFunction::Length => Some(vec![( + "value".into(), + Type::ListOf { + item_type: Box::new(Type::Any), + }, + )]), + BuiltInFunction::ReadLine => None, + BuiltInFunction::WriteLine => Some(vec![("output".into(), Type::Any)]), + } + } + + pub fn return_type(&self) -> Option { + match self { + BuiltInFunction::ToString { .. } => Some(Type::String), + BuiltInFunction::IsEven => Some(Type::Boolean), + BuiltInFunction::IsOdd => Some(Type::Boolean), + BuiltInFunction::Length => Some(Type::Number), + BuiltInFunction::ReadLine => Some(Type::String), + BuiltInFunction::WriteLine => None, } } @@ -63,17 +83,7 @@ impl BuiltInFunction { value_arguments: Option>, ) -> Result, BuiltInFunctionError> { match self { - BuiltInFunction::ToString => { - if let Some(value_arguments) = value_arguments { - if value_arguments.len() == 1 { - Ok(Some(Value::string(value_arguments[0].to_string()))) - } else { - Err(BuiltInFunctionError::WrongNumberOfValueArguments) - } - } else { - Err(BuiltInFunctionError::WrongNumberOfValueArguments) - } - } + BuiltInFunction::ToString { argument } => Ok(Some(Value::string(argument))), BuiltInFunction::IsEven => { if let Some(value_arguments) = value_arguments { if value_arguments.len() == 1 { @@ -145,17 +155,6 @@ impl BuiltInFunction { } } } - - pub fn expected_return_type(&self) -> Option { - match self { - BuiltInFunction::ToString => Some(Type::String), - BuiltInFunction::IsEven => Some(Type::Boolean), - BuiltInFunction::IsOdd => Some(Type::Boolean), - BuiltInFunction::Length => Some(Type::Integer), - BuiltInFunction::ReadLine => Some(Type::String), - BuiltInFunction::WriteLine => None, - } - } } impl Display for BuiltInFunction { diff --git a/dust-lang/src/dust_error.rs b/dust-lang/src/dust_error.rs index d97ca1f..1e9cc64 100644 --- a/dust-lang/src/dust_error.rs +++ b/dust-lang/src/dust_error.rs @@ -1,8 +1,8 @@ //! Top-level error handling for the Dust language. -use annotate_snippets::{Level, Renderer, Snippet}; +use annotate_snippets::{Level, Message, Renderer, Snippet}; use std::fmt::Display; -use crate::{AnalysisError, LexError, ParseError, RuntimeError}; +use crate::{ast::Span, AnalysisError, LexError, ParseError, RuntimeError}; /// An error that occurred during the execution of the Dust language and its /// corresponding source code. @@ -61,12 +61,12 @@ impl<'src> DustError<'src> { } } - pub fn position(&self) -> (usize, usize) { + pub fn position(&self) -> Option { match self { DustError::Runtime { runtime_error, .. } => runtime_error.position(), - DustError::Analysis { analysis_error, .. } => analysis_error.position(), - DustError::Parse { parse_error, .. } => parse_error.position(), - DustError::Lex { lex_error, .. } => lex_error.position(), + DustError::Analysis { analysis_error, .. } => Some(analysis_error.position()), + DustError::Parse { parse_error, .. } => Some(parse_error.position()), + DustError::Lex { lex_error, .. } => Some(lex_error.position()), } } @@ -83,10 +83,18 @@ impl<'src> DustError<'src> { let title = self.title(); let span = self.position(); let label = self.to_string(); - let message = Level::Error.title(title).snippet( - Snippet::source(self.source()) - .annotation(Level::Info.span(span.0..span.1).label(&label)), - ); + + let message = if let Some(span) = span { + Level::Error.title(title).snippet( + Snippet::source(self.source()) + .annotation(Level::Info.span(span.0..span.1).label(&label)), + ) + } else { + Level::Error + .title(title) + .snippet(Snippet::source(self.source())) + .footer(Level::Info.title("No position information available")) + }; let renderer = Renderer::styled(); format!("{}", renderer.render(message)) diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs index 8021c36..e94db90 100644 --- a/dust-lang/src/lexer.rs +++ b/dust-lang/src/lexer.rs @@ -437,7 +437,6 @@ impl<'src> Lexer<'src> { "mut" => Token::Mut, "read_line" => Token::ReadLine, "struct" => Token::Struct, - "to_string" => Token::ToString, "true" => Token::Boolean("true"), "while" => Token::While, "write_line" => Token::WriteLine, diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index 4ec2496..cf1b696 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -822,7 +822,7 @@ impl<'src> Parser<'src> { Expression::tuple_access(left, index_node, position) } else { let field = self.parse_identifier()?; - let position = (left.position().0, self.current_position.1); + let position = (left.position().0, field.position.1); Expression::field_access(left, field, position) } @@ -1145,6 +1145,26 @@ mod tests { use super::*; + #[test] + fn built_in_function() { + let source = "42.to_string()"; + + assert_eq!( + parse(source), + Ok(AbstractSyntaxTree::with_statements([ + Statement::Expression(Expression::call( + Expression::field_access( + Expression::literal(42, (0, 2)), + Node::new(Identifier::new("to_string"), (3, 12)), + (0, 12) + ), + vec![], + (0, 14) + )) + ])) + ); + } + #[test] fn map_expression() { let source = "map { x = '1', y = 2, z = 3.0 }"; diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index ca96e63..91bdcdd 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -36,7 +36,6 @@ pub enum Token<'src> { ReadLine, Str, Struct, - ToString, While, WriteLine, @@ -123,7 +122,6 @@ impl<'src> Token<'src> { 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, } @@ -181,7 +179,6 @@ impl<'src> Token<'src> { Token::Slash => "/", Token::Str => "str", Token::Struct => "struct", - Token::ToString => "to_string", Token::While => "while", Token::WriteLine => "write_line", } @@ -238,7 +235,6 @@ impl<'src> Token<'src> { Token::Str => TokenKind::Str, Token::String(_) => TokenKind::String, Token::Struct => TokenKind::Struct, - Token::ToString => TokenKind::ToString, Token::While => TokenKind::While, Token::WriteLine => TokenKind::WriteLine, } @@ -332,7 +328,6 @@ pub enum TokenOwned { Mut, ReadLine, Str, - ToString, While, WriteLine, @@ -421,7 +416,6 @@ impl Display for TokenOwned { TokenOwned::Str => Token::Str.fmt(f), 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), } @@ -455,7 +449,6 @@ pub enum TokenKind { Map, ReadLine, Str, - ToString, While, WriteLine, @@ -544,7 +537,6 @@ impl Display for TokenKind { TokenKind::Slash => Token::Slash.fmt(f), 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), } @@ -555,7 +547,7 @@ impl Display for TokenKind { pub(crate) mod tests { use super::*; - pub fn all_tokens<'src>() -> [Token<'src>; 52] { + pub fn all_tokens<'src>() -> [Token<'src>; 51] { [ Token::Identifier("foobar"), Token::Boolean("true"), @@ -606,7 +598,6 @@ pub(crate) mod tests { Token::Star, Token::Str, Token::Struct, - Token::ToString, Token::While, Token::WriteLine, ] diff --git a/dust-lang/src/type.rs b/dust-lang/src/type.rs index fe6afbd..b4a65c2 100644 --- a/dust-lang/src/type.rs +++ b/dust-lang/src/type.rs @@ -371,7 +371,7 @@ impl Ord for Type { #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] pub struct FunctionType { pub name: Identifier, - pub type_parameters: Option>, + pub type_parameters: Option>, pub value_parameters: Option>, pub return_type: Option>, } diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index fe4c3a7..2d67c59 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -16,8 +16,8 @@ use serde::{ }; use crate::{ - AbstractSyntaxTree, Context, EnumType, FunctionType, Identifier, RangeableType, RuntimeError, - StructType, Type, Vm, + AbstractSyntaxTree, BuiltInFunction, Context, EnumType, FunctionType, Identifier, + RangeableType, RuntimeError, StructType, Type, Vm, }; /// Dust value representation @@ -194,7 +194,15 @@ impl Value { Value::Character(_) => Type::Character, Value::Enum(Enum { r#type, .. }) => Type::Enum(r#type.clone()), Value::Float(_) => Type::Float, - Value::Function(function) => Type::Function(function.r#type.clone()), + Value::Function(Function::BuiltIn(built_in_function)) => Type::Function(FunctionType { + name: Identifier::new(built_in_function.name()), + type_parameters: built_in_function.type_parameters(), + value_parameters: built_in_function.value_parameters(), + return_type: built_in_function.return_type().map(Box::new), + }), + Value::Function(Function::Parsed { name, r#type, body }) => { + Type::Function(r#type.clone()) + } Value::Integer(_) => Type::Integer, Value::List(values) => { let item_type = values.first().unwrap().r#type(); @@ -255,6 +263,14 @@ impl Value { } pub fn get_field(&self, field: &Identifier) -> Option { + if let "to_string" = field.as_str() { + return Some(Value::Function(Function::BuiltIn( + BuiltInFunction::ToString { + argument: Box::new(self.clone()), + }, + ))); + } + match self { Value::Mutable(inner) => inner.read().unwrap().get_field(field), Value::Struct(Struct::Fields { fields, .. }) => fields.get(field).cloned(), @@ -1059,151 +1075,88 @@ impl<'de> Deserialize<'de> for Value { } } -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub struct Function { - pub name: Identifier, - pub r#type: FunctionType, - pub body: Arc, +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] +pub enum Function { + BuiltIn(BuiltInFunction), + Parsed { + name: Identifier, + r#type: FunctionType, + body: AbstractSyntaxTree, + }, } impl Function { pub fn call( - &self, + self, _type_arguments: Option>, value_arguments: Option>, context: &Context, ) -> Result, RuntimeError> { - let new_context = Context::with_data_from(context); + match self { + Function::BuiltIn(built_in_function) => built_in_function + .call(_type_arguments, value_arguments) + .map_err(|error| RuntimeError::BuiltInFunctionError { error }), + Function::Parsed { r#type, body, .. } => { + let new_context = Context::with_data_from(context); - if let (Some(value_parameters), Some(value_arguments)) = - (&self.r#type.value_parameters, value_arguments) - { - for ((identifier, _), value) in value_parameters.iter().zip(value_arguments) { - new_context.set_variable_value(identifier.clone(), value); + if let (Some(value_parameters), Some(value_arguments)) = + (&r#type.value_parameters, value_arguments) + { + for ((identifier, _), value) in value_parameters.iter().zip(value_arguments) { + new_context.set_variable_value(identifier.clone(), value); + } + } + + let mut vm = Vm::new(body, new_context); + + vm.run() } } - - let mut vm = Vm::new(self.body.as_ref().clone(), new_context); - - vm.run() } } impl Display for Function { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "fn {}", self.name)?; + match self { + Function::BuiltIn(built_in_function) => write!(f, "{}", built_in_function), + Function::Parsed { name, r#type, body } => { + write!(f, "fn {}", name)?; - if let Some(type_parameters) = &self.r#type.type_parameters { - write!(f, "<")?; + if let Some(type_parameters) = &r#type.type_parameters { + write!(f, "<")?; - for (index, type_parameter) in type_parameters.iter().enumerate() { - if index > 0 { - write!(f, ", ")?; + for (index, type_parameter) in type_parameters.iter().enumerate() { + if index > 0 { + write!(f, ", ")?; + } + + write!(f, "{}", type_parameter)?; + } + + write!(f, ">")?; } - write!(f, "{}", type_parameter)?; - } + write!(f, "(")?; - write!(f, ">")?; - } - - write!(f, "(")?; - - if let Some(value_paramers) = &self.r#type.value_parameters { - for (index, (identifier, r#type)) in value_paramers.iter().enumerate() { - if index > 0 { - write!(f, ", ")?; - } - - write!(f, "{identifier}: {type}")?; - } - } - - write!(f, ") {{")?; - - for statement in &self.body.statements { - write!(f, "{}", statement)?; - } - - write!(f, "}}") - } -} - -impl Serialize for Function { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut ser = serializer.serialize_struct("Function", 3)?; - - ser.serialize_field("name", &self.name)?; - ser.serialize_field("type", &self.r#type)?; - ser.serialize_field("body", self.body.as_ref())?; - - ser.end() - } -} - -impl<'de> Deserialize<'de> for Function { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct FunctionVisitor; - - impl<'de> Visitor<'de> for FunctionVisitor { - type Value = Function; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a function") - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut name = None; - let mut r#type = None; - let mut body = None; - - while let Some(key) = map.next_key()? { - match key { - "name" => { - if name.is_some() { - return Err(de::Error::duplicate_field("name")); - } - - name = Some(map.next_value()?); + if let Some(value_paramers) = &r#type.value_parameters { + for (index, (identifier, r#type)) in value_paramers.iter().enumerate() { + if index > 0 { + write!(f, ", ")?; } - "type" => { - if r#type.is_some() { - return Err(de::Error::duplicate_field("type")); - } - r#type = Some(map.next_value()?); - } - "body" => { - if body.is_some() { - return Err(de::Error::duplicate_field("body")); - } - - body = Some(map.next_value().map(Arc::new)?); - } - _ => { - return Err(de::Error::unknown_field(key, &["name", "type", "body"])); - } + write!(f, "{identifier}: {type}")?; } } - let name = name.ok_or_else(|| de::Error::missing_field("name"))?; - let r#type = r#type.ok_or_else(|| de::Error::missing_field("type"))?; - let body = body.ok_or_else(|| de::Error::missing_field("body"))?; + write!(f, ") {{")?; - Ok(Function { name, r#type, body }) + for statement in &body.statements { + write!(f, "{}", statement)?; + } + + write!(f, "}}") } } - - deserializer.deserialize_struct("Function", &["name", "type", "body"], FunctionVisitor) } } diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 0d98ebd..d35b003 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -20,9 +20,8 @@ use crate::{ OperatorExpression, PrimitiveValueExpression, RangeExpression, Span, Statement, StructDefinition, StructExpression, }, - constructor::FieldsConstructor, parse, Analyzer, BuiltInFunctionError, Constructor, Context, ContextData, DustError, - Expression, Identifier, ParseError, Struct, StructType, Type, Value, ValueError, + Expression, Function, Identifier, ParseError, StructType, Type, Value, ValueError, }; /// Run the source code and return the result. @@ -609,6 +608,9 @@ impl Vm { fn run_literal(&self, literal: LiteralExpression) -> Result { let value = match literal { + LiteralExpression::BuiltInFunction(built_in_function) => { + Value::Function(Function::BuiltIn(built_in_function)) + } LiteralExpression::String(string) => Value::String(string), LiteralExpression::Primitive(primitive_expression) => match primitive_expression { PrimitiveValueExpression::Boolean(boolean) => Value::Boolean(boolean), @@ -934,7 +936,6 @@ pub enum RuntimeError { // These should be prevented by running the analyzer before the VM BuiltInFunctionError { error: BuiltInFunctionError, - position: Span, }, EnumVariantNotFound { identifier: Identifier, @@ -1006,8 +1007,9 @@ pub enum RuntimeError { } impl RuntimeError { - pub fn position(&self) -> Span { - match self { + pub fn position(&self) -> Option { + let position = match self { + Self::BuiltInFunctionError { .. } => return None, Self::ParseError(parse_error) => parse_error.position(), Self::Expression { position, .. } => *position, Self::Statement { position, .. } => *position, @@ -1016,7 +1018,6 @@ impl RuntimeError { right_position, .. } => (left_position.0, right_position.1), - Self::BuiltInFunctionError { position, .. } => *position, Self::EnumVariantNotFound { position, .. } => *position, Self::ExpectedBoolean { position } => *position, Self::ExpectedConstructor { position, .. } => *position, @@ -1042,7 +1043,9 @@ impl RuntimeError { Self::UndefinedProperty { property_position, .. } => *property_position, - } + }; + + Some(position) } }