1
0

Implement matching for enums

This commit is contained in:
Jeff 2024-02-15 07:04:38 -05:00
parent 85cb641af8
commit a6e52e4ee6
12 changed files with 23810 additions and 23706 deletions

View File

@ -0,0 +1,70 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Format, Identifier, Type, Value,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct EnumPattern {
name: Identifier,
variant: Identifier,
inner_identifier: Option<Identifier>,
}
impl EnumPattern {
pub fn name(&self) -> &Identifier {
&self.name
}
pub fn variant(&self) -> &Identifier {
&self.variant
}
pub fn inner_identifier(&self) -> &Option<Identifier> {
&self.inner_identifier
}
}
impl AbstractTree for EnumPattern {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "enum_pattern", node)?;
let enum_name_node = node.child(0).unwrap();
let name = Identifier::from_syntax(enum_name_node, source, context)?;
let enum_variant_node = node.child(2).unwrap();
let variant = Identifier::from_syntax(enum_variant_node, source, context)?;
let inner_identifier = if let Some(child) = node.child(4) {
Some(Identifier::from_syntax(child, source, context)?)
} else {
None
};
Ok(EnumPattern {
name,
variant,
inner_identifier,
})
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
Ok(Value::none())
}
}
impl Format for EnumPattern {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -3,17 +3,18 @@
//! Note that this module is called "match" but is escaped as "r#match" because //! Note that this module is called "match" but is escaped as "r#match" because
//! "match" is a keyword in Rust. //! "match" is a keyword in Rust.
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{ use crate::{
error::{RuntimeError, SyntaxError, ValidationError}, error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, Expression, Format, Statement, SyntaxNode, Type, Value, AbstractTree, Context, Expression, Format, MatchPattern, Statement, Type, Value,
}; };
/// Abstract representation of a match statement. /// Abstract representation of a match statement.
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub struct Match { pub struct Match {
matcher: Expression, matcher: Expression,
options: Vec<(Expression, Statement)>, options: Vec<(MatchPattern, Statement)>,
fallback: Option<Box<Statement>>, fallback: Option<Box<Statement>>,
} }
@ -25,19 +26,15 @@ impl AbstractTree for Match {
let matcher = Expression::from_syntax(matcher_node, source, context)?; let matcher = Expression::from_syntax(matcher_node, source, context)?;
let mut options = Vec::new(); let mut options = Vec::new();
let mut previous_expression = None; let mut previous_pattern = None;
let mut next_statement_is_fallback = false; let mut next_statement_is_fallback = false;
let mut fallback = None; let mut fallback = None;
for index in 2..node.child_count() { for index in 2..node.child_count() {
let child = node.child(index).unwrap(); let child = node.child(index).unwrap();
if child.kind() == "*" { if child.kind() == "match_pattern" {
next_statement_is_fallback = true; previous_pattern = Some(MatchPattern::from_syntax(child, source, context)?);
}
if child.kind() == "expression" {
previous_expression = Some(Expression::from_syntax(child, source, context)?);
} }
if child.kind() == "statement" { if child.kind() == "statement" {
@ -46,7 +43,7 @@ impl AbstractTree for Match {
if next_statement_is_fallback { if next_statement_is_fallback {
fallback = Some(Box::new(statement)); fallback = Some(Box::new(statement));
next_statement_is_fallback = false; next_statement_is_fallback = false;
} else if let Some(expression) = &previous_expression { } else if let Some(expression) = &previous_pattern {
options.push((expression.clone(), statement)); options.push((expression.clone(), statement));
} }
} }
@ -83,10 +80,26 @@ impl AbstractTree for Match {
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> { fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
let matcher_value = self.matcher.run(source, context)?; let matcher_value = self.matcher.run(source, context)?;
for (expression, statement) in &self.options { for (pattern, statement) in &self.options {
let option_value = expression.run(source, context)?; if let (Value::Enum(enum_instance), MatchPattern::EnumPattern(enum_pattern)) =
(&matcher_value, pattern)
{
if enum_instance.name() == enum_pattern.name().inner()
&& enum_instance.variant_name() == enum_pattern.variant().inner()
{
let statement_context = Context::with_variables_from(context)?;
if matcher_value == option_value { if let Some(identifier) = enum_pattern.inner_identifier() {
statement_context
.set_value(identifier.inner().clone(), enum_instance.value().clone())?;
}
return statement.run(source, &statement_context);
}
}
let pattern_value = pattern.run(source, context)?;
if matcher_value == pattern_value {
return statement.run(source, context); return statement.run(source, context);
} }
} }

View File

@ -0,0 +1,65 @@
use serde::{Deserialize, Serialize};
use tree_sitter::Node as SyntaxNode;
use crate::{
error::{RuntimeError, SyntaxError, ValidationError},
AbstractTree, Context, EnumPattern, Format, Type, Value, ValueNode,
};
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
pub enum MatchPattern {
EnumPattern(EnumPattern),
Value(ValueNode),
Wildcard,
}
impl AbstractTree for MatchPattern {
fn from_syntax(node: SyntaxNode, source: &str, context: &Context) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node(source, "match_pattern", node)?;
let child = node.child(0).unwrap();
let pattern = match child.kind() {
"enum_pattern" => {
MatchPattern::EnumPattern(EnumPattern::from_syntax(child, source, context)?)
}
"value" => MatchPattern::Value(ValueNode::from_syntax(child, source, context)?),
"*" => MatchPattern::Wildcard,
_ => {
return Err(SyntaxError::UnexpectedSyntaxNode {
expected: "enum pattern or value".to_string(),
actual: child.kind().to_string(),
location: child.start_position(),
relevant_source: source[child.byte_range()].to_string(),
})
}
};
Ok(pattern)
}
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
match self {
MatchPattern::EnumPattern(enum_pattern) => enum_pattern.expected_type(_context),
MatchPattern::Value(value_node) => value_node.expected_type(_context),
MatchPattern::Wildcard => todo!(),
}
}
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(&self, _source: &str, _context: &Context) -> Result<Value, RuntimeError> {
match self {
MatchPattern::EnumPattern(enum_pattern) => enum_pattern.run(_source, _context),
MatchPattern::Value(value_node) => value_node.run(_source, _context),
MatchPattern::Wildcard => todo!(),
}
}
}
impl Format for MatchPattern {
fn format(&self, _output: &mut String, _indent_level: u8) {
todo!()
}
}

View File

@ -12,6 +12,7 @@ pub mod assignment_operator;
pub mod block; pub mod block;
pub mod command; pub mod command;
pub mod enum_defintion; pub mod enum_defintion;
pub mod enum_pattern;
pub mod expression; pub mod expression;
pub mod r#for; pub mod r#for;
pub mod function_call; pub mod function_call;
@ -26,6 +27,7 @@ pub mod logic;
pub mod logic_operator; pub mod logic_operator;
pub mod map_node; pub mod map_node;
pub mod r#match; pub mod r#match;
pub mod match_pattern;
pub mod math; pub mod math;
pub mod math_operator; pub mod math_operator;
pub mod new; pub mod new;
@ -38,12 +40,12 @@ pub mod value_node;
pub mod r#while; pub mod r#while;
pub use { pub use {
assignment::*, assignment_operator::*, block::*, command::*, enum_defintion::*, expression::*, assignment::*, assignment_operator::*, block::*, command::*, enum_defintion::*,
function_call::*, function_expression::*, function_node::*, identifier::*, if_else::*, enum_pattern::*, expression::*, function_call::*, function_expression::*, function_node::*,
index::*, index_assignment::IndexAssignment, index_expression::*, logic::*, logic_operator::*, identifier::*, if_else::*, index::*, index_assignment::IndexAssignment, index_expression::*,
map_node::*, math::*, math_operator::*, new::*, r#as::*, r#for::*, r#match::*, r#type::*, logic::*, logic_operator::*, map_node::*, match_pattern::*, math::*, math_operator::*, new::*,
r#while::*, statement::*, struct_definition::*, type_definition::*, type_specification::*, r#as::*, r#for::*, r#match::*, r#type::*, r#while::*, statement::*, struct_definition::*,
value_node::*, type_definition::*, type_specification::*, value_node::*,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@ -1,3 +1,5 @@
use std::fmt::{self, Display, Formatter};
use crate::Value; use crate::Value;
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)] #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
@ -15,4 +17,22 @@ impl EnumInstance {
value: Box::new(value), value: Box::new(value),
} }
} }
pub fn name(&self) -> &String {
&self.name
}
pub fn variant_name(&self) -> &String {
&self.variant_name
}
pub fn value(&self) -> &Value {
&self.value
}
}
impl Display for EnumInstance {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}::{}({})", self.name, self.variant_name, self.value)
}
} }

View File

@ -560,7 +560,7 @@ impl Display for Value {
Value::Function(function) => write!(f, "{function}"), Value::Function(function) => write!(f, "{function}"),
Value::Struct(structure) => write!(f, "{structure}"), Value::Struct(structure) => write!(f, "{structure}"),
Value::Range(range) => write!(f, "{}..{}", range.start(), range.end()), Value::Range(range) => write!(f, "{}..{}", range.start(), range.end()),
Value::Enum(_) => todo!(), Value::Enum(enum_instance) => write!(f, "{enum_instance}"),
} }
} }
} }

View File

@ -1,7 +1,7 @@
use dust_lang::*; use dust_lang::*;
#[test] #[test]
fn r#match() { fn match_value() {
let test = interpret( let test = interpret(
" "
match 1 { match 1 {
@ -32,3 +32,19 @@ fn match_assignment() {
assert_eq!(Value::Boolean(true), test); assert_eq!(Value::Boolean(true), test);
} }
#[test]
fn match_enum() {
let result = interpret(
"
foobar = Option::Some(true)
match foobar {
Option::None => false,
Option::Some(content) => content,
}
",
);
assert_eq!(result, Ok(Value::Boolean(true)));
}

View File

@ -16,7 +16,7 @@ match x {
(match (match
(expression (expression
(identifier)) (identifier))
(expression (match_pattern
(value (value
(integer))) (integer)))
(statement (statement
@ -25,10 +25,43 @@ match x {
(expression (expression
(value (value
(boolean)))))) (boolean))))))
(expression (match_pattern
(value (value
(integer))) (integer)))
(statement (statement
(expression (expression
(value (value
(boolean))))))) (boolean)))))))
================================================================================
Match Enum
================================================================================
match foobar {
FooBar::Foo => true
FooBar::Bar => false
}
--------------------------------------------------------------------------------
(root
(statement
(match
(expression
(identifier))
(match_pattern
(enum_pattern
(identifier)
(identifier)))
(statement
(expression
(value
(boolean))))
(match_pattern
(enum_pattern
(identifier)
(identifier)))
(statement
(expression
(value
(boolean)))))))

View File

@ -290,7 +290,7 @@ module.exports = grammar({
'{', '{',
repeat1( repeat1(
seq( seq(
choice($.expression, '*'), $.match_pattern,
'=>', '=>',
$.statement, $.statement,
optional(','), optional(','),
@ -300,6 +300,26 @@ module.exports = grammar({
), ),
), ),
match_pattern: $ =>
choice(
$.enum_pattern,
$.value,
'*',
),
enum_pattern: $ =>
prec(
1,
seq(
$.identifier,
'::',
$.identifier,
optional(
seq('(', $.identifier, ')'),
),
),
),
while: $ => while: $ =>
seq( seq(
'while', 'while',

View File

@ -855,19 +855,10 @@
"type": "REPEAT1", "type": "REPEAT1",
"content": { "content": {
"type": "SEQ", "type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [ "members": [
{ {
"type": "SYMBOL", "type": "SYMBOL",
"name": "expression" "name": "match_pattern"
},
{
"type": "STRING",
"value": "*"
}
]
}, },
{ {
"type": "STRING", "type": "STRING",
@ -899,6 +890,69 @@
] ]
} }
}, },
"match_pattern": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "enum_pattern"
},
{
"type": "SYMBOL",
"name": "value"
},
{
"type": "STRING",
"value": "*"
}
]
},
"enum_pattern": {
"type": "PREC",
"value": 1,
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "identifier"
},
{
"type": "STRING",
"value": "::"
},
{
"type": "SYMBOL",
"name": "identifier"
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "("
},
{
"type": "SYMBOL",
"name": "identifier"
},
{
"type": "STRING",
"value": ")"
}
]
},
{
"type": "BLANK"
}
]
}
]
}
},
"while": { "while": {
"type": "SEQ", "type": "SEQ",
"members": [ "members": [

View File

@ -170,6 +170,21 @@
] ]
} }
}, },
{
"type": "enum_pattern",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "identifier",
"named": true
}
]
}
},
{ {
"type": "expression", "type": "expression",
"named": true, "named": true,
@ -495,6 +510,10 @@
"type": "expression", "type": "expression",
"named": true "named": true
}, },
{
"type": "match_pattern",
"named": true
},
{ {
"type": "statement", "type": "statement",
"named": true "named": true
@ -502,6 +521,25 @@
] ]
} }
}, },
{
"type": "match_pattern",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": false,
"types": [
{
"type": "enum_pattern",
"named": true
},
{
"type": "value",
"named": true
}
]
}
},
{ {
"type": "math", "type": "math",
"named": true, "named": true,

File diff suppressed because it is too large Load Diff