From 923d478b33660a008e52bf1ee6f811c12c7d8e2f Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Mon, 12 Jul 2021 16:45:16 +0200 Subject: [PATCH 01/15] Start implementing parser --- .gitignore | 2 + Cargo.toml | 28 ++++++ build.rs | 4 + rust-toolchain | 1 + rustfmt.toml | 14 +++ src/ast.rs | 94 +++++++++++++++++++++ src/lib.rs | 35 ++++++++ src/parser.lalrpop | 57 +++++++++++++ src/snapshots/simplexpr__tests__test-2.snap | 16 ++++ src/snapshots/simplexpr__tests__test-3.snap | 34 ++++++++ src/snapshots/simplexpr__tests__test-4.snap | 22 +++++ src/snapshots/simplexpr__tests__test-5.snap | 24 ++++++ src/snapshots/simplexpr__tests__test-6.snap | 30 +++++++ src/snapshots/simplexpr__tests__test-7.snap | 30 +++++++ src/snapshots/simplexpr__tests__test-8.snap | 18 ++++ src/snapshots/simplexpr__tests__test.snap | 10 +++ 16 files changed, 419 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 build.rs create mode 100644 rust-toolchain create mode 100644 rustfmt.toml create mode 100644 src/ast.rs create mode 100644 src/lib.rs create mode 100644 src/parser.lalrpop create mode 100644 src/snapshots/simplexpr__tests__test-2.snap create mode 100644 src/snapshots/simplexpr__tests__test-3.snap create mode 100644 src/snapshots/simplexpr__tests__test-4.snap create mode 100644 src/snapshots/simplexpr__tests__test-5.snap create mode 100644 src/snapshots/simplexpr__tests__test-6.snap create mode 100644 src/snapshots/simplexpr__tests__test-7.snap create mode 100644 src/snapshots/simplexpr__tests__test-8.snap create mode 100644 src/snapshots/simplexpr__tests__test.snap diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..87e69fd --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "simplexpr" +version = "0.1.0" +edition = "2018" +authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"] + + +build = "build.rs" + +[dependencies] +lalrpop-util = "0.19.5" +regex = "1" +itertools = "0.10" +thiserror = "1.0" +maplit = "1.0" +codespan-reporting = "0.11" +logos = "0.12" + +serde = {version = "1.0", features = ["derive"]} +serde_json = "1.0" + + + +[build-dependencies] +lalrpop = "0.19.5" + +[dev-dependencies] +insta = "1.7" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..57684be --- /dev/null +++ b/build.rs @@ -0,0 +1,4 @@ +extern crate lalrpop; +fn main() { + lalrpop::process_root().unwrap(); +} diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..bf867e0 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +nightly diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..edce9c8 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,14 @@ +unstable_features = true +fn_single_line = false +max_width = 130 +reorder_impl_items = true +merge_imports = true +normalize_comments = true +use_field_init_shorthand = true +#wrap_comments = true +combine_control_expr = false +condense_wildcard_suffixes = true +format_code_in_doc_comments = true +format_macro_matchers = true +format_strings = true +use_small_heuristics = "Max" diff --git a/src/ast.rs b/src/ast.rs new file mode 100644 index 0000000..0bb2684 --- /dev/null +++ b/src/ast.rs @@ -0,0 +1,94 @@ +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +#[derive(Eq, PartialEq, Clone, Copy)] +pub struct Span(pub usize, pub usize, pub usize); + +impl std::fmt::Display for Span { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}..{}", self.0, self.1) + } +} + +impl std::fmt::Debug for Span { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}..{}", self.0, self.1) + } +} + +#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)] +pub enum BinOp { + Plus, + Minus, + Times, + Div, + Mod, + Equals, + NotEquals, + And, + Or, + GT, + LT, + Elvis, + RegexMatch, +} + +impl std::fmt::Display for BinOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BinOp::Plus => write!(f, "+"), + BinOp::Minus => write!(f, "-"), + BinOp::Times => write!(f, "*"), + BinOp::Div => write!(f, "/"), + BinOp::Mod => write!(f, "%"), + BinOp::Equals => write!(f, "=="), + BinOp::NotEquals => write!(f, "!="), + BinOp::And => write!(f, "&&"), + BinOp::Or => write!(f, "||"), + BinOp::GT => write!(f, ">"), + BinOp::LT => write!(f, "<"), + BinOp::Elvis => write!(f, "?:"), + BinOp::RegexMatch => write!(f, "=~"), + } + } +} + +#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)] +pub enum UnaryOp { + Not, +} + +impl std::fmt::Display for UnaryOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UnaryOp::Not => write!(f, "!"), + } + } +} + +#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)] +pub enum SimplExpr { + Literal(String), + VarRef(String), + BinOp(Box, BinOp, Box), + UnaryOp(UnaryOp, Box), + IfElse(Box, Box, Box), + JsonAccess(Box, Box), + FunctionCall(String, Vec), +} + +impl std::fmt::Display for SimplExpr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SimplExpr::VarRef(x) => write!(f, "{}", x), + SimplExpr::Literal(x) => write!(f, "\"{}\"", x), + SimplExpr::BinOp(l, op, r) => write!(f, "({} {} {})", l, op, r), + SimplExpr::UnaryOp(op, x) => write!(f, "{}{}", op, x), + SimplExpr::IfElse(a, b, c) => write!(f, "(if {} then {} else {})", a, b, c), + SimplExpr::JsonAccess(value, index) => write!(f, "{}[{}]", value, index), + SimplExpr::FunctionCall(function_name, args) => { + write!(f, "{}({})", function_name, args.iter().join(", ")) + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1636430 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,35 @@ +pub mod ast; +use lalrpop_util::lalrpop_mod; + +lalrpop_mod!(pub parser); + +macro_rules! test_parser { + ($($text:literal),*) => {{ + let p = crate::parser::ExprParser::new(); + //use crate::lexer::Lexer; + + ::insta::with_settings!({sort_maps => true}, { + $( + ::insta::assert_debug_snapshot!(p.parse($text)); + )* + }); + }} +} + +#[cfg(test)] +mod tests { + #[test] + fn test() { + test_parser!( + "1", + "2 + 5", + "2 * 5 + 1 * 1 + 3", + "(1 + 2) * 2", + "1 + true ? 2 : 5", + "1 + true ? 2 : 5 + 2", + "1 + (true ? 2 : 5) + 2", + "foo(1, 2)", + "! false || ! true" + ); + } +} diff --git a/src/parser.lalrpop b/src/parser.lalrpop new file mode 100644 index 0000000..2db6bf0 --- /dev/null +++ b/src/parser.lalrpop @@ -0,0 +1,57 @@ + +use crate::ast::{SimplExpr, Span, BinOp, UnaryOp}; + +grammar; + +Comma: Vec = { + ",")*> => match e { + None => v, + Some(e) => { + v.push(e); + v + } + } +}; + +pub Expr: SimplExpr = { + #[precedence(level="0")] + "true" => SimplExpr::Literal("true".to_string()), + "false" => SimplExpr::Literal("false".to_string()), + , + "(" ")", + "(" > ")" => SimplExpr::FunctionCall(ident, args), + + + #[precedence(level="1")] #[assoc(side="left")] + "!" => SimplExpr::UnaryOp(UnaryOp::Not, Box::new(<>)) + + + #[precedence(level="2")] #[assoc(side="left")] + "*" => SimplExpr::BinOp(Box::new(l), BinOp::Times, Box::new(r)), + "/" => SimplExpr::BinOp(Box::new(l), BinOp::Div, Box::new(r)), + "%" => SimplExpr::BinOp(Box::new(l), BinOp::Mod, Box::new(r)), + + #[precedence(level="3")] #[assoc(side="left")] + "+" => SimplExpr::BinOp(Box::new(l), BinOp::Plus, Box::new(r)), + "-" => SimplExpr::BinOp(Box::new(l), BinOp::Minus, Box::new(r)), + + #[precedence(level="4")] #[assoc(side="left")] + "==" => SimplExpr::BinOp(Box::new(l), BinOp::Equals, Box::new(r)), + "!=" => SimplExpr::BinOp(Box::new(l), BinOp::NotEquals, Box::new(r)), + "<" => SimplExpr::BinOp(Box::new(l), BinOp::GT, Box::new(r)), + ">" => SimplExpr::BinOp(Box::new(l), BinOp::LT, Box::new(r)), + "=~" => SimplExpr::BinOp(Box::new(l), BinOp::RegexMatch, Box::new(r)), + + #[precedence(level="5")] #[assoc(side="left")] + "&&" => SimplExpr::BinOp(Box::new(l), BinOp::And, Box::new(r)), + "||" => SimplExpr::BinOp(Box::new(l), BinOp::Or, Box::new(r)), + "?:" => SimplExpr::BinOp(Box::new(l), BinOp::Elvis, Box::new(r)), + + #[precedence(level="6")] #[assoc(side="right")] + "?" ":" => SimplExpr::IfElse(Box::new(cond), Box::new(then), Box::new(els)), +}; + +ExprReset = ; + +Number: SimplExpr = r"[+-]?(?:[0-9]+[.])?[0-9]+" => SimplExpr::Literal(<>.to_string()); +Ident: String = r"[a-zA-Z_][^\s{}\(\)]*" => <>.to_string(); diff --git a/src/snapshots/simplexpr__tests__test-2.snap b/src/snapshots/simplexpr__tests__test-2.snap new file mode 100644 index 0000000..9d2e4fb --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-2.snap @@ -0,0 +1,16 @@ +--- +source: src/lib.rs +expression: "p.parse(\"2 + 5\")" + +--- +Ok( + BinOp( + Literal( + "2", + ), + Plus, + Literal( + "5", + ), + ), +) diff --git a/src/snapshots/simplexpr__tests__test-3.snap b/src/snapshots/simplexpr__tests__test-3.snap new file mode 100644 index 0000000..ed8b2c9 --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-3.snap @@ -0,0 +1,34 @@ +--- +source: src/lib.rs +expression: "p.parse(\"2 * 5 + 1 * 1 + 3\")" + +--- +Ok( + BinOp( + BinOp( + BinOp( + Literal( + "2", + ), + Times, + Literal( + "5", + ), + ), + Plus, + BinOp( + Literal( + "1", + ), + Times, + Literal( + "1", + ), + ), + ), + Plus, + Literal( + "3", + ), + ), +) diff --git a/src/snapshots/simplexpr__tests__test-4.snap b/src/snapshots/simplexpr__tests__test-4.snap new file mode 100644 index 0000000..1c5c5f5 --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-4.snap @@ -0,0 +1,22 @@ +--- +source: src/lib.rs +expression: "p.parse(\"(1 + 2) * 2\")" + +--- +Ok( + BinOp( + BinOp( + Literal( + "1", + ), + Plus, + Literal( + "2", + ), + ), + Times, + Literal( + "2", + ), + ), +) diff --git a/src/snapshots/simplexpr__tests__test-5.snap b/src/snapshots/simplexpr__tests__test-5.snap new file mode 100644 index 0000000..f71ba69 --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-5.snap @@ -0,0 +1,24 @@ +--- +source: src/lib.rs +expression: "p.parse(\"1 + true ? 2 : 5\")" + +--- +Ok( + IfElse( + BinOp( + Literal( + "1", + ), + Plus, + Literal( + "true", + ), + ), + Literal( + "2", + ), + Literal( + "5", + ), + ), +) diff --git a/src/snapshots/simplexpr__tests__test-6.snap b/src/snapshots/simplexpr__tests__test-6.snap new file mode 100644 index 0000000..4efff48 --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-6.snap @@ -0,0 +1,30 @@ +--- +source: src/lib.rs +expression: "p.parse(\"1 + true ? 2 : 5 + 2\")" + +--- +Ok( + IfElse( + BinOp( + Literal( + "1", + ), + Plus, + Literal( + "true", + ), + ), + Literal( + "2", + ), + BinOp( + Literal( + "5", + ), + Plus, + Literal( + "2", + ), + ), + ), +) diff --git a/src/snapshots/simplexpr__tests__test-7.snap b/src/snapshots/simplexpr__tests__test-7.snap new file mode 100644 index 0000000..ff662bd --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-7.snap @@ -0,0 +1,30 @@ +--- +source: src/lib.rs +expression: "p.parse(\"1 + (if true then 2 else 5) + 2\")" + +--- +Ok( + BinOp( + BinOp( + Literal( + "1", + ), + Plus, + IfElse( + Literal( + "true", + ), + Literal( + "2", + ), + Literal( + "5", + ), + ), + ), + Plus, + Literal( + "2", + ), + ), +) diff --git a/src/snapshots/simplexpr__tests__test-8.snap b/src/snapshots/simplexpr__tests__test-8.snap new file mode 100644 index 0000000..976aa45 --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-8.snap @@ -0,0 +1,18 @@ +--- +source: src/lib.rs +expression: "p.parse(\"foo(1, 2)\")" + +--- +Ok( + FunctionCall( + "foo", + [ + Literal( + "1", + ), + Literal( + "2", + ), + ], + ), +) diff --git a/src/snapshots/simplexpr__tests__test.snap b/src/snapshots/simplexpr__tests__test.snap new file mode 100644 index 0000000..929695f --- /dev/null +++ b/src/snapshots/simplexpr__tests__test.snap @@ -0,0 +1,10 @@ +--- +source: src/lib.rs +expression: "p.parse(\"1\")" + +--- +Ok( + Literal( + "1", + ), +) From c5643424ca7367afc510dcaf64e28f021ad9e61f Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Fri, 16 Jul 2021 14:19:54 +0200 Subject: [PATCH 02/15] Finish basic parser --- Cargo.toml | 2 + build.rs | 2 +- examples/errors.rs | 19 ++++ src/ast.rs | 100 +++++++---------- src/error.rs | 46 ++++++++ src/lalrpop_helpers.rs | 3 + src/lexer.rs | 78 ++++++++++++++ src/lib.rs | 38 ++++--- src/parser.lalrpop | 108 ++++++++++++++----- src/snapshots/simplexpr__tests__test-10.snap | 8 ++ src/snapshots/simplexpr__tests__test-11.snap | 8 ++ src/snapshots/simplexpr__tests__test-12.snap | 8 ++ src/snapshots/simplexpr__tests__test-13.snap | 8 ++ src/snapshots/simplexpr__tests__test-14.snap | 30 ++++++ src/snapshots/simplexpr__tests__test-15.snap | 13 +++ src/snapshots/simplexpr__tests__test-16.snap | 18 ++++ src/snapshots/simplexpr__tests__test-17.snap | 12 +++ src/snapshots/simplexpr__tests__test-18.snap | 22 ++++ src/snapshots/simplexpr__tests__test-19.snap | 10 ++ src/snapshots/simplexpr__tests__test-2.snap | 12 +-- src/snapshots/simplexpr__tests__test-20.snap | 16 +++ src/snapshots/simplexpr__tests__test-21.snap | 11 ++ src/snapshots/simplexpr__tests__test-22.snap | 15 +++ src/snapshots/simplexpr__tests__test-23.snap | 12 +++ src/snapshots/simplexpr__tests__test-24.snap | 20 ++++ src/snapshots/simplexpr__tests__test-25.snap | 22 ++++ src/snapshots/simplexpr__tests__test-26.snap | 42 ++++++++ src/snapshots/simplexpr__tests__test-3.snap | 30 +----- src/snapshots/simplexpr__tests__test-4.snap | 18 +--- src/snapshots/simplexpr__tests__test-5.snap | 20 +--- src/snapshots/simplexpr__tests__test-6.snap | 26 +---- src/snapshots/simplexpr__tests__test-7.snap | 26 +---- src/snapshots/simplexpr__tests__test-8.snap | 14 +-- src/snapshots/simplexpr__tests__test-9.snap | 8 ++ src/snapshots/simplexpr__tests__test.snap | 6 +- 35 files changed, 596 insertions(+), 235 deletions(-) create mode 100644 examples/errors.rs create mode 100644 src/error.rs create mode 100644 src/lalrpop_helpers.rs create mode 100644 src/lexer.rs create mode 100644 src/snapshots/simplexpr__tests__test-10.snap create mode 100644 src/snapshots/simplexpr__tests__test-11.snap create mode 100644 src/snapshots/simplexpr__tests__test-12.snap create mode 100644 src/snapshots/simplexpr__tests__test-13.snap create mode 100644 src/snapshots/simplexpr__tests__test-14.snap create mode 100644 src/snapshots/simplexpr__tests__test-15.snap create mode 100644 src/snapshots/simplexpr__tests__test-16.snap create mode 100644 src/snapshots/simplexpr__tests__test-17.snap create mode 100644 src/snapshots/simplexpr__tests__test-18.snap create mode 100644 src/snapshots/simplexpr__tests__test-19.snap create mode 100644 src/snapshots/simplexpr__tests__test-20.snap create mode 100644 src/snapshots/simplexpr__tests__test-21.snap create mode 100644 src/snapshots/simplexpr__tests__test-22.snap create mode 100644 src/snapshots/simplexpr__tests__test-23.snap create mode 100644 src/snapshots/simplexpr__tests__test-24.snap create mode 100644 src/snapshots/simplexpr__tests__test-25.snap create mode 100644 src/snapshots/simplexpr__tests__test-26.snap create mode 100644 src/snapshots/simplexpr__tests__test-9.snap diff --git a/Cargo.toml b/Cargo.toml index 87e69fd..2d6c0c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,8 @@ logos = "0.12" serde = {version = "1.0", features = ["derive"]} serde_json = "1.0" +strum = { version = "0.21", features = ["derive"] } + [build-dependencies] diff --git a/build.rs b/build.rs index 57684be..cbc2c25 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,4 @@ extern crate lalrpop; fn main() { - lalrpop::process_root().unwrap(); + lalrpop::Configuration::new().log_verbose().process_current_dir().unwrap(); } diff --git a/examples/errors.rs b/examples/errors.rs new file mode 100644 index 0000000..d089959 --- /dev/null +++ b/examples/errors.rs @@ -0,0 +1,19 @@ +fn main() { + let mut files = codespan_reporting::files::SimpleFiles::new(); + + let input = "12 + \"hi\" * foo ) ? bar == baz : false"; + + let _ = files.add("foo.eww", input); + let ast = simplexpr::parse_string(input); + match ast { + Ok(ast) => { + println!("{:?}", ast); + } + Err(err) => { + let diag = err.pretty_diagnostic(); + use codespan_reporting::term; + let mut writer = term::termcolor::StandardStream::stderr(term::termcolor::ColorChoice::Always); + term::emit(&mut writer, &term::Config::default(), &files, &diag).unwrap(); + } + } +} diff --git a/src/ast.rs b/src/ast.rs index 0bb2684..9ef345a 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,8 +1,8 @@ use itertools::Itertools; use serde::{Deserialize, Serialize}; -#[derive(Eq, PartialEq, Clone, Copy)] -pub struct Span(pub usize, pub usize, pub usize); +#[derive(Eq, PartialEq, Clone, Copy, Serialize, Deserialize)] +pub struct Span(pub usize, pub usize); impl std::fmt::Display for Span { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -16,79 +16,59 @@ impl std::fmt::Debug for Span { } } -#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)] +#[rustfmt::skip] +#[derive(Clone, PartialEq, Serialize, Deserialize, Debug, strum::EnumString, strum::Display)] pub enum BinOp { - Plus, - Minus, - Times, - Div, - Mod, - Equals, - NotEquals, - And, - Or, - GT, - LT, - Elvis, - RegexMatch, + #[strum(serialize = "+") ] Plus, + #[strum(serialize = "-") ] Minus, + #[strum(serialize = "*") ] Times, + #[strum(serialize = "/") ] Div, + #[strum(serialize = "%") ] Mod, + #[strum(serialize = "==")] Equals, + #[strum(serialize = "!=")] NotEquals, + #[strum(serialize = "&&")] And, + #[strum(serialize = "||")] Or, + #[strum(serialize = ">") ] GT, + #[strum(serialize = "<") ] LT, + #[strum(serialize = "?:")] Elvis, + #[strum(serialize = "=~")] RegexMatch, } -impl std::fmt::Display for BinOp { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - BinOp::Plus => write!(f, "+"), - BinOp::Minus => write!(f, "-"), - BinOp::Times => write!(f, "*"), - BinOp::Div => write!(f, "/"), - BinOp::Mod => write!(f, "%"), - BinOp::Equals => write!(f, "=="), - BinOp::NotEquals => write!(f, "!="), - BinOp::And => write!(f, "&&"), - BinOp::Or => write!(f, "||"), - BinOp::GT => write!(f, ">"), - BinOp::LT => write!(f, "<"), - BinOp::Elvis => write!(f, "?:"), - BinOp::RegexMatch => write!(f, "=~"), - } - } -} - -#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)] +#[derive(Clone, PartialEq, Serialize, Deserialize, Debug, strum::EnumString, strum::Display)] pub enum UnaryOp { + #[strum(serialize = "!")] Not, } -impl std::fmt::Display for UnaryOp { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - UnaryOp::Not => write!(f, "!"), - } - } -} - -#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)] +#[derive(Clone, PartialEq, Serialize, Deserialize)] pub enum SimplExpr { - Literal(String), - VarRef(String), - BinOp(Box, BinOp, Box), - UnaryOp(UnaryOp, Box), - IfElse(Box, Box, Box), - JsonAccess(Box, Box), - FunctionCall(String, Vec), + Literal(Span, String), + VarRef(Span, String), + BinOp(Span, Box, BinOp, Box), + UnaryOp(Span, UnaryOp, Box), + IfElse(Span, Box, Box, Box), + JsonAccess(Span, Box, Box), + FunctionCall(Span, String, Vec), } impl std::fmt::Display for SimplExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - SimplExpr::VarRef(x) => write!(f, "{}", x), - SimplExpr::Literal(x) => write!(f, "\"{}\"", x), - SimplExpr::BinOp(l, op, r) => write!(f, "({} {} {})", l, op, r), - SimplExpr::UnaryOp(op, x) => write!(f, "{}{}", op, x), - SimplExpr::IfElse(a, b, c) => write!(f, "(if {} then {} else {})", a, b, c), - SimplExpr::JsonAccess(value, index) => write!(f, "{}[{}]", value, index), - SimplExpr::FunctionCall(function_name, args) => { + SimplExpr::VarRef(_, x) => write!(f, "{}", x), + SimplExpr::Literal(_, x) => write!(f, "\"{}\"", x), + SimplExpr::BinOp(_, l, op, r) => write!(f, "({} {} {})", l, op, r), + SimplExpr::UnaryOp(_, op, x) => write!(f, "{}{}", op, x), + SimplExpr::IfElse(_, a, b, c) => write!(f, "(if {} then {} else {})", a, b, c), + SimplExpr::JsonAccess(_, value, index) => write!(f, "{}[{}]", value, index), + SimplExpr::FunctionCall(_, function_name, args) => { write!(f, "{}({})", function_name, args.iter().join(", ")) } } } } + +impl std::fmt::Debug for SimplExpr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self) + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..2f04c9b --- /dev/null +++ b/src/error.rs @@ -0,0 +1,46 @@ +use crate::{ast::Span, lexer}; +use codespan_reporting::diagnostic; + +pub type Result = std::result::Result; +pub enum Error { + ParseError { source: lalrpop_util::ParseError }, +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::ParseError { source } => write!(f, "Parse error: {}", source), + } + } +} + +impl Error { + pub fn from_parse_error(err: lalrpop_util::ParseError) -> Self { + Error::ParseError { source: err } + } + + pub fn get_span(&self) -> Option { + match self { + Self::ParseError { source } => get_parse_error_span(source), + } + } + + pub fn pretty_diagnostic(&self) -> diagnostic::Diagnostic { + let diag = diagnostic::Diagnostic::error().with_message(format!("{}", self)); + if let Some(span) = self.get_span() { + diag.with_labels(vec![diagnostic::Label::primary(0, span.0..span.1)]) + } else { + diag + } + } +} + +fn get_parse_error_span(err: &lalrpop_util::ParseError) -> Option { + match err { + lalrpop_util::ParseError::InvalidToken { location } => Some(Span(*location, *location)), + lalrpop_util::ParseError::UnrecognizedEOF { location, expected: _ } => Some(Span(*location, *location)), + lalrpop_util::ParseError::UnrecognizedToken { token, expected: _ } => Some(Span(token.0, token.2)), + lalrpop_util::ParseError::ExtraToken { token } => Some(Span(token.0, token.2)), + lalrpop_util::ParseError::User { error: _ } => None, + } +} diff --git a/src/lalrpop_helpers.rs b/src/lalrpop_helpers.rs new file mode 100644 index 0000000..f642a30 --- /dev/null +++ b/src/lalrpop_helpers.rs @@ -0,0 +1,3 @@ +pub fn b(x: T) -> Box { + Box::new(x) +} diff --git a/src/lexer.rs b/src/lexer.rs new file mode 100644 index 0000000..ab73f36 --- /dev/null +++ b/src/lexer.rs @@ -0,0 +1,78 @@ +use logos::Logos; + +#[rustfmt::skip] +#[derive(Logos, Debug, PartialEq, Eq, Clone, strum::Display)] +pub enum Token { + #[strum(serialize = "+") ] #[token("+") ] Plus, + #[strum(serialize = "-") ] #[token("-") ] Minus, + #[strum(serialize = "*") ] #[token("*") ] Times, + #[strum(serialize = "/") ] #[token("/") ] Div, + #[strum(serialize = "%") ] #[token("%") ] Mod, + #[strum(serialize = "==")] #[token("==")] Equals, + #[strum(serialize = "!=")] #[token("!=")] NotEquals, + #[strum(serialize = "&&")] #[token("&&")] And, + #[strum(serialize = "||")] #[token("||")] Or, + #[strum(serialize = ">") ] #[token(">") ] GT, + #[strum(serialize = "<") ] #[token("<") ] LT, + #[strum(serialize = "?:")] #[token("?:")] Elvis, + #[strum(serialize = "=~")] #[token("=~")] RegexMatch, + + #[strum(serialize = "!") ] #[token("!") ] Not, + + #[strum(serialize = ",") ] #[token(",") ] Comma, + #[strum(serialize = "?") ] #[token("?") ] Question, + #[strum(serialize = ":") ] #[token(":") ] Colon, + #[strum(serialize = "(") ] #[token("(") ] LPren, + #[strum(serialize = ")") ] #[token(")") ] RPren, + #[strum(serialize = "[") ] #[token("[") ] LBrack, + #[strum(serialize = "]") ] #[token("]") ] RBrack, + #[strum(serialize = ".") ] #[token(".") ] Dot, + #[strum(serialize = "true") ] #[token("true") ] True, + #[strum(serialize = "false")] #[token("false")] False, + + #[regex(r"[a-zA-Z_-]+", |x| x.slice().to_string())] + Ident(String), + #[regex(r"[+-]?(?:[0-9]+[.])?[0-9]+", |x| x.slice().to_string())] + NumLit(String), + #[regex(r#""(?:[^"\\]|\\.)*""#, |x| x.slice().to_string())] + StrLit(String), + + + #[error] + #[regex(r"[ \t\n\f]+", logos::skip)] + Error, +} + +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +pub struct LexicalError(usize, usize); + +impl std::fmt::Display for LexicalError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Lexical error at {}..{}", self.0, self.1) + } +} + +pub type SpannedResult = Result<(Loc, Tok, Loc), Error>; + +pub struct Lexer<'input> { + lexer: logos::SpannedIter<'input, Token>, +} + +impl<'input> Lexer<'input> { + pub fn new(text: &'input str) -> Self { + Lexer { lexer: logos::Lexer::new(text).spanned() } + } +} + +impl<'input> Iterator for Lexer<'input> { + type Item = SpannedResult; + + fn next(&mut self) -> Option { + let (token, range) = self.lexer.next()?; + if token == Token::Error { + Some(Err(LexicalError(range.start, range.end))) + } else { + Some(Ok((range.start, token, range.end))) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 1636430..96fca9c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,23 +1,33 @@ pub mod ast; +pub mod error; +mod lalrpop_helpers; +mod lexer; +use ast::SimplExpr; +use error::{Error, Result}; use lalrpop_util::lalrpop_mod; lalrpop_mod!(pub parser); -macro_rules! test_parser { - ($($text:literal),*) => {{ - let p = crate::parser::ExprParser::new(); - //use crate::lexer::Lexer; - - ::insta::with_settings!({sort_maps => true}, { - $( - ::insta::assert_debug_snapshot!(p.parse($text)); - )* - }); - }} +pub fn parse_string(s: &str) -> Result { + let lexer = lexer::Lexer::new(s); + let parser = parser::ExprParser::new(); + Ok(parser.parse(lexer).map_err(|e| Error::from_parse_error(e))?) } #[cfg(test)] mod tests { + macro_rules! test_parser { + ($($text:literal),* $(,)?) => {{ + let p = crate::parser::ExprParser::new(); + use crate::lexer::Lexer; + ::insta::with_settings!({sort_maps => true}, { + $( + ::insta::assert_debug_snapshot!(p.parse(Lexer::new($text))); + )* + }); + }} + } + #[test] fn test() { test_parser!( @@ -29,7 +39,11 @@ mod tests { "1 + true ? 2 : 5 + 2", "1 + (true ? 2 : 5) + 2", "foo(1, 2)", - "! false || ! true" + "! false || ! true", + "\"foo\" + 12.4", + "hi[\"ho\"]", + "foo.bar.baz", + "foo.bar[2 + 2] * asdf[foo.bar]", ); } } diff --git a/src/parser.lalrpop b/src/parser.lalrpop index 2db6bf0..d574c7c 100644 --- a/src/parser.lalrpop +++ b/src/parser.lalrpop @@ -1,8 +1,50 @@ -use crate::ast::{SimplExpr, Span, BinOp, UnaryOp}; +use crate::ast::{SimplExpr::{self, *}, Span, BinOp::*, UnaryOp::*}; +use crate::lexer::{Token, LexicalError}; +use crate::lalrpop_helpers::*; + grammar; +extern { + type Location = usize; + type Error = LexicalError; + + enum Token { + "+" => Token::Plus, + "-" => Token::Minus, + "*" => Token::Times, + "/" => Token::Div, + "%" => Token::Mod, + "==" => Token::Equals, + "!=" => Token::NotEquals, + "&&" => Token::And, + "||" => Token::Or, + ">" => Token::GT, + "<" => Token::LT, + "?:" => Token::Elvis, + "=~" => Token::RegexMatch, + + "!" => Token::Not, + + "," => Token::Comma, + "?" => Token::Question, + ":" => Token::Colon, + "(" => Token::LPren, + ")" => Token::RPren, + "[" => Token::LBrack, + "]" => Token::RBrack, + "." => Token::Dot, + "true" => Token::True, + "false" => Token::False, + + "identifier" => Token::Ident(), + "number" => Token::NumLit(), + "string" => Token::StrLit(), + + } +} + Comma: Vec = { ",")*> => match e { None => v, @@ -15,43 +57,57 @@ Comma: Vec = { pub Expr: SimplExpr = { #[precedence(level="0")] - "true" => SimplExpr::Literal("true".to_string()), - "false" => SimplExpr::Literal("false".to_string()), - , + , + => VarRef(Span(l, r), ident.to_string()), "(" ")", - "(" > ")" => SimplExpr::FunctionCall(ident, args), + #[precedence(level="1")] #[assoc(side="right")] + "(" > ")" => FunctionCall(Span(l, r), ident, args), + "[" "]" => JsonAccess(Span(l, r), b(value), b(index)), - #[precedence(level="1")] #[assoc(side="left")] - "!" => SimplExpr::UnaryOp(UnaryOp::Not, Box::new(<>)) + "." => { + JsonAccess(Span(l, r), b(value), b(Literal(Span(lit_l, r), index))) + }, - - #[precedence(level="2")] #[assoc(side="left")] - "*" => SimplExpr::BinOp(Box::new(l), BinOp::Times, Box::new(r)), - "/" => SimplExpr::BinOp(Box::new(l), BinOp::Div, Box::new(r)), - "%" => SimplExpr::BinOp(Box::new(l), BinOp::Mod, Box::new(r)), + #[precedence(level="2")] #[assoc(side="right")] + "!" => UnaryOp(Span(l, r), Not, b(e)), #[precedence(level="3")] #[assoc(side="left")] - "+" => SimplExpr::BinOp(Box::new(l), BinOp::Plus, Box::new(r)), - "-" => SimplExpr::BinOp(Box::new(l), BinOp::Minus, Box::new(r)), + "*" => BinOp(Span(l, r), b(le), Times, b(re)), + "/" => BinOp(Span(l, r), b(le), Div, b(re)), + "%" => BinOp(Span(l, r), b(le), Mod, b(re)), #[precedence(level="4")] #[assoc(side="left")] - "==" => SimplExpr::BinOp(Box::new(l), BinOp::Equals, Box::new(r)), - "!=" => SimplExpr::BinOp(Box::new(l), BinOp::NotEquals, Box::new(r)), - "<" => SimplExpr::BinOp(Box::new(l), BinOp::GT, Box::new(r)), - ">" => SimplExpr::BinOp(Box::new(l), BinOp::LT, Box::new(r)), - "=~" => SimplExpr::BinOp(Box::new(l), BinOp::RegexMatch, Box::new(r)), + "+" => BinOp(Span(l, r), b(le), Plus, b(re)), + "-" => BinOp(Span(l, r), b(le), Minus, b(re)), #[precedence(level="5")] #[assoc(side="left")] - "&&" => SimplExpr::BinOp(Box::new(l), BinOp::And, Box::new(r)), - "||" => SimplExpr::BinOp(Box::new(l), BinOp::Or, Box::new(r)), - "?:" => SimplExpr::BinOp(Box::new(l), BinOp::Elvis, Box::new(r)), + "==" => BinOp(Span(l, r), b(le), Equals, b(re)), + "!=" => BinOp(Span(l, r), b(le), NotEquals, b(re)), + "<" => BinOp(Span(l, r), b(le), GT, b(re)), + ">" => BinOp(Span(l, r), b(le), LT, b(re)), + "=~" => BinOp(Span(l, r), b(le), RegexMatch, b(re)), - #[precedence(level="6")] #[assoc(side="right")] - "?" ":" => SimplExpr::IfElse(Box::new(cond), Box::new(then), Box::new(els)), + #[precedence(level="6")] #[assoc(side="left")] + "&&" => BinOp(Span(l, r), b(le), And, b(re)), + "||" => BinOp(Span(l, r), b(le), Or, b(re)), + "?:" => BinOp(Span(l, r), b(le), Elvis, b(re)), + + #[precedence(level="7")] #[assoc(side="right")] + "?" ":" => { + IfElse(Span(l, r), b(cond), b(then), b(els)) + }, }; ExprReset = ; -Number: SimplExpr = r"[+-]?(?:[0-9]+[.])?[0-9]+" => SimplExpr::Literal(<>.to_string()); -Ident: String = r"[a-zA-Z_][^\s{}\(\)]*" => <>.to_string(); +Literal: SimplExpr = { + => Literal(Span(l, r), x), + => Literal(Span(l, r), x.to_string()), + "true" => Literal(Span(l, r), "true".to_string()), + "false" => Literal(Span(l, r), "false".to_string()), +} + +StrLit: String = { + => x[1..x.len() - 1].to_owned(), +}; diff --git a/src/snapshots/simplexpr__tests__test-10.snap b/src/snapshots/simplexpr__tests__test-10.snap new file mode 100644 index 0000000..1fa28f0 --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-10.snap @@ -0,0 +1,8 @@ +--- +source: src/lib.rs +expression: "p.parse(Lexer::new(\"\\\"foo\\\" + 12.4\"))" + +--- +Ok( + ("foo" + "12.4"), +) diff --git a/src/snapshots/simplexpr__tests__test-11.snap b/src/snapshots/simplexpr__tests__test-11.snap new file mode 100644 index 0000000..44b65b7 --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-11.snap @@ -0,0 +1,8 @@ +--- +source: src/lib.rs +expression: "p.parse(Lexer::new(\"hi[\\\"ho\\\"]\"))" + +--- +Ok( + hi["ho"], +) diff --git a/src/snapshots/simplexpr__tests__test-12.snap b/src/snapshots/simplexpr__tests__test-12.snap new file mode 100644 index 0000000..e19354b --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-12.snap @@ -0,0 +1,8 @@ +--- +source: src/lib.rs +expression: "p.parse(Lexer::new(\"foo.bar.baz\"))" + +--- +Ok( + foo["bar"]["baz"], +) diff --git a/src/snapshots/simplexpr__tests__test-13.snap b/src/snapshots/simplexpr__tests__test-13.snap new file mode 100644 index 0000000..7494deb --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-13.snap @@ -0,0 +1,8 @@ +--- +source: src/lib.rs +expression: "p.parse(Lexer::new(\"foo.bar[2 + 2] * asdf[foo.bar]\"))" + +--- +Ok( + (foo["bar"][("2" + "2")] * asdf[foo["bar"]]), +) diff --git a/src/snapshots/simplexpr__tests__test-14.snap b/src/snapshots/simplexpr__tests__test-14.snap new file mode 100644 index 0000000..692ac35 --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-14.snap @@ -0,0 +1,30 @@ +--- +source: src/lib.rs +expression: "p.parse(Lexer::new(\"1 + (true ? 2 : 5) + 2\"))" + +--- +Ok( + BinOp( + BinOp( + Literal( + "1", + ), + Plus, + IfElse( + Literal( + "true", + ), + Literal( + "2", + ), + Literal( + "5", + ), + ), + ), + Plus, + Literal( + "2", + ), + ), +) diff --git a/src/snapshots/simplexpr__tests__test-15.snap b/src/snapshots/simplexpr__tests__test-15.snap new file mode 100644 index 0000000..16b8e8a --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-15.snap @@ -0,0 +1,13 @@ +--- +source: src/lib.rs +expression: "Lexer::new(\"foo(1, 2)\").filter_map(|x|\n x.ok()).map(|(_, x, _)|\n match x {\n Token::Ident(x) |\n Token::NumLit(x) |\n Token::StrLit(x) =>\n format!(\"{}\", x),\n x =>\n format!(\"{}\", x),\n }).collect::>()" + +--- +[ + "foo", + "LPren", + "1", + "Comma", + "2", + "RPren", +] diff --git a/src/snapshots/simplexpr__tests__test-16.snap b/src/snapshots/simplexpr__tests__test-16.snap new file mode 100644 index 0000000..e128f15 --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-16.snap @@ -0,0 +1,18 @@ +--- +source: src/lib.rs +expression: "p.parse(Lexer::new(\"foo(1, 2)\"))" + +--- +Ok( + FunctionCall( + "foo", + [ + Literal( + "1", + ), + Literal( + "2", + ), + ], + ), +) diff --git a/src/snapshots/simplexpr__tests__test-17.snap b/src/snapshots/simplexpr__tests__test-17.snap new file mode 100644 index 0000000..ae5f678 --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-17.snap @@ -0,0 +1,12 @@ +--- +source: src/lib.rs +expression: "Lexer::new(\"! false || ! true\").filter_map(|x|\n x.ok()).map(|(_, x, _)|\n match x {\n Token::Ident(x)\n |\n Token::NumLit(x)\n |\n Token::StrLit(x)\n =>\n format!(\"{}\",\n x),\n x =>\n format!(\"{}\",\n x),\n }).collect::>()" + +--- +[ + "!", + "False", + "||", + "!", + "True", +] diff --git a/src/snapshots/simplexpr__tests__test-18.snap b/src/snapshots/simplexpr__tests__test-18.snap new file mode 100644 index 0000000..9ffd512 --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-18.snap @@ -0,0 +1,22 @@ +--- +source: src/lib.rs +expression: "p.parse(Lexer::new(\"! false || ! true\"))" + +--- +Ok( + BinOp( + UnaryOp( + Not, + Literal( + "false", + ), + ), + Or, + UnaryOp( + Not, + Literal( + "true", + ), + ), + ), +) diff --git a/src/snapshots/simplexpr__tests__test-19.snap b/src/snapshots/simplexpr__tests__test-19.snap new file mode 100644 index 0000000..8d6b02b --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-19.snap @@ -0,0 +1,10 @@ +--- +source: src/lib.rs +expression: "Lexer::new(\"\\\"foo\\\" + 12.4\").filter_map(|x|\n x.ok()).map(|(_, x, _)|\n match x {\n Token::Ident(x)\n |\n Token::NumLit(x)\n |\n Token::StrLit(x)\n =>\n format!(\"{}\",\n x),\n x =>\n format!(\"{}\",\n x),\n }).collect::>()" + +--- +[ + "\"foo\"", + "+", + "12.4", +] diff --git a/src/snapshots/simplexpr__tests__test-2.snap b/src/snapshots/simplexpr__tests__test-2.snap index 9d2e4fb..3b4e5e4 100644 --- a/src/snapshots/simplexpr__tests__test-2.snap +++ b/src/snapshots/simplexpr__tests__test-2.snap @@ -1,16 +1,8 @@ --- source: src/lib.rs -expression: "p.parse(\"2 + 5\")" +expression: "p.parse(Lexer::new(\"2 + 5\"))" --- Ok( - BinOp( - Literal( - "2", - ), - Plus, - Literal( - "5", - ), - ), + ("2" + "5"), ) diff --git a/src/snapshots/simplexpr__tests__test-20.snap b/src/snapshots/simplexpr__tests__test-20.snap new file mode 100644 index 0000000..9d42e5a --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-20.snap @@ -0,0 +1,16 @@ +--- +source: src/lib.rs +expression: "p.parse(Lexer::new(\"\\\"foo\\\" + 12.4\"))" + +--- +Ok( + BinOp( + Literal( + "foo", + ), + Plus, + Literal( + "12.4", + ), + ), +) diff --git a/src/snapshots/simplexpr__tests__test-21.snap b/src/snapshots/simplexpr__tests__test-21.snap new file mode 100644 index 0000000..84a5eb7 --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-21.snap @@ -0,0 +1,11 @@ +--- +source: src/lib.rs +expression: "Lexer::new(\"hi[\\\"ho\\\"]\").filter_map(|x|\n x.ok()).map(|(_, x, _)|\n match x {\n Token::Ident(x) |\n Token::NumLit(x) |\n Token::StrLit(x)\n =>\n format!(\"{}\", x),\n x =>\n format!(\"{}\", x),\n }).collect::>()" + +--- +[ + "hi", + "LBrack", + "\"ho\"", + "RBrack", +] diff --git a/src/snapshots/simplexpr__tests__test-22.snap b/src/snapshots/simplexpr__tests__test-22.snap new file mode 100644 index 0000000..958cec3 --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-22.snap @@ -0,0 +1,15 @@ +--- +source: src/lib.rs +expression: "p.parse(Lexer::new(\"hi[\\\"ho\\\"]\"))" + +--- +Ok( + JsonAccess( + VarRef( + "hi", + ), + Literal( + "ho", + ), + ), +) diff --git a/src/snapshots/simplexpr__tests__test-23.snap b/src/snapshots/simplexpr__tests__test-23.snap new file mode 100644 index 0000000..6e91f79 --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-23.snap @@ -0,0 +1,12 @@ +--- +source: src/lib.rs +expression: "Lexer::new(\"foo.bar.baz\").filter_map(|x|\n x.ok()).map(|(_, x, _)|\n match x {\n Token::Ident(x) |\n Token::NumLit(x)\n |\n Token::StrLit(x)\n =>\n format!(\"{}\", x),\n x =>\n format!(\"{}\", x),\n }).collect::>()" + +--- +[ + "foo", + "Dot", + "bar", + "Dot", + "baz", +] diff --git a/src/snapshots/simplexpr__tests__test-24.snap b/src/snapshots/simplexpr__tests__test-24.snap new file mode 100644 index 0000000..be97009 --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-24.snap @@ -0,0 +1,20 @@ +--- +source: src/lib.rs +expression: "p.parse(Lexer::new(\"foo.bar.baz\"))" + +--- +Ok( + JsonAccess( + JsonAccess( + VarRef( + "foo", + ), + Literal( + "bar", + ), + ), + Literal( + "baz", + ), + ), +) diff --git a/src/snapshots/simplexpr__tests__test-25.snap b/src/snapshots/simplexpr__tests__test-25.snap new file mode 100644 index 0000000..3a21b7b --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-25.snap @@ -0,0 +1,22 @@ +--- +source: src/lib.rs +expression: "Lexer::new(\"foo.bar[2 + 2] * asdf[foo.bar]\").filter_map(|x|\n x.ok()).map(|(_,\n x,\n _)|\n match x\n {\n Token::Ident(x)\n |\n Token::NumLit(x)\n |\n Token::StrLit(x)\n =>\n format!(\"{}\",\n x),\n x\n =>\n format!(\"{}\",\n x),\n }).collect::>()" + +--- +[ + "foo", + "Dot", + "bar", + "LBrack", + "2", + "+", + "2", + "RBrack", + "*", + "asdf", + "LBrack", + "foo", + "Dot", + "bar", + "RBrack", +] diff --git a/src/snapshots/simplexpr__tests__test-26.snap b/src/snapshots/simplexpr__tests__test-26.snap new file mode 100644 index 0000000..bdabe4e --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-26.snap @@ -0,0 +1,42 @@ +--- +source: src/lib.rs +expression: "p.parse(Lexer::new(\"foo.bar[2 + 2] * asdf[foo.bar]\"))" + +--- +Ok( + BinOp( + JsonAccess( + JsonAccess( + VarRef( + "foo", + ), + Literal( + "bar", + ), + ), + BinOp( + Literal( + "2", + ), + Plus, + Literal( + "2", + ), + ), + ), + Times, + JsonAccess( + VarRef( + "asdf", + ), + JsonAccess( + VarRef( + "foo", + ), + Literal( + "bar", + ), + ), + ), + ), +) diff --git a/src/snapshots/simplexpr__tests__test-3.snap b/src/snapshots/simplexpr__tests__test-3.snap index ed8b2c9..7a11e2b 100644 --- a/src/snapshots/simplexpr__tests__test-3.snap +++ b/src/snapshots/simplexpr__tests__test-3.snap @@ -1,34 +1,8 @@ --- source: src/lib.rs -expression: "p.parse(\"2 * 5 + 1 * 1 + 3\")" +expression: "p.parse(Lexer::new(\"2 * 5 + 1 * 1 + 3\"))" --- Ok( - BinOp( - BinOp( - BinOp( - Literal( - "2", - ), - Times, - Literal( - "5", - ), - ), - Plus, - BinOp( - Literal( - "1", - ), - Times, - Literal( - "1", - ), - ), - ), - Plus, - Literal( - "3", - ), - ), + ((("2" * "5") + ("1" * "1")) + "3"), ) diff --git a/src/snapshots/simplexpr__tests__test-4.snap b/src/snapshots/simplexpr__tests__test-4.snap index 1c5c5f5..9025ed3 100644 --- a/src/snapshots/simplexpr__tests__test-4.snap +++ b/src/snapshots/simplexpr__tests__test-4.snap @@ -1,22 +1,8 @@ --- source: src/lib.rs -expression: "p.parse(\"(1 + 2) * 2\")" +expression: "p.parse(Lexer::new(\"(1 + 2) * 2\"))" --- Ok( - BinOp( - BinOp( - Literal( - "1", - ), - Plus, - Literal( - "2", - ), - ), - Times, - Literal( - "2", - ), - ), + (("1" + "2") * "2"), ) diff --git a/src/snapshots/simplexpr__tests__test-5.snap b/src/snapshots/simplexpr__tests__test-5.snap index f71ba69..683c97b 100644 --- a/src/snapshots/simplexpr__tests__test-5.snap +++ b/src/snapshots/simplexpr__tests__test-5.snap @@ -1,24 +1,8 @@ --- source: src/lib.rs -expression: "p.parse(\"1 + true ? 2 : 5\")" +expression: "p.parse(Lexer::new(\"1 + true ? 2 : 5\"))" --- Ok( - IfElse( - BinOp( - Literal( - "1", - ), - Plus, - Literal( - "true", - ), - ), - Literal( - "2", - ), - Literal( - "5", - ), - ), + (if ("1" + "true") then "2" else "5"), ) diff --git a/src/snapshots/simplexpr__tests__test-6.snap b/src/snapshots/simplexpr__tests__test-6.snap index 4efff48..ca3c9ba 100644 --- a/src/snapshots/simplexpr__tests__test-6.snap +++ b/src/snapshots/simplexpr__tests__test-6.snap @@ -1,30 +1,8 @@ --- source: src/lib.rs -expression: "p.parse(\"1 + true ? 2 : 5 + 2\")" +expression: "p.parse(Lexer::new(\"1 + true ? 2 : 5 + 2\"))" --- Ok( - IfElse( - BinOp( - Literal( - "1", - ), - Plus, - Literal( - "true", - ), - ), - Literal( - "2", - ), - BinOp( - Literal( - "5", - ), - Plus, - Literal( - "2", - ), - ), - ), + (if ("1" + "true") then "2" else ("5" + "2")), ) diff --git a/src/snapshots/simplexpr__tests__test-7.snap b/src/snapshots/simplexpr__tests__test-7.snap index ff662bd..bd2587c 100644 --- a/src/snapshots/simplexpr__tests__test-7.snap +++ b/src/snapshots/simplexpr__tests__test-7.snap @@ -1,30 +1,8 @@ --- source: src/lib.rs -expression: "p.parse(\"1 + (if true then 2 else 5) + 2\")" +expression: "p.parse(Lexer::new(\"1 + (true ? 2 : 5) + 2\"))" --- Ok( - BinOp( - BinOp( - Literal( - "1", - ), - Plus, - IfElse( - Literal( - "true", - ), - Literal( - "2", - ), - Literal( - "5", - ), - ), - ), - Plus, - Literal( - "2", - ), - ), + (("1" + (if "true" then "2" else "5")) + "2"), ) diff --git a/src/snapshots/simplexpr__tests__test-8.snap b/src/snapshots/simplexpr__tests__test-8.snap index 976aa45..cbd02d1 100644 --- a/src/snapshots/simplexpr__tests__test-8.snap +++ b/src/snapshots/simplexpr__tests__test-8.snap @@ -1,18 +1,8 @@ --- source: src/lib.rs -expression: "p.parse(\"foo(1, 2)\")" +expression: "p.parse(Lexer::new(\"foo(1, 2)\"))" --- Ok( - FunctionCall( - "foo", - [ - Literal( - "1", - ), - Literal( - "2", - ), - ], - ), + foo("1", "2"), ) diff --git a/src/snapshots/simplexpr__tests__test-9.snap b/src/snapshots/simplexpr__tests__test-9.snap new file mode 100644 index 0000000..eba646a --- /dev/null +++ b/src/snapshots/simplexpr__tests__test-9.snap @@ -0,0 +1,8 @@ +--- +source: src/lib.rs +expression: "p.parse(Lexer::new(\"! false || ! true\"))" + +--- +Ok( + (!"false" || !"true"), +) diff --git a/src/snapshots/simplexpr__tests__test.snap b/src/snapshots/simplexpr__tests__test.snap index 929695f..6c1e712 100644 --- a/src/snapshots/simplexpr__tests__test.snap +++ b/src/snapshots/simplexpr__tests__test.snap @@ -1,10 +1,8 @@ --- source: src/lib.rs -expression: "p.parse(\"1\")" +expression: "p.parse(Lexer::new(\"1\"))" --- Ok( - Literal( - "1", - ), + "1", ) From 3bb2e6516ec2c7b7c194266ba7d0d7c5e4a06b13 Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Fri, 16 Jul 2021 14:45:37 +0200 Subject: [PATCH 03/15] add error handling --- src/lib.rs | 1 + src/parser.lalrpop | 49 +++++++++++++++++++++++----------------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 96fca9c..84702b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod ast; +pub mod dynval; pub mod error; mod lalrpop_helpers; mod lexer; diff --git a/src/parser.lalrpop b/src/parser.lalrpop index d574c7c..f44a845 100644 --- a/src/parser.lalrpop +++ b/src/parser.lalrpop @@ -11,32 +11,33 @@ extern { type Error = LexicalError; enum Token { - "+" => Token::Plus, - "-" => Token::Minus, - "*" => Token::Times, - "/" => Token::Div, - "%" => Token::Mod, - "==" => Token::Equals, - "!=" => Token::NotEquals, - "&&" => Token::And, - "||" => Token::Or, - ">" => Token::GT, - "<" => Token::LT, - "?:" => Token::Elvis, - "=~" => Token::RegexMatch, + "+" => Token::Plus, + "-" => Token::Minus, + "*" => Token::Times, + "/" => Token::Div, + "%" => Token::Mod, + "==" => Token::Equals, + "!=" => Token::NotEquals, + "&&" => Token::And, + "||" => Token::Or, + ">" => Token::GT, + "<" => Token::LT, + "?:" => Token::Elvis, + "=~" => Token::RegexMatch, - "!" => Token::Not, + "!" => Token::Not, - "," => Token::Comma, - "?" => Token::Question, - ":" => Token::Colon, - "(" => Token::LPren, - ")" => Token::RPren, - "[" => Token::LBrack, - "]" => Token::RBrack, - "." => Token::Dot, - "true" => Token::True, - "false" => Token::False, + "," => Token::Comma, + "?" => Token::Question, + ":" => Token::Colon, + "(" => Token::LPren, + ")" => Token::RPren, + "[" => Token::LBrack, + "]" => Token::RBrack, + "." => Token::Dot, + + "true" => Token::True, + "false" => Token::False, "identifier" => Token::Ident(), "number" => Token::NumLit(), From d8ffd3153d951eb4d4c315cda4ba44bd438c95ae Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Fri, 16 Jul 2021 17:39:35 +0200 Subject: [PATCH 04/15] add expression evaluation and dynval --- examples/errors.rs | 2 +- src/EvalError.rs | 0 src/ast.rs | 16 +- src/dynval.rs | 184 +++++++++++++++++ src/error.rs | 52 ++++- src/eval.rs | 195 ++++++++++++++++++ src/lib.rs | 51 +---- src/{ => parser}/lalrpop_helpers.rs | 0 src/{ => parser}/lexer.rs | 0 src/parser/mod.rs | 47 +++++ .../simplexpr_parser.lalrpop} | 14 +- src/simplexpr_parser.lalrpop | 114 ++++++++++ 12 files changed, 613 insertions(+), 62 deletions(-) create mode 100644 src/EvalError.rs create mode 100644 src/dynval.rs create mode 100644 src/eval.rs rename src/{ => parser}/lalrpop_helpers.rs (100%) rename src/{ => parser}/lexer.rs (100%) create mode 100644 src/parser/mod.rs rename src/{parser.lalrpop => parser/simplexpr_parser.lalrpop} (90%) create mode 100644 src/simplexpr_parser.lalrpop diff --git a/examples/errors.rs b/examples/errors.rs index d089959..9bd7053 100644 --- a/examples/errors.rs +++ b/examples/errors.rs @@ -4,7 +4,7 @@ fn main() { let input = "12 + \"hi\" * foo ) ? bar == baz : false"; let _ = files.add("foo.eww", input); - let ast = simplexpr::parse_string(input); + let ast = simplexpr::parser::parse_string(input); match ast { Ok(ast) => { println!("{:?}", ast); diff --git a/src/EvalError.rs b/src/EvalError.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/ast.rs b/src/ast.rs index 9ef345a..96ab4bb 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,3 +1,4 @@ +use crate::dynval::DynVal; use itertools::Itertools; use serde::{Deserialize, Serialize}; @@ -42,7 +43,7 @@ pub enum UnaryOp { #[derive(Clone, PartialEq, Serialize, Deserialize)] pub enum SimplExpr { - Literal(Span, String), + Literal(Span, DynVal), VarRef(Span, String), BinOp(Span, Box, BinOp, Box), UnaryOp(Span, UnaryOp, Box), @@ -66,6 +67,19 @@ impl std::fmt::Display for SimplExpr { } } } +impl SimplExpr { + pub fn span(&self) -> Span { + match self { + SimplExpr::Literal(span, _) => *span, + SimplExpr::VarRef(span, _) => *span, + SimplExpr::BinOp(span, ..) => *span, + SimplExpr::UnaryOp(span, ..) => *span, + SimplExpr::IfElse(span, ..) => *span, + SimplExpr::JsonAccess(span, ..) => *span, + SimplExpr::FunctionCall(span, ..) => *span, + } + } +} impl std::fmt::Debug for SimplExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/src/dynval.rs b/src/dynval.rs new file mode 100644 index 0000000..f9352ec --- /dev/null +++ b/src/dynval.rs @@ -0,0 +1,184 @@ +use crate::ast::Span; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use std::{convert::TryFrom, fmt, iter::FromIterator}; + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +#[error("Type error: Failed to turn {value} into a {target_type}")] +pub struct ConversionError { + value: DynVal, + target_type: &'static str, + source: Option>, +} + +impl ConversionError { + fn new(value: DynVal, target_type: &'static str, source: Box) -> Self { + ConversionError { value, target_type, source: Some(source) } + } +} + +#[derive(Clone, Deserialize, Serialize, Default)] +pub struct DynVal(pub String, pub Option); + +impl From for DynVal { + fn from(s: String) -> Self { + DynVal(s, None) + } +} + +impl fmt::Display for DynVal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} +impl fmt::Debug for DynVal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "\"{}\"", self.0) + } +} + +/// Manually implement equality, to allow for values in different formats (i.e. "1" and "1.0") to still be considered as equal. +impl std::cmp::PartialEq for DynVal { + fn eq(&self, other: &Self) -> bool { + if let (Ok(a), Ok(b)) = (self.as_f64(), other.as_f64()) { + a == b + } else { + self.0 == other.0 + } + } +} + +impl FromIterator for DynVal { + fn from_iter>(iter: T) -> Self { + DynVal(iter.into_iter().join(""), None) + } +} + +impl std::str::FromStr for DynVal { + type Err = ConversionError; + + /// parses the value, trying to turn it into a number and a boolean first, + /// before deciding that it is a string. + fn from_str(s: &str) -> Result { + Ok(DynVal::from_string(s.to_string())) + } +} + +macro_rules! impl_try_from { + (impl From<$typ:ty> { + $(for $for:ty => |$arg:ident| $code:expr);*; + }) => { + $(impl TryFrom<$typ> for $for { + type Error = ConversionError; + fn try_from($arg: $typ) -> std::result::Result { $code } + })* + }; +} +macro_rules! impl_primval_from { + ($($t:ty),*) => { + $(impl From<$t> for DynVal { + fn from(x: $t) -> Self { DynVal(x.to_string(), None) } + })* + }; +} +impl_try_from!(impl From { + for String => |x| x.as_string(); + for f64 => |x| x.as_f64(); + for i32 => |x| x.as_i32(); + for bool => |x| x.as_bool(); + for Vec => |x| x.as_vec(); +}); + +impl_primval_from!(bool, i32, u32, f32, u8, f64, &str); + +impl From<&serde_json::Value> for DynVal { + fn from(v: &serde_json::Value) -> Self { + DynVal( + v.as_str() + .map(|x| x.to_string()) + .or_else(|| serde_json::to_string(v).ok()) + .unwrap_or_else(|| "".to_string()), + None, + ) + } +} + +impl DynVal { + pub fn from_string(s: String) -> Self { + DynVal(s, None) + } + + pub fn into_inner(self) -> String { + self.0 + } + + /// This will never fail + pub fn as_string(&self) -> Result { + Ok(self.0.to_owned()) + } + + pub fn as_f64(&self) -> Result { + self.0.parse().map_err(|e| ConversionError::new(self.clone(), "f64", Box::new(e))) + } + + pub fn as_i32(&self) -> Result { + self.0.parse().map_err(|e| ConversionError::new(self.clone(), "i32", Box::new(e))) + } + + pub fn as_bool(&self) -> Result { + self.0.parse().map_err(|e| ConversionError::new(self.clone(), "bool", Box::new(e))) + } + + pub fn as_vec(&self) -> Result> { + match self.0.strip_prefix('[').and_then(|x| x.strip_suffix(']')) { + Some(content) => { + let mut items: Vec = content.split(',').map(|x: &str| x.to_string()).collect(); + let mut removed = 0; + for times_ran in 0..items.len() { + // escapes `,` if there's a `\` before em + if items[times_ran - removed].ends_with('\\') { + items[times_ran - removed].pop(); + let it = items.remove((times_ran + 1) - removed); + items[times_ran - removed] += ","; + items[times_ran - removed] += ⁢ + removed += 1; + } + } + Ok(items) + } + None => Err(ConversionError { value: self.clone(), target_type: "vec", source: None }), + } + } + + pub fn as_json_value(&self) -> Result { + serde_json::from_str::(&self.0) + .map_err(|e| ConversionError::new(self.clone(), "json-value", Box::new(e))) + } +} + +#[cfg(test)] +mod test { + // use super::*; + // use pretty_assertions::assert_eq; + //#[test] + // fn test_parse_vec() { + // assert_eq!(vec![""], parse_vec("[]".to_string()).unwrap(), "should be able to parse empty lists"); + // assert_eq!(vec!["hi"], parse_vec("[hi]".to_string()).unwrap(), "should be able to parse single element list"); + // assert_eq!( + // vec!["hi", "ho", "hu"], + // parse_vec("[hi,ho,hu]".to_string()).unwrap(), + //"should be able to parse three element list" + //); + // assert_eq!(vec!["hi,ho"], parse_vec("[hi\\,ho]".to_string()).unwrap(), "should be able to parse list with escaped comma"); + // assert_eq!( + // vec!["hi,ho", "hu"], + // parse_vec("[hi\\,ho,hu]".to_string()).unwrap(), + //"should be able to parse two element list with escaped comma" + //); + // assert!(parse_vec("".to_string()).is_err(), "Should fail when parsing empty string"); + // assert!(parse_vec("[a,b".to_string()).is_err(), "Should fail when parsing unclosed list"); + // assert!(parse_vec("a]".to_string()).is_err(), "Should fail when parsing unopened list"); + //} +} diff --git a/src/error.rs b/src/error.rs index 2f04c9b..b794344 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,17 +1,24 @@ -use crate::{ast::Span, lexer}; +use crate::{ast::Span, dynval, parser::lexer}; use codespan_reporting::diagnostic; pub type Result = std::result::Result; +#[derive(thiserror::Error, Debug)] pub enum Error { + #[error("Parse error: {source}")] ParseError { source: lalrpop_util::ParseError }, -} + #[error("Conversion error: {source}")] + ConversionError { + #[from] + source: dynval::ConversionError, + }, + #[error("At: {0}: {1}")] + Spanned(Span, Box), -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::ParseError { source } => write!(f, "Parse error: {}", source), - } - } + #[error(transparent)] + Eval(crate::eval::EvalError), + + #[error(transparent)] + Other(#[from] Box), } impl Error { @@ -22,6 +29,9 @@ impl Error { pub fn get_span(&self) -> Option { match self { Self::ParseError { source } => get_parse_error_span(source), + Self::Spanned(span, _) => Some(*span), + Self::Eval(err) => err.span(), + _ => None, } } @@ -35,6 +45,23 @@ impl Error { } } +pub trait ErrorExt { + fn at(self, span: Span) -> Error; +} +impl ErrorExt for Box { + fn at(self, span: Span) -> Error { + Error::Spanned(span, self) + } +} +pub trait ResultExt { + fn at(self, span: Span) -> std::result::Result; +} +impl ResultExt for std::result::Result { + fn at(self, span: Span) -> std::result::Result { + self.map_err(|x| Error::Spanned(span, Box::new(x))) + } +} + fn get_parse_error_span(err: &lalrpop_util::ParseError) -> Option { match err { lalrpop_util::ParseError::InvalidToken { location } => Some(Span(*location, *location)), @@ -44,3 +71,12 @@ fn get_parse_error_span(err: &lalrpop_util::ParseError None, } } + +#[macro_export] +macro_rules! spanned { + ($err:ty, $span:expr, $block:expr) => {{ + let span = $span; + let result: Result<_, $err> = try { $block }; + result.at(span) + }}; +} diff --git a/src/eval.rs b/src/eval.rs new file mode 100644 index 0000000..66034c2 --- /dev/null +++ b/src/eval.rs @@ -0,0 +1,195 @@ +use itertools::Itertools; + +use crate::{ + ast::{BinOp, SimplExpr, Span, UnaryOp}, + dynval::{ConversionError, DynVal}, +}; +use std::collections::HashMap; + +#[derive(Debug, thiserror::Error)] +pub enum EvalError { + #[error("Invalid regex: {0}")] + InvalidRegex(#[from] regex::Error), + + #[error("got unresolved variable {0}")] + UnresolvedVariable(VarName), + + #[error("Conversion error: {0}")] + ConversionError(#[from] ConversionError), + + #[error("Incorrect number of arguments given to function: {0}")] + WrongArgCount(String), + + #[error("Unknown function {0}")] + UnknownFunction(String), + + #[error("Unable to index into value {0}")] + CannotIndex(String), + + #[error("At {0}: {1}")] + Spanned(Span, Box), +} + +impl EvalError { + pub fn span(&self) -> Option { + match self { + EvalError::Spanned(span, _) => Some(*span), + _ => None, + } + } + + pub fn at(self, span: Span) -> Self { + Self::Spanned(span, Box::new(self)) + } +} + +type VarName = String; + +impl SimplExpr { + pub fn map_terminals_into(self, f: impl Fn(Self) -> Self) -> Self { + use SimplExpr::*; + match self { + BinOp(span, box a, op, box b) => BinOp(span, box f(a), op, box f(b)), + UnaryOp(span, op, box a) => UnaryOp(span, op, box f(a)), + IfElse(span, box a, box b, box c) => IfElse(span, box f(a), box f(b), box f(c)), + other => f(other), + } + } + + /// resolve variable references in the expression. Fails if a variable cannot be resolved. + // pub fn resolve_refs(self, variables: &HashMap) -> Result { + // use SimplExpr::*; + // match self { + //// Literal(x) => Ok(Literal(AttrValue::from_primitive(x.resolve_fully(&variables)?))), + // Literal(x) => Ok(Literal(x)), + // VarRef(ref name) => Ok(Literal(AttrVal::from_primitive( + // variables.get(name).with_context(|| format!("Unknown variable {} referenced in {:?}", &name, &self))?.clone(), + //))), + // BinOp(box a, op, box b) => { + // Ok(BinOp(box a.resolve_refs(variables?), op, box b.resolve_refs(variables?))) + //} + // UnaryOp(op, box x) => Ok(UnaryOp(op, box x.resolve_refs(variables?))), + // IfElse(box a, box b, box c) => Ok(IfElse( + // box a.resolve_refs(variables?), + // box b.resolve_refs(variables?), + // box c.resolve_refs(variables?), + //)), + // JsonAccess(box a, box b) => { + // Ok(JsonAccess(box a.resolve_refs(variables?), box b.resolve_refs(variables?))) + //} + // FunctionCall(function_name, args) => { + // Ok(FunctionCall(function_name, args.into_iter().map(|a| a.resolve_refs(variables)).collect::>()?)) + //} + + pub fn var_refs(&self) -> Vec<&String> { + use SimplExpr::*; + match self { + Literal(..) => Vec::new(), + VarRef(_, name) => vec![name], + BinOp(_, box a, _, box b) | JsonAccess(_, box a, box b) => { + let mut refs = a.var_refs(); + refs.append(&mut b.var_refs()); + refs + } + UnaryOp(_, _, box x) => x.var_refs(), + IfElse(_, box a, box b, box c) => { + let mut refs = a.var_refs(); + refs.append(&mut b.var_refs()); + refs.append(&mut c.var_refs()); + refs + } + FunctionCall(_, _, args) => args.iter().flat_map(|a| a.var_refs()).collect_vec(), + } + } + + pub fn eval(self, values: &HashMap) -> Result { + match self { + SimplExpr::Literal(_, x) => Ok(x), + SimplExpr::VarRef(span, ref name) => { + values.get(name).cloned().ok_or_else(|| EvalError::UnresolvedVariable(name.to_string()).at(span)) + } + SimplExpr::BinOp(_, a, op, b) => { + let a = a.eval(values)?; + let b = b.eval(values)?; + Ok(match op { + BinOp::Equals => DynVal::from(a == b), + BinOp::NotEquals => DynVal::from(a != b), + BinOp::And => DynVal::from(a.as_bool()? && b.as_bool()?), + BinOp::Or => DynVal::from(a.as_bool()? || b.as_bool()?), + + BinOp::Plus => DynVal::from(a.as_f64()? + b.as_f64()?), + BinOp::Minus => DynVal::from(a.as_f64()? - b.as_f64()?), + BinOp::Times => DynVal::from(a.as_f64()? * b.as_f64()?), + BinOp::Div => DynVal::from(a.as_f64()? / b.as_f64()?), + BinOp::Mod => DynVal::from(a.as_f64()? % b.as_f64()?), + BinOp::GT => DynVal::from(a.as_f64()? > b.as_f64()?), + BinOp::LT => DynVal::from(a.as_f64()? < b.as_f64()?), + BinOp::Elvis => DynVal::from(if a.0.is_empty() { b } else { a }), + BinOp::RegexMatch => { + let regex = regex::Regex::new(&b.as_string()?)?; + DynVal::from(regex.is_match(&a.as_string()?)) + } + }) + } + SimplExpr::UnaryOp(_, op, a) => { + let a = a.eval(values)?; + Ok(match op { + UnaryOp::Not => DynVal::from(!a.as_bool()?), + }) + } + SimplExpr::IfElse(_, cond, yes, no) => { + if cond.eval(values)?.as_bool()? { + yes.eval(values) + } else { + no.eval(values) + } + } + SimplExpr::JsonAccess(span, val, index) => { + let val = val.eval(values)?; + let index = index.eval(values)?; + match val.as_json_value()? { + serde_json::Value::Array(val) => { + let index = index.as_i32()?; + let indexed_value = val.get(index as usize).unwrap_or(&serde_json::Value::Null); + Ok(DynVal::from(indexed_value)) + } + serde_json::Value::Object(val) => { + let indexed_value = val + .get(&index.as_string()?) + .or_else(|| val.get(&index.as_i32().ok()?.to_string())) + .unwrap_or(&serde_json::Value::Null); + Ok(DynVal::from(indexed_value)) + } + _ => Err(EvalError::CannotIndex(format!("{}", val)).at(span)), + } + } + SimplExpr::FunctionCall(span, function_name, args) => { + let args = args.into_iter().map(|a| a.eval(values)).collect::>()?; + call_expr_function(&function_name, args).map_err(|e| e.at(span)) + } + } + } +} + +fn call_expr_function(name: &str, args: Vec) -> Result { + match name { + "round" => match args.as_slice() { + [num, digits] => { + let num = num.as_f64()?; + let digits = digits.as_i32()?; + Ok(DynVal::from(format!("{:.1$}", num, digits as usize))) + } + _ => Err(EvalError::WrongArgCount(name.to_string())), + }, + "replace" => match args.as_slice() { + [string, pattern, replacement] => { + let string = string.as_string()?; + let pattern = regex::Regex::new(&pattern.as_string()?)?; + let replacement = replacement.as_string()?; + Ok(DynVal::from(pattern.replace_all(&string, replacement.replace("$", "$$").replace("\\", "$")).into_owned())) + } + _ => Err(EvalError::WrongArgCount(name.to_string())), + }, + _ => Err(EvalError::UnknownFunction(name.to_string())), + } +} diff --git a/src/lib.rs b/src/lib.rs index 84702b1..617a06b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,50 +1,11 @@ +#![feature(box_patterns)] +#![feature(box_syntax)] +#![feature(try_blocks)] pub mod ast; pub mod dynval; pub mod error; -mod lalrpop_helpers; -mod lexer; -use ast::SimplExpr; -use error::{Error, Result}; +pub mod eval; +pub mod parser; use lalrpop_util::lalrpop_mod; -lalrpop_mod!(pub parser); - -pub fn parse_string(s: &str) -> Result { - let lexer = lexer::Lexer::new(s); - let parser = parser::ExprParser::new(); - Ok(parser.parse(lexer).map_err(|e| Error::from_parse_error(e))?) -} - -#[cfg(test)] -mod tests { - macro_rules! test_parser { - ($($text:literal),* $(,)?) => {{ - let p = crate::parser::ExprParser::new(); - use crate::lexer::Lexer; - ::insta::with_settings!({sort_maps => true}, { - $( - ::insta::assert_debug_snapshot!(p.parse(Lexer::new($text))); - )* - }); - }} - } - - #[test] - fn test() { - test_parser!( - "1", - "2 + 5", - "2 * 5 + 1 * 1 + 3", - "(1 + 2) * 2", - "1 + true ? 2 : 5", - "1 + true ? 2 : 5 + 2", - "1 + (true ? 2 : 5) + 2", - "foo(1, 2)", - "! false || ! true", - "\"foo\" + 12.4", - "hi[\"ho\"]", - "foo.bar.baz", - "foo.bar[2 + 2] * asdf[foo.bar]", - ); - } -} +lalrpop_mod!(pub simplexpr_parser); diff --git a/src/lalrpop_helpers.rs b/src/parser/lalrpop_helpers.rs similarity index 100% rename from src/lalrpop_helpers.rs rename to src/parser/lalrpop_helpers.rs diff --git a/src/lexer.rs b/src/parser/lexer.rs similarity index 100% rename from src/lexer.rs rename to src/parser/lexer.rs diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..ab47e58 --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,47 @@ +pub mod lalrpop_helpers; +pub mod lexer; + +use crate::{ + ast::SimplExpr, + error::{Error, Result}, +}; + +pub fn parse_string(s: &str) -> Result { + let lexer = lexer::Lexer::new(s); + let parser = crate::simplexpr_parser::ExprParser::new(); + Ok(parser.parse(lexer).map_err(|e| Error::from_parse_error(e))?) +} + +#[cfg(test)] +mod tests { + macro_rules! test_parser { + ($($text:literal),* $(,)?) => {{ + let p = crate::simplexpr_parser::ExprParser::new(); + use crate::parser::lexer::Lexer; + ::insta::with_settings!({sort_maps => true}, { + $( + ::insta::assert_debug_snapshot!(p.parse(Lexer::new($text))); + )* + }); + }} + } + + #[test] + fn test() { + test_parser!( + "1", + "2 + 5", + "2 * 5 + 1 * 1 + 3", + "(1 + 2) * 2", + "1 + true ? 2 : 5", + "1 + true ? 2 : 5 + 2", + "1 + (true ? 2 : 5) + 2", + "foo(1, 2)", + "! false || ! true", + "\"foo\" + 12.4", + "hi[\"ho\"]", + "foo.bar.baz", + "foo.bar[2 + 2] * asdf[foo.bar]", + ); + } +} diff --git a/src/parser.lalrpop b/src/parser/simplexpr_parser.lalrpop similarity index 90% rename from src/parser.lalrpop rename to src/parser/simplexpr_parser.lalrpop index f44a845..46dc13a 100644 --- a/src/parser.lalrpop +++ b/src/parser/simplexpr_parser.lalrpop @@ -1,7 +1,7 @@ use crate::ast::{SimplExpr::{self, *}, Span, BinOp::*, UnaryOp::*}; -use crate::lexer::{Token, LexicalError}; -use crate::lalrpop_helpers::*; +use crate::parser::lexer::{Token, LexicalError}; +use crate::parser::lalrpop_helpers::*; grammar; @@ -67,7 +67,7 @@ pub Expr: SimplExpr = { "[" "]" => JsonAccess(Span(l, r), b(value), b(index)), "." => { - JsonAccess(Span(l, r), b(value), b(Literal(Span(lit_l, r), index))) + JsonAccess(Span(l, r), b(value), b(Literal(Span(lit_l, r), index.into()))) }, #[precedence(level="2")] #[assoc(side="right")] @@ -103,10 +103,10 @@ pub Expr: SimplExpr = { ExprReset = ; Literal: SimplExpr = { - => Literal(Span(l, r), x), - => Literal(Span(l, r), x.to_string()), - "true" => Literal(Span(l, r), "true".to_string()), - "false" => Literal(Span(l, r), "false".to_string()), + => Literal(Span(l, r), x.into()), + => Literal(Span(l, r), x.into()), + "true" => Literal(Span(l, r), "true".into()), + "false" => Literal(Span(l, r), "false".into()), } StrLit: String = { diff --git a/src/simplexpr_parser.lalrpop b/src/simplexpr_parser.lalrpop new file mode 100644 index 0000000..46dc13a --- /dev/null +++ b/src/simplexpr_parser.lalrpop @@ -0,0 +1,114 @@ + +use crate::ast::{SimplExpr::{self, *}, Span, BinOp::*, UnaryOp::*}; +use crate::parser::lexer::{Token, LexicalError}; +use crate::parser::lalrpop_helpers::*; + + +grammar; + +extern { + type Location = usize; + type Error = LexicalError; + + enum Token { + "+" => Token::Plus, + "-" => Token::Minus, + "*" => Token::Times, + "/" => Token::Div, + "%" => Token::Mod, + "==" => Token::Equals, + "!=" => Token::NotEquals, + "&&" => Token::And, + "||" => Token::Or, + ">" => Token::GT, + "<" => Token::LT, + "?:" => Token::Elvis, + "=~" => Token::RegexMatch, + + "!" => Token::Not, + + "," => Token::Comma, + "?" => Token::Question, + ":" => Token::Colon, + "(" => Token::LPren, + ")" => Token::RPren, + "[" => Token::LBrack, + "]" => Token::RBrack, + "." => Token::Dot, + + "true" => Token::True, + "false" => Token::False, + + "identifier" => Token::Ident(), + "number" => Token::NumLit(), + "string" => Token::StrLit(), + + } +} + +Comma: Vec = { + ",")*> => match e { + None => v, + Some(e) => { + v.push(e); + v + } + } +}; + +pub Expr: SimplExpr = { + #[precedence(level="0")] + , + => VarRef(Span(l, r), ident.to_string()), + "(" ")", + + #[precedence(level="1")] #[assoc(side="right")] + "(" > ")" => FunctionCall(Span(l, r), ident, args), + "[" "]" => JsonAccess(Span(l, r), b(value), b(index)), + + "." => { + JsonAccess(Span(l, r), b(value), b(Literal(Span(lit_l, r), index.into()))) + }, + + #[precedence(level="2")] #[assoc(side="right")] + "!" => UnaryOp(Span(l, r), Not, b(e)), + + #[precedence(level="3")] #[assoc(side="left")] + "*" => BinOp(Span(l, r), b(le), Times, b(re)), + "/" => BinOp(Span(l, r), b(le), Div, b(re)), + "%" => BinOp(Span(l, r), b(le), Mod, b(re)), + + #[precedence(level="4")] #[assoc(side="left")] + "+" => BinOp(Span(l, r), b(le), Plus, b(re)), + "-" => BinOp(Span(l, r), b(le), Minus, b(re)), + + #[precedence(level="5")] #[assoc(side="left")] + "==" => BinOp(Span(l, r), b(le), Equals, b(re)), + "!=" => BinOp(Span(l, r), b(le), NotEquals, b(re)), + "<" => BinOp(Span(l, r), b(le), GT, b(re)), + ">" => BinOp(Span(l, r), b(le), LT, b(re)), + "=~" => BinOp(Span(l, r), b(le), RegexMatch, b(re)), + + #[precedence(level="6")] #[assoc(side="left")] + "&&" => BinOp(Span(l, r), b(le), And, b(re)), + "||" => BinOp(Span(l, r), b(le), Or, b(re)), + "?:" => BinOp(Span(l, r), b(le), Elvis, b(re)), + + #[precedence(level="7")] #[assoc(side="right")] + "?" ":" => { + IfElse(Span(l, r), b(cond), b(then), b(els)) + }, +}; + +ExprReset = ; + +Literal: SimplExpr = { + => Literal(Span(l, r), x.into()), + => Literal(Span(l, r), x.into()), + "true" => Literal(Span(l, r), "true".into()), + "false" => Literal(Span(l, r), "false".into()), +} + +StrLit: String = { + => x[1..x.len() - 1].to_owned(), +}; From 3b6180ad7d9541a2e15fb25040e9aac85df77774 Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Fri, 16 Jul 2021 18:57:22 +0200 Subject: [PATCH 05/15] Better error handling --- examples/errors.rs | 12 ++- src/ast.rs | 23 ++++++ src/dynval.rs | 20 ++++- src/error.rs | 10 +-- src/eval.rs | 13 ++-- src/parser/simplexpr_parser.lalrpop | 114 ---------------------------- src/simplexpr_parser.lalrpop | 9 +-- 7 files changed, 66 insertions(+), 135 deletions(-) delete mode 100644 src/parser/simplexpr_parser.lalrpop diff --git a/examples/errors.rs b/examples/errors.rs index 9bd7053..0ecc8e3 100644 --- a/examples/errors.rs +++ b/examples/errors.rs @@ -1,11 +1,19 @@ +use std::collections::HashMap; + +use simplexpr::dynval::DynVal; + fn main() { let mut files = codespan_reporting::files::SimpleFiles::new(); - let input = "12 + \"hi\" * foo ) ? bar == baz : false"; + let input = "12 + foo * 2 < 2 ? bar == true : false"; let _ = files.add("foo.eww", input); let ast = simplexpr::parser::parse_string(input); - match ast { + + let mut vars = HashMap::new(); + vars.insert("foo".to_string(), "2".into()); + + match ast.and_then(|x| x.eval(&vars).map_err(|e| e.into())) { Ok(ast) => { println!("{:?}", ast); } diff --git a/src/ast.rs b/src/ast.rs index 96ab4bb..ab81e03 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -5,6 +5,20 @@ use serde::{Deserialize, Serialize}; #[derive(Eq, PartialEq, Clone, Copy, Serialize, Deserialize)] pub struct Span(pub usize, pub usize); +pub trait MaybeSpanned { + fn try_span(&self) -> Option; +} + +impl MaybeSpanned for Span { + fn try_span(&self) -> Option { + Some(*self) + } +} + +pub fn span_between(a: impl MaybeSpanned, b: impl MaybeSpanned) -> Option { + Some(Span(a.try_span()?.0, b.try_span()?.1)) +} + impl std::fmt::Display for Span { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}..{}", self.0, self.1) @@ -51,6 +65,11 @@ pub enum SimplExpr { JsonAccess(Span, Box, Box), FunctionCall(Span, String, Vec), } +impl MaybeSpanned for SimplExpr { + fn try_span(&self) -> Option { + Some(self.span()) + } +} impl std::fmt::Display for SimplExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -68,6 +87,10 @@ impl std::fmt::Display for SimplExpr { } } impl SimplExpr { + pub fn literal(span: Span, s: String) -> Self { + Self::Literal(span, DynVal(s, Some(span))) + } + pub fn span(&self) -> Span { match self { SimplExpr::Literal(span, _) => *span, diff --git a/src/dynval.rs b/src/dynval.rs index f9352ec..5a38635 100644 --- a/src/dynval.rs +++ b/src/dynval.rs @@ -1,4 +1,4 @@ -use crate::ast::Span; +use crate::ast::{MaybeSpanned, Span}; use itertools::Itertools; use serde::{Deserialize, Serialize}; use std::{convert::TryFrom, fmt, iter::FromIterator}; @@ -6,7 +6,7 @@ use std::{convert::TryFrom, fmt, iter::FromIterator}; pub type Result = std::result::Result; #[derive(Debug, thiserror::Error)] -#[error("Type error: Failed to turn {value} into a {target_type}")] +#[error("Failed to turn {value} into a {target_type}")] pub struct ConversionError { value: DynVal, target_type: &'static str, @@ -17,11 +17,21 @@ impl ConversionError { fn new(value: DynVal, target_type: &'static str, source: Box) -> Self { ConversionError { value, target_type, source: Some(source) } } + + pub fn span(&self) -> Option { + self.value.1 + } } #[derive(Clone, Deserialize, Serialize, Default)] pub struct DynVal(pub String, pub Option); +impl MaybeSpanned for DynVal { + fn try_span(&self) -> Option { + self.1 + } +} + impl From for DynVal { fn from(s: String) -> Self { DynVal(s, None) @@ -30,7 +40,7 @@ impl From for DynVal { impl fmt::Display for DynVal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) + write!(f, "\"{}\"", self.0) } } impl fmt::Debug for DynVal { @@ -106,6 +116,10 @@ impl From<&serde_json::Value> for DynVal { } impl DynVal { + pub fn at(self, span: Span) -> Self { + DynVal(self.0, Some(span)) + } + pub fn from_string(s: String) -> Self { DynVal(s, None) } diff --git a/src/error.rs b/src/error.rs index b794344..b6a50c1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,16 +6,13 @@ pub type Result = std::result::Result; pub enum Error { #[error("Parse error: {source}")] ParseError { source: lalrpop_util::ParseError }, - #[error("Conversion error: {source}")] - ConversionError { - #[from] - source: dynval::ConversionError, - }, + #[error("Conversion error: {0}")] + ConversionError(#[from] dynval::ConversionError), #[error("At: {0}: {1}")] Spanned(Span, Box), #[error(transparent)] - Eval(crate::eval::EvalError), + Eval(#[from] crate::eval::EvalError), #[error(transparent)] Other(#[from] Box), @@ -31,6 +28,7 @@ impl Error { Self::ParseError { source } => get_parse_error_span(source), Self::Spanned(span, _) => Some(*span), Self::Eval(err) => err.span(), + Self::ConversionError(err) => err.span(), _ => None, } } diff --git a/src/eval.rs b/src/eval.rs index 66034c2..689818c 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -11,10 +11,10 @@ pub enum EvalError { #[error("Invalid regex: {0}")] InvalidRegex(#[from] regex::Error), - #[error("got unresolved variable {0}")] + #[error("got unresolved variable `{0}`")] UnresolvedVariable(VarName), - #[error("Conversion error: {0}")] + #[error("Type error: {0}")] ConversionError(#[from] ConversionError), #[error("Incorrect number of arguments given to function: {0}")] @@ -34,6 +34,7 @@ impl EvalError { pub fn span(&self) -> Option { match self { EvalError::Spanned(span, _) => Some(*span), + EvalError::ConversionError(err) => err.span(), _ => None, } } @@ -103,10 +104,11 @@ impl SimplExpr { } pub fn eval(self, values: &HashMap) -> Result { - match self { + let span = self.span(); + let value = match self { SimplExpr::Literal(_, x) => Ok(x), SimplExpr::VarRef(span, ref name) => { - values.get(name).cloned().ok_or_else(|| EvalError::UnresolvedVariable(name.to_string()).at(span)) + Ok(values.get(name).cloned().ok_or_else(|| EvalError::UnresolvedVariable(name.to_string()).at(span))?.at(span)) } SimplExpr::BinOp(_, a, op, b) => { let a = a.eval(values)?; @@ -167,7 +169,8 @@ impl SimplExpr { let args = args.into_iter().map(|a| a.eval(values)).collect::>()?; call_expr_function(&function_name, args).map_err(|e| e.at(span)) } - } + }; + Ok(value?.at(span)) } } diff --git a/src/parser/simplexpr_parser.lalrpop b/src/parser/simplexpr_parser.lalrpop deleted file mode 100644 index 46dc13a..0000000 --- a/src/parser/simplexpr_parser.lalrpop +++ /dev/null @@ -1,114 +0,0 @@ - -use crate::ast::{SimplExpr::{self, *}, Span, BinOp::*, UnaryOp::*}; -use crate::parser::lexer::{Token, LexicalError}; -use crate::parser::lalrpop_helpers::*; - - -grammar; - -extern { - type Location = usize; - type Error = LexicalError; - - enum Token { - "+" => Token::Plus, - "-" => Token::Minus, - "*" => Token::Times, - "/" => Token::Div, - "%" => Token::Mod, - "==" => Token::Equals, - "!=" => Token::NotEquals, - "&&" => Token::And, - "||" => Token::Or, - ">" => Token::GT, - "<" => Token::LT, - "?:" => Token::Elvis, - "=~" => Token::RegexMatch, - - "!" => Token::Not, - - "," => Token::Comma, - "?" => Token::Question, - ":" => Token::Colon, - "(" => Token::LPren, - ")" => Token::RPren, - "[" => Token::LBrack, - "]" => Token::RBrack, - "." => Token::Dot, - - "true" => Token::True, - "false" => Token::False, - - "identifier" => Token::Ident(), - "number" => Token::NumLit(), - "string" => Token::StrLit(), - - } -} - -Comma: Vec = { - ",")*> => match e { - None => v, - Some(e) => { - v.push(e); - v - } - } -}; - -pub Expr: SimplExpr = { - #[precedence(level="0")] - , - => VarRef(Span(l, r), ident.to_string()), - "(" ")", - - #[precedence(level="1")] #[assoc(side="right")] - "(" > ")" => FunctionCall(Span(l, r), ident, args), - "[" "]" => JsonAccess(Span(l, r), b(value), b(index)), - - "." => { - JsonAccess(Span(l, r), b(value), b(Literal(Span(lit_l, r), index.into()))) - }, - - #[precedence(level="2")] #[assoc(side="right")] - "!" => UnaryOp(Span(l, r), Not, b(e)), - - #[precedence(level="3")] #[assoc(side="left")] - "*" => BinOp(Span(l, r), b(le), Times, b(re)), - "/" => BinOp(Span(l, r), b(le), Div, b(re)), - "%" => BinOp(Span(l, r), b(le), Mod, b(re)), - - #[precedence(level="4")] #[assoc(side="left")] - "+" => BinOp(Span(l, r), b(le), Plus, b(re)), - "-" => BinOp(Span(l, r), b(le), Minus, b(re)), - - #[precedence(level="5")] #[assoc(side="left")] - "==" => BinOp(Span(l, r), b(le), Equals, b(re)), - "!=" => BinOp(Span(l, r), b(le), NotEquals, b(re)), - "<" => BinOp(Span(l, r), b(le), GT, b(re)), - ">" => BinOp(Span(l, r), b(le), LT, b(re)), - "=~" => BinOp(Span(l, r), b(le), RegexMatch, b(re)), - - #[precedence(level="6")] #[assoc(side="left")] - "&&" => BinOp(Span(l, r), b(le), And, b(re)), - "||" => BinOp(Span(l, r), b(le), Or, b(re)), - "?:" => BinOp(Span(l, r), b(le), Elvis, b(re)), - - #[precedence(level="7")] #[assoc(side="right")] - "?" ":" => { - IfElse(Span(l, r), b(cond), b(then), b(els)) - }, -}; - -ExprReset = ; - -Literal: SimplExpr = { - => Literal(Span(l, r), x.into()), - => Literal(Span(l, r), x.into()), - "true" => Literal(Span(l, r), "true".into()), - "false" => Literal(Span(l, r), "false".into()), -} - -StrLit: String = { - => x[1..x.len() - 1].to_owned(), -}; diff --git a/src/simplexpr_parser.lalrpop b/src/simplexpr_parser.lalrpop index 46dc13a..ea1ba6f 100644 --- a/src/simplexpr_parser.lalrpop +++ b/src/simplexpr_parser.lalrpop @@ -1,4 +1,3 @@ - use crate::ast::{SimplExpr::{self, *}, Span, BinOp::*, UnaryOp::*}; use crate::parser::lexer::{Token, LexicalError}; use crate::parser::lalrpop_helpers::*; @@ -103,10 +102,10 @@ pub Expr: SimplExpr = { ExprReset = ; Literal: SimplExpr = { - => Literal(Span(l, r), x.into()), - => Literal(Span(l, r), x.into()), - "true" => Literal(Span(l, r), "true".into()), - "false" => Literal(Span(l, r), "false".into()), + => SimplExpr::literal(Span(l, r), x), + => SimplExpr::literal(Span(l, r), x), + "true" => SimplExpr::literal(Span(l, r), "true".into()), + "false" => SimplExpr::literal(Span(l, r), "false".into()), } StrLit: String = { From 752a842cbfad99f82a0c37355c49593ddb771a76 Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Fri, 16 Jul 2021 19:19:48 +0200 Subject: [PATCH 06/15] add resolve --- src/eval.rs | 52 +++++++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index 689818c..4ebd4cf 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -23,6 +23,9 @@ pub enum EvalError { #[error("Unknown function {0}")] UnknownFunction(String), + #[error("Unknown variable {0}")] + UnknownVariable(String), + #[error("Unable to index into value {0}")] CannotIndex(String), @@ -53,34 +56,37 @@ impl SimplExpr { BinOp(span, box a, op, box b) => BinOp(span, box f(a), op, box f(b)), UnaryOp(span, op, box a) => UnaryOp(span, op, box f(a)), IfElse(span, box a, box b, box c) => IfElse(span, box f(a), box f(b), box f(c)), + JsonAccess(span, box a, box b) => JsonAccess(span, box f(a), box f(b)), + FunctionCall(span, name, args) => FunctionCall(span, name, args.into_iter().map(f).collect()), other => f(other), } } /// resolve variable references in the expression. Fails if a variable cannot be resolved. - // pub fn resolve_refs(self, variables: &HashMap) -> Result { - // use SimplExpr::*; - // match self { - //// Literal(x) => Ok(Literal(AttrValue::from_primitive(x.resolve_fully(&variables)?))), - // Literal(x) => Ok(Literal(x)), - // VarRef(ref name) => Ok(Literal(AttrVal::from_primitive( - // variables.get(name).with_context(|| format!("Unknown variable {} referenced in {:?}", &name, &self))?.clone(), - //))), - // BinOp(box a, op, box b) => { - // Ok(BinOp(box a.resolve_refs(variables?), op, box b.resolve_refs(variables?))) - //} - // UnaryOp(op, box x) => Ok(UnaryOp(op, box x.resolve_refs(variables?))), - // IfElse(box a, box b, box c) => Ok(IfElse( - // box a.resolve_refs(variables?), - // box b.resolve_refs(variables?), - // box c.resolve_refs(variables?), - //)), - // JsonAccess(box a, box b) => { - // Ok(JsonAccess(box a.resolve_refs(variables?), box b.resolve_refs(variables?))) - //} - // FunctionCall(function_name, args) => { - // Ok(FunctionCall(function_name, args.into_iter().map(|a| a.resolve_refs(variables)).collect::>()?)) - //} + pub fn resolve_refs(self, variables: &HashMap) -> Result { + use SimplExpr::*; + match self { + // Literal(x) => Ok(Literal(AttrValue::from_primitive(x.resolve_fully(&variables)?))), + Literal(span, x) => Ok(Literal(span, x)), + BinOp(span, box a, op, box b) => Ok(BinOp(span, box a.resolve_refs(variables)?, op, box b.resolve_refs(variables)?)), + UnaryOp(span, op, box x) => Ok(UnaryOp(span, op, box x.resolve_refs(variables)?)), + IfElse(span, box a, box b, box c) => { + Ok(IfElse(span, box a.resolve_refs(variables)?, box b.resolve_refs(variables)?, box c.resolve_refs(variables)?)) + } + JsonAccess(span, box a, box b) => { + Ok(JsonAccess(span, box a.resolve_refs(variables)?, box b.resolve_refs(variables)?)) + } + FunctionCall(span, function_name, args) => Ok(FunctionCall( + span, + function_name, + args.into_iter().map(|a| a.resolve_refs(variables)).collect::>()?, + )), + VarRef(span, ref name) => match variables.get(name) { + Some(value) => Ok(Literal(span, value.clone())), + None => Err(EvalError::UnknownVariable(name.to_string()).at(span)), + }, + } + } pub fn var_refs(&self) -> Vec<&String> { use SimplExpr::*; From 228d10aeb31690e755fc3ed1b7a1eabc9c604b5b Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Fri, 16 Jul 2021 19:46:23 +0200 Subject: [PATCH 07/15] small error handling improvement, allow adding strings --- examples/errors.rs | 2 -- src/error.rs | 6 ++++-- src/eval.rs | 13 ++++++++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/examples/errors.rs b/examples/errors.rs index 0ecc8e3..5b36355 100644 --- a/examples/errors.rs +++ b/examples/errors.rs @@ -1,7 +1,5 @@ use std::collections::HashMap; -use simplexpr::dynval::DynVal; - fn main() { let mut files = codespan_reporting::files::SimpleFiles::new(); diff --git a/src/error.rs b/src/error.rs index b6a50c1..6e91360 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,9 +6,11 @@ pub type Result = std::result::Result; pub enum Error { #[error("Parse error: {source}")] ParseError { source: lalrpop_util::ParseError }, - #[error("Conversion error: {0}")] + + #[error("Type error: {0}")] ConversionError(#[from] dynval::ConversionError), - #[error("At: {0}: {1}")] + + #[error("{1}")] Spanned(Span, Box), #[error(transparent)] diff --git a/src/eval.rs b/src/eval.rs index 4ebd4cf..2fcb3ce 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -29,7 +29,7 @@ pub enum EvalError { #[error("Unable to index into value {0}")] CannotIndex(String), - #[error("At {0}: {1}")] + #[error("{1}")] Spanned(Span, Box), } @@ -49,6 +49,11 @@ impl EvalError { type VarName = String; +pub trait FunctionSource { + type Err; + fn run_fn(&self, name: &str, args: &Vec) -> Result; +} + impl SimplExpr { pub fn map_terminals_into(self, f: impl Fn(Self) -> Self) -> Self { use SimplExpr::*; @@ -124,8 +129,10 @@ impl SimplExpr { BinOp::NotEquals => DynVal::from(a != b), BinOp::And => DynVal::from(a.as_bool()? && b.as_bool()?), BinOp::Or => DynVal::from(a.as_bool()? || b.as_bool()?), - - BinOp::Plus => DynVal::from(a.as_f64()? + b.as_f64()?), + BinOp::Plus => match a.as_f64() { + Ok(num) => DynVal::from(num + b.as_f64()?), + Err(_) => DynVal::from(format!("{}{}", a.as_string()?, b.as_string()?)), + }, BinOp::Minus => DynVal::from(a.as_f64()? - b.as_f64()?), BinOp::Times => DynVal::from(a.as_f64()? * b.as_f64()?), BinOp::Div => DynVal::from(a.as_f64()? / b.as_f64()?), From 1118182156fc098b5373a60d3e1af9e84141462b Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Sat, 17 Jul 2021 12:57:12 +0200 Subject: [PATCH 08/15] Small adjustments for integration --- src/ast.rs | 6 +++--- src/dynval.rs | 2 +- src/lib.rs | 6 ++++++ src/parser/lexer.rs | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index ab81e03..8b70dd9 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -32,7 +32,7 @@ impl std::fmt::Debug for Span { } #[rustfmt::skip] -#[derive(Clone, PartialEq, Serialize, Deserialize, Debug, strum::EnumString, strum::Display)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, strum::EnumString, strum::Display)] pub enum BinOp { #[strum(serialize = "+") ] Plus, #[strum(serialize = "-") ] Minus, @@ -49,13 +49,13 @@ pub enum BinOp { #[strum(serialize = "=~")] RegexMatch, } -#[derive(Clone, PartialEq, Serialize, Deserialize, Debug, strum::EnumString, strum::Display)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, strum::EnumString, strum::Display)] pub enum UnaryOp { #[strum(serialize = "!")] Not, } -#[derive(Clone, PartialEq, Serialize, Deserialize)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum SimplExpr { Literal(Span, DynVal), VarRef(Span, String), diff --git a/src/dynval.rs b/src/dynval.rs index 5a38635..62b2de4 100644 --- a/src/dynval.rs +++ b/src/dynval.rs @@ -23,7 +23,7 @@ impl ConversionError { } } -#[derive(Clone, Deserialize, Serialize, Default)] +#[derive(Clone, Deserialize, Serialize, Default, Eq)] pub struct DynVal(pub String, pub Option); impl MaybeSpanned for DynVal { diff --git a/src/lib.rs b/src/lib.rs index 617a06b..991b775 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,12 @@ pub mod dynval; pub mod error; pub mod eval; pub mod parser; +use ast::SimplExpr; use lalrpop_util::lalrpop_mod; lalrpop_mod!(pub simplexpr_parser); + +pub fn parse_string(s: &str) -> Result { + parser::parse_string(s) +} +pub use ast::Span; diff --git a/src/parser/lexer.rs b/src/parser/lexer.rs index ab73f36..7d49959 100644 --- a/src/parser/lexer.rs +++ b/src/parser/lexer.rs @@ -1,7 +1,7 @@ use logos::Logos; #[rustfmt::skip] -#[derive(Logos, Debug, PartialEq, Eq, Clone, strum::Display)] +#[derive(Logos, Debug, PartialEq, Eq, Clone, strum::Display, strum::EnumString)] pub enum Token { #[strum(serialize = "+") ] #[token("+") ] Plus, #[strum(serialize = "-") ] #[token("-") ] Minus, From b12b7e9977e4fba103d23997cc8f5b031507deb1 Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Sat, 17 Jul 2021 13:31:23 +0200 Subject: [PATCH 09/15] add readme --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..4a60e3d --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# simplexpr + +simplexpr is a parser and interpreter for a simple expression syntax that can be embedded into other applications or crates. +It is being developed to be used in [eww](https://github.com/elkowar/eww), but may also other uses. + +For now, this is highly experimental, unstable, and ugly. You most definitely do not want to use this crate. From 3a79d8650a631045e592dd8818560a719d32f904 Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Sat, 17 Jul 2021 17:14:00 +0200 Subject: [PATCH 10/15] Make more fields public --- src/dynval.rs | 6 +++--- src/error.rs | 12 ++++++++++-- src/parser/lexer.rs | 2 +- src/simplexpr_parser.lalrpop | 8 ++++++++ 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/dynval.rs b/src/dynval.rs index 62b2de4..dbdd9d3 100644 --- a/src/dynval.rs +++ b/src/dynval.rs @@ -8,9 +8,9 @@ pub type Result = std::result::Result; #[derive(Debug, thiserror::Error)] #[error("Failed to turn {value} into a {target_type}")] pub struct ConversionError { - value: DynVal, - target_type: &'static str, - source: Option>, + pub value: DynVal, + pub target_type: &'static str, + pub source: Option>, } impl ConversionError { diff --git a/src/error.rs b/src/error.rs index 6e91360..e66b50c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,8 @@ -use crate::{ast::Span, dynval, parser::lexer}; +use crate::{ + ast::Span, + dynval, + parser::lexer::{self, LexicalError}, +}; use codespan_reporting::diagnostic; pub type Result = std::result::Result; @@ -25,6 +29,10 @@ impl Error { Error::ParseError { source: err } } + pub fn at(self, span: Span) -> Self { + Self::Spanned(span, Box::new(self)) + } + pub fn get_span(&self) -> Option { match self { Self::ParseError { source } => get_parse_error_span(source), @@ -68,7 +76,7 @@ fn get_parse_error_span(err: &lalrpop_util::ParseError Some(Span(*location, *location)), lalrpop_util::ParseError::UnrecognizedToken { token, expected: _ } => Some(Span(token.0, token.2)), lalrpop_util::ParseError::ExtraToken { token } => Some(Span(token.0, token.2)), - lalrpop_util::ParseError::User { error: _ } => None, + lalrpop_util::ParseError::User { error: LexicalError(l, r) } => Some(Span(*l, *r)), } } diff --git a/src/parser/lexer.rs b/src/parser/lexer.rs index 7d49959..e3c2447 100644 --- a/src/parser/lexer.rs +++ b/src/parser/lexer.rs @@ -44,7 +44,7 @@ pub enum Token { } #[derive(Debug, Eq, PartialEq, Copy, Clone)] -pub struct LexicalError(usize, usize); +pub struct LexicalError(pub usize, pub usize); impl std::fmt::Display for LexicalError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/src/simplexpr_parser.lalrpop b/src/simplexpr_parser.lalrpop index ea1ba6f..2f1c8dd 100644 --- a/src/simplexpr_parser.lalrpop +++ b/src/simplexpr_parser.lalrpop @@ -1,6 +1,7 @@ use crate::ast::{SimplExpr::{self, *}, Span, BinOp::*, UnaryOp::*}; use crate::parser::lexer::{Token, LexicalError}; use crate::parser::lalrpop_helpers::*; +use lalrpop_util::ParseError; grammar; @@ -42,6 +43,8 @@ extern { "number" => Token::NumLit(), "string" => Token::StrLit(), + "lexer_error" => Token::Error, + } } @@ -56,7 +59,12 @@ Comma: Vec = { }; pub Expr: SimplExpr = { + #[precedence(level="0")] + "lexer_error" =>? { + Err(ParseError::User { error: LexicalError(l, r) }) + }, + , => VarRef(Span(l, r), ident.to_string()), "(" ")", From a6fe813ed115d3b3432a85e1c831644968c9c614 Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Sat, 17 Jul 2021 18:07:25 +0200 Subject: [PATCH 11/15] fix clippy lints --- Cargo.toml | 1 - examples/errors.rs | 25 ------------------------ src/ast.rs | 19 ------------------ src/dynval.rs | 48 +++++++++++++++++++--------------------------- src/error.rs | 27 -------------------------- src/eval.rs | 3 ++- src/lib.rs | 5 ++++- src/parser/mod.rs | 2 +- 8 files changed, 27 insertions(+), 103 deletions(-) delete mode 100644 examples/errors.rs diff --git a/Cargo.toml b/Cargo.toml index 2d6c0c2..df354ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ regex = "1" itertools = "0.10" thiserror = "1.0" maplit = "1.0" -codespan-reporting = "0.11" logos = "0.12" serde = {version = "1.0", features = ["derive"]} diff --git a/examples/errors.rs b/examples/errors.rs deleted file mode 100644 index 5b36355..0000000 --- a/examples/errors.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::collections::HashMap; - -fn main() { - let mut files = codespan_reporting::files::SimpleFiles::new(); - - let input = "12 + foo * 2 < 2 ? bar == true : false"; - - let _ = files.add("foo.eww", input); - let ast = simplexpr::parser::parse_string(input); - - let mut vars = HashMap::new(); - vars.insert("foo".to_string(), "2".into()); - - match ast.and_then(|x| x.eval(&vars).map_err(|e| e.into())) { - Ok(ast) => { - println!("{:?}", ast); - } - Err(err) => { - let diag = err.pretty_diagnostic(); - use codespan_reporting::term; - let mut writer = term::termcolor::StandardStream::stderr(term::termcolor::ColorChoice::Always); - term::emit(&mut writer, &term::Config::default(), &files, &diag).unwrap(); - } - } -} diff --git a/src/ast.rs b/src/ast.rs index 8b70dd9..8b7562c 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -5,20 +5,6 @@ use serde::{Deserialize, Serialize}; #[derive(Eq, PartialEq, Clone, Copy, Serialize, Deserialize)] pub struct Span(pub usize, pub usize); -pub trait MaybeSpanned { - fn try_span(&self) -> Option; -} - -impl MaybeSpanned for Span { - fn try_span(&self) -> Option { - Some(*self) - } -} - -pub fn span_between(a: impl MaybeSpanned, b: impl MaybeSpanned) -> Option { - Some(Span(a.try_span()?.0, b.try_span()?.1)) -} - impl std::fmt::Display for Span { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}..{}", self.0, self.1) @@ -65,11 +51,6 @@ pub enum SimplExpr { JsonAccess(Span, Box, Box), FunctionCall(Span, String, Vec), } -impl MaybeSpanned for SimplExpr { - fn try_span(&self) -> Option { - Some(self.span()) - } -} impl std::fmt::Display for SimplExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/src/dynval.rs b/src/dynval.rs index dbdd9d3..b3b5b2b 100644 --- a/src/dynval.rs +++ b/src/dynval.rs @@ -1,4 +1,4 @@ -use crate::ast::{MaybeSpanned, Span}; +use crate::ast::Span; use itertools::Itertools; use serde::{Deserialize, Serialize}; use std::{convert::TryFrom, fmt, iter::FromIterator}; @@ -26,12 +26,6 @@ impl ConversionError { #[derive(Clone, Deserialize, Serialize, Default, Eq)] pub struct DynVal(pub String, pub Option); -impl MaybeSpanned for DynVal { - fn try_span(&self) -> Option { - self.1 - } -} - impl From for DynVal { fn from(s: String) -> Self { DynVal(s, None) @@ -98,7 +92,7 @@ impl_try_from!(impl From { for f64 => |x| x.as_f64(); for i32 => |x| x.as_i32(); for bool => |x| x.as_bool(); - for Vec => |x| x.as_vec(); + //for Vec => |x| x.as_vec(); }); impl_primval_from!(bool, i32, u32, f32, u8, f64, &str); @@ -145,26 +139,24 @@ impl DynVal { self.0.parse().map_err(|e| ConversionError::new(self.clone(), "bool", Box::new(e))) } - pub fn as_vec(&self) -> Result> { - match self.0.strip_prefix('[').and_then(|x| x.strip_suffix(']')) { - Some(content) => { - let mut items: Vec = content.split(',').map(|x: &str| x.to_string()).collect(); - let mut removed = 0; - for times_ran in 0..items.len() { - // escapes `,` if there's a `\` before em - if items[times_ran - removed].ends_with('\\') { - items[times_ran - removed].pop(); - let it = items.remove((times_ran + 1) - removed); - items[times_ran - removed] += ","; - items[times_ran - removed] += ⁢ - removed += 1; - } - } - Ok(items) - } - None => Err(ConversionError { value: self.clone(), target_type: "vec", source: None }), - } - } + // pub fn as_vec(&self) -> Result> { + // match self.0.strip_prefix('[').and_then(|x| x.strip_suffix(']')) { + // Some(content) => { + // let mut items: Vec = content.split(',').map(|x: &str| x.to_string()).collect(); + // let mut removed = 0; + // for times_ran in 0..items.len() { + //// escapes `,` if there's a `\` before em + // if items[times_ran - removed].ends_with('\\') { + // items[times_ran - removed].pop(); + // let it = items.remove((times_ran + 1) - removed); + // items[times_ran - removed] += ","; + // items[times_ran - removed] += ⁢ + // removed += 1; + //} + // Ok(items) + //} + // None => Err(ConversionError { value: self.clone(), target_type: "vec", source: None }), + //} pub fn as_json_value(&self) -> Result { serde_json::from_str::(&self.0) diff --git a/src/error.rs b/src/error.rs index e66b50c..22b6041 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,7 +3,6 @@ use crate::{ dynval, parser::lexer::{self, LexicalError}, }; -use codespan_reporting::diagnostic; pub type Result = std::result::Result; #[derive(thiserror::Error, Debug)] @@ -42,32 +41,6 @@ impl Error { _ => None, } } - - pub fn pretty_diagnostic(&self) -> diagnostic::Diagnostic { - let diag = diagnostic::Diagnostic::error().with_message(format!("{}", self)); - if let Some(span) = self.get_span() { - diag.with_labels(vec![diagnostic::Label::primary(0, span.0..span.1)]) - } else { - diag - } - } -} - -pub trait ErrorExt { - fn at(self, span: Span) -> Error; -} -impl ErrorExt for Box { - fn at(self, span: Span) -> Error { - Error::Spanned(span, self) - } -} -pub trait ResultExt { - fn at(self, span: Span) -> std::result::Result; -} -impl ResultExt for std::result::Result { - fn at(self, span: Span) -> std::result::Result { - self.map_err(|x| Error::Spanned(span, Box::new(x))) - } } fn get_parse_error_span(err: &lalrpop_util::ParseError) -> Option { diff --git a/src/eval.rs b/src/eval.rs index 2fcb3ce..01d58bd 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -51,7 +51,7 @@ type VarName = String; pub trait FunctionSource { type Err; - fn run_fn(&self, name: &str, args: &Vec) -> Result; + fn run_fn(&self, name: &str, args: &[DynVal]) -> Result; } impl SimplExpr { @@ -139,6 +139,7 @@ impl SimplExpr { BinOp::Mod => DynVal::from(a.as_f64()? % b.as_f64()?), BinOp::GT => DynVal::from(a.as_f64()? > b.as_f64()?), BinOp::LT => DynVal::from(a.as_f64()? < b.as_f64()?), + #[allow(clippy::useless_conversion)] BinOp::Elvis => DynVal::from(if a.0.is_empty() { b } else { a }), BinOp::RegexMatch => { let regex = regex::Regex::new(&b.as_string()?)?; diff --git a/src/lib.rs b/src/lib.rs index 991b775..2398211 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,10 @@ pub mod parser; use ast::SimplExpr; use lalrpop_util::lalrpop_mod; -lalrpop_mod!(pub simplexpr_parser); +lalrpop_mod!( + #[allow(clippy::all)] + pub simplexpr_parser +); pub fn parse_string(s: &str) -> Result { parser::parse_string(s) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ab47e58..a867374 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -9,7 +9,7 @@ use crate::{ pub fn parse_string(s: &str) -> Result { let lexer = lexer::Lexer::new(s); let parser = crate::simplexpr_parser::ExprParser::new(); - Ok(parser.parse(lexer).map_err(|e| Error::from_parse_error(e))?) + parser.parse(lexer).map_err(Error::from_parse_error) } #[cfg(test)] From 7539dda162bba856b8b00e724f9b1f734e81a0ca Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Sun, 18 Jul 2021 20:52:38 +0200 Subject: [PATCH 12/15] add fileid to span --- src/ast.rs | 3 ++- src/error.rs | 23 +++++++++-------- src/lib.rs | 9 ++++--- src/parser/mod.rs | 6 ++--- src/simplexpr_parser.lalrpop | 48 ++++++++++++++++++------------------ 5 files changed, 47 insertions(+), 42 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 8b7562c..519b0d3 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -2,8 +2,9 @@ use crate::dynval::DynVal; use itertools::Itertools; use serde::{Deserialize, Serialize}; +/// stores the left and right end of a span, and a given file identifier. #[derive(Eq, PartialEq, Clone, Copy, Serialize, Deserialize)] -pub struct Span(pub usize, pub usize); +pub struct Span(pub usize, pub usize, pub usize); impl std::fmt::Display for Span { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/src/error.rs b/src/error.rs index 22b6041..74e193f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,7 +8,7 @@ pub type Result = std::result::Result; #[derive(thiserror::Error, Debug)] pub enum Error { #[error("Parse error: {source}")] - ParseError { source: lalrpop_util::ParseError }, + ParseError { file_id: usize, source: lalrpop_util::ParseError }, #[error("Type error: {0}")] ConversionError(#[from] dynval::ConversionError), @@ -24,8 +24,8 @@ pub enum Error { } impl Error { - pub fn from_parse_error(err: lalrpop_util::ParseError) -> Self { - Error::ParseError { source: err } + pub fn from_parse_error(file_id: usize, err: lalrpop_util::ParseError) -> Self { + Error::ParseError { file_id, source: err } } pub fn at(self, span: Span) -> Self { @@ -34,7 +34,7 @@ impl Error { pub fn get_span(&self) -> Option { match self { - Self::ParseError { source } => get_parse_error_span(source), + Self::ParseError { file_id, source } => get_parse_error_span(*file_id, source), Self::Spanned(span, _) => Some(*span), Self::Eval(err) => err.span(), Self::ConversionError(err) => err.span(), @@ -43,13 +43,16 @@ impl Error { } } -fn get_parse_error_span(err: &lalrpop_util::ParseError) -> Option { +fn get_parse_error_span( + file_id: usize, + err: &lalrpop_util::ParseError, +) -> Option { match err { - lalrpop_util::ParseError::InvalidToken { location } => Some(Span(*location, *location)), - lalrpop_util::ParseError::UnrecognizedEOF { location, expected: _ } => Some(Span(*location, *location)), - lalrpop_util::ParseError::UnrecognizedToken { token, expected: _ } => Some(Span(token.0, token.2)), - lalrpop_util::ParseError::ExtraToken { token } => Some(Span(token.0, token.2)), - lalrpop_util::ParseError::User { error: LexicalError(l, r) } => Some(Span(*l, *r)), + lalrpop_util::ParseError::InvalidToken { location } => Some(Span(*location, *location, file_id)), + lalrpop_util::ParseError::UnrecognizedEOF { location, expected: _ } => Some(Span(*location, *location, file_id)), + lalrpop_util::ParseError::UnrecognizedToken { token, expected: _ } => Some(Span(token.0, token.2, file_id)), + lalrpop_util::ParseError::ExtraToken { token } => Some(Span(token.0, token.2, file_id)), + lalrpop_util::ParseError::User { error: LexicalError(l, r) } => Some(Span(*l, *r, file_id)), } } diff --git a/src/lib.rs b/src/lib.rs index 2398211..74df615 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,9 @@ pub mod dynval; pub mod error; pub mod eval; pub mod parser; -use ast::SimplExpr; + +pub use ast::{SimplExpr, Span}; + use lalrpop_util::lalrpop_mod; lalrpop_mod!( @@ -14,7 +16,6 @@ lalrpop_mod!( pub simplexpr_parser ); -pub fn parse_string(s: &str) -> Result { - parser::parse_string(s) +pub fn parse_string(file_id: usize, s: &str) -> Result { + parser::parse_string(file_id, s) } -pub use ast::Span; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index a867374..f833b61 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6,10 +6,10 @@ use crate::{ error::{Error, Result}, }; -pub fn parse_string(s: &str) -> Result { +pub fn parse_string(file_id: usize, s: &str) -> Result { let lexer = lexer::Lexer::new(s); let parser = crate::simplexpr_parser::ExprParser::new(); - parser.parse(lexer).map_err(Error::from_parse_error) + parser.parse(file_id, lexer).map_err(|e| Error::from_parse_error(file_id, e)) } #[cfg(test)] @@ -20,7 +20,7 @@ mod tests { use crate::parser::lexer::Lexer; ::insta::with_settings!({sort_maps => true}, { $( - ::insta::assert_debug_snapshot!(p.parse(Lexer::new($text))); + ::insta::assert_debug_snapshot!(p.parse(0, Lexer::new($text))); )* }); }} diff --git a/src/simplexpr_parser.lalrpop b/src/simplexpr_parser.lalrpop index 2f1c8dd..f351087 100644 --- a/src/simplexpr_parser.lalrpop +++ b/src/simplexpr_parser.lalrpop @@ -4,7 +4,7 @@ use crate::parser::lalrpop_helpers::*; use lalrpop_util::ParseError; -grammar; +grammar(fid: usize); extern { type Location = usize; @@ -66,54 +66,54 @@ pub Expr: SimplExpr = { }, , - => VarRef(Span(l, r), ident.to_string()), + => VarRef(Span(l, r, fid), ident.to_string()), "(" ")", #[precedence(level="1")] #[assoc(side="right")] - "(" > ")" => FunctionCall(Span(l, r), ident, args), - "[" "]" => JsonAccess(Span(l, r), b(value), b(index)), + "(" > ")" => FunctionCall(Span(l, r, fid), ident, args), + "[" "]" => JsonAccess(Span(l, r, fid), b(value), b(index)), "." => { - JsonAccess(Span(l, r), b(value), b(Literal(Span(lit_l, r), index.into()))) + JsonAccess(Span(l, r, fid), b(value), b(Literal(Span(lit_l, r, fid), index.into()))) }, #[precedence(level="2")] #[assoc(side="right")] - "!" => UnaryOp(Span(l, r), Not, b(e)), + "!" => UnaryOp(Span(l, r, fid), Not, b(e)), #[precedence(level="3")] #[assoc(side="left")] - "*" => BinOp(Span(l, r), b(le), Times, b(re)), - "/" => BinOp(Span(l, r), b(le), Div, b(re)), - "%" => BinOp(Span(l, r), b(le), Mod, b(re)), + "*" => BinOp(Span(l, r, fid), b(le), Times, b(re)), + "/" => BinOp(Span(l, r, fid), b(le), Div, b(re)), + "%" => BinOp(Span(l, r, fid), b(le), Mod, b(re)), #[precedence(level="4")] #[assoc(side="left")] - "+" => BinOp(Span(l, r), b(le), Plus, b(re)), - "-" => BinOp(Span(l, r), b(le), Minus, b(re)), + "+" => BinOp(Span(l, r, fid), b(le), Plus, b(re)), + "-" => BinOp(Span(l, r, fid), b(le), Minus, b(re)), #[precedence(level="5")] #[assoc(side="left")] - "==" => BinOp(Span(l, r), b(le), Equals, b(re)), - "!=" => BinOp(Span(l, r), b(le), NotEquals, b(re)), - "<" => BinOp(Span(l, r), b(le), GT, b(re)), - ">" => BinOp(Span(l, r), b(le), LT, b(re)), - "=~" => BinOp(Span(l, r), b(le), RegexMatch, b(re)), + "==" => BinOp(Span(l, r, fid), b(le), Equals, b(re)), + "!=" => BinOp(Span(l, r, fid), b(le), NotEquals, b(re)), + "<" => BinOp(Span(l, r, fid), b(le), GT, b(re)), + ">" => BinOp(Span(l, r, fid), b(le), LT, b(re)), + "=~" => BinOp(Span(l, r, fid), b(le), RegexMatch, b(re)), #[precedence(level="6")] #[assoc(side="left")] - "&&" => BinOp(Span(l, r), b(le), And, b(re)), - "||" => BinOp(Span(l, r), b(le), Or, b(re)), - "?:" => BinOp(Span(l, r), b(le), Elvis, b(re)), + "&&" => BinOp(Span(l, r, fid), b(le), And, b(re)), + "||" => BinOp(Span(l, r, fid), b(le), Or, b(re)), + "?:" => BinOp(Span(l, r, fid), b(le), Elvis, b(re)), #[precedence(level="7")] #[assoc(side="right")] "?" ":" => { - IfElse(Span(l, r), b(cond), b(then), b(els)) + IfElse(Span(l, r, fid), b(cond), b(then), b(els)) }, }; ExprReset = ; Literal: SimplExpr = { - => SimplExpr::literal(Span(l, r), x), - => SimplExpr::literal(Span(l, r), x), - "true" => SimplExpr::literal(Span(l, r), "true".into()), - "false" => SimplExpr::literal(Span(l, r), "false".into()), + => SimplExpr::literal(Span(l, r, fid), x), + => SimplExpr::literal(Span(l, r, fid), x), + "true" => SimplExpr::literal(Span(l, r, fid), "true".into()), + "false" => SimplExpr::literal(Span(l, r, fid), "false".into()), } StrLit: String = { From 5748185fb7a9b13266a69e8f15420cf839cd70ca Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Tue, 20 Jul 2021 18:51:29 +0200 Subject: [PATCH 13/15] FromDynVal-trait --- src/dynval.rs | 54 ++++++++++++++++++++++++++++++++++----------------- src/eval.rs | 21 +++++++++++++++----- 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/src/dynval.rs b/src/dynval.rs index b3b5b2b..463eae7 100644 --- a/src/dynval.rs +++ b/src/dynval.rs @@ -1,7 +1,7 @@ use crate::ast::Span; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use std::{convert::TryFrom, fmt, iter::FromIterator}; +use std::{fmt, iter::FromIterator, str::FromStr}; pub type Result = std::result::Result; @@ -34,7 +34,7 @@ impl From for DynVal { impl fmt::Display for DynVal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "\"{}\"", self.0) + write!(f, "{}", self.0) } } impl fmt::Debug for DynVal { @@ -70,32 +70,46 @@ impl std::str::FromStr for DynVal { } } -macro_rules! impl_try_from { - (impl From<$typ:ty> { - $(for $for:ty => |$arg:ident| $code:expr);*; - }) => { - $(impl TryFrom<$typ> for $for { - type Error = ConversionError; - fn try_from($arg: $typ) -> std::result::Result { $code } +pub trait FromDynVal: Sized { + type Err; + fn from_dynval(x: &DynVal) -> std::result::Result; +} + +impl> FromDynVal for T { + type Err = E; + + fn from_dynval(x: &DynVal) -> std::result::Result { + x.0.parse() + } +} + +macro_rules! impl_from_dynval { + ( + $(for $for:ty => |$name:ident| $code:expr);*; + ) => { + $(impl FromDynVal for $for { + type Err = ConversionError; + fn from_dynval($name: DynVal) -> std::result::Result { $code } })* }; } -macro_rules! impl_primval_from { +macro_rules! impl_dynval_from { ($($t:ty),*) => { $(impl From<$t> for DynVal { fn from(x: $t) -> Self { DynVal(x.to_string(), None) } })* }; } -impl_try_from!(impl From { - for String => |x| x.as_string(); - for f64 => |x| x.as_f64(); - for i32 => |x| x.as_i32(); - for bool => |x| x.as_bool(); - //for Vec => |x| x.as_vec(); -}); -impl_primval_from!(bool, i32, u32, f32, u8, f64, &str); +// impl_from_dynval! { +// for String => |x| x.as_string(); +// for f64 => |x| x.as_f64(); +// for i32 => |x| x.as_i32(); +// for bool => |x| x.as_bool(); +////for Vec => |x| x.as_vec(); +//} + +impl_dynval_from!(bool, i32, u32, f32, u8, f64, &str); impl From<&serde_json::Value> for DynVal { fn from(v: &serde_json::Value) -> Self { @@ -118,6 +132,10 @@ impl DynVal { DynVal(s, None) } + pub fn read_as>(&self) -> std::result::Result { + T::from_dynval(self) + } + pub fn into_inner(self) -> String { self.0 } diff --git a/src/eval.rs b/src/eval.rs index 01d58bd..3c70583 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -8,6 +8,9 @@ use std::collections::HashMap; #[derive(Debug, thiserror::Error)] pub enum EvalError { + #[error("Tried to reference variable `{0}`, but we cannot access variables here")] + NoVariablesAllowed(String), + #[error("Invalid regex: {0}")] InvalidRegex(#[from] regex::Error), @@ -114,12 +117,20 @@ impl SimplExpr { } } - pub fn eval(self, values: &HashMap) -> Result { + pub fn eval_no_vars(&self) -> Result { + match self.eval(&HashMap::new()) { + Ok(x) => Ok(x), + Err(EvalError::UnknownVariable(name)) => Err(EvalError::NoVariablesAllowed(name)), + Err(x) => Err(x), + } + } + + pub fn eval(&self, values: &HashMap) -> Result { let span = self.span(); let value = match self { - SimplExpr::Literal(_, x) => Ok(x), + SimplExpr::Literal(_, x) => Ok(x.clone()), SimplExpr::VarRef(span, ref name) => { - Ok(values.get(name).cloned().ok_or_else(|| EvalError::UnresolvedVariable(name.to_string()).at(span))?.at(span)) + Ok(values.get(name).cloned().ok_or_else(|| EvalError::UnresolvedVariable(name.to_string()).at(*span))?.at(*span)) } SimplExpr::BinOp(_, a, op, b) => { let a = a.eval(values)?; @@ -176,12 +187,12 @@ impl SimplExpr { .unwrap_or(&serde_json::Value::Null); Ok(DynVal::from(indexed_value)) } - _ => Err(EvalError::CannotIndex(format!("{}", val)).at(span)), + _ => Err(EvalError::CannotIndex(format!("{}", val)).at(*span)), } } SimplExpr::FunctionCall(span, function_name, args) => { let args = args.into_iter().map(|a| a.eval(values)).collect::>()?; - call_expr_function(&function_name, args).map_err(|e| e.at(span)) + call_expr_function(&function_name, args).map_err(|e| e.at(*span)) } }; Ok(value?.at(span)) From 6f51f263cd684cbf76f2e6cf45d89aa47e6cd565 Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Wed, 21 Jul 2021 19:15:23 +0200 Subject: [PATCH 14/15] cleanup dynval --- src/dynval.rs | 58 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/src/dynval.rs b/src/dynval.rs index 463eae7..2ee7c5a 100644 --- a/src/dynval.rs +++ b/src/dynval.rs @@ -6,7 +6,7 @@ use std::{fmt, iter::FromIterator, str::FromStr}; pub type Result = std::result::Result; #[derive(Debug, thiserror::Error)] -#[error("Failed to turn {value} into a {target_type}")] +#[error("Failed to turn {value} into a value of type {target_type}")] pub struct ConversionError { pub value: DynVal, pub target_type: &'static str, @@ -14,8 +14,8 @@ pub struct ConversionError { } impl ConversionError { - fn new(value: DynVal, target_type: &'static str, source: Box) -> Self { - ConversionError { value, target_type, source: Some(source) } + fn new(value: DynVal, target_type: &'static str, source: impl std::error::Error + 'static) -> Self { + ConversionError { value, target_type, source: Some(Box::new(source)) } } pub fn span(&self) -> Option { @@ -83,16 +83,6 @@ impl> FromDynVal for T { } } -macro_rules! impl_from_dynval { - ( - $(for $for:ty => |$name:ident| $code:expr);*; - ) => { - $(impl FromDynVal for $for { - type Err = ConversionError; - fn from_dynval($name: DynVal) -> std::result::Result { $code } - })* - }; -} macro_rules! impl_dynval_from { ($($t:ty),*) => { $(impl From<$t> for DynVal { @@ -101,14 +91,6 @@ macro_rules! impl_dynval_from { }; } -// impl_from_dynval! { -// for String => |x| x.as_string(); -// for f64 => |x| x.as_f64(); -// for i32 => |x| x.as_i32(); -// for bool => |x| x.as_bool(); -////for Vec => |x| x.as_vec(); -//} - impl_dynval_from!(bool, i32, u32, f32, u8, f64, &str); impl From<&serde_json::Value> for DynVal { @@ -128,6 +110,10 @@ impl DynVal { DynVal(self.0, Some(span)) } + pub fn span(&self) -> Option { + self.1 + } + pub fn from_string(s: String) -> Self { DynVal(s, None) } @@ -146,15 +132,39 @@ impl DynVal { } pub fn as_f64(&self) -> Result { - self.0.parse().map_err(|e| ConversionError::new(self.clone(), "f64", Box::new(e))) + self.0.parse().map_err(|e| ConversionError::new(self.clone(), "f64", e)) } pub fn as_i32(&self) -> Result { - self.0.parse().map_err(|e| ConversionError::new(self.clone(), "i32", Box::new(e))) + self.0.parse().map_err(|e| ConversionError::new(self.clone(), "i32", e)) } pub fn as_bool(&self) -> Result { - self.0.parse().map_err(|e| ConversionError::new(self.clone(), "bool", Box::new(e))) + self.0.parse().map_err(|e| ConversionError::new(self.clone(), "bool", e)) + } + + pub fn as_duration(&self) -> Result { + use std::time::Duration; + let s = &self.0; + if s.ends_with("ms") { + Ok(Duration::from_millis( + s.trim_end_matches("ms").parse().map_err(|e| ConversionError::new(self.clone(), "integer", e))?, + )) + } else if s.ends_with('s') { + Ok(Duration::from_secs( + s.trim_end_matches('s').parse().map_err(|e| ConversionError::new(self.clone(), "integer", e))?, + )) + } else if s.ends_with('m') { + Ok(Duration::from_secs( + s.trim_end_matches('m').parse::().map_err(|e| ConversionError::new(self.clone(), "integer", e))? * 60, + )) + } else if s.ends_with('h') { + Ok(Duration::from_secs( + s.trim_end_matches('h').parse::().map_err(|e| ConversionError::new(self.clone(), "integer", e))? * 60 * 60, + )) + } else { + Err(ConversionError { value: self.clone(), target_type: "duration", source: None }) + } } // pub fn as_vec(&self) -> Result> { From 0c137c8b209180fd4ef4aaf87e119db2b9a101c4 Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Wed, 21 Jul 2021 19:18:43 +0200 Subject: [PATCH 15/15] Move to crates directory --- .gitignore | 2 +- crates/simplexpr/Cargo.lock | 656 ++++++++++++++++++ Cargo.toml => crates/simplexpr/Cargo.toml | 0 README.md => crates/simplexpr/README.md | 0 build.rs => crates/simplexpr/build.rs | 0 .../simplexpr/rust-toolchain | 0 rustfmt.toml => crates/simplexpr/rustfmt.toml | 0 {src => crates/simplexpr/src}/EvalError.rs | 0 {src => crates/simplexpr/src}/ast.rs | 0 {src => crates/simplexpr/src}/dynval.rs | 0 {src => crates/simplexpr/src}/error.rs | 0 {src => crates/simplexpr/src}/eval.rs | 0 {src => crates/simplexpr/src}/lib.rs | 0 .../simplexpr/src}/parser/lalrpop_helpers.rs | 0 {src => crates/simplexpr/src}/parser/lexer.rs | 0 {src => crates/simplexpr/src}/parser/mod.rs | 0 .../simplexpr/src}/simplexpr_parser.lalrpop | 0 .../snapshots/simplexpr__tests__test-10.snap | 0 .../snapshots/simplexpr__tests__test-11.snap | 0 .../snapshots/simplexpr__tests__test-12.snap | 0 .../snapshots/simplexpr__tests__test-13.snap | 0 .../snapshots/simplexpr__tests__test-14.snap | 0 .../snapshots/simplexpr__tests__test-15.snap | 0 .../snapshots/simplexpr__tests__test-16.snap | 0 .../snapshots/simplexpr__tests__test-17.snap | 0 .../snapshots/simplexpr__tests__test-18.snap | 0 .../snapshots/simplexpr__tests__test-19.snap | 0 .../snapshots/simplexpr__tests__test-2.snap | 0 .../snapshots/simplexpr__tests__test-20.snap | 0 .../snapshots/simplexpr__tests__test-21.snap | 0 .../snapshots/simplexpr__tests__test-22.snap | 0 .../snapshots/simplexpr__tests__test-23.snap | 0 .../snapshots/simplexpr__tests__test-24.snap | 0 .../snapshots/simplexpr__tests__test-25.snap | 0 .../snapshots/simplexpr__tests__test-26.snap | 0 .../snapshots/simplexpr__tests__test-3.snap | 0 .../snapshots/simplexpr__tests__test-4.snap | 0 .../snapshots/simplexpr__tests__test-5.snap | 0 .../snapshots/simplexpr__tests__test-6.snap | 0 .../snapshots/simplexpr__tests__test-7.snap | 0 .../snapshots/simplexpr__tests__test-8.snap | 0 .../snapshots/simplexpr__tests__test-9.snap | 0 .../snapshots/simplexpr__tests__test.snap | 0 43 files changed, 657 insertions(+), 1 deletion(-) create mode 100644 crates/simplexpr/Cargo.lock rename Cargo.toml => crates/simplexpr/Cargo.toml (100%) rename README.md => crates/simplexpr/README.md (100%) rename build.rs => crates/simplexpr/build.rs (100%) rename rust-toolchain => crates/simplexpr/rust-toolchain (100%) rename rustfmt.toml => crates/simplexpr/rustfmt.toml (100%) rename {src => crates/simplexpr/src}/EvalError.rs (100%) rename {src => crates/simplexpr/src}/ast.rs (100%) rename {src => crates/simplexpr/src}/dynval.rs (100%) rename {src => crates/simplexpr/src}/error.rs (100%) rename {src => crates/simplexpr/src}/eval.rs (100%) rename {src => crates/simplexpr/src}/lib.rs (100%) rename {src => crates/simplexpr/src}/parser/lalrpop_helpers.rs (100%) rename {src => crates/simplexpr/src}/parser/lexer.rs (100%) rename {src => crates/simplexpr/src}/parser/mod.rs (100%) rename {src => crates/simplexpr/src}/simplexpr_parser.lalrpop (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-10.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-11.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-12.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-13.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-14.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-15.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-16.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-17.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-18.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-19.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-2.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-20.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-21.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-22.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-23.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-24.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-25.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-26.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-3.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-4.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-5.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-6.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-7.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-8.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test-9.snap (100%) rename {src => crates/simplexpr/src}/snapshots/simplexpr__tests__test.snap (100%) diff --git a/.gitignore b/.gitignore index 96ef6c0..d03f68d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ /target -Cargo.lock +/**/target diff --git a/crates/simplexpr/Cargo.lock b/crates/simplexpr/Cargo.lock new file mode 100644 index 0000000..774d4b5 --- /dev/null +++ b/crates/simplexpr/Cargo.lock @@ -0,0 +1,656 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "beef" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6736e2428df2ca2848d846c43e88745121a6654696e349ce0054a420815a7409" + +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "terminal_size", + "winapi", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "ena" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" +dependencies = [ + "log", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "fixedbitset" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "insta" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1b21a2971cea49ca4613c0e9fe8225ecaf5de64090fddc6002284726e9244" +dependencies = [ + "console", + "lazy_static", + "serde", + "serde_json", + "serde_yaml", + "similar", + "uuid", +] + +[[package]] +name = "itertools" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "lalrpop" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15174f1c529af5bf1283c3bc0058266b483a67156f79589fab2a25e23cf8988" +dependencies = [ + "ascii-canvas", + "atty", + "bit-set", + "diff", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "lalrpop-util" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e58cce361efcc90ba8a0a5f982c741ff86b603495bb15a998412e957dcd278" +dependencies = [ + "regex", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "logos" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427e2abca5be13136da9afdbf874e6b34ad9001dd70f2b103b083a85daa7b345" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56a7d287fd2ac3f75b11f19a1c8a874a7d55744bd91f7a1b3e7cf87d4343c36d" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-syntax", + "syn", + "utf8-ranges", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "memchr" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "petgraph" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "rustversion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "serde" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23" +dependencies = [ + "dtoa", + "linked-hash-map", + "serde", + "yaml-rust", +] + +[[package]] +name = "similar" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec" + +[[package]] +name = "simplexpr" +version = "0.1.0" +dependencies = [ + "insta", + "itertools", + "lalrpop", + "lalrpop-util", + "logos", + "maplit", + "regex", + "serde", + "serde_json", + "strum", + "thiserror", +] + +[[package]] +name = "siphasher" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27" + +[[package]] +name = "string_cache" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a" +dependencies = [ + "lazy_static", + "new_debug_unreachable", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strum" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "utf8-ranges" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/crates/simplexpr/Cargo.toml similarity index 100% rename from Cargo.toml rename to crates/simplexpr/Cargo.toml diff --git a/README.md b/crates/simplexpr/README.md similarity index 100% rename from README.md rename to crates/simplexpr/README.md diff --git a/build.rs b/crates/simplexpr/build.rs similarity index 100% rename from build.rs rename to crates/simplexpr/build.rs diff --git a/rust-toolchain b/crates/simplexpr/rust-toolchain similarity index 100% rename from rust-toolchain rename to crates/simplexpr/rust-toolchain diff --git a/rustfmt.toml b/crates/simplexpr/rustfmt.toml similarity index 100% rename from rustfmt.toml rename to crates/simplexpr/rustfmt.toml diff --git a/src/EvalError.rs b/crates/simplexpr/src/EvalError.rs similarity index 100% rename from src/EvalError.rs rename to crates/simplexpr/src/EvalError.rs diff --git a/src/ast.rs b/crates/simplexpr/src/ast.rs similarity index 100% rename from src/ast.rs rename to crates/simplexpr/src/ast.rs diff --git a/src/dynval.rs b/crates/simplexpr/src/dynval.rs similarity index 100% rename from src/dynval.rs rename to crates/simplexpr/src/dynval.rs diff --git a/src/error.rs b/crates/simplexpr/src/error.rs similarity index 100% rename from src/error.rs rename to crates/simplexpr/src/error.rs diff --git a/src/eval.rs b/crates/simplexpr/src/eval.rs similarity index 100% rename from src/eval.rs rename to crates/simplexpr/src/eval.rs diff --git a/src/lib.rs b/crates/simplexpr/src/lib.rs similarity index 100% rename from src/lib.rs rename to crates/simplexpr/src/lib.rs diff --git a/src/parser/lalrpop_helpers.rs b/crates/simplexpr/src/parser/lalrpop_helpers.rs similarity index 100% rename from src/parser/lalrpop_helpers.rs rename to crates/simplexpr/src/parser/lalrpop_helpers.rs diff --git a/src/parser/lexer.rs b/crates/simplexpr/src/parser/lexer.rs similarity index 100% rename from src/parser/lexer.rs rename to crates/simplexpr/src/parser/lexer.rs diff --git a/src/parser/mod.rs b/crates/simplexpr/src/parser/mod.rs similarity index 100% rename from src/parser/mod.rs rename to crates/simplexpr/src/parser/mod.rs diff --git a/src/simplexpr_parser.lalrpop b/crates/simplexpr/src/simplexpr_parser.lalrpop similarity index 100% rename from src/simplexpr_parser.lalrpop rename to crates/simplexpr/src/simplexpr_parser.lalrpop diff --git a/src/snapshots/simplexpr__tests__test-10.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-10.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-10.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-10.snap diff --git a/src/snapshots/simplexpr__tests__test-11.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-11.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-11.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-11.snap diff --git a/src/snapshots/simplexpr__tests__test-12.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-12.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-12.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-12.snap diff --git a/src/snapshots/simplexpr__tests__test-13.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-13.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-13.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-13.snap diff --git a/src/snapshots/simplexpr__tests__test-14.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-14.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-14.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-14.snap diff --git a/src/snapshots/simplexpr__tests__test-15.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-15.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-15.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-15.snap diff --git a/src/snapshots/simplexpr__tests__test-16.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-16.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-16.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-16.snap diff --git a/src/snapshots/simplexpr__tests__test-17.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-17.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-17.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-17.snap diff --git a/src/snapshots/simplexpr__tests__test-18.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-18.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-18.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-18.snap diff --git a/src/snapshots/simplexpr__tests__test-19.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-19.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-19.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-19.snap diff --git a/src/snapshots/simplexpr__tests__test-2.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-2.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-2.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-2.snap diff --git a/src/snapshots/simplexpr__tests__test-20.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-20.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-20.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-20.snap diff --git a/src/snapshots/simplexpr__tests__test-21.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-21.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-21.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-21.snap diff --git a/src/snapshots/simplexpr__tests__test-22.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-22.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-22.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-22.snap diff --git a/src/snapshots/simplexpr__tests__test-23.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-23.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-23.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-23.snap diff --git a/src/snapshots/simplexpr__tests__test-24.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-24.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-24.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-24.snap diff --git a/src/snapshots/simplexpr__tests__test-25.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-25.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-25.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-25.snap diff --git a/src/snapshots/simplexpr__tests__test-26.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-26.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-26.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-26.snap diff --git a/src/snapshots/simplexpr__tests__test-3.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-3.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-3.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-3.snap diff --git a/src/snapshots/simplexpr__tests__test-4.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-4.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-4.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-4.snap diff --git a/src/snapshots/simplexpr__tests__test-5.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-5.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-5.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-5.snap diff --git a/src/snapshots/simplexpr__tests__test-6.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-6.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-6.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-6.snap diff --git a/src/snapshots/simplexpr__tests__test-7.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-7.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-7.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-7.snap diff --git a/src/snapshots/simplexpr__tests__test-8.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-8.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-8.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-8.snap diff --git a/src/snapshots/simplexpr__tests__test-9.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test-9.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test-9.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test-9.snap diff --git a/src/snapshots/simplexpr__tests__test.snap b/crates/simplexpr/src/snapshots/simplexpr__tests__test.snap similarity index 100% rename from src/snapshots/simplexpr__tests__test.snap rename to crates/simplexpr/src/snapshots/simplexpr__tests__test.snap