diff --git a/Cargo.lock b/Cargo.lock index 23253d9..0168fe9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,10 +35,17 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd002a6223f12c7a95cdd4b1cb3a0149d22d37f7a9ecdb2cb691a071fe236c29" dependencies = [ + "concolor", "unicode-width", "yansi", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "cc" version = "1.0.86" @@ -64,6 +71,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "concolor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b946244a988c390a94667ae0e3958411fa40cc46ea496a929b263d883f5f9c3" +dependencies = [ + "bitflags", + "concolor-query", + "is-terminal", +] + +[[package]] +name = "concolor-query" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" +dependencies = [ + "windows-sys 0.45.0", +] + [[package]] name = "dust-lang" version = "0.5.0" @@ -82,6 +109,23 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hermit-abi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "libc" version = "0.2.153" @@ -228,6 +272,138 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 98f56e8..b94fde4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,5 +13,5 @@ opt-level = 1 opt-level = 3 [dependencies] -ariadne = "0.4.0" -chumsky = { version = "1.0.0-alpha.6", features = ["pratt"] } +ariadne = { version = "0.4.0", features = ["auto-color"] } +chumsky = { version = "1.0.0-alpha.6", features = ["pratt", "label"] } diff --git a/src/abstract_tree/assignment.rs b/src/abstract_tree/assignment.rs new file mode 100644 index 0000000..9d7cdac --- /dev/null +++ b/src/abstract_tree/assignment.rs @@ -0,0 +1,50 @@ +use crate::{error::RuntimeError, Context}; + +use super::{AbstractTree, Identifier, Statement, Value}; + +#[derive(Clone, Debug, PartialEq)] +pub struct Assignment { + identifier: Identifier, + statement: Box, +} + +impl Assignment { + pub fn new(identifier: Identifier, statement: Statement) -> Self { + Self { + identifier, + statement: Box::new(statement), + } + } +} + +impl AbstractTree for Assignment { + fn run(self, context: &Context) -> Result { + let value = self.statement.run(context)?; + + context.set(self.identifier, value)?; + + Ok(Value::none()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn assign_value() { + let context = Context::new(); + + Assignment::new( + Identifier::new("foobar"), + Statement::Value(Value::integer(42)), + ) + .run(&context) + .unwrap(); + + assert_eq!( + context.get(&Identifier::new("foobar")).unwrap(), + Some(Value::integer(42)) + ) + } +} diff --git a/src/abstract_tree/block.rs b/src/abstract_tree/block.rs new file mode 100644 index 0000000..ff4c940 --- /dev/null +++ b/src/abstract_tree/block.rs @@ -0,0 +1,20 @@ +use crate::{context::Context, error::RuntimeError}; + +use super::{AbstractTree, Statement, Value}; + +#[derive(Clone, Debug, PartialEq)] +pub struct Block { + statements: Vec, +} + +impl Block { + pub fn new(statements: Vec) -> Self { + Self { statements } + } +} + +impl AbstractTree for Block { + fn run(self, _: &Context) -> Result { + todo!() + } +} diff --git a/src/abstract_tree/expression.rs b/src/abstract_tree/expression.rs new file mode 100644 index 0000000..1df2d84 --- /dev/null +++ b/src/abstract_tree/expression.rs @@ -0,0 +1,20 @@ +use crate::{context::Context, error::RuntimeError}; + +use super::{AbstractTree, Identifier, Logic, Value}; + +#[derive(Clone, Debug, PartialEq)] +pub enum Expression { + Identifier(Identifier), + Logic(Box), + Value(Value), +} + +impl AbstractTree for Expression { + fn run(self, context: &Context) -> Result { + match self { + Expression::Identifier(identifier) => identifier.run(context), + Expression::Logic(logic) => logic.run(context), + Expression::Value(value) => value.run(context), + } + } +} diff --git a/src/abstract_tree/identifier.rs b/src/abstract_tree/identifier.rs new file mode 100644 index 0000000..30f44e3 --- /dev/null +++ b/src/abstract_tree/identifier.rs @@ -0,0 +1,20 @@ +use crate::{context::Context, error::RuntimeError}; + +use super::{AbstractTree, Value}; + +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct Identifier(String); + +impl Identifier { + pub fn new(string: T) -> Identifier { + Identifier(string.to_string()) + } +} + +impl AbstractTree for Identifier { + fn run(self, context: &Context) -> Result { + let value = context.get(&self)?.unwrap_or_else(Value::none).clone(); + + Ok(value) + } +} diff --git a/src/abstract_tree/logic.rs b/src/abstract_tree/logic.rs new file mode 100644 index 0000000..0fd7994 --- /dev/null +++ b/src/abstract_tree/logic.rs @@ -0,0 +1,22 @@ +use crate::{context::Context, error::RuntimeError}; + +use super::{AbstractTree, Statement, Value}; + +#[derive(Clone, Debug, PartialEq)] +pub enum Logic { + Equal(Statement, Statement), + NotEqual(Statement, Statement), + Greater(Statement, Statement), + Less(Statement, Statement), + GreaterOrEqual(Statement, Statement), + LessOrEqual(Statement, Statement), + And(Statement, Statement), + Or(Statement, Statement), + Not(Statement), +} + +impl AbstractTree for Logic { + fn run(self, _: &Context) -> Result { + todo!() + } +} diff --git a/src/abstract_tree/loop.rs b/src/abstract_tree/loop.rs new file mode 100644 index 0000000..0419ffe --- /dev/null +++ b/src/abstract_tree/loop.rs @@ -0,0 +1,14 @@ +use crate::{context::Context, error::RuntimeError}; + +use super::{AbstractTree, Block, Value}; + +#[derive(Clone, Debug, PartialEq)] +pub struct Loop { + block: Block, +} + +impl AbstractTree for Loop { + fn run(self, _: &Context) -> Result { + todo!() + } +} diff --git a/src/abstract_tree/mod.rs b/src/abstract_tree/mod.rs new file mode 100644 index 0000000..a437700 --- /dev/null +++ b/src/abstract_tree/mod.rs @@ -0,0 +1,18 @@ +pub mod assignment; +pub mod block; +pub mod identifier; +pub mod logic; +pub mod r#loop; +pub mod statement; +pub mod value; + +pub use self::{ + assignment::Assignment, block::Block, identifier::Identifier, logic::Logic, r#loop::Loop, + statement::Statement, value::Value, +}; + +use crate::{context::Context, error::RuntimeError}; + +pub trait AbstractTree { + fn run(self, context: &Context) -> Result; +} diff --git a/src/abstract_tree/statement.rs b/src/abstract_tree/statement.rs new file mode 100644 index 0000000..8cb7ca5 --- /dev/null +++ b/src/abstract_tree/statement.rs @@ -0,0 +1,26 @@ +use crate::{context::Context, error::RuntimeError}; + +use super::{AbstractTree, Assignment, Block, Identifier, Logic, Loop, Value}; + +#[derive(Clone, Debug, PartialEq)] +pub enum Statement { + Assignment(Assignment), + Block(Block), + Identifier(Identifier), + Loop(Loop), + Value(Value), + Logic(Box), +} + +impl AbstractTree for Statement { + fn run(self, _context: &Context) -> Result { + match self { + Statement::Assignment(assignment) => assignment.run(_context), + Statement::Block(_) => todo!(), + Statement::Identifier(identifier) => identifier.run(_context), + Statement::Loop(_) => todo!(), + Statement::Value(value) => value.run(_context), + Statement::Logic(_) => todo!(), + } + } +} diff --git a/src/abstract_tree/value.rs b/src/abstract_tree/value.rs new file mode 100644 index 0000000..3cc57ac --- /dev/null +++ b/src/abstract_tree/value.rs @@ -0,0 +1,86 @@ +use std::{ + collections::BTreeMap, + ops::Range, + sync::{Arc, OnceLock}, +}; + +use crate::{context::Context, error::RuntimeError}; + +use super::{AbstractTree, Identifier, Statement}; + +pub static NONE: OnceLock = OnceLock::new(); + +#[derive(Clone, Debug, PartialEq)] +pub struct Value(Arc); + +impl Value { + pub fn inner(&self) -> &Arc { + &self.0 + } + + pub fn none() -> Self { + NONE.get_or_init(|| { + Value::r#enum(EnumInstance { + type_name: Identifier::new("Option"), + variant: Identifier::new("None"), + }) + }) + .clone() + } + + pub fn boolean(boolean: bool) -> Self { + Value(Arc::new(ValueInner::Boolean(boolean))) + } + + pub fn float(float: f64) -> Self { + Value(Arc::new(ValueInner::Float(float))) + } + + pub fn integer(integer: i64) -> Self { + Value(Arc::new(ValueInner::Integer(integer))) + } + + pub fn list(list: Vec) -> Self { + Value(Arc::new(ValueInner::List(list))) + } + + pub fn map(map: BTreeMap) -> Self { + Value(Arc::new(ValueInner::Map(map))) + } + + pub fn range(range: Range) -> Self { + Value(Arc::new(ValueInner::Range(range))) + } + + pub fn string(string: T) -> Self { + Value(Arc::new(ValueInner::String(string.to_string()))) + } + + pub fn r#enum(r#enum: EnumInstance) -> Self { + Value(Arc::new(ValueInner::Enum(r#enum))) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ValueInner { + Boolean(bool), + Float(f64), + Integer(i64), + List(Vec), + Map(BTreeMap), + Range(Range), + String(String), + Enum(EnumInstance), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct EnumInstance { + type_name: Identifier, + variant: Identifier, +} + +impl AbstractTree for Value { + fn run(self, _: &Context) -> Result { + Ok(self) + } +} diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..1891278 --- /dev/null +++ b/src/context.rs @@ -0,0 +1,39 @@ +use std::{ + collections::BTreeMap, + sync::{Arc, RwLock}, +}; + +use crate::{ + abstract_tree::{Identifier, Value}, + error::RwLockPoisonError, +}; + +pub struct Context { + inner: Arc>>, +} + +impl Context { + pub fn new() -> Self { + Self { + inner: Arc::new(RwLock::new(BTreeMap::new())), + } + } + + pub fn with_values(values: BTreeMap) -> Self { + Self { + inner: Arc::new(RwLock::new(values)), + } + } + + pub fn get(&self, identifier: &Identifier) -> Result, RwLockPoisonError> { + let value = self.inner.read()?.get(&identifier).cloned(); + + Ok(value) + } + + pub fn set(&self, identifier: Identifier, value: Value) -> Result<(), RwLockPoisonError> { + self.inner.write()?.insert(identifier, value); + + Ok(()) + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..50b140e --- /dev/null +++ b/src/error.rs @@ -0,0 +1,50 @@ +use std::sync::PoisonError; + +use chumsky::prelude::Rich; + +use crate::lexer::Token; + +#[derive(Debug, PartialEq)] +pub enum Error<'src> { + Parse(Vec>>), + Lex(Vec>), + Runtime(RuntimeError), +} + +impl<'src> From>>> for Error<'src> { + fn from(errors: Vec>>) -> Self { + Error::Parse(errors) + } +} + +impl<'src> From>> for Error<'src> { + fn from(errors: Vec>) -> Self { + Error::Lex(errors) + } +} + +impl<'src> From for Error<'src> { + fn from(error: RuntimeError) -> Self { + Error::Runtime(error) + } +} + +#[derive(Debug, PartialEq)] +pub enum RuntimeError { + RwLockPoison(RwLockPoisonError), +} + +impl From for RuntimeError { + fn from(error: RwLockPoisonError) -> Self { + RuntimeError::RwLockPoison(error) + } +} + +#[derive(Debug, PartialEq)] +pub struct RwLockPoisonError; + +impl From> for RwLockPoisonError { + fn from(_: PoisonError) -> Self { + RwLockPoisonError + } +} diff --git a/src/lexer.rs b/src/lexer.rs new file mode 100644 index 0000000..fc1814c --- /dev/null +++ b/src/lexer.rs @@ -0,0 +1,226 @@ +use chumsky::prelude::*; + +use crate::error::Error; + +#[derive(Clone, Debug, PartialEq)] +pub enum Token<'src> { + None, + Boolean(bool), + Integer(i64), + Float(f64), + String(&'src str), + Identifier(&'src str), + Operator(&'src str), + Control(char), +} + +pub fn lex<'src>(source: &'src str) -> Result, Error<'src>> { + lexer() + .parse(source) + .into_result() + .map_err(|error| Error::Lex(error)) +} + +pub fn lexer<'src>() -> impl Parser< + 'src, + &'src str, + Vec<(Token<'src>, SimpleSpan)>, + extra::Err>>, +> { + let boolean = just("true") + .or(just("false")) + .map(|s: &str| Token::Boolean(s.parse().unwrap())); + + let float_numeric = just('-') + .or_not() + .then(text::int(10)) + .then(just('.').then(text::digits(10))) + .to_slice() + .map(|text: &str| Token::Float(text.parse().unwrap())); + + let float_other = choice((just("Infinity"), just("-Infinity"), just("NaN"))) + .map(|text| Token::Float(text.parse().unwrap())); + + let float = choice((float_numeric, float_other)); + + let integer = just('-') + .or_not() + .then(text::int(10).padded()) + .to_slice() + .map(|text: &str| { + let integer = text.parse::().unwrap(); + + Token::Integer(integer) + }); + + let delimited_string = |delimiter| { + just(delimiter) + .then(none_of(delimiter).repeated()) + .then(just(delimiter)) + .to_slice() + .map(|text: &str| Token::String(&text[1..text.len() - 1])) + }; + + let string = choice(( + delimited_string('\''), + delimited_string('"'), + delimited_string('`'), + )); + + let identifier = text::ident().map(|text: &str| Token::Identifier(text)); + + let operator = choice(( + just("==").padded(), + just("!=").padded(), + just(">").padded(), + just("<").padded(), + just(">=").padded(), + just("<=").padded(), + just("&&").padded(), + just("||").padded(), + just("=").padded(), + just("+=").padded(), + just("-=").padded(), + )) + .map(Token::Operator); + + let control = one_of("[](){},;'").map(Token::Control); + + choice(( + boolean, float, integer, string, identifier, operator, control, + )) + .map_with(|token, state| (token, state.span())) + .padded() + .repeated() + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn identifier() { + assert_eq!(lex("x").unwrap()[0].0, Token::Identifier("x")); + assert_eq!(lex("foobar").unwrap()[0].0, Token::Identifier("foobar")); + assert_eq!(lex("HELLO").unwrap()[0].0, Token::Identifier("HELLO")); + } + + #[test] + fn r#true() { + assert_eq!(lex("true").unwrap()[0].0, Token::Boolean(true)); + } + + #[test] + fn r#false() { + assert_eq!(lex("false").unwrap()[0].0, Token::Boolean(false)); + } + + #[test] + fn positive_float() { + assert_eq!(lex("0.0").unwrap()[0].0, Token::Float(0.0)); + assert_eq!(lex("42.0").unwrap()[0].0, Token::Float(42.0)); + + let max_float = f64::MAX.to_string() + ".0"; + + assert_eq!(lex(&max_float).unwrap()[0].0, Token::Float(f64::MAX)); + + let min_positive_float = f64::MIN_POSITIVE.to_string(); + + assert_eq!( + lex(&min_positive_float).unwrap()[0].0, + Token::Float(f64::MIN_POSITIVE) + ); + } + + #[test] + fn negative_float() { + assert_eq!(lex("-0.0").unwrap()[0].0, Token::Float(-0.0)); + assert_eq!(lex("-42.0").unwrap()[0].0, Token::Float(-42.0)); + + let min_float = f64::MIN.to_string() + ".0"; + + assert_eq!(lex(&min_float).unwrap()[0].0, Token::Float(f64::MIN)); + + let max_negative_float = format!("-{}", f64::MIN_POSITIVE); + + assert_eq!( + lex(&max_negative_float).unwrap()[0].0, + Token::Float(-f64::MIN_POSITIVE) + ); + } + + #[test] + fn other_float() { + assert_eq!(lex("Infinity").unwrap()[0].0, Token::Float(f64::INFINITY)); + assert_eq!( + lex("-Infinity").unwrap()[0].0, + Token::Float(f64::NEG_INFINITY) + ); + + if let Token::Float(float) = &lex("NaN").unwrap()[0].0 { + assert!(float.is_nan()); + } else { + panic!("Expected a float.") + } + } + + #[test] + fn positive_integer() { + for i in 0..10 { + let source = i.to_string(); + let tokens = lex(&source).unwrap(); + + assert_eq!(tokens[0].0, Token::Integer(i)) + } + + assert_eq!(lex("42").unwrap()[0].0, Token::Integer(42)); + + let maximum_integer = i64::MAX.to_string(); + + assert_eq!( + lex(&maximum_integer).unwrap()[0].0, + Token::Integer(i64::MAX) + ); + } + + #[test] + fn negative_integer() { + for i in -9..1 { + let source = i.to_string(); + let tokens = lex(&source).unwrap(); + + assert_eq!(tokens[0].0, Token::Integer(i)) + } + + assert_eq!(lex("-42").unwrap()[0].0, Token::Integer(-42)); + + let minimum_integer = i64::MIN.to_string(); + + assert_eq!( + lex(&minimum_integer).unwrap()[0].0, + Token::Integer(i64::MIN) + ); + } + + #[test] + fn double_quoted_string() { + assert_eq!(lex("\"\"").unwrap()[0].0, Token::String("")); + assert_eq!(lex("\"42\"").unwrap()[0].0, Token::String("42")); + assert_eq!(lex("\"foobar\"").unwrap()[0].0, Token::String("foobar")); + } + + #[test] + fn single_quoted_string() { + assert_eq!(lex("''").unwrap()[0].0, Token::String("")); + assert_eq!(lex("'42'").unwrap()[0].0, Token::String("42")); + assert_eq!(lex("'foobar'").unwrap()[0].0, Token::String("foobar")); + } + + #[test] + fn grave_quoted_string() { + assert_eq!(lex("``").unwrap()[0].0, Token::String("")); + assert_eq!(lex("`42`").unwrap()[0].0, Token::String("42")); + assert_eq!(lex("`foobar`").unwrap()[0].0, Token::String("foobar")); + } +} diff --git a/src/lib.rs b/src/lib.rs index 20162fe..a6f7ba3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,621 +1,32 @@ -use std::{ - collections::BTreeMap, - ops::Range, - sync::{Arc, OnceLock, PoisonError, RwLock}, -}; +pub mod abstract_tree; +pub mod context; +pub mod error; +pub mod lexer; +pub mod parser; +use abstract_tree::{Statement, Value}; use chumsky::{prelude::*, Parser}; - -pub static NONE: OnceLock = OnceLock::new(); - -pub enum BuiltInValue { - None, -} - -impl BuiltInValue { - pub fn get(&self) -> Value { - match self { - BuiltInValue::None => NONE.get_or_init(|| { - Value::r#enum(EnumInstance { - type_name: Identifier("Option".to_string()), - variant: Identifier("None".to_string()), - }) - }), - } - .clone() - } -} - -pub enum Error<'src> { - Parse(Vec>), - Runtime(RuntimeError), -} - -impl<'src> From>> for Error<'src> { - fn from(errors: Vec>) -> Self { - Error::Parse(errors) - } -} - -impl<'src> From for Error<'src> { - fn from(error: RuntimeError) -> Self { - Error::Runtime(error) - } -} - -pub enum RuntimeError { - RwLockPoison(RwLockPoisonError), -} - -impl From for RuntimeError { - fn from(error: RwLockPoisonError) -> Self { - RuntimeError::RwLockPoison(error) - } -} - -pub struct RwLockPoisonError; - -impl From> for RwLockPoisonError { - fn from(_: PoisonError) -> Self { - RwLockPoisonError - } -} - -pub struct Context { - inner: Arc>>, -} - -impl Context { - pub fn get(&self, identifier: &Identifier) -> Result, RwLockPoisonError> { - let value = self.inner.read()?.get(&identifier).cloned(); - - Ok(value) - } - - pub fn set(&self, identifier: Identifier, value: Value) -> Result<(), RwLockPoisonError> { - self.inner.write()?.insert(identifier, value); - - Ok(()) - } -} - -pub trait AbstractTree { - fn run(self, context: &Context) -> Result; -} +use context::Context; +use error::Error; pub struct Interpreter

{ - parser: P, - context: Context, + _parser: P, + _context: Context, } impl<'src, P> Interpreter

where P: Parser<'src, &'src str, Statement, extra::Err>>, { - pub fn run(&self, source: &'src str) -> Result> { - let final_value = self - .parser - .parse(source) - .into_result()? - .run(&self.context)?; + pub fn run(&self, _source: &'src str) -> Result> { + todo!(); - Ok(final_value) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum Statement { - Assignment(Assignment), - Expression(Expression), -} - -impl AbstractTree for Statement { - fn run(self, context: &Context) -> Result { - match self { - Statement::Assignment(assignment) => assignment.run(context), - Statement::Expression(expression) => expression.run(context), - } - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum Expression { - Identifier(Identifier), - Logic(Logic), - Value(Value), -} - -impl AbstractTree for Expression { - fn run(self, context: &Context) -> Result { - match self { - Expression::Identifier(identifier) => identifier.run(context), - Expression::Logic(logic) => logic.run(context), - Expression::Value(value) => value.run(context), - } - } -} - -#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub struct Identifier(String); - -impl AbstractTree for Identifier { - fn run(self, context: &Context) -> Result { - let value = context - .get(&self)? - .unwrap_or_else(|| BuiltInValue::None.get()) - .clone(); - - Ok(value) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub struct Assignment { - identifier: Identifier, - value: Value, -} - -impl AbstractTree for Assignment { - fn run(self, context: &Context) -> Result { - context.set(self.identifier, self.value)?; - - Ok(BuiltInValue::None.get().clone()) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub struct Logic { - left: LogicExpression, - operator: LogicOperator, - right: LogicExpression, -} - -impl AbstractTree for Logic { - fn run(self, _: &Context) -> Result { - todo!() - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum LogicOperator { - Equal, - NotEqual, - Greater, - Less, - GreaterOrEqual, - LessOrEqual, - And, - Or, -} - -#[derive(Clone, Debug, PartialEq)] -pub enum LogicExpression { - Identifier(Identifier), - Logic(Box), - Value(Value), -} - -#[derive(Clone, Debug, PartialEq)] -pub struct Value(Arc); - -impl Value { - pub fn boolean(boolean: bool) -> Self { - Value(Arc::new(ValueInner::Boolean(boolean))) - } - - pub fn float(float: f64) -> Self { - Value(Arc::new(ValueInner::Float(float))) - } - - pub fn integer(integer: i64) -> Self { - Value(Arc::new(ValueInner::Integer(integer))) - } - - pub fn list(list: Vec) -> Self { - Value(Arc::new(ValueInner::List(list))) - } - - pub fn map(map: BTreeMap) -> Self { - Value(Arc::new(ValueInner::Map(map))) - } - - pub fn range(range: Range) -> Self { - Value(Arc::new(ValueInner::Range(range))) - } - - pub fn string(string: String) -> Self { - Value(Arc::new(ValueInner::String(string))) - } - - pub fn r#enum(r#enum: EnumInstance) -> Self { - Value(Arc::new(ValueInner::Enum(r#enum))) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum ValueInner { - Boolean(bool), - Float(f64), - Integer(i64), - List(Vec), - Map(BTreeMap), - Range(Range), - String(String), - Enum(EnumInstance), -} - -#[derive(Clone, Debug, PartialEq)] -pub struct EnumInstance { - type_name: Identifier, - variant: Identifier, -} - -impl AbstractTree for Value { - fn run(self, _: &Context) -> Result { - Ok(self) - } -} - -pub fn parser<'src>() -> impl Parser<'src, &'src str, Statement, extra::Err>> { - let operator = |text: &'src str| just(text).padded(); - - let value = recursive(|value| { - let boolean = just("true") - .or(just("false")) - .map(|s: &str| Value::boolean(s.parse().unwrap())); - - let float_numeric = just('-') - .or_not() - .then(text::int(10)) - .then(just('.').then(text::digits(10))) - .to_slice() - .map(|text: &str| Value::float(text.parse().unwrap())); - - let float_other = choice((just("Infinity"), just("-Infinity"), just("NaN"))) - .map(|text| Value::float(text.parse().unwrap())); - - let float = choice((float_numeric, float_other)); - - let integer = just('-') - .or_not() - .then(text::int(10).padded()) - .to_slice() - .map(|text: &str| { - let integer = text.parse::().unwrap(); - - Value::integer(integer) - }); - - let delimited_string = |delimiter| { - just(delimiter) - .ignore_then(none_of(delimiter).repeated()) - .then_ignore(just(delimiter)) - .to_slice() - .map(|text: &str| Value::string(text[1..text.len() - 1].to_string())) - }; - - let string = choice(( - delimited_string('\''), - delimited_string('"'), - delimited_string('`'), - )); - - let list = value - .clone() - .separated_by(just(',').padded()) - .allow_trailing() - .collect() - .padded() - .delimited_by(just('['), just(']')) - .map(|values| Value::list(values)); - - choice((boolean, float, integer, string, list)) - }); - - let identifier = text::ident().map(|text: &str| Identifier(text.to_string())); - - let assignment = identifier - .then_ignore(operator("=")) - .then(value.clone()) - .map(|(identifier, value)| Assignment { identifier, value }); - - let logic = recursive(|logic| { - choice(( - value.clone().map(|value| LogicExpression::Value(value)), - identifier.map(|identifier| LogicExpression::Identifier(identifier)), - logic - .clone() - .map(|logic| LogicExpression::Logic(Box::new(logic))), - )) - .then(choice(( - operator("==").map(|_| LogicOperator::Equal), - operator("!=").map(|_| LogicOperator::NotEqual), - operator(">").map(|_| LogicOperator::Greater), - operator("<").map(|_| LogicOperator::Less), - operator(">=").map(|_| LogicOperator::GreaterOrEqual), - operator("<=").map(|_| LogicOperator::LessOrEqual), - operator("&&").map(|_| LogicOperator::And), - operator("||").map(|_| LogicOperator::Or), - ))) - .then(choice(( - value.clone().map(|value| LogicExpression::Value(value)), - identifier.map(|identifier| LogicExpression::Identifier(identifier)), - logic.map(|logic| LogicExpression::Logic(Box::new(logic))), - ))) - .map(|((left, operator), right)| Logic { - left, - operator, - right, - }) - }); - - let expression = choice(( - logic.map(|logic| Expression::Logic(logic)), - value.map(|value| Expression::Value(value)), - identifier.map(|identifier| Expression::Identifier(identifier)), - )); - - let statement = choice(( - assignment.map(|assignment| Statement::Assignment(assignment)), - expression.map(|expression| Statement::Expression(expression)), - )); - - statement -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parse_identifier() { - assert_eq!( - parser().parse("x").unwrap(), - Statement::Expression(Expression::Identifier(Identifier("x".to_string()))) - ); - assert_eq!( - parser().parse("foobar").unwrap(), - Statement::Expression(Expression::Identifier(Identifier("foobar".to_string()))) - ); - assert_eq!( - parser().parse("HELLO").unwrap(), - Statement::Expression(Expression::Identifier(Identifier("HELLO".to_string()))) - ); - } - - #[test] - fn parse_assignment() { - assert_eq!( - parser().parse("foobar = 1").unwrap(), - Statement::Assignment(Assignment { - identifier: Identifier("foobar".to_string()), - value: Value::integer(1) - }) - ); - } - - #[test] - fn parse_logic() { - assert_eq!( - parser().parse("x == 1").unwrap(), - Statement::Expression(Expression::Logic(Logic { - left: LogicExpression::Identifier(Identifier("x".to_string())), - operator: LogicOperator::Equal, - right: LogicExpression::Value(Value::integer(1)) - })) - ); - } - - #[test] - fn parse_list() { - assert_eq!( - parser().parse("[]").unwrap(), - Statement::Expression(Expression::Value(Value::list(vec![]))) - ); - assert_eq!( - parser().parse("[42]").unwrap(), - Statement::Expression(Expression::Value(Value::list(vec![Value::integer(42)]))) - ); - assert_eq!( - parser().parse("[42, 'foo', \"bar\", [1, 2, 3,]]").unwrap(), - Statement::Expression(Expression::Value(Value::list(vec![ - Value::integer(42), - Value::string("foo".to_string()), - Value::string("bar".to_string()), - Value::list(vec![ - Value::integer(1), - Value::integer(2), - Value::integer(3), - ]) - ]))) - ); - } - - #[test] - fn parse_true() { - assert_eq!( - parser().parse("true").unwrap(), - Statement::Expression(Expression::Value(Value::boolean(true))) - ); - } - - #[test] - fn parse_false() { - assert_eq!( - parser().parse("false").unwrap(), - Statement::Expression(Expression::Value(Value::boolean(false))) - ); - } - - #[test] - fn parse_positive_float() { - assert_eq!( - parser().parse("0.0").unwrap(), - Statement::Expression(Expression::Value(Value::float(0.0))) - ); - assert_eq!( - parser().parse("42.0").unwrap(), - Statement::Expression(Expression::Value(Value::float(42.0))) - ); - - let max_float = f64::MAX.to_string() + ".0"; - - assert_eq!( - parser().parse(&max_float).unwrap(), - Statement::Expression(Expression::Value(Value::float(f64::MAX))) - ); - - let min_positive_float = f64::MIN_POSITIVE.to_string(); - - assert_eq!( - parser().parse(&min_positive_float).unwrap(), - Statement::Expression(Expression::Value(Value::float(f64::MIN_POSITIVE))) - ); - } - - #[test] - fn parse_negative_float() { - assert_eq!( - parser().parse("-0.0").unwrap(), - Statement::Expression(Expression::Value(Value::float(-0.0))) - ); - assert_eq!( - parser().parse("-42.0").unwrap(), - Statement::Expression(Expression::Value(Value::float(-42.0))) - ); - - let min_float = f64::MIN.to_string() + ".0"; - - assert_eq!( - parser().parse(&min_float).unwrap(), - Statement::Expression(Expression::Value(Value::float(f64::MIN))) - ); - - let max_negative_float = format!("-{}", f64::MIN_POSITIVE); - - assert_eq!( - parser().parse(&max_negative_float).unwrap(), - Statement::Expression(Expression::Value(Value::float(-f64::MIN_POSITIVE))) - ); - } - - #[test] - fn parse_other_float() { - assert_eq!( - parser().parse("Infinity").unwrap(), - Statement::Expression(Expression::Value(Value::float(f64::INFINITY))) - ); - assert_eq!( - parser().parse("-Infinity").unwrap(), - Statement::Expression(Expression::Value(Value::float(f64::NEG_INFINITY))) - ); - - if let Statement::Expression(Expression::Value(Value(value_inner))) = - parser().parse("NaN").unwrap() - { - if let ValueInner::Float(float) = value_inner.as_ref() { - return assert!(float.is_nan()); - } - } - - panic!("Expected a float.") - } - - #[test] - fn parse_positive_integer() { - for i in 0..10 { - let source = i.to_string(); - let result = parser().parse(&source); - - assert_eq!( - result.unwrap(), - Statement::Expression(Expression::Value(Value::integer(i))) - ) - } - - assert_eq!( - parser().parse("42").unwrap(), - Statement::Expression(Expression::Value(Value::integer(42))) - ); - - let maximum_integer = i64::MAX.to_string(); - - assert_eq!( - parser().parse(&maximum_integer).unwrap(), - Statement::Expression(Expression::Value(Value::integer(i64::MAX))) - ); - } - - #[test] - fn parse_negative_integer() { - for i in -9..1 { - let source = i.to_string(); - let result = parser().parse(&source); - - assert_eq!( - result.unwrap(), - Statement::Expression(Expression::Value(Value::integer(i))) - ) - } - - assert_eq!( - parser().parse("-42").unwrap(), - Statement::Expression(Expression::Value(Value::integer(-42))) - ); - - let minimum_integer = i64::MIN.to_string(); - - assert_eq!( - parser().parse(&minimum_integer).unwrap(), - Statement::Expression(Expression::Value(Value::integer(i64::MIN))) - ); - } - - #[test] - fn double_quoted_string() { - assert_eq!( - parser().parse("\"\"").unwrap(), - Statement::Expression(Expression::Value(Value::string("".to_string()))) - ); - assert_eq!( - parser().parse("\"42\"").unwrap(), - Statement::Expression(Expression::Value(Value::string("42".to_string()))) - ); - assert_eq!( - parser().parse("\"foobar\"").unwrap(), - Statement::Expression(Expression::Value(Value::string("foobar".to_string()))) - ); - } - - #[test] - fn single_quoted_string() { - assert_eq!( - parser().parse("''").unwrap(), - Statement::Expression(Expression::Value(Value::string("".to_string()))) - ); - assert_eq!( - parser().parse("'42'").unwrap(), - Statement::Expression(Expression::Value(Value::string("42".to_string()))) - ); - assert_eq!( - parser().parse("'foobar'").unwrap(), - Statement::Expression(Expression::Value(Value::string("foobar".to_string()))) - ); - } - - #[test] - fn grave_quoted_string() { - assert_eq!( - parser().parse("``").unwrap(), - Statement::Expression(Expression::Value(Value::string("".to_string()))) - ); - assert_eq!( - parser().parse("`42`").unwrap(), - Statement::Expression(Expression::Value(Value::string("42".to_string()))) - ); - assert_eq!( - parser().parse("`foobar`").unwrap(), - Statement::Expression(Expression::Value(Value::string("foobar".to_string()))) - ); + // let final_value = self + // .parser + // .parse(source) + // .into_result()? + // .run(&self.context)?; + + // Ok(final_value) } } diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..b2f6a58 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,355 @@ +use chumsky::{input::SpannedInput, pratt::*, prelude::*}; + +use crate::{abstract_tree::*, error::Error, lexer::Token}; + +type ParserInput<'tokens, 'src> = + SpannedInput, SimpleSpan, &'tokens [(Token<'src>, SimpleSpan)]>; + +fn parser<'tokens, 'src: 'tokens>() -> impl Parser< + 'tokens, + ParserInput<'tokens, 'src>, + Vec<(Statement, SimpleSpan)>, + extra::Err, SimpleSpan>>, +> { + recursive(|statement| { + let identifier = select! { + Token::Identifier(text) => Identifier::new(text), + }; + + let identifier_statement = identifier.map(|identifier| Statement::Identifier(identifier)); + + let basic_value = select! { + Token::None => Value::none(), + Token::Boolean(boolean) => Value::boolean(boolean), + Token::Integer(integer) => Value::integer(integer), + Token::Float(float) => Value::float(float), + Token::String(string) => Value::string(string.to_string()), + }; + + let list = statement + .clone() + .separated_by(just(Token::Control(','))) + .allow_trailing() + .collect() + .delimited_by(just(Token::Control('[')), just(Token::Control(']'))) + .map(Value::list); + + let value = choice(( + basic_value.map(|value| Statement::Value(value)), + list.map(|list| Statement::Value(list)), + )); + + let assignment = identifier + .then_ignore(just(Token::Operator("="))) + .then(statement.clone()) + .map(|(identifier, statement)| { + Statement::Assignment(Assignment::new(identifier, statement)) + }); + + let atom = choice(( + identifier_statement, + value.clone(), + assignment.clone(), + statement + .clone() + .delimited_by(just(Token::Control('(')), just(Token::Control(')'))), + )); + + let logic = atom.pratt(( + prefix(2, just(Token::Operator("!")), |statement| { + Statement::Logic(Box::new(Logic::Not(statement))) + }), + infix(left(1), just(Token::Operator("==")), |left, right| { + Statement::Logic(Box::new(Logic::Equal(left, right))) + }), + infix(left(1), just(Token::Operator("!=")), |left, right| { + Statement::Logic(Box::new(Logic::NotEqual(left, right))) + }), + infix(left(1), just(Token::Operator(">")), |left, right| { + Statement::Logic(Box::new(Logic::Greater(left, right))) + }), + infix(left(1), just(Token::Operator("<")), |left, right| { + Statement::Logic(Box::new(Logic::Less(left, right))) + }), + infix(left(1), just(Token::Operator(">=")), |left, right| { + Statement::Logic(Box::new(Logic::GreaterOrEqual(left, right))) + }), + infix(left(1), just(Token::Operator("<=")), |left, right| { + Statement::Logic(Box::new(Logic::LessOrEqual(left, right))) + }), + infix(left(1), just(Token::Operator("&&")), |left, right| { + Statement::Logic(Box::new(Logic::And(left, right))) + }), + infix(left(1), just(Token::Operator("||")), |left, right| { + Statement::Logic(Box::new(Logic::Or(left, right))) + }), + )); + + choice((assignment, logic, value, identifier_statement)) + }) + .map_with(|statement, state| (statement, state.span())) + .repeated() + .collect() +} + +pub fn parse<'tokens>( + tokens: &'tokens [(Token, SimpleSpan)], +) -> Result, Error<'tokens>> { + parser() + .parse(tokens.spanned((0..0).into())) + .into_result() + .map_err(|error| Error::Parse(error)) +} + +#[cfg(test)] +mod tests { + use crate::{ + abstract_tree::{value::ValueInner, Logic}, + lexer::lex, + }; + + use super::*; + + #[test] + fn identifier() { + assert_eq!( + parse(&lex("x").unwrap()).unwrap()[0].0, + Statement::Identifier(Identifier::new("x")), + ); + assert_eq!( + parse(&lex("foobar").unwrap()).unwrap()[0].0, + Statement::Identifier(Identifier::new("foobar")), + ); + assert_eq!( + parse(&lex("HELLO").unwrap()).unwrap()[0].0, + Statement::Identifier(Identifier::new("HELLO")), + ); + } + + #[test] + fn assignment() { + assert_eq!( + parse(&lex("foobar = 1").unwrap()).unwrap()[0].0, + Statement::Assignment(Assignment::new( + Identifier::new("foobar"), + Statement::Value(Value::integer(1)) + )), + ); + } + + #[test] + fn logic() { + assert_eq!( + parse(&lex("x == 1").unwrap()).unwrap()[0].0, + Statement::Logic(Box::new(Logic::Equal( + Statement::Identifier(Identifier::new("x")), + Statement::Value(Value::integer(1)) + ))), + ); + } + + #[test] + fn list() { + assert_eq!( + parse(&lex("[]").unwrap()).unwrap()[0].0, + Statement::Value(Value::list(vec![])), + ); + assert_eq!( + parse(&lex("[42]").unwrap()).unwrap()[0].0, + Statement::Value(Value::list(vec![Statement::Value(Value::integer(42))])), + ); + assert_eq!( + parse(&lex("[42, 'foo', 'bar', [1, 2, 3,]]").unwrap()).unwrap()[0].0, + Statement::Value(Value::list(vec![ + Statement::Value(Value::integer(42)), + Statement::Value(Value::string("foo")), + Statement::Value(Value::string("bar")), + Statement::Value(Value::list(vec![ + Statement::Value(Value::integer(1)), + Statement::Value(Value::integer(2)), + Statement::Value(Value::integer(3)), + ])) + ])), + ); + } + + #[test] + fn r#true() { + assert_eq!( + parse(&lex("true").unwrap()).unwrap()[0].0, + Statement::Value(Value::boolean(true)) + ); + } + + #[test] + fn r#false() { + assert_eq!( + parse(&lex("false").unwrap()).unwrap()[0].0, + Statement::Value(Value::boolean(false)) + ); + } + + #[test] + fn positive_float() { + assert_eq!( + parse(&lex("0.0").unwrap()).unwrap()[0].0, + Statement::Value(Value::float(0.0)) + ); + assert_eq!( + parse(&lex("42.0").unwrap()).unwrap()[0].0, + Statement::Value(Value::float(42.0)) + ); + + let max_float = f64::MAX.to_string() + ".0"; + + assert_eq!( + parse(&lex(&max_float).unwrap()).unwrap()[0].0, + Statement::Value(Value::float(f64::MAX)) + ); + + let min_positive_float = f64::MIN_POSITIVE.to_string(); + + assert_eq!( + parse(&lex(&min_positive_float).unwrap()).unwrap()[0].0, + Statement::Value(Value::float(f64::MIN_POSITIVE)) + ); + } + + #[test] + fn negative_float() { + assert_eq!( + parse(&lex("-0.0").unwrap()).unwrap()[0].0, + Statement::Value(Value::float(-0.0)) + ); + assert_eq!( + parse(&lex("-42.0").unwrap()).unwrap()[0].0, + Statement::Value(Value::float(-42.0)) + ); + + let min_float = f64::MIN.to_string() + ".0"; + + assert_eq!( + parse(&lex(&min_float).unwrap()).unwrap()[0].0, + Statement::Value(Value::float(f64::MIN)) + ); + + let max_negative_float = format!("-{}", f64::MIN_POSITIVE); + + assert_eq!( + parse(&lex(&max_negative_float).unwrap()).unwrap()[0].0, + Statement::Value(Value::float(-f64::MIN_POSITIVE)) + ); + } + + #[test] + fn other_float() { + assert_eq!( + parse(&lex("Infinity").unwrap()).unwrap()[0].0, + Statement::Value(Value::float(f64::INFINITY)) + ); + assert_eq!( + parse(&lex("-Infinity").unwrap()).unwrap()[0].0, + Statement::Value(Value::float(f64::NEG_INFINITY)) + ); + + if let Statement::Value(value) = &parse(&lex("NaN").unwrap()).unwrap()[0].0 { + if let ValueInner::Float(float) = value.inner().as_ref() { + return assert!(float.is_nan()); + } + } + + panic!("Expected a float.") + } + + #[test] + fn positive_integer() { + for i in 0..10 { + let source = i.to_string(); + let statements = parse(&lex(&source).unwrap()).unwrap(); + + assert_eq!(statements[0].0, Statement::Value(Value::integer(i))) + } + + assert_eq!( + parse(&lex("42").unwrap()).unwrap()[0].0, + Statement::Value(Value::integer(42)) + ); + + let maximum_integer = i64::MAX.to_string(); + + assert_eq!( + parse(&lex(&maximum_integer).unwrap()).unwrap()[0].0, + Statement::Value(Value::integer(i64::MAX)) + ); + } + + #[test] + fn negative_integer() { + for i in -9..1 { + let source = i.to_string(); + let statements = parse(&lex(&source).unwrap()).unwrap(); + + assert_eq!(statements[0].0, Statement::Value(Value::integer(i))) + } + + assert_eq!( + parse(&lex("-42").unwrap()).unwrap()[0].0, + Statement::Value(Value::integer(-42)) + ); + + let minimum_integer = i64::MIN.to_string(); + + assert_eq!( + parse(&lex(&minimum_integer).unwrap()).unwrap()[0].0, + Statement::Value(Value::integer(i64::MIN)) + ); + } + + #[test] + fn double_quoted_string() { + assert_eq!( + parse(&lex("\"\"").unwrap()).unwrap()[0].0, + Statement::Value(Value::string("".to_string())) + ); + assert_eq!( + parse(&lex("\"42\"").unwrap()).unwrap()[0].0, + Statement::Value(Value::string("42".to_string())) + ); + assert_eq!( + parse(&lex("\"foobar\"").unwrap()).unwrap()[0].0, + Statement::Value(Value::string("foobar".to_string())) + ); + } + + #[test] + fn single_quoted_string() { + assert_eq!( + parse(&lex("''").unwrap()).unwrap()[0].0, + Statement::Value(Value::string("".to_string())) + ); + assert_eq!( + parse(&lex("'42'").unwrap()).unwrap()[0].0, + Statement::Value(Value::string("42".to_string())) + ); + assert_eq!( + parse(&lex("'foobar'").unwrap()).unwrap()[0].0, + Statement::Value(Value::string("foobar".to_string())) + ); + } + + #[test] + fn grave_quoted_string() { + assert_eq!( + parse(&lex("``").unwrap()).unwrap()[0].0, + Statement::Value(Value::string("".to_string())) + ); + assert_eq!( + parse(&lex("`42`").unwrap()).unwrap()[0].0, + Statement::Value(Value::string("42".to_string())) + ); + assert_eq!( + parse(&lex("`foobar`").unwrap()).unwrap()[0].0, + Statement::Value(Value::string("foobar".to_string())) + ); + } +}