Expand lexer and parser with more tests

This commit is contained in:
Jeff 2024-02-28 21:04:38 -05:00
parent 9d5b7b6606
commit e5aeaa67d8
14 changed files with 273 additions and 28 deletions

View File

@ -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)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Assignment<'src> { pub struct Assignment<'src> {
identifier: Identifier, identifier: Identifier,
r#type: Option<Type>,
statement: Box<Statement<'src>>, statement: Box<Statement<'src>>,
} }
impl<'src> Assignment<'src> { impl<'src> Assignment<'src> {
pub fn new(identifier: Identifier, statement: Statement<'src>) -> Self { pub fn new(identifier: Identifier, r#type: Option<Type>, statement: Statement<'src>) -> Self {
Self { Self {
identifier, identifier,
r#type,
statement: Box::new(statement), statement: Box::new(statement),
} }
} }
} }
impl<'src> AbstractTree for Assignment<'src> { impl<'src> AbstractTree for Assignment<'src> {
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
todo!()
}
fn validate(&self, _context: &Context) -> Result<(), ValidationError> {
todo!()
}
fn run(self, _context: &Context) -> Result<Value, RuntimeError> { fn run(self, _context: &Context) -> Result<Value, RuntimeError> {
todo!() todo!()
// let value = self.statement.run(context)?; // let value = self.statement.run(context)?;

View File

@ -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)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Block<'src> { pub struct Block<'src> {
@ -14,6 +18,14 @@ impl<'src> Block<'src> {
} }
impl<'src> AbstractTree for Block<'src> { impl<'src> AbstractTree for Block<'src> {
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
todo!()
}
fn validate(&self, _context: &Context) -> Result<(), ValidationError> {
todo!()
}
fn run(self, _: &Context) -> Result<Value, RuntimeError> { fn run(self, _: &Context) -> Result<Value, RuntimeError> {
todo!() todo!()
} }

View File

@ -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)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum Expression<'src> { pub enum Expression<'src> {
@ -10,6 +14,14 @@ pub enum Expression<'src> {
} }
impl<'src> AbstractTree for Expression<'src> { impl<'src> AbstractTree for Expression<'src> {
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
todo!()
}
fn validate(&self, _context: &Context) -> Result<(), ValidationError> {
todo!()
}
fn run(self, context: &Context) -> Result<Value, RuntimeError> { fn run(self, context: &Context) -> Result<Value, RuntimeError> {
match self { match self {
Expression::Identifier(identifier) => identifier.run(context), Expression::Identifier(identifier) => identifier.run(context),

View File

@ -1,8 +1,12 @@
use std::sync::Arc; 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)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Identifier(Arc<String>); pub struct Identifier(Arc<String>);
@ -14,6 +18,14 @@ impl Identifier {
} }
impl AbstractTree for Identifier { impl AbstractTree for Identifier {
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
todo!()
}
fn validate(&self, _context: &Context) -> Result<(), ValidationError> {
todo!()
}
fn run(self, _context: &Context) -> Result<Value, RuntimeError> { fn run(self, _context: &Context) -> Result<Value, RuntimeError> {
todo!() todo!()
// let value = context.get(&self)?.unwrap_or_else(Value::none).clone(); // let value = context.get(&self)?.unwrap_or_else(Value::none).clone();

View File

@ -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)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum Logic<'src> { pub enum Logic<'src> {
@ -16,6 +20,14 @@ pub enum Logic<'src> {
} }
impl<'src> AbstractTree for Logic<'src> { impl<'src> AbstractTree for Logic<'src> {
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
todo!()
}
fn validate(&self, _context: &Context) -> Result<(), ValidationError> {
todo!()
}
fn run(self, _context: &Context) -> Result<Value, RuntimeError> { fn run(self, _context: &Context) -> Result<Value, RuntimeError> {
let boolean = match self { let boolean = match self {
Logic::Equal(left, right) => left.run(_context)? == right.run(_context)?, Logic::Equal(left, right) => left.run(_context)? == right.run(_context)?,

View File

@ -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)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Loop<'src> { pub struct Loop<'src> {
@ -8,6 +12,14 @@ pub struct Loop<'src> {
} }
impl<'src> AbstractTree for Loop<'src> { impl<'src> AbstractTree for Loop<'src> {
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
todo!()
}
fn validate(&self, _context: &Context) -> Result<(), ValidationError> {
todo!()
}
fn run(self, _: &Context) -> Result<Value, RuntimeError> { fn run(self, _: &Context) -> Result<Value, RuntimeError> {
todo!() todo!()
} }

View File

@ -5,15 +5,22 @@ pub mod identifier;
pub mod logic; pub mod logic;
pub mod r#loop; pub mod r#loop;
pub mod statement; pub mod statement;
pub mod r#type;
pub mod value_node; pub mod value_node;
pub use self::{ pub use self::{
assignment::Assignment, block::Block, expression::Expression, identifier::Identifier, 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 { pub trait AbstractTree {
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError>;
fn validate(&self, context: &Context) -> Result<(), ValidationError>;
fn run(self, context: &Context) -> Result<Value, RuntimeError>; fn run(self, context: &Context) -> Result<Value, RuntimeError>;
} }

View File

@ -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)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum Statement<'src> { pub enum Statement<'src> {
@ -11,6 +14,14 @@ pub enum Statement<'src> {
} }
impl<'src> AbstractTree for Statement<'src> { impl<'src> AbstractTree for Statement<'src> {
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
todo!()
}
fn validate(&self, _context: &Context) -> Result<(), ValidationError> {
todo!()
}
fn run(self, _context: &Context) -> Result<Value, RuntimeError> { fn run(self, _context: &Context) -> Result<Value, RuntimeError> {
match self { match self {
Statement::Assignment(assignment) => assignment.run(_context), Statement::Assignment(assignment) => assignment.run(_context),

40
src/abstract_tree/type.rs Normal file
View File

@ -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<Type>),
ListExact(Vec<Type>),
Map,
Range,
String,
}
impl AbstractTree for Type {
fn expected_type(
&self,
context: &crate::context::Context,
) -> Result<Type, crate::error::ValidationError> {
todo!()
}
fn validate(
&self,
context: &crate::context::Context,
) -> Result<(), crate::error::ValidationError> {
todo!()
}
fn run(
self,
context: &crate::context::Context,
) -> Result<crate::Value, crate::error::RuntimeError> {
todo!()
}
}

View File

@ -1,8 +1,12 @@
use std::{cmp::Ordering, collections::BTreeMap, ops::Range}; 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)] #[derive(Clone, Debug, PartialEq)]
pub enum ValueNode<'src> { pub enum ValueNode<'src> {
@ -17,6 +21,25 @@ pub enum ValueNode<'src> {
} }
impl<'src> AbstractTree for ValueNode<'src> { impl<'src> AbstractTree for ValueNode<'src> {
fn expected_type(&self, _context: &Context) -> Result<Type, ValidationError> {
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<Value, RuntimeError> { fn run(self, _context: &Context) -> Result<Value, RuntimeError> {
let value = match self { let value = match self {
ValueNode::Boolean(boolean) => Value::boolean(boolean), ValueNode::Boolean(boolean) => Value::boolean(boolean),

View File

@ -32,7 +32,7 @@ impl<'src> From<RuntimeError> for Error<'src> {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum RuntimeError { pub enum RuntimeError {
RwLockPoison(RwLockPoisonError), RwLockPoison(RwLockPoisonError),
ExpectedBoolean, ValidationFailure(ValidationError),
} }
impl From<RwLockPoisonError> for RuntimeError { impl From<RwLockPoisonError> for RuntimeError {
@ -41,6 +41,24 @@ impl From<RwLockPoisonError> for RuntimeError {
} }
} }
impl From<ValidationError> for RuntimeError {
fn from(error: ValidationError) -> Self {
RuntimeError::ValidationFailure(error)
}
}
#[derive(Debug, PartialEq)]
pub enum ValidationError {
RwLockPoison(RwLockPoisonError),
ExpectedBoolean,
}
impl From<RwLockPoisonError> for ValidationError {
fn from(error: RwLockPoisonError) -> Self {
ValidationError::RwLockPoison(error)
}
}
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct RwLockPoisonError; pub struct RwLockPoisonError;

View File

@ -1,3 +1,5 @@
use std::fmt::{self, Display, Formatter};
use chumsky::prelude::*; use chumsky::prelude::*;
use crate::error::Error; use crate::error::Error;
@ -11,6 +13,22 @@ pub enum Token<'src> {
Identifier(&'src str), Identifier(&'src str),
Operator(&'src str), Operator(&'src str),
Control(&'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<Vec<(Token, SimpleSpan)>, Error<'src>> { pub fn lex<'src>(source: &'src str) -> Result<Vec<(Token, SimpleSpan)>, Error<'src>> {
@ -93,11 +111,23 @@ pub fn lexer<'src>() -> impl Parser<
just(",").padded(), just(",").padded(),
just(";").padded(), just(";").padded(),
just("::").padded(), just("::").padded(),
just(":").padded(),
)) ))
.map(Token::Control); .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(( choice((
boolean, float, integer, string, identifier, operator, control, boolean, float, integer, string, keyword, identifier, operator, control,
)) ))
.map_with(|token, state| (token, state.span())) .map_with(|token, state| (token, state.span()))
.padded() .padded()
@ -109,6 +139,11 @@ pub fn lexer<'src>() -> impl Parser<
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn keywords() {
assert_eq!(lex("int").unwrap()[0].0, Token::Keyword("int"))
}
#[test] #[test]
fn identifier() { fn identifier() {
assert_eq!(lex("x").unwrap()[0].0, Token::Identifier("x")); assert_eq!(lex("x").unwrap()[0].0, Token::Identifier("x"));

View File

@ -104,7 +104,7 @@ fn parser<'tokens, 'src: 'tokens>() -> impl Parser<
)) ))
.boxed(); .boxed();
choice([r#enum, logic, identifier_expression, list, basic_value]) choice((r#enum, logic, identifier_expression, list, basic_value))
}); });
let statement = recursive(|statement| { let statement = recursive(|statement| {
@ -112,11 +112,23 @@ fn parser<'tokens, 'src: 'tokens>() -> impl Parser<
.map(|expression| Statement::Expression(expression)) .map(|expression| Statement::Expression(expression))
.boxed(); .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 let assignment = identifier
.then(type_specification.clone().or_not())
.then_ignore(just(Token::Operator("="))) .then_ignore(just(Token::Operator("=")))
.then(statement.clone()) .then(statement.clone())
.map(|(identifier, statement)| { .map(|((identifier, r#type), statement)| {
Statement::Assignment(Assignment::new(identifier, statement)) Statement::Assignment(Assignment::new(identifier, r#type, statement))
}) })
.boxed(); .boxed();
@ -128,7 +140,7 @@ fn parser<'tokens, 'src: 'tokens>() -> impl Parser<
.map(|statements| Statement::Block(Block::new(statements))) .map(|statements| Statement::Block(Block::new(statements)))
.boxed(); .boxed();
choice([assignment, expression_statement, block]) choice((assignment, expression_statement, block))
}); });
statement statement
@ -225,11 +237,36 @@ mod tests {
parse(&lex("foobar = 1").unwrap()).unwrap()[0].0, parse(&lex("foobar = 1").unwrap()).unwrap()[0].0,
Statement::Assignment(Assignment::new( Statement::Assignment(Assignment::new(
Identifier::new("foobar"), Identifier::new("foobar"),
None,
Statement::Expression(Expression::Value(ValueNode::Integer(1))) 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] #[test]
fn logic() { fn logic() {
assert_eq!( assert_eq!(

View File

@ -5,7 +5,7 @@ use std::{
sync::{Arc, OnceLock}, sync::{Arc, OnceLock},
}; };
use crate::{abstract_tree::Identifier, error::RuntimeError}; use crate::{abstract_tree::Identifier, error::ValidationError};
pub static NONE: OnceLock<Value> = OnceLock::new(); pub static NONE: OnceLock<Value> = OnceLock::new();
@ -49,12 +49,12 @@ impl Value {
Value(Arc::new(ValueInner::Enum(name, variant))) Value(Arc::new(ValueInner::Enum(name, variant)))
} }
pub fn as_boolean(&self) -> Result<bool, RuntimeError> { pub fn as_boolean(&self) -> Result<bool, ValidationError> {
if let ValueInner::Boolean(boolean) = self.0.as_ref() { if let ValueInner::Boolean(boolean) = self.0.as_ref() {
return Ok(*boolean); return Ok(*boolean);
} }
Err(RuntimeError::ExpectedBoolean) Err(ValidationError::ExpectedBoolean)
} }
} }