Start implementing parser
This commit is contained in:
commit
923d478b33
16 changed files with 419 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
28
Cargo.toml
Normal file
28
Cargo.toml
Normal file
|
@ -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"
|
4
build.rs
Normal file
4
build.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
extern crate lalrpop;
|
||||
fn main() {
|
||||
lalrpop::process_root().unwrap();
|
||||
}
|
1
rust-toolchain
Normal file
1
rust-toolchain
Normal file
|
@ -0,0 +1 @@
|
|||
nightly
|
14
rustfmt.toml
Normal file
14
rustfmt.toml
Normal file
|
@ -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"
|
94
src/ast.rs
Normal file
94
src/ast.rs
Normal file
|
@ -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<SimplExpr>, BinOp, Box<SimplExpr>),
|
||||
UnaryOp(UnaryOp, Box<SimplExpr>),
|
||||
IfElse(Box<SimplExpr>, Box<SimplExpr>, Box<SimplExpr>),
|
||||
JsonAccess(Box<SimplExpr>, Box<SimplExpr>),
|
||||
FunctionCall(String, Vec<SimplExpr>),
|
||||
}
|
||||
|
||||
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(", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
src/lib.rs
Normal file
35
src/lib.rs
Normal file
|
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
57
src/parser.lalrpop
Normal file
57
src/parser.lalrpop
Normal file
|
@ -0,0 +1,57 @@
|
|||
|
||||
use crate::ast::{SimplExpr, Span, BinOp, UnaryOp};
|
||||
|
||||
grammar;
|
||||
|
||||
Comma<T>: Vec<T> = {
|
||||
<mut v:(<T> ",")*> <e:T?> => 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()),
|
||||
<Number>,
|
||||
"(" <ExprReset> ")",
|
||||
<ident:Ident> "(" <args: Comma<ExprReset>> ")" => SimplExpr::FunctionCall(ident, args),
|
||||
|
||||
|
||||
#[precedence(level="1")] #[assoc(side="left")]
|
||||
"!" <Expr> => SimplExpr::UnaryOp(UnaryOp::Not, Box::new(<>))
|
||||
|
||||
|
||||
#[precedence(level="2")] #[assoc(side="left")]
|
||||
<l:Expr> "*" <r:Expr> => SimplExpr::BinOp(Box::new(l), BinOp::Times, Box::new(r)),
|
||||
<l:Expr> "/" <r:Expr> => SimplExpr::BinOp(Box::new(l), BinOp::Div, Box::new(r)),
|
||||
<l:Expr> "%" <r:Expr> => SimplExpr::BinOp(Box::new(l), BinOp::Mod, Box::new(r)),
|
||||
|
||||
#[precedence(level="3")] #[assoc(side="left")]
|
||||
<l:Expr> "+" <r:Expr> => SimplExpr::BinOp(Box::new(l), BinOp::Plus, Box::new(r)),
|
||||
<l:Expr> "-" <r:Expr> => SimplExpr::BinOp(Box::new(l), BinOp::Minus, Box::new(r)),
|
||||
|
||||
#[precedence(level="4")] #[assoc(side="left")]
|
||||
<l:Expr> "==" <r:Expr> => SimplExpr::BinOp(Box::new(l), BinOp::Equals, Box::new(r)),
|
||||
<l:Expr> "!=" <r:Expr> => SimplExpr::BinOp(Box::new(l), BinOp::NotEquals, Box::new(r)),
|
||||
<l:Expr> "<" <r:Expr> => SimplExpr::BinOp(Box::new(l), BinOp::GT, Box::new(r)),
|
||||
<l:Expr> ">" <r:Expr> => SimplExpr::BinOp(Box::new(l), BinOp::LT, Box::new(r)),
|
||||
<l:Expr> "=~" <r:Expr> => SimplExpr::BinOp(Box::new(l), BinOp::RegexMatch, Box::new(r)),
|
||||
|
||||
#[precedence(level="5")] #[assoc(side="left")]
|
||||
<l:Expr> "&&" <r:Expr> => SimplExpr::BinOp(Box::new(l), BinOp::And, Box::new(r)),
|
||||
<l:Expr> "||" <r:Expr> => SimplExpr::BinOp(Box::new(l), BinOp::Or, Box::new(r)),
|
||||
<l:Expr> "?:" <r:Expr> => SimplExpr::BinOp(Box::new(l), BinOp::Elvis, Box::new(r)),
|
||||
|
||||
#[precedence(level="6")] #[assoc(side="right")]
|
||||
<cond:Expr> "?" <then:ExprReset> ":" <els:Expr> => SimplExpr::IfElse(Box::new(cond), Box::new(then), Box::new(els)),
|
||||
};
|
||||
|
||||
ExprReset = <Expr>;
|
||||
|
||||
Number: SimplExpr = r"[+-]?(?:[0-9]+[.])?[0-9]+" => SimplExpr::Literal(<>.to_string());
|
||||
Ident: String = r"[a-zA-Z_][^\s{}\(\)]*" => <>.to_string();
|
16
src/snapshots/simplexpr__tests__test-2.snap
Normal file
16
src/snapshots/simplexpr__tests__test-2.snap
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
source: src/lib.rs
|
||||
expression: "p.parse(\"2 + 5\")"
|
||||
|
||||
---
|
||||
Ok(
|
||||
BinOp(
|
||||
Literal(
|
||||
"2",
|
||||
),
|
||||
Plus,
|
||||
Literal(
|
||||
"5",
|
||||
),
|
||||
),
|
||||
)
|
34
src/snapshots/simplexpr__tests__test-3.snap
Normal file
34
src/snapshots/simplexpr__tests__test-3.snap
Normal file
|
@ -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",
|
||||
),
|
||||
),
|
||||
)
|
22
src/snapshots/simplexpr__tests__test-4.snap
Normal file
22
src/snapshots/simplexpr__tests__test-4.snap
Normal file
|
@ -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",
|
||||
),
|
||||
),
|
||||
)
|
24
src/snapshots/simplexpr__tests__test-5.snap
Normal file
24
src/snapshots/simplexpr__tests__test-5.snap
Normal file
|
@ -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",
|
||||
),
|
||||
),
|
||||
)
|
30
src/snapshots/simplexpr__tests__test-6.snap
Normal file
30
src/snapshots/simplexpr__tests__test-6.snap
Normal file
|
@ -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",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
30
src/snapshots/simplexpr__tests__test-7.snap
Normal file
30
src/snapshots/simplexpr__tests__test-7.snap
Normal file
|
@ -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",
|
||||
),
|
||||
),
|
||||
)
|
18
src/snapshots/simplexpr__tests__test-8.snap
Normal file
18
src/snapshots/simplexpr__tests__test-8.snap
Normal file
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
source: src/lib.rs
|
||||
expression: "p.parse(\"foo(1, 2)\")"
|
||||
|
||||
---
|
||||
Ok(
|
||||
FunctionCall(
|
||||
"foo",
|
||||
[
|
||||
Literal(
|
||||
"1",
|
||||
),
|
||||
Literal(
|
||||
"2",
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
10
src/snapshots/simplexpr__tests__test.snap
Normal file
10
src/snapshots/simplexpr__tests__test.snap
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
source: src/lib.rs
|
||||
expression: "p.parse(\"1\")"
|
||||
|
||||
---
|
||||
Ok(
|
||||
Literal(
|
||||
"1",
|
||||
),
|
||||
)
|
Loading…
Add table
Reference in a new issue