diff --git a/src/ast/expr.rs b/src/ast/expr.rs index ab2dc53..cf1a744 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -20,6 +20,14 @@ pub enum BinOp { Eq, /// `!=` Neq, + /// `>` + Gt, + /// `>=` + Ge, + /// `<` + Lt, + /// `<=` + Le, /// `and` And, /// `or` diff --git a/src/parser.rs b/src/parser.rs index e7c1a64..69b147e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,3 +1,5 @@ +// TODO Turn multiple calls to subparsers into clone-s + mod basic; mod expr; mod lit; diff --git a/src/parser/basic.rs b/src/parser/basic.rs index 0fbad2e..3d3fa24 100644 --- a/src/parser/basic.rs +++ b/src/parser/basic.rs @@ -10,17 +10,17 @@ pub type Error = Simple; // TODO https://github.com/rust-lang/rust/issues/63063 -pub fn inline() -> impl Parser { +pub fn inline() -> impl Parser + Clone { filter(|c: &char| c.is_whitespace() && *c != '\n') .repeated() .to(()) } -pub fn newline() -> impl Parser { +pub fn newline() -> impl Parser + Clone { just('\n').to(()) } -pub fn line() -> impl Parser { +pub fn line() -> impl Parser + Clone { let empty = newline().to(Line::Empty); let comment = just('#') @@ -32,7 +32,7 @@ pub fn line() -> impl Parser { empty.or(comment) } -pub fn space() -> impl Parser { +pub fn space() -> impl Parser + Clone { inline() .ignore_then(line()) .repeated() @@ -40,7 +40,7 @@ pub fn space() -> impl Parser { .map_with_span(|lines, span| Space { lines, span }) } -pub fn ident() -> impl Parser { +pub fn ident() -> impl Parser + Clone { text::ident().try_map(|name, span| { if matches!( &name as &str, diff --git a/src/parser/expr.rs b/src/parser/expr.rs index 8c3c316..4d012ff 100644 --- a/src/parser/expr.rs +++ b/src/parser/expr.rs @@ -2,7 +2,8 @@ use chumsky::prelude::*; -use crate::ast::Expr; +use crate::ast::{BinOp, Expr}; +use crate::span::HasSpan; use super::basic::{space, Error}; use super::lit::lit; @@ -14,7 +15,7 @@ use super::var::var; fn atom_paren( expr: impl Parser + Clone, -) -> impl Parser { +) -> impl Parser + Clone { just('(') .ignore_then(space()) .then(expr) @@ -30,7 +31,7 @@ fn atom_paren( fn atom( expr: impl Parser + Clone, -) -> impl Parser { +) -> impl Parser + Clone { let lit = lit(expr.clone()).map(Expr::Lit); let var = var(expr.clone()).map(Expr::Var); let table_constr = table_constr(expr.clone()).map(Expr::TableConstr); @@ -41,8 +42,57 @@ fn atom( prefixed(suffixed(base, expr)) } +fn left_assoc( + op: impl Parser + Clone, + over: impl Parser + Clone, +) -> impl Parser + Clone { + let op_over = space() + .then(op) + .then(space()) + .then(over.clone()) + .map(|(((s0, op), s1), right)| (s0, op, s1, right)); + + over.then(op_over.repeated()) + .foldl(|left, (s0, op, s1, right)| Expr::BinOp { + span: left.span().join(right.span()), + left: Box::new(left), + s0, + op, + s1, + right: Box::new(right), + }) +} + pub fn expr( expr: impl Parser + Clone, ) -> impl Parser { - atom(expr) + // * / % + let prec0 = (just('*').to(BinOp::Mul)) + .or(just('/').to(BinOp::Div)) + .or(just('%').to(BinOp::Mod)); + + // + - + let prec1 = (just('+').to(BinOp::Add)).or(just('-').to(BinOp::Sub)); + + // == != > >= < <= + let prec2 = (just("==").to(BinOp::Eq)) + .or(just("!=").to(BinOp::Neq)) + .or(just('>').to(BinOp::Gt)) + .or(just(">=").to(BinOp::Ge)) + .or(just('<').to(BinOp::Lt)) + .or(just("<=").to(BinOp::Le)); + + // and + let prec3 = text::keyword("and").to(BinOp::And); + + // or + let prec4 = text::keyword("or").to(BinOp::Or); + + left_assoc( + prec4, + left_assoc( + prec3, + left_assoc(prec2, left_assoc(prec1, left_assoc(prec0, atom(expr)))), + ), + ) } diff --git a/src/parser/lit.rs b/src/parser/lit.rs index a81d7cf..cf0f2bb 100644 --- a/src/parser/lit.rs +++ b/src/parser/lit.rs @@ -7,7 +7,7 @@ use crate::builtin::Builtin; use super::basic::{ident, space, Error}; -fn builtin_lit() -> impl Parser { +fn builtin_lit() -> impl Parser + Clone { just('\'').ignore_then(choice(( text::keyword("get").to(Builtin::Get), text::keyword("set").to(Builtin::Set), @@ -78,14 +78,14 @@ pub fn num_lit() -> impl Parser + Clone { .map_with_span(|(value, str), span| NumLit { value, str, span }) } -pub fn string_lit() -> impl Parser { +pub fn string_lit() -> impl Parser + Clone { // TODO Parse string literals filter(|_| false).map(|_| unreachable!()) } pub fn table_lit_elem( expr: impl Parser + Clone, -) -> impl Parser { +) -> impl Parser + Clone { let positional = expr .clone() .map(|value| TableLitElem::Positional(Box::new(value))); @@ -108,7 +108,7 @@ pub fn table_lit_elem( pub fn table_lit( expr: impl Parser + Clone, -) -> impl Parser { +) -> impl Parser + Clone { let elem = space() .then(table_lit_elem(expr)) .then(space()) @@ -130,7 +130,7 @@ pub fn table_lit( pub fn lit( expr: impl Parser + Clone, -) -> impl Parser { +) -> impl Parser + Clone { let nil = text::keyword("nil").map_with_span(|_, span| Lit::Nil(span)); let r#true = text::keyword("true").map_with_span(|_, span| Lit::Bool(true, span)); let r#false = text::keyword("false").map_with_span(|_, span| Lit::Bool(false, span)); diff --git a/src/parser/prefix.rs b/src/parser/prefix.rs index 81727d6..d99f1db 100644 --- a/src/parser/prefix.rs +++ b/src/parser/prefix.rs @@ -35,14 +35,14 @@ impl Prefix { } } -fn prefix_neg() -> impl Parser { +fn prefix_neg() -> impl Parser + Clone { just('-') .map_with_span(|_, span| span) .then(space()) .map(|(minus, s0)| Prefix::Neg { minus, s0 }) } -fn prefix_not() -> impl Parser { +fn prefix_not() -> impl Parser + Clone { text::keyword("not") .map_with_span(|_, span| span) .then(space()) @@ -50,8 +50,8 @@ fn prefix_not() -> impl Parser { } pub fn prefixed( - suffixed: impl Parser, -) -> impl Parser { + suffixed: impl Parser + Clone, +) -> impl Parser + Clone { let prefix = prefix_neg() .or(prefix_not()) .map_with_span(|prefix, span| (prefix, span)); diff --git a/src/parser/suffix.rs b/src/parser/suffix.rs index 417960e..64666f3 100644 --- a/src/parser/suffix.rs +++ b/src/parser/suffix.rs @@ -131,8 +131,8 @@ impl Suffix { } fn suffix_call_arg( - expr: impl Parser, -) -> impl Parser { + expr: impl Parser + Clone, +) -> impl Parser + Clone { space() .then_ignore(just('(')) .then(space()) @@ -147,7 +147,7 @@ fn suffix_call_arg( }) } -fn suffix_call_no_arg() -> impl Parser { +fn suffix_call_no_arg() -> impl Parser + Clone { space() .then_ignore(just('(')) .then(space()) @@ -157,7 +157,7 @@ fn suffix_call_no_arg() -> impl Parser { fn suffix_call_constr( expr: impl Parser + Clone, -) -> impl Parser { +) -> impl Parser + Clone { space() .then(table_constr(expr)) .map(|(s0, constr)| Suffix::CallConstr { s0, constr }) @@ -165,7 +165,7 @@ fn suffix_call_constr( fn suffix_field_access( expr: impl Parser + Clone, -) -> impl Parser { +) -> impl Parser + Clone { space() .then_ignore(just('[')) .then(space()) @@ -182,7 +182,7 @@ fn suffix_field_access( fn suffix_field_assign( expr: impl Parser + Clone, -) -> impl Parser { +) -> impl Parser + Clone { space() .then_ignore(just('[')) .then(space()) @@ -206,7 +206,7 @@ fn suffix_field_assign( ) } -fn suffix_field_access_ident() -> impl Parser { +fn suffix_field_access_ident() -> impl Parser + Clone { space() .then_ignore(just('.')) .then(space()) @@ -215,8 +215,8 @@ fn suffix_field_access_ident() -> impl Parser { } fn suffix_field_assign_ident( - expr: impl Parser, -) -> impl Parser { + expr: impl Parser + Clone, +) -> impl Parser + Clone { space() .then_ignore(just('.')) .then(space()) @@ -238,9 +238,9 @@ fn suffix_field_assign_ident( } pub fn suffixed( - atom: impl Parser, + atom: impl Parser + Clone, expr: impl Parser + Clone, -) -> impl Parser { +) -> impl Parser + Clone { let call_arg = suffix_call_arg(expr.clone()); let call_no_arg = suffix_call_no_arg(); let call_constr = suffix_call_constr(expr.clone()); diff --git a/src/parser/table_constr.rs b/src/parser/table_constr.rs index 7c75229..190cd37 100644 --- a/src/parser/table_constr.rs +++ b/src/parser/table_constr.rs @@ -9,7 +9,7 @@ use super::lit::table_lit_elem; pub fn table_constr_elem( expr: impl Parser + Clone, -) -> impl Parser { +) -> impl Parser + Clone { let lit = table_lit_elem(expr.clone()).map(TableConstrElem::Lit); let indexed = just('[') @@ -38,7 +38,7 @@ pub fn table_constr_elem( pub fn table_constr( expr: impl Parser + Clone, -) -> impl Parser { +) -> impl Parser + Clone { let elem = space() .then(table_constr_elem(expr)) .then(space()) diff --git a/src/parser/table_destr.rs b/src/parser/table_destr.rs index d25590b..9803715 100644 --- a/src/parser/table_destr.rs +++ b/src/parser/table_destr.rs @@ -6,7 +6,7 @@ use crate::ast::{Expr, TableDestr, TablePattern, TablePatternElem}; use super::basic::{ident, space, Error}; -pub fn table_pattern_elem() -> impl Parser { +pub fn table_pattern_elem() -> impl Parser + Clone { let positional = ident().map(TablePatternElem::Positional); let named = ident() @@ -25,7 +25,7 @@ pub fn table_pattern_elem() -> impl Parser impl Parser { +pub fn table_pattern() -> impl Parser + Clone { let elem = space() .then(table_pattern_elem()) .then(space()) @@ -46,8 +46,8 @@ pub fn table_pattern() -> impl Parser { } pub fn table_destr( - expr: impl Parser, -) -> impl Parser { + expr: impl Parser + Clone, +) -> impl Parser + Clone { let local = text::keyword("local").ignore_then(space()).or_not(); local diff --git a/src/parser/var.rs b/src/parser/var.rs index 2db31d8..8d3fd38 100644 --- a/src/parser/var.rs +++ b/src/parser/var.rs @@ -8,7 +8,7 @@ use super::basic::{ident, space, Error}; fn var_access( expr: impl Parser + Clone, -) -> impl Parser { +) -> impl Parser + Clone { just('[') .ignore_then(space()) .then(expr) @@ -24,7 +24,7 @@ fn var_access( fn var_assign( expr: impl Parser + Clone, -) -> impl Parser { +) -> impl Parser + Clone { let local = text::keyword("local").ignore_then(space()).or_not(); local @@ -53,7 +53,7 @@ fn var_assign( fn var_assign_ident( expr: impl Parser + Clone, -) -> impl Parser { +) -> impl Parser + Clone { let local = text::keyword("local").ignore_then(space()).or_not(); local @@ -76,7 +76,7 @@ fn var_assign_ident( pub fn var( expr: impl Parser + Clone, -) -> impl Parser { +) -> impl Parser + Clone { let access = var_access(expr.clone()); let assign = var_assign(expr.clone()); let access_ident = ident().map(Var::AccessIdent);