diff --git a/Cargo.lock b/Cargo.lock index d5cd11a..53d7c73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,6 +120,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + [[package]] name = "getrandom" version = "0.2.8" @@ -146,6 +155,15 @@ dependencies = [ "libc", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "libc" version = "0.2.137" @@ -233,6 +251,24 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "strsim" version = "0.10.0" @@ -258,6 +294,21 @@ dependencies = [ "chumsky", "clap", "pretty", + "tempfile", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3a7b52e..2e725f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,4 @@ anyhow = "1.0.66" chumsky = "0.8.0" clap = { version = "4.0.26", features = ["derive", "deprecated"] } pretty = "0.11.3" +tempfile = "3.3.0" diff --git a/src/ast/basic.rs b/src/ast/basic.rs index e472e39..8a722c3 100644 --- a/src/ast/basic.rs +++ b/src/ast/basic.rs @@ -2,6 +2,8 @@ use std::fmt; use crate::span::{HasSpan, Span}; +use super::{TableConstr, TableConstrElem, TableLit, TableLitElem}; + #[derive(Clone)] pub enum Line { Empty, @@ -30,12 +32,30 @@ impl HasSpan for Space { } } +impl Space { + pub fn empty(span: Span) -> Self { + Self { + lines: vec![], + span, + } + } +} + #[derive(Clone)] pub struct Ident { pub name: String, pub span: Span, } +impl Ident { + pub fn new(name: S, span: Span) -> Self { + Self { + name: name.to_string(), + span, + } + } +} + impl fmt::Debug for Ident { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "i#{}", self.name) @@ -47,3 +67,78 @@ impl HasSpan for Ident { self.span } } + +#[derive(Debug, Clone)] +pub struct BoundedSeparated { + pub elems: Vec<(Space, E, Space)>, + pub trailing: Option, + pub span: Span, +} + +impl HasSpan for BoundedSeparated { + fn span(&self) -> Span { + self.span + } +} + +impl BoundedSeparated { + pub fn new(span: Span) -> Self { + Self { + elems: vec![], + trailing: None, + span, + } + } + + pub fn then(mut self, elem: E) -> Self { + self.elems + .push((Space::empty(self.span), elem, Space::empty(self.span))); + self + } + + pub fn map(self, f: impl Fn(E) -> E2) -> BoundedSeparated { + let elems = self + .elems + .into_iter() + .map(|(s0, e, s1)| (s0, f(e), s1)) + .collect::>(); + + BoundedSeparated { + elems, + trailing: self.trailing, + span: self.span, + } + } + + pub fn remove_map( + self, + f: impl Fn(E) -> Result, + ) -> (BoundedSeparated, Vec<(Space, E2, Space)>) { + let mut kept = vec![]; + let mut removed = vec![]; + for (s0, elem, s1) in self.elems { + match f(elem) { + Ok(elem) => kept.push((s0, elem, s1)), + Err(elem) => removed.push((s0, elem, s1)), + } + } + let new = BoundedSeparated { + elems: kept, + trailing: self.trailing, + span: self.span, + }; + (new, removed) + } +} + +impl BoundedSeparated { + pub fn table_lit(self) -> TableLit { + TableLit(self) + } +} + +impl BoundedSeparated { + pub fn table_constr(self) -> TableConstr { + TableConstr(self) + } +} diff --git a/src/ast/call.rs b/src/ast/call.rs index 10f82e8..6a9d13e 100644 --- a/src/ast/call.rs +++ b/src/ast/call.rs @@ -40,9 +40,44 @@ pub enum Call { impl HasSpan for Call { fn span(&self) -> Span { match self { - Call::Arg { span, .. } => *span, - Call::NoArg { span, .. } => *span, - Call::Constr { span, .. } => *span, + Self::Arg { span, .. } => *span, + Self::NoArg { span, .. } => *span, + Self::Constr { span, .. } => *span, } } } + +impl Call { + pub fn arg(base: Box, arg: Box, span: Span) -> Self { + Self::Arg { + expr: base, + s0: Space::empty(span), + s1: Space::empty(span), + arg, + s2: Space::empty(span), + span, + } + } + + pub fn no_arg(base: Box, span: Span) -> Self { + Self::NoArg { + expr: base, + s0: Space::empty(span), + s1: Space::empty(span), + span, + } + } + + pub fn constr(base: Box, constr: TableConstr, span: Span) -> Self { + Self::Constr { + expr: base, + s0: Space::empty(span), + constr, + span, + } + } + + pub fn expr(self) -> Expr { + Expr::Call(self) + } +} diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 522f2f1..d0fa1ff 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -4,18 +4,30 @@ use crate::span::{HasSpan, Span}; use super::{Call, Field, FuncDef, Lit, Space, TableConstr, TableDestr, Var}; +// Warning: If you change these precedences and associativities, you need to +// update the parser and pretty-printer as well. + +// Warning: Operators at the same precedence must also have the same +// associativity. + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Assoc { + Left, + Right, +} + #[derive(Debug, Clone, Copy)] pub enum BinOp { - /// `+` - Add, - /// `-` - Sub, /// `*` Mul, /// `/` Div, /// `%` Mod, + /// `+` + Add, + /// `-` + Sub, /// `==` Eq, /// `!=` @@ -34,6 +46,36 @@ pub enum BinOp { Or, } +impl BinOp { + /// The higher the precedence, the more strongly the operator binds. + pub fn precedence(self) -> u8 { + match self { + Self::Mul | Self::Div | Self::Mod => 4, + Self::Add | Self::Sub => 3, + Self::Eq | Self::Neq | Self::Gt | Self::Ge | Self::Lt | Self::Le => 2, + Self::And => 1, + Self::Or => 0, + } + } + + pub fn assoc(self) -> Assoc { + match self { + Self::Mul + | Self::Div + | Self::Mod + | Self::Add + | Self::Sub + | Self::Eq + | Self::Neq + | Self::Gt + | Self::Ge + | Self::Lt + | Self::Le => Assoc::Left, + Self::And | Self::Or => Assoc::Right, + } + } +} + #[derive(Clone)] pub enum Expr { Lit(Lit), @@ -186,17 +228,23 @@ impl fmt::Debug for Expr { impl HasSpan for Expr { fn span(&self) -> Span { match self { - Expr::Lit(lit) => lit.span(), - Expr::Call(call) => call.span(), - Expr::Field(field) => field.span(), - Expr::Var(var) => var.span(), - Expr::TableConstr(constr) => constr.span(), - Expr::TableDestr(destr) => destr.span(), - Expr::FuncDef(def) => def.span(), - Expr::Paren { span, .. } => *span, - Expr::Neg { span, .. } => *span, - Expr::Not { span, .. } => *span, - Expr::BinOp { span, .. } => *span, + Self::Lit(lit) => lit.span(), + Self::Call(call) => call.span(), + Self::Field(field) => field.span(), + Self::Var(var) => var.span(), + Self::TableConstr(constr) => constr.span(), + Self::TableDestr(destr) => destr.span(), + Self::FuncDef(def) => def.span(), + Self::Paren { span, .. } => *span, + Self::Neg { span, .. } => *span, + Self::Not { span, .. } => *span, + Self::BinOp { span, .. } => *span, } } } + +impl Expr { + pub fn boxed(self) -> Box { + Box::new(self) + } +} diff --git a/src/ast/field.rs b/src/ast/field.rs index c8c10b4..5afd98f 100644 --- a/src/ast/field.rs +++ b/src/ast/field.rs @@ -60,10 +60,41 @@ pub enum Field { impl HasSpan for Field { fn span(&self) -> Span { match self { - Field::Access { span, .. } => *span, - Field::Assign { span, .. } => *span, - Field::AccessIdent { span, .. } => *span, - Field::AssignIdent { span, .. } => *span, + Self::Access { span, .. } => *span, + Self::Assign { span, .. } => *span, + Self::AccessIdent { span, .. } => *span, + Self::AssignIdent { span, .. } => *span, } } } + +impl Field { + pub fn access(base: Box, index: Box, span: Span) -> Self { + Self::Access { + expr: base, + s0: Space::empty(span), + s1: Space::empty(span), + index, + s2: Space::empty(span), + span, + } + } + + pub fn assign(base: Box, index: Box, value: Box, span: Span) -> Self { + Self::Assign { + expr: base, + s0: Space::empty(span), + s1: Space::empty(span), + index, + s2: Space::empty(span), + s3: Space::empty(span), + s4: Space::empty(span), + value, + span, + } + } + + pub fn expr(self) -> Expr { + Expr::Field(self) + } +} diff --git a/src/ast/func_def.rs b/src/ast/func_def.rs index 3ce0b34..5cdd210 100644 --- a/src/ast/func_def.rs +++ b/src/ast/func_def.rs @@ -90,12 +90,50 @@ pub enum FuncDef { impl HasSpan for FuncDef { fn span(&self) -> Span { match self { - FuncDef::AnonNoArg { span, .. } => *span, - FuncDef::AnonArg { span, .. } => *span, - FuncDef::AnonDestr { span, .. } => *span, - FuncDef::NamedNoArg { span, .. } => *span, - FuncDef::NamedArg { span, .. } => *span, - FuncDef::NamedDestr { span, .. } => *span, + Self::AnonNoArg { span, .. } => *span, + Self::AnonArg { span, .. } => *span, + Self::AnonDestr { span, .. } => *span, + Self::NamedNoArg { span, .. } => *span, + Self::NamedArg { span, .. } => *span, + Self::NamedDestr { span, .. } => *span, } } } + +impl FuncDef { + pub fn anon_no_arg(body: Box, span: Span) -> Self { + Self::AnonNoArg { + s0: Space::empty(span), + s1: Space::empty(span), + s2: Space::empty(span), + body, + span, + } + } + + pub fn anon_arg(arg: Ident, body: Box, span: Span) -> Self { + Self::AnonArg { + s0: Space::empty(span), + s1: Space::empty(span), + arg, + s2: Space::empty(span), + s3: Space::empty(span), + body, + span, + } + } + + pub fn anon_destr(pattern: TablePattern, body: Box, span: Span) -> Self { + Self::AnonDestr { + s0: Space::empty(span), + pattern, + s1: Space::empty(span), + body, + span, + } + } + + pub fn expr(self) -> Expr { + Expr::FuncDef(self) + } +} diff --git a/src/ast/lit.rs b/src/ast/lit.rs index 299bc1c..ecd166c 100644 --- a/src/ast/lit.rs +++ b/src/ast/lit.rs @@ -3,7 +3,7 @@ use std::fmt; use crate::builtin::Builtin; use crate::span::{HasSpan, Span}; -use super::{Expr, Ident, Space}; +use super::{BoundedSeparated, Expr, Ident, Space}; #[derive(Clone)] pub enum NumLitStr { @@ -93,6 +93,19 @@ pub struct StringLit { pub span: Span, } +impl StringLit { + pub fn from_ident(ident: Ident) -> Self { + Self { + elems: vec![StringLitElem::Plain(ident.name)], + span: ident.span, + } + } + + pub fn lit(self) -> Lit { + Lit::String(self) + } +} + impl HasSpan for StringLit { fn span(&self) -> Span { self.span @@ -119,24 +132,37 @@ pub enum TableLitElem { impl HasSpan for TableLitElem { fn span(&self) -> Span { match self { - TableLitElem::Positional(value) => value.span(), - TableLitElem::Named { span, .. } => *span, + Self::Positional(value) => value.span(), + Self::Named { span, .. } => *span, + } + } +} + +impl TableLitElem { + pub fn named(name: Ident, value: Box, span: Span) -> Self { + Self::Named { + name, + s0: Space::empty(span), + s1: Space::empty(span), + value, + span, } } } /// `'{ a, foo: b }` #[derive(Debug, Clone)] -pub struct TableLit { - pub elems: Vec<(Space, TableLitElem, Space)>, - /// `Some` if there is a trailing comma, `None` otherwise. - pub trailing_comma: Option, - pub span: Span, -} +pub struct TableLit(pub BoundedSeparated); impl HasSpan for TableLit { fn span(&self) -> Span { - self.span + self.0.span() + } +} + +impl TableLit { + pub fn lit(self) -> Lit { + Lit::Table(self) } } @@ -185,12 +211,18 @@ impl fmt::Debug for Lit { impl HasSpan for Lit { fn span(&self) -> Span { match self { - Lit::Nil(span) => *span, - Lit::Bool(_, span) => *span, - Lit::Builtin(_, span) => *span, - Lit::Num(n) => n.span(), - Lit::String(s) => s.span(), - Lit::Table(t) => t.span(), + Self::Nil(span) => *span, + Self::Bool(_, span) => *span, + Self::Builtin(_, span) => *span, + Self::Num(n) => n.span(), + Self::String(s) => s.span(), + Self::Table(t) => t.span(), } } } + +impl Lit { + pub fn expr(self) -> Expr { + Expr::Lit(self) + } +} diff --git a/src/ast/program.rs b/src/ast/program.rs index 5a198d0..a77abf4 100644 --- a/src/ast/program.rs +++ b/src/ast/program.rs @@ -1,6 +1,6 @@ use crate::span::{HasSpan, Span}; -use super::{Expr, Space, TableLitElem}; +use super::{BoundedSeparated, Expr, Space, TableLitElem}; #[derive(Debug, Clone)] pub enum Program { @@ -12,12 +12,10 @@ pub enum Program { span: Span, }, - /// Structure: `s0 module elems trailing_comma` + /// Structure: `s0 module elems` Module { s0: Space, - elems: Vec<(Space, TableLitElem, Space)>, - /// `Some` if there is a trailing comma, `None` otherwise. - trailing_comma: Option, + elems: BoundedSeparated, span: Span, }, } @@ -25,8 +23,8 @@ pub enum Program { impl HasSpan for Program { fn span(&self) -> Span { match self { - Program::Expr { span, .. } => *span, - Program::Module { span, .. } => *span, + Self::Expr { span, .. } => *span, + Self::Module { span, .. } => *span, } } } diff --git a/src/ast/table_constr.rs b/src/ast/table_constr.rs index 0b699cc..38c409d 100644 --- a/src/ast/table_constr.rs +++ b/src/ast/table_constr.rs @@ -1,6 +1,6 @@ use crate::span::{HasSpan, Span}; -use super::{Expr, Space, TableLitElem}; +use super::{BoundedSeparated, Expr, Ident, Space, TableLitElem}; #[derive(Debug, Clone)] pub enum TableConstrElem { @@ -24,23 +24,34 @@ pub enum TableConstrElem { impl HasSpan for TableConstrElem { fn span(&self) -> Span { match self { - TableConstrElem::Lit(lit) => lit.span(), - TableConstrElem::Indexed { span, .. } => *span, + Self::Lit(lit) => lit.span(), + Self::Indexed { span, .. } => *span, } } } +impl TableConstrElem { + pub fn positional(value: Box) -> Self { + Self::Lit(TableLitElem::Positional(value)) + } + + pub fn named(name: Ident, value: Box, span: Span) -> Self { + Self::Lit(TableLitElem::named(name, value, span)) + } +} + /// `{ a, b, foo: c, [d]: e }` #[derive(Debug, Clone)] -pub struct TableConstr { - pub elems: Vec<(Space, TableConstrElem, Space)>, - /// `Some` if there is a trailing comma, `None` otherwise. - pub trailing_comma: Option, - pub span: Span, -} +pub struct TableConstr(pub BoundedSeparated); impl HasSpan for TableConstr { fn span(&self) -> Span { - self.span + self.0.span() + } +} + +impl TableConstr { + pub fn expr(self) -> Expr { + Expr::TableConstr(self) } } diff --git a/src/ast/table_destr.rs b/src/ast/table_destr.rs index 56c8d36..d94b4eb 100644 --- a/src/ast/table_destr.rs +++ b/src/ast/table_destr.rs @@ -1,6 +1,6 @@ use crate::span::{HasSpan, Span}; -use super::{Expr, Ident, Space}; +use super::{BoundedSeparated, Expr, Ident, Space}; // TODO Make table patterns recursive @@ -24,24 +24,21 @@ pub enum TablePatternElem { impl HasSpan for TablePatternElem { fn span(&self) -> Span { match self { - TablePatternElem::Positional(ident) => ident.span(), - TablePatternElem::Named { span, .. } => *span, + Self::Positional(ident) => ident.span(), + Self::Named { span, .. } => *span, } } } -/// `'{ foo, bar: baz }` +/// `{ foo, bar: baz }` +/// +/// Structure: `{ s0 elems s1 }` #[derive(Debug, Clone)] -pub struct TablePattern { - pub elems: Vec<(Space, TablePatternElem, Space)>, - /// `Some` if there is a trailing comma, `None` otherwise. - pub trailing_comma: Option, - pub span: Span, -} +pub struct TablePattern(pub BoundedSeparated); impl HasSpan for TablePattern { fn span(&self) -> Span { - self.span + self.0.span() } } @@ -64,3 +61,26 @@ impl HasSpan for TableDestr { self.span } } + +impl TableDestr { + pub fn new(local: bool, pattern: TablePattern, value: Box, span: Span) -> Self { + let local = if local { + Some(Space::empty(span)) + } else { + None + }; + + Self { + local, + pattern, + s0: Space::empty(span), + s1: Space::empty(span), + value, + span, + } + } + + pub fn expr(self) -> Expr { + Expr::TableDestr(self) + } +} diff --git a/src/ast/var.rs b/src/ast/var.rs index bc32e7a..d5f0eab 100644 --- a/src/ast/var.rs +++ b/src/ast/var.rs @@ -49,10 +49,61 @@ pub enum Var { impl HasSpan for Var { fn span(&self) -> Span { match self { - Var::Access { span, .. } => *span, - Var::Assign { span, .. } => *span, - Var::AccessIdent(ident) => ident.span(), - Var::AssignIdent { span, .. } => *span, + Self::Access { span, .. } => *span, + Self::Assign { span, .. } => *span, + Self::AccessIdent(ident) => ident.span(), + Self::AssignIdent { span, .. } => *span, } } } + +impl Var { + pub fn access(index: Box, span: Span) -> Self { + Self::Access { + s0: Space::empty(span), + index, + s1: Space::empty(span), + span, + } + } + + pub fn assign(local: bool, index: Box, value: Box, span: Span) -> Self { + let local = if local { + Some(Space::empty(span)) + } else { + None + }; + + Self::Assign { + local, + s0: Space::empty(span), + index, + s1: Space::empty(span), + s2: Space::empty(span), + s3: Space::empty(span), + value, + span, + } + } + + pub fn assign_ident(local: bool, name: Ident, value: Box, span: Span) -> Self { + let local = if local { + Some(Space::empty(span)) + } else { + None + }; + + Self::AssignIdent { + local, + name, + s0: Space::empty(span), + s1: Space::empty(span), + value, + span, + } + } + + pub fn expr(self) -> Expr { + Expr::Var(self) + } +} diff --git a/src/builtin.rs b/src/builtin.rs index f0e504d..6ef18c8 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -12,6 +12,21 @@ pub enum Builtin { Scope, Arg, Destructure, + Neg, + Not, + Mul, + Div, + Mod, + Add, + Sub, + Eq, + Ne, + Gt, + Ge, + Lt, + Le, + And, + Or, } impl fmt::Debug for Builtin { @@ -26,6 +41,21 @@ impl fmt::Debug for Builtin { Self::Scope => write!(f, "'scope"), Self::Arg => write!(f, "'arg"), Self::Destructure => write!(f, "'destructure"), + Self::Neg => write!(f, "'neg"), + Self::Not => write!(f, "'not"), + Self::Mul => write!(f, "'mul"), + Self::Div => write!(f, "'div"), + Self::Mod => write!(f, "'mod"), + Self::Add => write!(f, "'add"), + Self::Sub => write!(f, "'sub"), + Self::Eq => write!(f, "'eq"), + Self::Ne => write!(f, "'ne"), + Self::Gt => write!(f, "'gt"), + Self::Ge => write!(f, "'ge"), + Self::Lt => write!(f, "'lt"), + Self::Le => write!(f, "'le"), + Self::And => write!(f, "'and"), + Self::Or => write!(f, "'or"), } } } diff --git a/src/desugar.rs b/src/desugar.rs new file mode 100644 index 0000000..2be2a3f --- /dev/null +++ b/src/desugar.rs @@ -0,0 +1,10 @@ +mod basic; +mod call; +mod expr; +mod field; +mod func_def; +mod lit; +mod program; +mod table_constr; +mod table_destr; +mod var; diff --git a/src/desugar/basic.rs b/src/desugar/basic.rs new file mode 100644 index 0000000..156445f --- /dev/null +++ b/src/desugar/basic.rs @@ -0,0 +1,24 @@ +use crate::ast::BoundedSeparated; + +impl BoundedSeparated { + pub fn desugar(self, desugar_elem: impl Fn(E) -> (E, bool)) -> (Self, bool) { + let mut desugared = false; + let mut elems = vec![]; + for (s0, elem, s1) in self.elems { + if desugared { + elems.push((s0, elem, s1)); + } else { + let (elem, elem_desugared) = desugar_elem(elem); + desugared = desugared || elem_desugared; + elems.push((s0, elem, s1)); + } + } + + let new = Self { + elems, + trailing: self.trailing, + span: self.span, + }; + (new, desugared) + } +} diff --git a/src/desugar/call.rs b/src/desugar/call.rs new file mode 100644 index 0000000..c231875 --- /dev/null +++ b/src/desugar/call.rs @@ -0,0 +1,44 @@ +use crate::ast::{BoundedSeparated, Call, Expr, Ident, Lit, TableLitElem}; + +// TODO Add span for just the parentheses to ast, or limit span to parentheses + +impl Call { + pub fn desugar(self) -> (Expr, bool) { + match self { + Self::Arg { + expr, + s0: _, + s1: _, + arg, + s2: _, + span, + } => { + let new = BoundedSeparated::new(span) + .then(TableLitElem::named(Ident::new("call", span), expr, span)) + .then(TableLitElem::named(Ident::new("arg", span), arg, span)) + .table_lit(); + (new.lit().expr(), true) + } + + Self::NoArg { + expr, + s0: _, + s1: _, + span, + } => { + let new = Self::arg(expr, Lit::Nil(span).expr().boxed(), span); + (new.expr(), true) + } + + Self::Constr { + expr, + s0: _, + constr, + span, + } => { + let new = Self::arg(expr, constr.expr().boxed(), span); + (new.expr(), true) + } + } + } +} diff --git a/src/desugar/expr.rs b/src/desugar/expr.rs new file mode 100644 index 0000000..b7f4b08 --- /dev/null +++ b/src/desugar/expr.rs @@ -0,0 +1,78 @@ +use crate::ast::{BinOp, BoundedSeparated, Call, Expr, Lit, TableConstrElem}; +use crate::builtin::Builtin; + +impl Expr { + pub fn desugar(self) -> (Self, bool) { + match self { + Self::Lit(lit) => { + let (lit, desugared) = lit.desugar(); + (lit.expr(), desugared) + } + + Self::Call(call) => call.desugar(), + Self::Field(field) => field.desugar(), + Self::Var(var) => var.desugar(), + Self::TableConstr(constr) => constr.desugar(), + Self::TableDestr(destr) => destr.desugar(), + Self::FuncDef(def) => def.desugar(), + + Self::Paren { + s0: _, + inner, + s1: _, + span: _, + } => (*inner, true), + + Self::Neg { + minus, + s0: _, + expr, + span, + } => { + let new = Call::arg(Lit::Builtin(Builtin::Neg, minus).expr().boxed(), expr, span); + (new.expr(), true) + } + + Self::Not { + not, + s0: _, + expr, + span, + } => { + let new = Call::arg(Lit::Builtin(Builtin::Not, not).expr().boxed(), expr, span); + (new.expr(), true) + } + + Self::BinOp { + left, + s0: _, + op, + s1: _, + right, + span, + } => { + let builtin = match op { + BinOp::Mul => Builtin::Mul, + BinOp::Div => Builtin::Div, + BinOp::Mod => Builtin::Mod, + BinOp::Add => Builtin::Add, + BinOp::Sub => Builtin::Sub, + BinOp::Eq => Builtin::Eq, + BinOp::Neq => Builtin::Ne, + BinOp::Gt => Builtin::Gt, + BinOp::Ge => Builtin::Ge, + BinOp::Lt => Builtin::Lt, + BinOp::Le => Builtin::Le, + BinOp::And => Builtin::And, + BinOp::Or => Builtin::Or, + }; + let constr = BoundedSeparated::new(span) + .then(TableConstrElem::positional(left)) + .then(TableConstrElem::positional(right)) + .table_constr(); + let new = Call::constr(Lit::Builtin(builtin, span).expr().boxed(), constr, span); + (new.expr(), true) + } + } + } +} diff --git a/src/desugar/field.rs b/src/desugar/field.rs new file mode 100644 index 0000000..3c269e2 --- /dev/null +++ b/src/desugar/field.rs @@ -0,0 +1,86 @@ +use crate::ast::{BoundedSeparated, Call, Expr, Field, Lit, StringLit, TableConstrElem}; +use crate::builtin::Builtin; + +impl Field { + pub fn desugar(self) -> (Expr, bool) { + match self { + Self::Access { + expr, + s0: _, + s1: _, + index, + s2: _, + span, + } => { + let constr = BoundedSeparated::new(span) + .then(TableConstrElem::positional(expr)) + .then(TableConstrElem::positional(index)) + .table_constr(); + let new = Call::constr( + Lit::Builtin(Builtin::Get, span).expr().boxed(), + constr, + span, + ); + (new.expr(), true) + } + + Self::Assign { + expr, + s0: _, + s1: _, + index, + s2: _, + s3: _, + s4: _, + value, + span, + } => { + let constr = BoundedSeparated::new(span) + .then(TableConstrElem::positional(expr)) + .then(TableConstrElem::positional(index)) + .then(TableConstrElem::positional(value)) + .table_constr(); + let new = Call::constr( + Lit::Builtin(Builtin::Set, span).expr().boxed(), + constr, + span, + ); + (new.expr(), true) + } + + Self::AccessIdent { + expr, + s0: _, + s1: _, + ident, + span, + } => { + let new = Self::access( + expr, + StringLit::from_ident(ident).lit().expr().boxed(), + span, + ); + (new.expr(), true) + } + + Self::AssignIdent { + expr, + s0: _, + s1: _, + ident, + s2: _, + s3: _, + value, + span, + } => { + let new = Self::assign( + expr, + StringLit::from_ident(ident).lit().expr().boxed(), + value, + span, + ); + (new.expr(), true) + } + } + } +} diff --git a/src/desugar/func_def.rs b/src/desugar/func_def.rs new file mode 100644 index 0000000..2a549d1 --- /dev/null +++ b/src/desugar/func_def.rs @@ -0,0 +1,128 @@ +use crate::ast::{ + BoundedSeparated, Call, Expr, FuncDef, Ident, Lit, Space, TableConstrElem, TableDestr, + TableLitElem, Var, +}; +use crate::builtin::Builtin; + +impl FuncDef { + pub fn desugar(self) -> (Expr, bool) { + match self { + Self::AnonNoArg { + s0: _, + s1: _, + s2: _, + body, + span, + } => { + let quote = BoundedSeparated::new(span) + .then(TableLitElem::named(Ident::new("quote", span), body, span)) + .table_lit(); + let scope = Call::no_arg(Lit::Builtin(Builtin::Scope, span).expr().boxed(), span); + let new = BoundedSeparated::new(span) + .then(TableConstrElem::positional(Box::new(quote.lit().expr()))) + .then(TableConstrElem::named( + Ident::new("scope", span), + scope.expr().boxed(), + span, + )) + .table_constr(); + (new.expr(), true) + } + + Self::AnonArg { + s0: _, + s1: _, + arg, + s2: _, + s3: _, + body, + span, + } => { + let arg_call = Call::no_arg(Lit::Builtin(Builtin::Arg, span).expr().boxed(), span); + let arg_assign = Var::assign_ident(true, arg, arg_call.expr().boxed(), span); + let body = BoundedSeparated::new(span) + .then(TableLitElem::Positional(arg_assign.expr().boxed())) + .then(TableLitElem::Positional(body)) + .table_lit(); + let new = Self::AnonNoArg { + s0: Space::empty(span), + s1: Space::empty(span), + s2: Space::empty(span), + body: body.lit().expr().boxed(), + span, + }; + (new.expr(), true) + } + + Self::AnonDestr { + s0: _, + pattern, + s1: _, + body, + span, + } => { + let arg_call = Call::no_arg(Lit::Builtin(Builtin::Arg, span).expr().boxed(), span); + let arg_destr = TableDestr::new(true, pattern, arg_call.expr().boxed(), span); + let body = BoundedSeparated::new(span) + .then(TableLitElem::Positional(arg_destr.expr().boxed())) + .then(TableLitElem::Positional(body)) + .table_lit(); + let new = Self::AnonNoArg { + s0: Space::empty(span), + s1: Space::empty(span), + s2: Space::empty(span), + body: body.lit().expr().boxed(), + span, + }; + (new.expr(), true) + } + + Self::NamedNoArg { + local, + s0: _, + name, + s1: _, + s2: _, + s3: _, + body, + span, + } => { + let anon = Self::anon_no_arg(body, span); + let new = Var::assign_ident(local.is_some(), name, anon.expr().boxed(), span); + (new.expr(), true) + } + + Self::NamedArg { + local, + s0: _, + name, + s1: _, + s2: _, + arg, + s3: _, + s4: _, + body, + span, + } => { + let anon = Self::anon_arg(arg, body, span); + let new = Var::assign_ident(local.is_some(), name, anon.expr().boxed(), span); + (new.expr(), true) + } + + Self::NamedDestr { + local, + s0: _, + name, + s1: _, + pattern, + s2: _, + body, + span, + } => { + let anon = Self::anon_destr(pattern, body, span); + let new = Var::assign_ident(local.is_some(), name, anon.expr().boxed(), span); + (new.expr(), true) + } + } + } +} diff --git a/src/desugar/lit.rs b/src/desugar/lit.rs new file mode 100644 index 0000000..3b6fd80 --- /dev/null +++ b/src/desugar/lit.rs @@ -0,0 +1,60 @@ +use crate::ast::{Expr, Lit, TableLit, TableLitElem}; + +impl TableLitElem { + pub fn desugar(self) -> (Self, bool) { + match self { + Self::Positional(expr) => { + let (expr, desugared) = expr.desugar(); + (Self::Positional(expr.boxed()), desugared) + } + + Self::Named { + name, + s0, + s1, + value, + span, + } => { + let (value, desugared) = value.desugar(); + let new = Self::Named { + name, + s0, + s1, + value: value.boxed(), + span, + }; + (new, desugared) + } + } + } +} + +impl TableLit { + pub fn desugar(self) -> (Self, bool) { + let (elems, removed) = self.0.remove_map(|e| match e { + TableLitElem::Named { value, .. } if matches!(*value, Expr::Lit(Lit::Nil(_))) => { + Err(()) + } + e => Ok(e), + }); + if removed.is_empty() { + let (elems, desugared) = elems.desugar(|e| e.desugar()); + (elems.table_lit(), desugared) + } else { + (elems.table_lit(), true) + } + } +} + +impl Lit { + pub fn desugar(self) -> (Self, bool) { + match self { + Self::Table(table) => { + let (table, desugared) = table.desugar(); + (table.lit(), desugared) + } + + lit => (lit, false), + } + } +} diff --git a/src/desugar/program.rs b/src/desugar/program.rs new file mode 100644 index 0000000..615998b --- /dev/null +++ b/src/desugar/program.rs @@ -0,0 +1,25 @@ +use crate::ast::{Program, Space}; + +impl Program { + pub fn desugar(self) -> (Self, bool) { + match self { + Self::Expr { s0, expr, s1, span } => { + let (expr, desugared) = expr.desugar(); + let new = Self::Expr { s0, expr, s1, span }; + (new, desugared) + } + + Self::Module { s0, elems, span } => { + // `s0 module elems` + // -> `s0 table` + let new = Self::Expr { + s0, + expr: elems.table_lit().lit().expr(), + s1: Space::empty(span), + span, + }; + (new, true) + } + } + } +} diff --git a/src/desugar/table_constr.rs b/src/desugar/table_constr.rs new file mode 100644 index 0000000..263d9d4 --- /dev/null +++ b/src/desugar/table_constr.rs @@ -0,0 +1,39 @@ +use crate::ast::{ + BoundedSeparated, Expr, Field, Ident, TableConstr, TableConstrElem, TableLitElem, +}; +use crate::span::HasSpan; + +impl TableConstr { + pub fn desugar(self) -> (Expr, bool) { + let span = self.span(); + + let (elems, setters) = self.0.remove_map(|e| match e { + TableConstrElem::Lit(lit) => Ok(lit), + TableConstrElem::Indexed { + s0: _, + index, + s1: _, + s2: _, + s3: _, + value, + span, + } => Err((index, value, span)), + }); + + let mut expr = BoundedSeparated::new(span) + .then(TableLitElem::named( + Ident::new("raw", span), + elems.table_lit().lit().expr().boxed(), + span, + )) + .table_lit() + .lit() + .expr(); + + for (_, (index, value, span), _) in setters { + expr = Field::assign(expr.boxed(), index, value, span).expr(); + } + + (expr, true) + } +} diff --git a/src/desugar/table_destr.rs b/src/desugar/table_destr.rs new file mode 100644 index 0000000..2216977 --- /dev/null +++ b/src/desugar/table_destr.rs @@ -0,0 +1,63 @@ +use crate::ast::{ + BoundedSeparated, Call, Expr, Ident, Lit, StringLit, TableConstr, TableConstrElem, TableDestr, + TableLitElem, TablePattern, TablePatternElem, +}; +use crate::builtin::Builtin; + +fn pattern_to_constr(pattern: TablePattern) -> TableConstr { + pattern + .0 + .map(|e| match e { + TablePatternElem::Positional(ident) => { + TableConstrElem::positional(StringLit::from_ident(ident).lit().expr().boxed()) + } + + TablePatternElem::Named { + name, + s0, + s1, + ident, + span, + } => TableConstrElem::Lit(TableLitElem::Named { + name, + s0, + s1, + value: StringLit::from_ident(ident).lit().expr().boxed(), + span, + }), + }) + .table_constr() +} + +impl TableDestr { + pub fn desugar(self) -> (Expr, bool) { + let Self { + local, + pattern, + s0: _, + s1: _, + value, + span, + } = self; + + let mut constr = BoundedSeparated::new(span) + .then(TableConstrElem::positional( + pattern_to_constr(pattern).expr().boxed(), + )) + .then(TableConstrElem::positional(value)); + if local.is_some() { + constr = constr.then(TableConstrElem::named( + Ident::new("local", span), + Lit::Bool(true, span).expr().boxed(), + span, + )); + } + + let new = Call::constr( + Lit::Builtin(Builtin::Destructure, span).expr().boxed(), + constr.table_constr(), + span, + ); + (new.expr(), true) + } +} diff --git a/src/desugar/var.rs b/src/desugar/var.rs new file mode 100644 index 0000000..0ab0a76 --- /dev/null +++ b/src/desugar/var.rs @@ -0,0 +1,82 @@ +use crate::ast::{BoundedSeparated, Call, Expr, Field, Lit, StringLit, TableConstrElem, Var}; +use crate::builtin::Builtin; +use crate::span::HasSpan; + +impl Var { + pub fn desugar(self) -> (Expr, bool) { + match self { + Self::Access { + s0: _, + index, + s1: _, + span, + } => { + let scope = Call::no_arg(Lit::Builtin(Builtin::Scope, span).expr().boxed(), span); + let new = Field::access(scope.expr().boxed(), index, span); + (new.expr(), true) + } + + Self::Assign { + local: None, + s0: _, + index, + s1: _, + s2: _, + s3: _, + value, + span, + } => { + let scope = Call::no_arg(Lit::Builtin(Builtin::Scope, span).expr().boxed(), span); + let new = Field::assign(scope.expr().boxed(), index, value, span); + (new.expr(), true) + } + + Self::Assign { + local: Some(_), + s0: _, + index, + s1: _, + s2: _, + s3: _, + value, + span, + } => { + let scope = Call::no_arg(Lit::Builtin(Builtin::Scope, span).expr().boxed(), span); + let constr = BoundedSeparated::new(span) + .then(TableConstrElem::positional(scope.expr().boxed())) + .then(TableConstrElem::positional(index)) + .then(TableConstrElem::positional(value)) + .table_constr(); + let new = Call::constr( + Lit::Builtin(Builtin::SetRaw, span).expr().boxed(), + constr, + span, + ); + (new.expr(), true) + } + + Self::AccessIdent(name) => { + let span = name.span(); + let new = Self::access(StringLit::from_ident(name).lit().expr().boxed(), span); + (new.expr(), true) + } + + Self::AssignIdent { + local, + name, + s0: _, + s1: _, + value, + span, + } => { + let new = Self::assign( + local.is_some(), + StringLit::from_ident(name).lit().expr().boxed(), + value, + span, + ); + (new.expr(), true) + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 57f1850..0ac0aed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,30 @@ -use std::fs; -use std::path::PathBuf; +#![deny(unsafe_code)] +// Rustc lint groups +#![warn(future_incompatible)] +#![warn(rust_2018_idioms)] +// Rustc lints +#![warn(noop_method_call)] +#![warn(single_use_lifetimes)] +#![warn(trivial_numeric_casts)] +#![warn(unused_crate_dependencies)] +#![warn(unused_extern_crates)] +#![warn(unused_import_braces)] +#![warn(unused_lifetimes)] +#![warn(unused_qualifications)] +// Clippy lints +#![warn(clippy::use_self)] -use ::pretty::{Pretty, RcAllocator}; +use std::io::Write; +use std::path::PathBuf; +use std::{fs, process}; + +use anyhow::anyhow; use chumsky::Parser as _; use clap::Parser; mod ast; mod builtin; +mod desugar; mod parser; mod pretty; mod span; @@ -15,8 +33,19 @@ mod value; #[derive(Parser)] enum Command { - Parse { file: PathBuf }, - Pretty { file: PathBuf }, + Parse { + file: PathBuf, + }, + Pretty { + file: PathBuf, + }, + Desugar { + file: PathBuf, + #[arg(long, short, default_value = "diff")] + difftool: String, + #[arg(long, short = 'a')] + diffarg: Vec, + }, } #[derive(Parser)] @@ -43,24 +72,54 @@ fn main() -> anyhow::Result<()> { } } } + Command::Pretty { file } => { let content = fs::read_to_string(&file)?; let stream = span::stream_from_str(&content); - match parser::parser().parse(stream) { - Ok(program) => { - println!("Successful parse"); - let doc = program.pretty(&RcAllocator); - let mut out = vec![]; - doc.render(100, &mut out)?; - let str = String::from_utf8(out)?; - println!("{str}"); - } - Err(errs) => { - println!("Parsing failed"); - for err in errs { - println!("{err:?}"); - } + let program = parser::parser() + .parse(stream) + .map_err(|e| anyhow!("{e:?}"))?; + + print!("{}", pretty::pretty_to_string(program, 100)); + } + + Command::Desugar { + file, + difftool, + diffarg, + } => { + let content = fs::read_to_string(&file)?; + let stream = span::stream_from_str(&content); + let mut program = parser::parser() + .parse(stream) + .map_err(|e| anyhow!("{e:?}"))?; + + let mut builder = tempfile::Builder::new(); + builder.suffix(".tada"); + + let mut prev = builder.tempfile()?; + prev.write_all(pretty::pretty_to_string(program.clone(), 100).as_bytes())?; + prev.flush()?; + + loop { + let (new_program, desugared) = program.desugar(); + program = new_program; + if !desugared { + break; } + + let mut cur = builder.tempfile()?; + cur.write_all(pretty::pretty_to_string(program.clone(), 100).as_bytes())?; + cur.flush()?; + + process::Command::new(&difftool) + .args(&diffarg) + .arg(prev.path()) + .arg(cur.path()) + .spawn()? + .wait()?; + + prev = cur; } } } diff --git a/src/parser.rs b/src/parser.rs index 72d26c6..ea6fa99 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,6 +2,7 @@ //! //! # Rules //! +//! - Parsers must not consume surrounding whitespace. //! - Public parser functions must return [`basic::EParser`]. //! - Public parser functions must receive public subparsers via their arguments. //! - Each public parser function must be called exactly once, inside this file. @@ -17,7 +18,7 @@ mod basic; mod expr; -mod func_defs; +mod func_def; mod lit; mod prefix; mod program; @@ -51,7 +52,7 @@ pub fn parser() -> impl Parser { table_pattern.clone(), expr.clone(), ); - let func_def = func_defs::func_def( + let func_def = func_def::func_def( space.clone(), ident.clone(), local, diff --git a/src/parser/basic.rs b/src/parser/basic.rs index 8d7ba1d..9ca8d09 100644 --- a/src/parser/basic.rs +++ b/src/parser/basic.rs @@ -3,7 +3,7 @@ use chumsky::prelude::*; use chumsky::text::Character; -use crate::ast::{Ident, Line, Space}; +use crate::ast::{BoundedSeparated, Ident, Line, Space}; use crate::span::Span; pub type Error = Simple; @@ -58,3 +58,46 @@ pub fn ident() -> EParser { pub fn local(space: EParser) -> EParser> { text::keyword("local").ignore_then(space).or_not().boxed() } + +// This function is more of a utility function. Because of this and to keep the +// code nicer, I have decided that the rules specified in the `parser` module +// don't apply to it. +pub fn bounded_separated( + space: impl Parser + Clone + 'static, + start: impl Parser + 'static, + end: impl Parser + 'static, + separator: impl Parser + 'static, + elem: impl Parser + Clone + 'static, +) -> EParser> { + start + .ignore_then(space.clone()) + .then( + elem.clone() + .then(space.clone()) + .then_ignore(separator) + .then(space.clone()) + .repeated(), + ) + .then(elem.then(space).or_not()) + .then_ignore(end) + .map_with_span(|((s0, first_elems), last_elem), span| { + let mut space_before_elem = s0; + let mut elems = vec![]; + for ((elem, s1), s2) in first_elems { + elems.push((space_before_elem, elem, s1)); + space_before_elem = s2; + } + let trailing = if let Some((elem, s1)) = last_elem { + elems.push((space_before_elem, elem, s1)); + None + } else { + Some(space_before_elem) + }; + BoundedSeparated { + elems, + trailing, + span, + } + }) + .boxed() +} diff --git a/src/parser/expr.rs b/src/parser/expr.rs index 3bae445..bd2b373 100644 --- a/src/parser/expr.rs +++ b/src/parser/expr.rs @@ -18,7 +18,7 @@ fn atom_paren( .then_ignore(just(')')) .map_with_span(|((s0, inner), s1), span| Expr::Paren { s0, - inner: Box::new(inner), + inner: inner.boxed(), s1, span, }) @@ -63,11 +63,11 @@ fn left_assoc( over.then(op_over.repeated()) .foldl(|left, (s0, op, s1, right)| Expr::BinOp { span: left.span().join(right.span()), - left: Box::new(left), + left: left.boxed(), s0, op, s1, - right: Box::new(right), + right: right.boxed(), }) .boxed() } @@ -89,11 +89,11 @@ fn right_assoc( .then(over) .foldr(|(left, s0, op, s1), right| Expr::BinOp { span: left.span().join(right.span()), - left: Box::new(left), + left: left.boxed(), s0, op, s1, - right: Box::new(right), + right: right.boxed(), }) .boxed() } diff --git a/src/parser/func_defs.rs b/src/parser/func_def.rs similarity index 94% rename from src/parser/func_defs.rs rename to src/parser/func_def.rs index e290b95..3bb273d 100644 --- a/src/parser/func_defs.rs +++ b/src/parser/func_def.rs @@ -1,5 +1,3 @@ -// TODO Rename this module to func_def for consistency - use chumsky::prelude::*; use crate::ast::{Expr, FuncDef, Ident, Space, TablePattern}; @@ -21,7 +19,7 @@ fn func_def_anon_no_arg( s0, s1, s2, - body: Box::new(body), + body: body.boxed(), span, }) } @@ -47,7 +45,7 @@ fn func_def_anon_arg( arg, s2, s3, - body: Box::new(body), + body: body.boxed(), span, }, ) @@ -67,7 +65,7 @@ fn func_def_anon_destr( s0, pattern, s1, - body: Box::new(body), + body: body.boxed(), span, }) } @@ -96,7 +94,7 @@ fn func_def_named_no_arg( s1, s2, s3, - body: Box::new(body), + body: body.boxed(), span, }, ) @@ -130,7 +128,7 @@ fn func_def_named_arg( arg, s3, s4, - body: Box::new(body), + body: body.boxed(), span, }, ) @@ -159,7 +157,7 @@ fn func_def_named_destr( s1, pattern, s2, - body: Box::new(body), + body: body.boxed(), span, } }) diff --git a/src/parser/lit.rs b/src/parser/lit.rs index 7d04e1f..e368684 100644 --- a/src/parser/lit.rs +++ b/src/parser/lit.rs @@ -7,7 +7,7 @@ use crate::ast::{ }; use crate::builtin::Builtin; -use super::basic::{EParser, Error}; +use super::basic::{bounded_separated, EParser, Error}; fn builtin_lit() -> impl Parser { just('\'').ignore_then(choice(( @@ -132,7 +132,7 @@ pub fn table_lit_elem( ) -> EParser { let positional = expr .clone() - .map(|value| TableLitElem::Positional(Box::new(value))); + .map(|value| TableLitElem::Positional(value.boxed())); let named = ident .then(space.clone()) @@ -143,7 +143,7 @@ pub fn table_lit_elem( name, s0, s1, - value: Box::new(value), + value: value.boxed(), span, }); @@ -154,24 +154,14 @@ fn table_lit( space: EParser, table_lit_elem: EParser, ) -> impl Parser { - let elem = space - .clone() - .then(table_lit_elem) - .then(space.clone()) - .map(|((s0, elem), s1)| (s0, elem, s1)); - - let trailing_comma = just(',').ignore_then(space).or_not(); - - let elems = elem.separated_by(just(',')).then(trailing_comma); - - just("'{") - .ignore_then(elems) - .then_ignore(just('}')) - .map_with_span(|(elems, trailing_comma), span| TableLit { - elems, - trailing_comma, - span, - }) + bounded_separated( + space, + just("'{").to(()), + just('}').to(()), + just(',').to(()), + table_lit_elem, + ) + .map(TableLit) } pub fn lit(space: EParser, table_lit_elem: EParser) -> EParser { diff --git a/src/parser/prefix.rs b/src/parser/prefix.rs index ddae0bd..f53c78f 100644 --- a/src/parser/prefix.rs +++ b/src/parser/prefix.rs @@ -17,7 +17,7 @@ enum Prefix { impl Prefix { fn into_expr(self, span: Span, expr: Expr) -> Expr { - let expr = Box::new(expr); + let expr = expr.boxed(); match self { Self::Neg { minus, s0 } => Expr::Neg { minus, diff --git a/src/parser/program.rs b/src/parser/program.rs index 26e12fd..96c3cab 100644 --- a/src/parser/program.rs +++ b/src/parser/program.rs @@ -4,7 +4,7 @@ use chumsky::prelude::*; use crate::ast::{Expr, Program, Space, TableLitElem}; -use super::basic::EParser; +use super::basic::{bounded_separated, EParser}; pub fn program( space: EParser, @@ -17,22 +17,17 @@ pub fn program( .then(space.clone()) .map_with_span(|((s0, expr), s1), span| Program::Expr { s0, expr, s1, span }); - let elem = space - .clone() - .then(table_lit_elem) - .then(space.clone()) - .map(|((s0, elem), s1)| (s0, elem, s1)); - let trailing_comma = just(',').ignore_then(space.clone()).or_not(); let module = space + .clone() .then_ignore(text::keyword("module")) - .then(elem.separated_by(just(','))) - .then(trailing_comma) - .map_with_span(|((s0, elems), trailing_comma), span| Program::Module { - s0, - elems, - trailing_comma, - span, - }); + .then(bounded_separated( + space, + empty(), + empty(), + just(',').to(()), + table_lit_elem, + )) + .map_with_span(|(s0, elems), span| Program::Module { s0, elems, span }); module.or(lit).boxed() } diff --git a/src/parser/suffix.rs b/src/parser/suffix.rs index 6c10491..ca25830 100644 --- a/src/parser/suffix.rs +++ b/src/parser/suffix.rs @@ -57,32 +57,39 @@ enum Suffix { impl Suffix { fn into_expr(self, span: Span, expr: Expr) -> Expr { - let expr = Box::new(expr); + let expr = expr.boxed(); match self { - Suffix::CallArg { s0, s1, arg, s2 } => Expr::Call(Call::Arg { + Self::CallArg { s0, s1, arg, s2 } => Call::Arg { expr, s0, s1, arg, s2, span, - }), - Suffix::CallNoArg { s0, s1 } => Expr::Call(Call::NoArg { expr, s0, s1, span }), - Suffix::CallConstr { s0, constr } => Expr::Call(Call::Constr { + } + .expr(), + + Self::CallNoArg { s0, s1 } => Call::NoArg { expr, s0, s1, span }.expr(), + + Self::CallConstr { s0, constr } => Call::Constr { expr, s0, constr, span, - }), - Suffix::FieldAccess { s0, s1, index, s2 } => Expr::Field(Field::Access { + } + .expr(), + + Self::FieldAccess { s0, s1, index, s2 } => Field::Access { expr, s0, s1, index, s2, span, - }), - Suffix::FieldAssign { + } + .expr(), + + Self::FieldAssign { s0, s1, index, @@ -90,7 +97,7 @@ impl Suffix { s3, s4, value, - } => Expr::Field(Field::Assign { + } => Field::Assign { expr, s0, s1, @@ -100,22 +107,26 @@ impl Suffix { s4, value, span, - }), - Suffix::FieldAccessIdent { s0, s1, ident } => Expr::Field(Field::AccessIdent { + } + .expr(), + + Self::FieldAccessIdent { s0, s1, ident } => Field::AccessIdent { expr, s0, s1, ident, span, - }), - Suffix::FieldAssignIdent { + } + .expr(), + + Self::FieldAssignIdent { s0, s1, ident, s2, s3, value, - } => Expr::Field(Field::AssignIdent { + } => Field::AssignIdent { expr, s0, s1, @@ -124,7 +135,8 @@ impl Suffix { s3, value, span, - }), + } + .expr(), } } } @@ -143,7 +155,7 @@ fn suffix_call_arg( .map(|(((s0, s1), arg), s2)| Suffix::CallArg { s0, s1, - arg: Box::new(arg), + arg: arg.boxed(), s2, }) } @@ -180,7 +192,7 @@ fn suffix_field_access( .map(|(((s0, s1), index), s2)| Suffix::FieldAccess { s0, s1, - index: Box::new(index), + index: index.boxed(), s2, }) } @@ -204,11 +216,11 @@ fn suffix_field_assign( |((((((s0, s1), index), s2), s3), s4), value)| Suffix::FieldAssign { s0, s1, - index: Box::new(index), + index: index.boxed(), s2, s3, s4, - value: Box::new(value), + value: value.boxed(), }, ) } @@ -246,7 +258,7 @@ fn suffix_field_assign_ident( ident, s2, s3, - value: Box::new(value), + value: value.boxed(), }, ) } diff --git a/src/parser/table_constr.rs b/src/parser/table_constr.rs index ac0bb04..b294329 100644 --- a/src/parser/table_constr.rs +++ b/src/parser/table_constr.rs @@ -4,13 +4,13 @@ use chumsky::prelude::*; use crate::ast::{Expr, Space, TableConstr, TableConstrElem, TableLitElem}; -use super::basic::{EParser, Error}; +use super::basic::{bounded_separated, EParser, Error}; fn table_constr_elem( space: EParser, table_lit_elem: EParser, expr: EParser, -) -> impl Parser { +) -> impl Parser + Clone { let lit = table_lit_elem.map(TableConstrElem::Lit); let indexed = just('[') @@ -25,11 +25,11 @@ fn table_constr_elem( .map_with_span( |(((((s0, index), s1), s2), s3), value), span| TableConstrElem::Indexed { s0, - index: Box::new(index), + index: index.boxed(), s1, s2, s3, - value: Box::new(value), + value: value.boxed(), span, }, ); @@ -42,23 +42,14 @@ pub fn table_constr( table_lit_elem: EParser, expr: EParser, ) -> EParser { - let elem = space - .clone() - .then(table_constr_elem(space.clone(), table_lit_elem, expr)) - .then(space.clone()) - .map(|((s0, elem), s1)| (s0, elem, s1)); - - let trailing_comma = just(',').ignore_then(space).or_not(); - - let elems = elem.separated_by(just(',')).then(trailing_comma); - - just('{') - .ignore_then(elems) - .then_ignore(just('}')) - .map_with_span(|(elems, trailing_comma), span| TableConstr { - elems, - trailing_comma, - span, - }) - .boxed() + let elem = table_constr_elem(space.clone(), table_lit_elem, expr); + bounded_separated( + space, + just('{').to(()), + just('}').to(()), + just(',').to(()), + elem, + ) + .map(TableConstr) + .boxed() } diff --git a/src/parser/table_destr.rs b/src/parser/table_destr.rs index dfde71e..c215a19 100644 --- a/src/parser/table_destr.rs +++ b/src/parser/table_destr.rs @@ -4,12 +4,12 @@ use chumsky::prelude::*; use crate::ast::{Expr, Ident, Space, TableDestr, TablePattern, TablePatternElem}; -use super::basic::{EParser, Error}; +use super::basic::{bounded_separated, EParser, Error}; fn table_pattern_elem( space: EParser, ident: EParser, -) -> impl Parser { +) -> impl Parser + Clone { let positional = ident.clone().map(TablePatternElem::Positional); let named = ident @@ -30,25 +30,16 @@ fn table_pattern_elem( } pub fn table_pattern(space: EParser, ident: EParser) -> EParser { - let elem = space - .clone() - .then(table_pattern_elem(space.clone(), ident)) - .then(space.clone()) - .map(|((s0, elem), s1)| (s0, elem, s1)); - - let trailing_comma = just(',').ignore_then(space).or_not(); - - let elems = elem.separated_by(just(',')).then(trailing_comma); - - just('{') - .ignore_then(elems) - .then_ignore(just('}')) - .map_with_span(|(elems, trailing_comma), span| TablePattern { - elems, - trailing_comma, - span, - }) - .boxed() + let elem = table_pattern_elem(space.clone(), ident); + bounded_separated( + space, + just('{').to(()), + just('}').to(()), + just(',').to(()), + elem, + ) + .map(TablePattern) + .boxed() } pub fn table_destr( @@ -68,7 +59,7 @@ pub fn table_destr( pattern, s0, s1, - value: Box::new(value), + value: value.boxed(), span, }) .boxed() diff --git a/src/parser/var.rs b/src/parser/var.rs index 80722e1..c5806dc 100644 --- a/src/parser/var.rs +++ b/src/parser/var.rs @@ -14,7 +14,7 @@ fn var_access(space: EParser, expr: EParser) -> impl Parser> Pretty<'a, A> for Program { - fn pretty(self, allocator: &'a A) -> DocBuilder<'a, A, ()> { - allocator.text("Hello world") - } +mod basic; +mod call; +mod expr; +mod field; +mod func_def; +mod lit; +mod program; +mod table_constr; +mod table_destr; +mod var; + +const NEST_DEPTH: isize = 4; + +pub fn pretty_to_string>(p: P, width: usize) -> String { + let mut out = vec![]; + p.pretty(&RcAllocator) + .render(width, &mut out) + .expect("p could not be rendered"); + let mut s = String::from_utf8(out).expect("p created non-utf8 string"); + s.push('\n'); + s } diff --git a/src/pretty/basic.rs b/src/pretty/basic.rs new file mode 100644 index 0000000..abc55ee --- /dev/null +++ b/src/pretty/basic.rs @@ -0,0 +1,41 @@ +use pretty::{DocAllocator, DocBuilder, Pretty}; + +use crate::ast::{BoundedSeparated, Ident}; + +use super::NEST_DEPTH; + +impl<'a, D: DocAllocator<'a>> Pretty<'a, D> for Ident { + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + allocator.text(self.name) + } +} + +impl BoundedSeparated { + pub fn pretty<'a, D, FE>( + self, + allocator: &'a D, + start: DocBuilder<'a, D>, + end: DocBuilder<'a, D>, + separator: DocBuilder<'a, D>, + elem_pretty: FE, + ) -> DocBuilder<'a, D> + where + D: DocAllocator<'a>, + D::Doc: Clone, + FE: Fn(E) -> DocBuilder<'a, D>, + { + let elems_empty = self.elems.is_empty(); + allocator + .intersperse( + self.elems + .into_iter() + .map(|(s0, elem, s1)| allocator.line().append(elem_pretty(elem))), + separator.clone(), + ) + .append(self.trailing.filter(|_| !elems_empty).map(|s| separator)) + .nest(NEST_DEPTH) + .append(allocator.line()) + .enclose(start, end) + .group() + } +} diff --git a/src/pretty/call.rs b/src/pretty/call.rs new file mode 100644 index 0000000..6cf26fc --- /dev/null +++ b/src/pretty/call.rs @@ -0,0 +1,36 @@ +use pretty::{DocAllocator, DocBuilder, Pretty}; + +use crate::ast::Call; + +impl<'a, D> Pretty<'a, D> for Call +where + D: DocAllocator<'a>, + D::Doc: Clone, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + match self { + Self::Arg { + expr, + s0, + s1, + arg, + s2, + span: _, + } => expr + .pretty(allocator) + .append(arg.pretty(allocator).parens()), + Self::NoArg { + expr, + s0, + s1, + span: _, + } => expr.pretty(allocator).append(allocator.nil().parens()), + Self::Constr { + expr, + s0, + constr, + span: _, + } => expr.pretty(allocator).append(constr.pretty(allocator)), + } + } +} diff --git a/src/pretty/expr.rs b/src/pretty/expr.rs new file mode 100644 index 0000000..ddf7634 --- /dev/null +++ b/src/pretty/expr.rs @@ -0,0 +1,131 @@ +use pretty::{DocAllocator, DocBuilder, Pretty}; + +use crate::ast::{Assoc, BinOp, Expr, Field, Var}; + +impl<'a, D: DocAllocator<'a>> Pretty<'a, D> for BinOp { + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + allocator.text(match self { + Self::Mul => "*", + Self::Div => "/", + Self::Mod => "%", + Self::Add => "+", + Self::Sub => "-", + Self::Eq => "==", + Self::Neq => "!=", + Self::Gt => ">", + Self::Ge => ">=", + Self::Lt => "<", + Self::Le => "<=", + Self::And => "and", + Self::Or => "or", + }) + } +} + +impl<'a, D> Pretty<'a, D> for Expr +where + D: DocAllocator<'a>, + D::Doc: Clone, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + match self { + Self::Lit(lit) => lit.pretty(allocator), + Self::Call(call) => call.pretty(allocator), + Self::Field(field) => field.pretty(allocator), + Self::Var(var) => var.pretty(allocator), + Self::TableConstr(constr) => constr.pretty(allocator), + Self::TableDestr(destr) => destr.pretty(allocator), + Self::FuncDef(def) => def.pretty(allocator), + Self::Paren { + s0, + inner, + s1, + span: _, + } => inner.pretty(allocator).parens(), + + Self::Neg { + minus: _, + s0, + expr, + span: _, + } => { + let parenthesize = matches!(*expr, Self::BinOp { .. }); + let inner = expr.pretty(allocator); + allocator + .text("-") + .append(if parenthesize { inner.parens() } else { inner }) + } + + Self::Not { + not: _, + s0, + expr, + span: _, + } => { + let parenthesize = matches!(*expr, Self::BinOp { .. }); + let inner = expr.pretty(allocator); + allocator + .text("not ") + .append(if parenthesize { inner.parens() } else { inner }) + } + + // TODO Add newlines and group properly + Self::BinOp { + left, + s0, + op, + s1, + right, + span: _, + } => { + // If we're left-associative, then the left subexpression can be + // at the same precedence and the right subexpression must be at + // a higher precedence. + + // If we're right-associative, then the left subexpression must + // be at a higher precedence and the right subexpression can be + // at the same precedence. + + // Minimum precedence that the left subexpression can be at + // without requiring parentheses. + let min_left_prec = match op.assoc() { + Assoc::Left => op.precedence(), + Assoc::Right => op.precedence() + 1, + }; + + // Minimum precedence that the right subexpression can be at + // without requiring parentheses. + let min_right_prec = match op.assoc() { + Assoc::Left => op.precedence() + 1, + Assoc::Right => op.precedence(), + }; + + let left_paren = match *left { + // These end with an arbitrary expression on the right. If + // we don't add parentheses, we'll be assimilated into that + // expression. + Self::Field(Field::Assign { .. } | Field::AssignIdent { .. }) => true, + Self::Var(Var::Assign { .. } | Var::AssignIdent { .. }) => true, + + Self::BinOp { op, .. } if op.precedence() < min_left_prec => true, + + _ => false, + }; + + let right_paren = + matches!(*right, Self::BinOp { op, .. } if op.precedence() < min_right_prec); + + let left = left.pretty(allocator); + let left = if left_paren { left.parens() } else { left }; + + let right = right.pretty(allocator); + let right = if right_paren { right.parens() } else { right }; + + left.append(allocator.space()) + .append(op.pretty(allocator)) + .append(allocator.space()) + .append(right) + } + } + } +} diff --git a/src/pretty/field.rs b/src/pretty/field.rs new file mode 100644 index 0000000..eb547f0 --- /dev/null +++ b/src/pretty/field.rs @@ -0,0 +1,68 @@ +use pretty::{DocAllocator, DocBuilder, Pretty}; + +use crate::ast::Field; + +impl<'a, D> Pretty<'a, D> for Field +where + D: DocAllocator<'a>, + D::Doc: Clone, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + match self { + Self::Access { + expr, + s0, + s1, + index, + s2, + span: _, + } => expr + .pretty(allocator) + .append(index.pretty(allocator).brackets()), + Self::Assign { + expr, + s0, + s1, + index, + s2, + s3, + s4, + value, + span: _, + } => expr + .pretty(allocator) + .append(index.pretty(allocator).brackets()) + .append(allocator.text(" = ")) + .append(value.pretty(allocator)), + Self::AccessIdent { + expr, + s0, + s1, + ident, + span: _, + } => expr + .pretty(allocator) + .append(allocator.line_()) + .append(allocator.text(".")) + .append(ident.pretty(allocator)) + .group(), + Self::AssignIdent { + expr, + s0, + s1, + ident, + s2, + s3, + value, + span: _, + } => expr + .pretty(allocator) + .append(allocator.line_()) + .append(allocator.text(".")) + .append(ident.pretty(allocator)) + .append(allocator.text(" = ")) + .append(value.pretty(allocator)) + .group(), + } + } +} diff --git a/src/pretty/func_def.rs b/src/pretty/func_def.rs new file mode 100644 index 0000000..dce23e6 --- /dev/null +++ b/src/pretty/func_def.rs @@ -0,0 +1,102 @@ +use pretty::{DocAllocator, DocBuilder, Pretty}; + +use crate::ast::FuncDef; + +impl<'a, D> Pretty<'a, D> for FuncDef +where + D: DocAllocator<'a>, + D::Doc: Clone, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + match self { + Self::AnonNoArg { + s0, + s1, + s2, + body, + span: _, + } => allocator.text("function() ").append(body.pretty(allocator)), + + Self::AnonArg { + s0, + s1, + arg, + s2, + s3, + body, + span: _, + } => allocator + .text("function") + .append(arg.pretty(allocator).parens()) + .append(allocator.space()) + .append(body.pretty(allocator)), + + Self::AnonDestr { + s0, + pattern, + s1, + body, + span: _, + } => allocator + .text("function") + .append(pattern.pretty(allocator)) + .append(allocator.space()) + .append(body.pretty(allocator)), + + Self::NamedNoArg { + local, + s0, + name, + s1, + s2, + s3, + body, + span: _, + } => local + .map(|s| allocator.text("local ")) + .unwrap_or_else(|| allocator.nil()) + .append(allocator.text("function ")) + .append(name) + .append(allocator.text("() ")) + .append(body.pretty(allocator)), + + Self::NamedArg { + local, + s0, + name, + s1, + s2, + arg, + s3, + s4, + body, + span: _, + } => local + .map(|s| allocator.text("local ")) + .unwrap_or_else(|| allocator.nil()) + .append(allocator.text("function ")) + .append(name) + .append(arg.pretty(allocator).parens()) + .append(allocator.space()) + .append(body.pretty(allocator)), + + Self::NamedDestr { + local, + s0, + name, + s1, + pattern, + s2, + body, + span: _, + } => local + .map(|s| allocator.text("local ")) + .unwrap_or_else(|| allocator.nil()) + .append(allocator.text("function ")) + .append(name) + .append(pattern.pretty(allocator)) + .append(allocator.space()) + .append(body.pretty(allocator)), + } + } +} diff --git a/src/pretty/lit.rs b/src/pretty/lit.rs new file mode 100644 index 0000000..84f3055 --- /dev/null +++ b/src/pretty/lit.rs @@ -0,0 +1,87 @@ +use pretty::{DocAllocator, DocBuilder, Pretty}; + +use crate::ast::{Lit, NumLit, StringLit, StringLitElem, TableLit, TableLitElem}; + +impl<'a, D: DocAllocator<'a>> Pretty<'a, D> for NumLit { + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + allocator.text(format!("{self:?}")) + } +} + +impl<'a, D: DocAllocator<'a>> Pretty<'a, D> for StringLitElem { + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + match self { + Self::Plain(str) => allocator.text(str), + Self::Unicode(char) => allocator.text(format!("\\u{{{:x}}}", char as u32)), + Self::Backslash => allocator.text("\\\\"), + Self::DoubleQuote => allocator.text("\\\""), + Self::Tab => allocator.text("\\t"), + Self::CarriageReturn => allocator.text("\\r"), + Self::Newline => allocator.text("\\n"), + } + } +} + +impl<'a, D: DocAllocator<'a>> Pretty<'a, D> for StringLit { + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + allocator + .concat(self.elems.into_iter().map(|e| e.pretty(allocator))) + .enclose(allocator.text("\""), allocator.text("\"")) + } +} + +impl<'a, D> Pretty<'a, D> for TableLitElem +where + D: DocAllocator<'a>, + D::Doc: Clone, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + match self { + Self::Positional(expr) => expr.pretty(allocator), + Self::Named { + name, + s0, + s1, + value, + span: _, + } => name + .pretty(allocator) + .append(allocator.text(": ")) + .append(value.pretty(allocator)), + } + } +} + +impl<'a, D> Pretty<'a, D> for TableLit +where + D: DocAllocator<'a>, + D::Doc: Clone, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + self.0.pretty( + allocator, + allocator.text("'{"), + allocator.text("}"), + allocator.text(","), + |e| e.pretty(allocator), + ) + } +} + +impl<'a, D> Pretty<'a, D> for Lit +where + D: DocAllocator<'a>, + D::Doc: Clone, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + match self { + Self::Nil(_) => allocator.text("nil"), + Self::Bool(false, _) => allocator.text("false"), + Self::Bool(true, _) => allocator.text("true"), + Self::Builtin(builtin, _) => allocator.text(format!("{builtin:?}")), + Self::Num(num) => num.pretty(allocator), + Self::String(string) => string.pretty(allocator), + Self::Table(table) => table.pretty(allocator), + } + } +} diff --git a/src/pretty/program.rs b/src/pretty/program.rs new file mode 100644 index 0000000..1ff7863 --- /dev/null +++ b/src/pretty/program.rs @@ -0,0 +1,33 @@ +use pretty::{DocAllocator, DocBuilder, Pretty}; + +use crate::ast::Program; + +impl<'a, D> Pretty<'a, D> for Program +where + D: DocAllocator<'a>, + D::Doc: Clone, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + match self { + Self::Expr { + s0, + expr, + s1, + span: _, + } => expr.pretty(allocator), + + Self::Module { s0, elems, span: _ } => { + allocator.text("module").append(allocator.line()).append( + allocator + .intersperse( + elems.elems.into_iter().map(|(s0, elem, s1)| { + allocator.line().append(elem.pretty(allocator)) + }), + allocator.text(","), + ) + .append(elems.trailing.map(|s| allocator.text(","))), + ) + } + } + } +} diff --git a/src/pretty/table_constr.rs b/src/pretty/table_constr.rs new file mode 100644 index 0000000..3b3fdb1 --- /dev/null +++ b/src/pretty/table_constr.rs @@ -0,0 +1,44 @@ +use pretty::{DocAllocator, DocBuilder, Pretty}; + +use crate::ast::{TableConstr, TableConstrElem}; + +impl<'a, D> Pretty<'a, D> for TableConstrElem +where + D: DocAllocator<'a>, + D::Doc: Clone, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + match self { + Self::Lit(lit) => lit.pretty(allocator), + Self::Indexed { + s0, + index, + s1, + s2, + s3, + value, + span: _, + } => index + .pretty(allocator) + .brackets() + .append(allocator.text(": ")) + .append(value.pretty(allocator)), + } + } +} + +impl<'a, D> Pretty<'a, D> for TableConstr +where + D: DocAllocator<'a>, + D::Doc: Clone, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + self.0.pretty( + allocator, + allocator.text("{"), + allocator.text("}"), + allocator.text(","), + |e| e.pretty(allocator), + ) + } +} diff --git a/src/pretty/table_destr.rs b/src/pretty/table_destr.rs new file mode 100644 index 0000000..777d0ae --- /dev/null +++ b/src/pretty/table_destr.rs @@ -0,0 +1,53 @@ +use pretty::{DocAllocator, DocBuilder, Pretty}; + +use crate::ast::{TableDestr, TablePattern, TablePatternElem}; + +impl<'a, D: DocAllocator<'a>> Pretty<'a, D> for TablePatternElem { + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + match self { + Self::Positional(ident) => ident.pretty(allocator), + Self::Named { + name, + s0, + s1, + ident, + span: _, + } => name + .pretty(allocator) + .append(allocator.text(": ")) + .append(ident.pretty(allocator)), + } + } +} + +impl<'a, D> Pretty<'a, D> for TablePattern +where + D: DocAllocator<'a>, + D::Doc: Clone, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + self.0.pretty( + allocator, + allocator.text("{"), + allocator.text("}"), + allocator.text(","), + |e| e.pretty(allocator), + ) + } +} + +impl<'a, D> Pretty<'a, D> for TableDestr +where + D: DocAllocator<'a>, + D::Doc: Clone, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + // TODO Handle spaces + self.local + .map(|s| allocator.text("local ")) + .unwrap_or_else(|| allocator.nil()) + .append(self.pattern.pretty(allocator)) + .append(allocator.text(" = ")) + .append(self.value.pretty(allocator)) + } +} diff --git a/src/pretty/var.rs b/src/pretty/var.rs new file mode 100644 index 0000000..662b34e --- /dev/null +++ b/src/pretty/var.rs @@ -0,0 +1,49 @@ +use pretty::{DocAllocator, DocBuilder, Pretty}; + +use crate::ast::Var; + +impl<'a, D> Pretty<'a, D> for Var +where + D: DocAllocator<'a>, + D::Doc: Clone, +{ + fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> { + match self { + Self::Access { + s0, + index, + s1, + span: _, + } => index.pretty(allocator).brackets(), + Self::Assign { + local, + s0, + index, + s1, + s2, + s3, + value, + span: _, + } => local + .map(|s| allocator.text("local ")) + .unwrap_or_else(|| allocator.nil()) + .append(index.pretty(allocator).brackets()) + .append(allocator.text(" = ")) + .append(value.pretty(allocator)), + Self::AccessIdent(ident) => ident.pretty(allocator), + Self::AssignIdent { + local, + name, + s0, + s1, + value, + span: _, + } => local + .map(|s| allocator.text("local ")) + .unwrap_or_else(|| allocator.nil()) + .append(name.pretty(allocator)) + .append(allocator.text(" = ")) + .append(value.pretty(allocator)), + } + } +}