Start implementing parser

This commit is contained in:
elkowar 2021-07-12 16:45:16 +02:00
commit 923d478b33
No known key found for this signature in database
GPG key ID: E321AD71B1D1F27F
16 changed files with 419 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
Cargo.lock

28
Cargo.toml Normal file
View 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
View file

@ -0,0 +1,4 @@
extern crate lalrpop;
fn main() {
lalrpop::process_root().unwrap();
}

1
rust-toolchain Normal file
View file

@ -0,0 +1 @@
nightly

14
rustfmt.toml Normal file
View 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
View 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
View 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
View 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();

View file

@ -0,0 +1,16 @@
---
source: src/lib.rs
expression: "p.parse(\"2 + 5\")"
---
Ok(
BinOp(
Literal(
"2",
),
Plus,
Literal(
"5",
),
),
)

View 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",
),
),
)

View 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",
),
),
)

View 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",
),
),
)

View 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",
),
),
),
)

View 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",
),
),
)

View file

@ -0,0 +1,18 @@
---
source: src/lib.rs
expression: "p.parse(\"foo(1, 2)\")"
---
Ok(
FunctionCall(
"foo",
[
Literal(
"1",
),
Literal(
"2",
),
],
),
)

View file

@ -0,0 +1,10 @@
---
source: src/lib.rs
expression: "p.parse(\"1\")"
---
Ok(
Literal(
"1",
),
)