From e5aeaa67d802c9462e2413d6e7a5970468b8513e Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 28 Feb 2024 21:04:38 -0500 Subject: [PATCH] Expand lexer and parser with more tests --- src/abstract_tree/assignment.rs | 20 ++++++++++++--- src/abstract_tree/block.rs | 16 ++++++++++-- src/abstract_tree/expression.rs | 16 ++++++++++-- src/abstract_tree/identifier.rs | 16 ++++++++++-- src/abstract_tree/logic.rs | 16 ++++++++++-- src/abstract_tree/loop.rs | 16 ++++++++++-- src/abstract_tree/mod.rs | 11 ++++++-- src/abstract_tree/statement.rs | 15 +++++++++-- src/abstract_tree/type.rs | 40 +++++++++++++++++++++++++++++ src/abstract_tree/value_node.rs | 27 ++++++++++++++++++-- src/error.rs | 20 ++++++++++++++- src/lexer.rs | 37 ++++++++++++++++++++++++++- src/parser.rs | 45 ++++++++++++++++++++++++++++++--- src/value.rs | 6 ++--- 14 files changed, 273 insertions(+), 28 deletions(-) create mode 100644 src/abstract_tree/type.rs diff --git a/src/abstract_tree/assignment.rs b/src/abstract_tree/assignment.rs index 71f3720..a6ce593 100644 --- a/src/abstract_tree/assignment.rs +++ b/src/abstract_tree/assignment.rs @@ -1,23 +1,37 @@ -use crate::{error::RuntimeError, value::Value, Context}; +use crate::{ + error::{RuntimeError, ValidationError}, + value::Value, + Context, +}; -use super::{AbstractTree, Identifier, Statement}; +use super::{AbstractTree, Identifier, Statement, Type}; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub struct Assignment<'src> { identifier: Identifier, + r#type: Option, statement: Box>, } impl<'src> Assignment<'src> { - pub fn new(identifier: Identifier, statement: Statement<'src>) -> Self { + pub fn new(identifier: Identifier, r#type: Option, statement: Statement<'src>) -> Self { Self { identifier, + r#type, statement: Box::new(statement), } } } impl<'src> AbstractTree for Assignment<'src> { + fn expected_type(&self, _context: &Context) -> Result { + todo!() + } + + fn validate(&self, _context: &Context) -> Result<(), ValidationError> { + todo!() + } + fn run(self, _context: &Context) -> Result { todo!() // let value = self.statement.run(context)?; diff --git a/src/abstract_tree/block.rs b/src/abstract_tree/block.rs index 616c39a..28d9e13 100644 --- a/src/abstract_tree/block.rs +++ b/src/abstract_tree/block.rs @@ -1,6 +1,10 @@ -use crate::{context::Context, error::RuntimeError, Value}; +use crate::{ + context::Context, + error::{RuntimeError, ValidationError}, + Value, +}; -use super::{AbstractTree, Statement}; +use super::{AbstractTree, Statement, Type}; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub struct Block<'src> { @@ -14,6 +18,14 @@ impl<'src> Block<'src> { } impl<'src> AbstractTree for Block<'src> { + fn expected_type(&self, _context: &Context) -> Result { + todo!() + } + + fn validate(&self, _context: &Context) -> Result<(), ValidationError> { + todo!() + } + fn run(self, _: &Context) -> Result { todo!() } diff --git a/src/abstract_tree/expression.rs b/src/abstract_tree/expression.rs index 01adb2a..386e6c0 100644 --- a/src/abstract_tree/expression.rs +++ b/src/abstract_tree/expression.rs @@ -1,6 +1,10 @@ -use crate::{context::Context, error::RuntimeError, Value}; +use crate::{ + context::Context, + error::{RuntimeError, ValidationError}, + Value, +}; -use super::{AbstractTree, Identifier, Logic, ValueNode}; +use super::{AbstractTree, Identifier, Logic, Type, ValueNode}; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub enum Expression<'src> { @@ -10,6 +14,14 @@ pub enum Expression<'src> { } impl<'src> AbstractTree for Expression<'src> { + fn expected_type(&self, _context: &Context) -> Result { + todo!() + } + + fn validate(&self, _context: &Context) -> Result<(), ValidationError> { + todo!() + } + fn run(self, context: &Context) -> Result { match self { Expression::Identifier(identifier) => identifier.run(context), diff --git a/src/abstract_tree/identifier.rs b/src/abstract_tree/identifier.rs index 2bdaf32..fd45f60 100644 --- a/src/abstract_tree/identifier.rs +++ b/src/abstract_tree/identifier.rs @@ -1,8 +1,12 @@ use std::sync::Arc; -use crate::{context::Context, error::RuntimeError, Value}; +use crate::{ + context::Context, + error::{RuntimeError, ValidationError}, + Value, +}; -use super::AbstractTree; +use super::{AbstractTree, Type}; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub struct Identifier(Arc); @@ -14,6 +18,14 @@ impl Identifier { } impl AbstractTree for Identifier { + fn expected_type(&self, _context: &Context) -> Result { + todo!() + } + + fn validate(&self, _context: &Context) -> Result<(), ValidationError> { + todo!() + } + fn run(self, _context: &Context) -> Result { todo!() // let value = context.get(&self)?.unwrap_or_else(Value::none).clone(); diff --git a/src/abstract_tree/logic.rs b/src/abstract_tree/logic.rs index 75a40e4..b453197 100644 --- a/src/abstract_tree/logic.rs +++ b/src/abstract_tree/logic.rs @@ -1,6 +1,10 @@ -use crate::{context::Context, error::RuntimeError, Value}; +use crate::{ + context::Context, + error::{RuntimeError, ValidationError}, + Value, +}; -use super::{AbstractTree, Expression}; +use super::{AbstractTree, Expression, Type}; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub enum Logic<'src> { @@ -16,6 +20,14 @@ pub enum Logic<'src> { } impl<'src> AbstractTree for Logic<'src> { + fn expected_type(&self, _context: &Context) -> Result { + todo!() + } + + fn validate(&self, _context: &Context) -> Result<(), ValidationError> { + todo!() + } + fn run(self, _context: &Context) -> Result { let boolean = match self { Logic::Equal(left, right) => left.run(_context)? == right.run(_context)?, diff --git a/src/abstract_tree/loop.rs b/src/abstract_tree/loop.rs index 938a1c4..7bbadf5 100644 --- a/src/abstract_tree/loop.rs +++ b/src/abstract_tree/loop.rs @@ -1,6 +1,10 @@ -use crate::{context::Context, error::RuntimeError, Value}; +use crate::{ + context::Context, + error::{RuntimeError, ValidationError}, + Value, +}; -use super::{AbstractTree, Block}; +use super::{AbstractTree, Block, Type}; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub struct Loop<'src> { @@ -8,6 +12,14 @@ pub struct Loop<'src> { } impl<'src> AbstractTree for Loop<'src> { + fn expected_type(&self, _context: &Context) -> Result { + todo!() + } + + fn validate(&self, _context: &Context) -> Result<(), ValidationError> { + todo!() + } + fn run(self, _: &Context) -> Result { todo!() } diff --git a/src/abstract_tree/mod.rs b/src/abstract_tree/mod.rs index c3822e9..0684e5c 100644 --- a/src/abstract_tree/mod.rs +++ b/src/abstract_tree/mod.rs @@ -5,15 +5,22 @@ pub mod identifier; pub mod logic; pub mod r#loop; pub mod statement; +pub mod r#type; pub mod value_node; pub use self::{ assignment::Assignment, block::Block, expression::Expression, identifier::Identifier, - logic::Logic, r#loop::Loop, statement::Statement, value_node::ValueNode, + logic::Logic, r#loop::Loop, r#type::Type, statement::Statement, value_node::ValueNode, }; -use crate::{context::Context, error::RuntimeError, Value}; +use crate::{ + context::Context, + error::{RuntimeError, ValidationError}, + Value, +}; pub trait AbstractTree { + fn expected_type(&self, context: &Context) -> Result; + fn validate(&self, context: &Context) -> Result<(), ValidationError>; fn run(self, context: &Context) -> Result; } diff --git a/src/abstract_tree/statement.rs b/src/abstract_tree/statement.rs index e37083f..6e2311a 100644 --- a/src/abstract_tree/statement.rs +++ b/src/abstract_tree/statement.rs @@ -1,6 +1,9 @@ -use crate::{context::Context, error::RuntimeError}; +use crate::{ + context::Context, + error::{RuntimeError, ValidationError}, +}; -use super::{AbstractTree, Assignment, Block, Expression, Loop, Value}; +use super::{AbstractTree, Assignment, Block, Expression, Loop, Type, Value}; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub enum Statement<'src> { @@ -11,6 +14,14 @@ pub enum Statement<'src> { } impl<'src> AbstractTree for Statement<'src> { + fn expected_type(&self, _context: &Context) -> Result { + todo!() + } + + fn validate(&self, _context: &Context) -> Result<(), ValidationError> { + todo!() + } + fn run(self, _context: &Context) -> Result { match self { Statement::Assignment(assignment) => assignment.run(_context), diff --git a/src/abstract_tree/type.rs b/src/abstract_tree/type.rs new file mode 100644 index 0000000..a87b26c --- /dev/null +++ b/src/abstract_tree/type.rs @@ -0,0 +1,40 @@ +use crate::abstract_tree::Identifier; + +use super::AbstractTree; + +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub enum Type { + Boolean, + Custom(Identifier), + Float, + Integer, + List, + ListOf(Box), + ListExact(Vec), + Map, + Range, + String, +} + +impl AbstractTree for Type { + fn expected_type( + &self, + context: &crate::context::Context, + ) -> Result { + todo!() + } + + fn validate( + &self, + context: &crate::context::Context, + ) -> Result<(), crate::error::ValidationError> { + todo!() + } + + fn run( + self, + context: &crate::context::Context, + ) -> Result { + todo!() + } +} diff --git a/src/abstract_tree/value_node.rs b/src/abstract_tree/value_node.rs index a20929e..4ef0f3a 100644 --- a/src/abstract_tree/value_node.rs +++ b/src/abstract_tree/value_node.rs @@ -1,8 +1,12 @@ use std::{cmp::Ordering, collections::BTreeMap, ops::Range}; -use crate::{context::Context, error::RuntimeError, Value}; +use crate::{ + context::Context, + error::{RuntimeError, ValidationError}, + Value, +}; -use super::{AbstractTree, Expression, Identifier}; +use super::{AbstractTree, Expression, Identifier, Type}; #[derive(Clone, Debug, PartialEq)] pub enum ValueNode<'src> { @@ -17,6 +21,25 @@ pub enum ValueNode<'src> { } impl<'src> AbstractTree for ValueNode<'src> { + fn expected_type(&self, _context: &Context) -> Result { + let r#type = match self { + ValueNode::Boolean(_) => Type::Boolean, + ValueNode::Float(_) => Type::Float, + ValueNode::Integer(_) => Type::Integer, + ValueNode::List(_) => Type::List, + ValueNode::Map(_) => Type::Map, + ValueNode::Range(_) => Type::Range, + ValueNode::String(_) => Type::String, + ValueNode::Enum(name, _) => Type::Custom(name.clone()), + }; + + Ok(r#type) + } + + fn validate(&self, _context: &Context) -> Result<(), ValidationError> { + todo!() + } + fn run(self, _context: &Context) -> Result { let value = match self { ValueNode::Boolean(boolean) => Value::boolean(boolean), diff --git a/src/error.rs b/src/error.rs index 2b324ca..e33a738 100644 --- a/src/error.rs +++ b/src/error.rs @@ -32,7 +32,7 @@ impl<'src> From for Error<'src> { #[derive(Debug, PartialEq)] pub enum RuntimeError { RwLockPoison(RwLockPoisonError), - ExpectedBoolean, + ValidationFailure(ValidationError), } impl From for RuntimeError { @@ -41,6 +41,24 @@ impl From for RuntimeError { } } +impl From for RuntimeError { + fn from(error: ValidationError) -> Self { + RuntimeError::ValidationFailure(error) + } +} + +#[derive(Debug, PartialEq)] +pub enum ValidationError { + RwLockPoison(RwLockPoisonError), + ExpectedBoolean, +} + +impl From for ValidationError { + fn from(error: RwLockPoisonError) -> Self { + ValidationError::RwLockPoison(error) + } +} + #[derive(Debug, PartialEq)] pub struct RwLockPoisonError; diff --git a/src/lexer.rs b/src/lexer.rs index 9d081e3..cb7b7e5 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -1,3 +1,5 @@ +use std::fmt::{self, Display, Formatter}; + use chumsky::prelude::*; use crate::error::Error; @@ -11,6 +13,22 @@ pub enum Token<'src> { Identifier(&'src str), Operator(&'src str), Control(&'src str), + Keyword(&'src str), +} + +impl<'src> Display for Token<'src> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Token::Boolean(boolean) => write!(f, "{boolean}"), + Token::Integer(integer) => write!(f, "{integer}"), + Token::Float(float) => write!(f, "{float}"), + Token::String(string) => write!(f, "{string}"), + Token::Identifier(string) => write!(f, "{string}"), + Token::Operator(string) => write!(f, "{string}"), + Token::Control(string) => write!(f, "{string}"), + Token::Keyword(string) => write!(f, "{string}"), + } + } } pub fn lex<'src>(source: &'src str) -> Result, Error<'src>> { @@ -93,11 +111,23 @@ pub fn lexer<'src>() -> impl Parser< just(",").padded(), just(";").padded(), just("::").padded(), + just(":").padded(), )) .map(Token::Control); + let keyword = choice(( + just("bool").padded(), + just("float").padded(), + just("int").padded(), + just("list").padded(), + just("map").padded(), + just("range").padded(), + just("str").padded(), + )) + .map(Token::Keyword); + choice(( - boolean, float, integer, string, identifier, operator, control, + boolean, float, integer, string, keyword, identifier, operator, control, )) .map_with(|token, state| (token, state.span())) .padded() @@ -109,6 +139,11 @@ pub fn lexer<'src>() -> impl Parser< mod tests { use super::*; + #[test] + fn keywords() { + assert_eq!(lex("int").unwrap()[0].0, Token::Keyword("int")) + } + #[test] fn identifier() { assert_eq!(lex("x").unwrap()[0].0, Token::Identifier("x")); diff --git a/src/parser.rs b/src/parser.rs index 83d1dd9..ae94e31 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -104,7 +104,7 @@ fn parser<'tokens, 'src: 'tokens>() -> impl Parser< )) .boxed(); - choice([r#enum, logic, identifier_expression, list, basic_value]) + choice((r#enum, logic, identifier_expression, list, basic_value)) }); let statement = recursive(|statement| { @@ -112,11 +112,23 @@ fn parser<'tokens, 'src: 'tokens>() -> impl Parser< .map(|expression| Statement::Expression(expression)) .boxed(); + let type_specification = just(Token::Control(":")).ignore_then(choice(( + just(Token::Keyword("bool")).to(Type::Boolean), + just(Token::Keyword("float")).to(Type::Float), + just(Token::Keyword("int")).to(Type::Integer), + just(Token::Keyword("range")).to(Type::Range), + just(Token::Keyword("str")).to(Type::String), + identifier + .clone() + .map(|identifier| Type::Custom(identifier)), + ))); + let assignment = identifier + .then(type_specification.clone().or_not()) .then_ignore(just(Token::Operator("="))) .then(statement.clone()) - .map(|(identifier, statement)| { - Statement::Assignment(Assignment::new(identifier, statement)) + .map(|((identifier, r#type), statement)| { + Statement::Assignment(Assignment::new(identifier, r#type, statement)) }) .boxed(); @@ -128,7 +140,7 @@ fn parser<'tokens, 'src: 'tokens>() -> impl Parser< .map(|statements| Statement::Block(Block::new(statements))) .boxed(); - choice([assignment, expression_statement, block]) + choice((assignment, expression_statement, block)) }); statement @@ -225,11 +237,36 @@ mod tests { parse(&lex("foobar = 1").unwrap()).unwrap()[0].0, Statement::Assignment(Assignment::new( Identifier::new("foobar"), + None, Statement::Expression(Expression::Value(ValueNode::Integer(1))) )), ); } + #[test] + fn assignment_with_type() { + assert_eq!( + parse(&lex("foobar: int = 1").unwrap()).unwrap()[0].0, + Statement::Assignment(Assignment::new( + Identifier::new("foobar"), + Some(Type::Integer), + Statement::Expression(Expression::Value(ValueNode::Integer(1))) + )), + ); + + assert_eq!( + parse(&lex("foobar: Foo = Foo::Bar").unwrap()).unwrap()[0].0, + Statement::Assignment(Assignment::new( + Identifier::new("foobar"), + Some(Type::Custom(Identifier::new("Foo"))), + Statement::Expression(Expression::Value(ValueNode::Enum( + Identifier::new("Foo"), + Identifier::new("Bar") + ))) + )), + ); + } + #[test] fn logic() { assert_eq!( diff --git a/src/value.rs b/src/value.rs index 9f57646..f78d8f7 100644 --- a/src/value.rs +++ b/src/value.rs @@ -5,7 +5,7 @@ use std::{ sync::{Arc, OnceLock}, }; -use crate::{abstract_tree::Identifier, error::RuntimeError}; +use crate::{abstract_tree::Identifier, error::ValidationError}; pub static NONE: OnceLock = OnceLock::new(); @@ -49,12 +49,12 @@ impl Value { Value(Arc::new(ValueInner::Enum(name, variant))) } - pub fn as_boolean(&self) -> Result { + pub fn as_boolean(&self) -> Result { if let ValueInner::Boolean(boolean) = self.0.as_ref() { return Ok(*boolean); } - Err(RuntimeError::ExpectedBoolean) + Err(ValidationError::ExpectedBoolean) } }