From 0de25215b8dd9f6c0c5e025b46381c439c1ba8f5 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 19 Jun 2024 10:48:22 -0400 Subject: [PATCH] Implement basic enum instances --- dust-lang/src/abstract_tree/value_node.rs | 69 +++++++++++++++ dust-lang/src/error.rs | 1 + dust-lang/src/lib.rs | 2 + dust-lang/src/parser.rs | 100 ++++++++++++++-------- dust-lang/src/value.rs | 81 ++++++++++++++++++ dust-lang/tests/enums.rs | 23 +++++ 6 files changed, 240 insertions(+), 36 deletions(-) create mode 100644 dust-lang/tests/enums.rs diff --git a/dust-lang/src/abstract_tree/value_node.rs b/dust-lang/src/abstract_tree/value_node.rs index 33b50bf..b85febf 100644 --- a/dust-lang/src/abstract_tree/value_node.rs +++ b/dust-lang/src/abstract_tree/value_node.rs @@ -16,6 +16,11 @@ use super::{ #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum ValueNode { Boolean(bool), + EnumInstance { + type_name: WithPosition, + variant: WithPosition, + content: Option>, + }, Float(f64), Integer(i64), List(Vec), @@ -142,6 +147,34 @@ impl Evaluate for ValueNode { ) -> Result { let value = match self { ValueNode::Boolean(boolean) => Value::boolean(boolean), + + ValueNode::EnumInstance { + type_name, + variant, + content: expressions, + } => { + let content = if let Some(expressions) = expressions { + let mut values = Vec::with_capacity(expressions.len()); + + for expression in expressions { + let position = expression.position(); + let evaluation = expression.evaluate(context, _manage_memory)?; + + if let Evaluation::Return(value) = evaluation { + values.push(value); + } else { + return Err(RuntimeError::ValidationFailure( + ValidationError::InterpreterExpectedReturn(position), + )); + } + } + Some(values) + } else { + None + }; + + Value::enum_instance(type_name.node, variant.node, content) + } ValueNode::Float(float) => Value::float(float), ValueNode::Integer(integer) => Value::integer(integer), ValueNode::List(expression_list) => { @@ -267,6 +300,33 @@ impl Ord for ValueNode { (Range(_), _) => Ordering::Greater, (String(left), String(right)) => left.cmp(right), (String(_), _) => Ordering::Greater, + ( + EnumInstance { + type_name: left_name, + variant: left_variant, + content: left_content, + }, + EnumInstance { + type_name: right_name, + variant: right_variant, + content: right_content, + }, + ) => { + let name_cmp = left_name.cmp(right_name); + + if name_cmp.is_eq() { + let variant_cmp = left_variant.cmp(right_variant); + + if variant_cmp.is_eq() { + left_content.cmp(right_content) + } else { + variant_cmp + } + } else { + name_cmp + } + } + (EnumInstance { .. }, _) => Ordering::Greater, ( Function { type_parameters: left_type_arguments, @@ -329,6 +389,15 @@ impl ExpectedType for ValueNode { fn expected_type(&self, context: &mut Context) -> Result { let r#type = match self { ValueNode::Boolean(_) => Type::Boolean, + ValueNode::EnumInstance { type_name, .. } => { + if let Some(r#type) = context.get_type(&type_name.node)? { + r#type + } else { + return Err(ValidationError::EnumDefinitionNotFound( + type_name.node.clone(), + )); + } + } ValueNode::Float(_) => Type::Float, ValueNode::Integer(_) => Type::Integer, ValueNode::List(items) => { diff --git a/dust-lang/src/error.rs b/dust-lang/src/error.rs index 6c343c7..923e5c9 100644 --- a/dust-lang/src/error.rs +++ b/dust-lang/src/error.rs @@ -160,6 +160,7 @@ pub enum ValidationError { identifier: Identifier, position: SourcePosition, }, + EnumDefinitionNotFound(Identifier), } impl From for ValidationError { diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs index 046440e..fec5c14 100644 --- a/dust-lang/src/lib.rs +++ b/dust-lang/src/lib.rs @@ -425,8 +425,10 @@ impl InterpreterError { ) } ValidationError::ExpectedString { .. } => todo!(), + ValidationError::EnumDefinitionNotFound(_) => todo!(), } } + let report = builder.finish(); reports.push(report); diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index 129e731..4973b8b 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -316,6 +316,32 @@ pub fn parser<'src>( }, ); + let enum_instance = positioned_identifier + .clone() + .then_ignore(just(Token::Control(Control::DoubleColon))) + .then(positioned_identifier.clone()) + .then( + expression + .clone() + .separated_by(just(Token::Control(Control::Comma))) + .collect() + .delimited_by( + just(Token::Control(Control::ParenOpen)), + just(Token::Control(Control::ParenClose)), + ) + .or_not(), + ) + .map_with(|((type_name, variant), content), state| { + Expression::Value( + ValueNode::EnumInstance { + type_name, + variant, + content, + } + .with_position(state.span()), + ) + }); + let built_in_function_call = choice(( just(Token::Keyword(Keyword::Length)) .ignore_then(expression.clone()) @@ -390,6 +416,7 @@ pub fn parser<'src>( ); let atom = choice(( + enum_instance.clone(), range.clone(), function.clone(), list.clone(), @@ -411,41 +438,6 @@ pub fn parser<'src>( Expression::Logic(Box::new(Logic::Not(expression)).with_position(span)) }, ), - postfix( - 3, - expression.clone().delimited_by( - just(Token::Control(Control::SquareOpen)), - just(Token::Control(Control::SquareClose)), - ), - |left, right, span| { - Expression::ListIndex( - Box::new(ListIndex::new(left, right)).with_position(span), - ) - }, - ), - postfix( - 3, - turbofish.clone().or_not().then( - expression - .clone() - .separated_by(just(Token::Control(Control::Comma))) - .collect() - .delimited_by( - just(Token::Control(Control::ParenOpen)), - just(Token::Control(Control::ParenClose)), - ), - ), - |function_expression, (type_parameters, value_parameters), span| { - Expression::FunctionCall( - FunctionCall::new( - function_expression, - type_parameters, - value_parameters, - ) - .with_position(span), - ) - }, - ), infix( left(1), just(Token::Operator(Operator::Equal)), @@ -554,6 +546,42 @@ pub fn parser<'src>( ) }, ), + postfix( + 3, + expression.clone().delimited_by( + just(Token::Control(Control::SquareOpen)), + just(Token::Control(Control::SquareClose)), + ), + |left, right, span| { + Expression::ListIndex( + Box::new(ListIndex::new(left, right)).with_position(span), + ) + }, + ), + // Function call + postfix( + 3, + turbofish.clone().or_not().then( + expression + .clone() + .separated_by(just(Token::Control(Control::Comma))) + .collect() + .delimited_by( + just(Token::Control(Control::ParenOpen)), + just(Token::Control(Control::ParenClose)), + ), + ), + |function_expression, (type_parameters, value_parameters), span| { + Expression::FunctionCall( + FunctionCall::new( + function_expression, + type_parameters, + value_parameters, + ) + .with_position(span), + ) + }, + ), // As postfix( 2, @@ -568,6 +596,7 @@ pub fn parser<'src>( choice(( logic_math_indexes_as_and_function_calls, + enum_instance, built_in_function_call, range, function, @@ -576,7 +605,6 @@ pub fn parser<'src>( basic_value, identifier_expression, )) - // .delimited_by(comment.clone().or_not(), comment.or_not()) }); let expression_statement = expression diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index abe5d2c..1d1df04 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -32,6 +32,18 @@ impl Value { Value(Arc::new(ValueInner::Boolean(boolean))) } + pub fn enum_instance( + type_name: Identifier, + variant: Identifier, + content: Option>, + ) -> Self { + Value(Arc::new(ValueInner::EnumInstance { + type_name, + variant, + content, + })) + } + pub fn float(float: f64) -> Self { Value(Arc::new(ValueInner::Float(float))) } @@ -107,6 +119,23 @@ impl Display for Value { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self.inner().as_ref() { ValueInner::Boolean(boolean) => write!(f, "{boolean}"), + ValueInner::EnumInstance { + type_name, + variant, + content, + } => { + if let Some(values) = content { + write!(f, "{type_name}::{variant}(")?; + + for value in values { + write!(f, "{value}")?; + } + + write!(f, ")") + } else { + write!(f, "{type_name}::{variant}") + } + } ValueInner::Float(float) => write!(f, "{float}"), ValueInner::Integer(integer) => write!(f, "{integer}"), ValueInner::List(list) => { @@ -195,6 +224,19 @@ impl Serialize for Value { { match self.0.as_ref() { ValueInner::Boolean(boolean) => serializer.serialize_bool(*boolean), + ValueInner::EnumInstance { + type_name, + variant, + content, + } => { + let mut struct_ser = serializer.serialize_struct("EnumInstance", 3)?; + + struct_ser.serialize_field("type_name", type_name)?; + struct_ser.serialize_field("variant", variant)?; + struct_ser.serialize_field("content", content)?; + + struct_ser.end() + } ValueInner::Float(float) => serializer.serialize_f64(*float), ValueInner::Function(Function { type_parameters, @@ -496,6 +538,11 @@ impl<'de> Deserialize<'de> for Value { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum ValueInner { Boolean(bool), + EnumInstance { + type_name: Identifier, + variant: Identifier, + content: Option>, + }, Float(f64), Function(Function), Integer(i64), @@ -513,6 +560,13 @@ impl ValueInner { pub fn r#type(&self, context: &Context) -> Result { let r#type = match self { ValueInner::Boolean(_) => Type::Boolean, + ValueInner::EnumInstance { type_name, .. } => { + if let Some(r#type) = context.get_type(type_name)? { + r#type + } else { + return Err(ValidationError::EnumDefinitionNotFound(type_name.clone())); + } + } ValueInner::Float(_) => Type::Float, ValueInner::Integer(_) => Type::Integer, ValueInner::List(values) => { @@ -591,6 +645,33 @@ impl Ord for ValueInner { (Range(_), _) => Ordering::Greater, (String(left), String(right)) => left.cmp(right), (String(_), _) => Ordering::Greater, + ( + EnumInstance { + type_name: left_name, + variant: left_variant, + content: left_content, + }, + EnumInstance { + type_name: right_name, + variant: right_variant, + content: right_content, + }, + ) => { + let name_cmp = left_name.cmp(right_name); + + if name_cmp.is_eq() { + let variant_cmp = left_variant.cmp(right_variant); + + if variant_cmp.is_eq() { + left_content.cmp(right_content) + } else { + variant_cmp + } + } else { + name_cmp + } + } + (EnumInstance { .. }, _) => Ordering::Greater, (Function(left), Function(right)) => left.cmp(right), (Function(_), _) => Ordering::Greater, ( diff --git a/dust-lang/tests/enums.rs b/dust-lang/tests/enums.rs new file mode 100644 index 0000000..a4c7caa --- /dev/null +++ b/dust-lang/tests/enums.rs @@ -0,0 +1,23 @@ +use dust_lang::{identifier::Identifier, *}; + +#[test] +fn simple_enum() { + assert_eq!( + interpret( + "test", + " + type FooBar = enum { + Foo, + Bar, + } + + FooBar::Foo + " + ), + Ok(Some(Value::enum_instance( + Identifier::new("FooBar"), + Identifier::new("Foo"), + None + ))) + ); +}