Lex, parse and run with passing tests
This commit is contained in:
parent
f70656c837
commit
8ff4b4ba82
176
Cargo.lock
generated
176
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"] }
|
||||
|
50
src/abstract_tree/assignment.rs
Normal file
50
src/abstract_tree/assignment.rs
Normal file
@ -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<Statement>,
|
||||
}
|
||||
|
||||
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<Value, RuntimeError> {
|
||||
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))
|
||||
)
|
||||
}
|
||||
}
|
20
src/abstract_tree/block.rs
Normal file
20
src/abstract_tree/block.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use crate::{context::Context, error::RuntimeError};
|
||||
|
||||
use super::{AbstractTree, Statement, Value};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Block {
|
||||
statements: Vec<Statement>,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn new(statements: Vec<Statement>) -> Self {
|
||||
Self { statements }
|
||||
}
|
||||
}
|
||||
|
||||
impl AbstractTree for Block {
|
||||
fn run(self, _: &Context) -> Result<Value, RuntimeError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
20
src/abstract_tree/expression.rs
Normal file
20
src/abstract_tree/expression.rs
Normal file
@ -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<Logic>),
|
||||
Value(Value),
|
||||
}
|
||||
|
||||
impl AbstractTree for Expression {
|
||||
fn run(self, context: &Context) -> Result<Value, RuntimeError> {
|
||||
match self {
|
||||
Expression::Identifier(identifier) => identifier.run(context),
|
||||
Expression::Logic(logic) => logic.run(context),
|
||||
Expression::Value(value) => value.run(context),
|
||||
}
|
||||
}
|
||||
}
|
20
src/abstract_tree/identifier.rs
Normal file
20
src/abstract_tree/identifier.rs
Normal file
@ -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<T: ToString>(string: T) -> Identifier {
|
||||
Identifier(string.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl AbstractTree for Identifier {
|
||||
fn run(self, context: &Context) -> Result<Value, RuntimeError> {
|
||||
let value = context.get(&self)?.unwrap_or_else(Value::none).clone();
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
}
|
22
src/abstract_tree/logic.rs
Normal file
22
src/abstract_tree/logic.rs
Normal file
@ -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<Value, RuntimeError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
14
src/abstract_tree/loop.rs
Normal file
14
src/abstract_tree/loop.rs
Normal file
@ -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<Value, RuntimeError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
18
src/abstract_tree/mod.rs
Normal file
18
src/abstract_tree/mod.rs
Normal file
@ -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<Value, RuntimeError>;
|
||||
}
|
26
src/abstract_tree/statement.rs
Normal file
26
src/abstract_tree/statement.rs
Normal file
@ -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<Logic>),
|
||||
}
|
||||
|
||||
impl AbstractTree for Statement {
|
||||
fn run(self, _context: &Context) -> Result<Value, RuntimeError> {
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
}
|
86
src/abstract_tree/value.rs
Normal file
86
src/abstract_tree/value.rs
Normal file
@ -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<Value> = OnceLock::new();
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Value(Arc<ValueInner>);
|
||||
|
||||
impl Value {
|
||||
pub fn inner(&self) -> &Arc<ValueInner> {
|
||||
&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<Statement>) -> Self {
|
||||
Value(Arc::new(ValueInner::List(list)))
|
||||
}
|
||||
|
||||
pub fn map(map: BTreeMap<Identifier, Value>) -> Self {
|
||||
Value(Arc::new(ValueInner::Map(map)))
|
||||
}
|
||||
|
||||
pub fn range(range: Range<i64>) -> Self {
|
||||
Value(Arc::new(ValueInner::Range(range)))
|
||||
}
|
||||
|
||||
pub fn string<T: ToString>(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<Statement>),
|
||||
Map(BTreeMap<Identifier, Value>),
|
||||
Range(Range<i64>),
|
||||
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<Value, RuntimeError> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
39
src/context.rs
Normal file
39
src/context.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
abstract_tree::{Identifier, Value},
|
||||
error::RwLockPoisonError,
|
||||
};
|
||||
|
||||
pub struct Context {
|
||||
inner: Arc<RwLock<BTreeMap<Identifier, Value>>>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(RwLock::new(BTreeMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_values(values: BTreeMap<Identifier, Value>) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(RwLock::new(values)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, identifier: &Identifier) -> Result<Option<Value>, 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(())
|
||||
}
|
||||
}
|
50
src/error.rs
Normal file
50
src/error.rs
Normal file
@ -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<Rich<'src, Token<'src>>>),
|
||||
Lex(Vec<Rich<'src, char>>),
|
||||
Runtime(RuntimeError),
|
||||
}
|
||||
|
||||
impl<'src> From<Vec<Rich<'src, Token<'src>>>> for Error<'src> {
|
||||
fn from(errors: Vec<Rich<'src, Token<'src>>>) -> Self {
|
||||
Error::Parse(errors)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'src> From<Vec<Rich<'src, char>>> for Error<'src> {
|
||||
fn from(errors: Vec<Rich<'src, char>>) -> Self {
|
||||
Error::Lex(errors)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'src> From<RuntimeError> for Error<'src> {
|
||||
fn from(error: RuntimeError) -> Self {
|
||||
Error::Runtime(error)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum RuntimeError {
|
||||
RwLockPoison(RwLockPoisonError),
|
||||
}
|
||||
|
||||
impl From<RwLockPoisonError> for RuntimeError {
|
||||
fn from(error: RwLockPoisonError) -> Self {
|
||||
RuntimeError::RwLockPoison(error)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct RwLockPoisonError;
|
||||
|
||||
impl<T> From<PoisonError<T>> for RwLockPoisonError {
|
||||
fn from(_: PoisonError<T>) -> Self {
|
||||
RwLockPoisonError
|
||||
}
|
||||
}
|
226
src/lexer.rs
Normal file
226
src/lexer.rs
Normal file
@ -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<Vec<(Token, SimpleSpan)>, 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<usize>)>,
|
||||
extra::Err<Rich<'src, char, SimpleSpan<usize>>>,
|
||||
> {
|
||||
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::<i64>().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"));
|
||||
}
|
||||
}
|
627
src/lib.rs
627
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<Value> = 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<Rich<'src, char>>),
|
||||
Runtime(RuntimeError),
|
||||
}
|
||||
|
||||
impl<'src> From<Vec<Rich<'src, char>>> for Error<'src> {
|
||||
fn from(errors: Vec<Rich<'src, char>>) -> Self {
|
||||
Error::Parse(errors)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'src> From<RuntimeError> for Error<'src> {
|
||||
fn from(error: RuntimeError) -> Self {
|
||||
Error::Runtime(error)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RuntimeError {
|
||||
RwLockPoison(RwLockPoisonError),
|
||||
}
|
||||
|
||||
impl From<RwLockPoisonError> for RuntimeError {
|
||||
fn from(error: RwLockPoisonError) -> Self {
|
||||
RuntimeError::RwLockPoison(error)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RwLockPoisonError;
|
||||
|
||||
impl<T> From<PoisonError<T>> for RwLockPoisonError {
|
||||
fn from(_: PoisonError<T>) -> Self {
|
||||
RwLockPoisonError
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Context {
|
||||
inner: Arc<RwLock<BTreeMap<Identifier, Value>>>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn get(&self, identifier: &Identifier) -> Result<Option<Value>, 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<Value, RuntimeError>;
|
||||
}
|
||||
use context::Context;
|
||||
use error::Error;
|
||||
|
||||
pub struct Interpreter<P> {
|
||||
parser: P,
|
||||
context: Context,
|
||||
_parser: P,
|
||||
_context: Context,
|
||||
}
|
||||
|
||||
impl<'src, P> Interpreter<P>
|
||||
where
|
||||
P: Parser<'src, &'src str, Statement, extra::Err<Rich<'src, char>>>,
|
||||
{
|
||||
pub fn run(&self, source: &'src str) -> Result<Value, Error<'src>> {
|
||||
let final_value = self
|
||||
.parser
|
||||
.parse(source)
|
||||
.into_result()?
|
||||
.run(&self.context)?;
|
||||
pub fn run(&self, _source: &'src str) -> Result<Value, Error<'src>> {
|
||||
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<Value, RuntimeError> {
|
||||
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<Value, RuntimeError> {
|
||||
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<Value, RuntimeError> {
|
||||
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<Value, RuntimeError> {
|
||||
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<Value, RuntimeError> {
|
||||
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<Logic>),
|
||||
Value(Value),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Value(Arc<ValueInner>);
|
||||
|
||||
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<Value>) -> Self {
|
||||
Value(Arc::new(ValueInner::List(list)))
|
||||
}
|
||||
|
||||
pub fn map(map: BTreeMap<Identifier, Value>) -> Self {
|
||||
Value(Arc::new(ValueInner::Map(map)))
|
||||
}
|
||||
|
||||
pub fn range(range: Range<i64>) -> 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<Value>),
|
||||
Map(BTreeMap<Identifier, Value>),
|
||||
Range(Range<i64>),
|
||||
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<Value, RuntimeError> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parser<'src>() -> impl Parser<'src, &'src str, Statement, extra::Err<Rich<'src, char>>> {
|
||||
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::<i64>().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)
|
||||
}
|
||||
}
|
||||
|
355
src/parser.rs
Normal file
355
src/parser.rs
Normal file
@ -0,0 +1,355 @@
|
||||
use chumsky::{input::SpannedInput, pratt::*, prelude::*};
|
||||
|
||||
use crate::{abstract_tree::*, error::Error, lexer::Token};
|
||||
|
||||
type ParserInput<'tokens, 'src> =
|
||||
SpannedInput<Token<'src>, SimpleSpan, &'tokens [(Token<'src>, SimpleSpan)]>;
|
||||
|
||||
fn parser<'tokens, 'src: 'tokens>() -> impl Parser<
|
||||
'tokens,
|
||||
ParserInput<'tokens, 'src>,
|
||||
Vec<(Statement, SimpleSpan)>,
|
||||
extra::Err<Rich<'tokens, Token<'src>, 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<Vec<(Statement, SimpleSpan)>, 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()))
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user