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