Compare commits

...
Sign in to create a new pull request.

66 commits

Author SHA1 Message Date
802d776f29 Include trailing newline in pretty print output 2022-11-22 23:14:07 +01:00
af4311ba98 Remove named nils as early as possible 2022-11-22 21:56:50 +01:00
f6c9835e29 Hide warnings in pretty module for now 2022-11-22 21:50:20 +01:00
736174d470 Desugar away named nil in table literals 2022-11-22 17:49:12 +01:00
39099037a5 Remove unused functions 2022-11-22 17:39:56 +01:00
f8fa044259 Desugar all expressions 2022-11-22 17:39:02 +01:00
26dc3c3469 Desugar all function definitions 2022-11-22 17:27:06 +01:00
40c28d9496 Simplify creating Var 2022-11-22 17:14:52 +01:00
6f7683ad1e Simplify creating Field 2022-11-22 17:06:56 +01:00
009be99aaa Simplify creating Call 2022-11-22 16:55:27 +01:00
78d08968eb Move BoundedSeparated helper functions into ast 2022-11-22 16:45:31 +01:00
58106c4c5a Simplify creating TableConstrElem 2022-11-22 16:44:33 +01:00
c191486864 Simplify creating TableLitElem 2022-11-22 16:35:16 +01:00
45caafdc38 Simplify creating Lit 2022-11-22 16:29:31 +01:00
fafc567447 Simplify creating Expr 2022-11-22 16:29:21 +01:00
42369628b6 Simplify boxing Expr 2022-11-22 15:32:23 +01:00
5a977e6dde Simplify creating TableLit and TableConstr 2022-11-22 15:21:40 +01:00
c769d9e16f Simplify creating BoundedSeparated
Along with this comes the decision to no longer track whitespace and
comments across desugarings in most cases. The main reason for this
decision is that not having to track whitespace simplifies the code and
reasoning necessary.

While implementing desugarings, I also realized that the current comment
model doesn't let me accurately track whitespace across desugarings. I'm
fairly sure I haven't yet found a good model for whitespace and
comments. Once I find one, adding pretty printing support for those
should hopefully be easy though.
2022-11-22 15:21:35 +01:00
d6a0bbf2af Desugar simple anonymous function definitions 2022-11-22 12:56:09 +01:00
830ffa92c4 Desugar table destructuring 2022-11-22 10:32:27 +01:00
d4797c5894 Simplify creating string literal from ident 2022-11-22 10:32:17 +01:00
af6c171eb4 Desugar table constructors 2022-11-22 09:44:29 +01:00
74d1f640b5 Fix pretty printing of empty BoundedSeparated 2022-11-22 09:21:44 +01:00
94ea196933 Remove Separated 2022-11-22 00:00:43 +01:00
198f56226e Switch TablePattern to BoundedSeparated 2022-11-21 23:57:58 +01:00
0e9cfd67c2 Switch TablConstr to BoundedSeparated 2022-11-21 23:54:40 +01:00
e3fa3500d4 Switch TableLit and Program to BoundedSeparated 2022-11-21 23:54:30 +01:00
a1867fdc4e Add BoundedSeparated with parser and printer
This type should be able to replace Separated (with a bit of effort).

The hope is that this type is a better representation for table-like
syntax with optional trailing delimiter that will let me remove_map
elements without too much difficulty. This is necessary for desugaring
table constructors.
2022-11-21 22:56:50 +01:00
9591b23082 Fix span of field ident string expression 2022-11-21 15:14:28 +01:00
ca979edd7c Desugar variable access and assignment 2022-11-21 15:14:18 +01:00
27ba13e8a2 Desugar field assignment via ident 2022-11-21 14:44:15 +01:00
621ea8f1d4 Desugar field access via ident 2022-11-21 14:38:45 +01:00
290cef06cb Desugar from the outside in 2022-11-21 14:35:10 +01:00
ebc48fff5a Desugar field assignment 2022-11-21 14:05:07 +01:00
3f8320941a Desugar field access 2022-11-21 13:49:37 +01:00
b84d5ae0c8 Create new objects with existing span
I've decided that attempting to create smaller spans is more difficult
and will also lead to worse error messages later on. It makes more sense
to just tag every newly created syntax tree element with the span of the
element it comes from.
2022-11-21 13:36:21 +01:00
5c8dd1969f Desugar function calls with argument 2022-11-21 13:35:36 +01:00
7bfaebc05f Desugar function calls with table constructor 2022-11-21 13:07:10 +01:00
29aa474b6a Use diff tool to visualize desugaring changes 2022-11-21 13:01:15 +01:00
a3d6efcaec Desugar function calls without args 2022-11-21 12:35:53 +01:00
52c1aeba35 Desugar literals 2022-11-21 12:05:24 +01:00
8278442d3f Expand expression desugaring 2022-11-21 11:43:31 +01:00
13b7db79b0 Desugar separated elements 2022-11-21 11:35:58 +01:00
8b21acac9e Add desugar command and desugar programs 2022-11-21 09:33:34 +01:00
6eee1ba930 Simplify pretty printing separated elements 2022-11-21 00:49:04 +01:00
9d6cd580d4 Pretty print module elems 2022-11-21 00:17:51 +01:00
b3eaa40902 Pretty print function definitions 2022-11-21 00:16:15 +01:00
c45a45f0b6 Rename parser::func_defs to parser::func_def 2022-11-21 00:08:59 +01:00
e7416fbc1e Pretty print table destructors 2022-11-21 00:07:57 +01:00
c7fc8584ff Pretty print variable accesses and assignments 2022-11-21 00:02:12 +01:00
5bd43ee37a Pretty print field accesses and assignments 2022-11-20 23:50:53 +01:00
81e2a28b06 Pretty print function calls 2022-11-20 23:45:28 +01:00
fc139c31f4 Pretty print table constructors 2022-11-20 23:41:39 +01:00
412eaffc07 Pretty print table literals 2022-11-20 23:33:39 +01:00
f91e8ac9a2 Pretty print string literals 2022-11-20 23:06:04 +01:00
e6bbb37323 Pretty print numeric literals 2022-11-20 22:59:20 +01:00
7833ef533d Place parentheses in expressions when necessary 2022-11-20 22:53:19 +01:00
4156006ada Pretty print literals partially 2022-11-20 22:22:36 +01:00
03e7f10739 Satisfy warnings 2022-11-20 22:16:10 +01:00
3ed3c4e8f8 Add warnings from cove 2022-11-20 22:16:10 +01:00
2ba56f0c92 Make code generic over allocator
This way, I can use the same code with any allocator I want. More
importantly, I can also use the nice DocBuilder-exclusive helper
functions!
2022-11-20 21:41:57 +01:00
1a3772e6f7 Pretty print expressions partially 2022-11-20 21:19:54 +01:00
23796e53a9 Pretty print program elements 2022-11-20 21:11:38 +01:00
200b653e61 Pretty print programs partially 2022-11-20 21:09:59 +01:00
1b364061e4 Make rule about consuming surrounding whitespace 2022-11-20 21:09:59 +01:00
6533c9dcf7 Handle things separated by things differently
I noticed that programs like '{} would parse correctly while '{ } would
expect an inner element. This was because the leading space was actually
part of the element parser, which is a violation of the (as of yet
unspoken) rule that parsers should not parse surrounding whitespace.

Because whitespace whas treated differently from everywhere else and
because this implementation was wrong, I decided to reimplement it,
abstracting the concept of things separated by other things with
optional trailing things. I did this in such a way that surrounding
whitespace is not touched.
2022-11-20 21:09:59 +01:00
47 changed files with 2046 additions and 224 deletions

51
Cargo.lock generated
View file

@ -120,6 +120,15 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "fastrand"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
dependencies = [
"instant",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.8" version = "0.2.8"
@ -146,6 +155,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.137" version = "0.2.137"
@ -233,6 +251,24 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"
@ -258,6 +294,21 @@ dependencies = [
"chumsky", "chumsky",
"clap", "clap",
"pretty", "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]] [[package]]

View file

@ -8,3 +8,4 @@ anyhow = "1.0.66"
chumsky = "0.8.0" chumsky = "0.8.0"
clap = { version = "4.0.26", features = ["derive", "deprecated"] } clap = { version = "4.0.26", features = ["derive", "deprecated"] }
pretty = "0.11.3" pretty = "0.11.3"
tempfile = "3.3.0"

View file

@ -2,6 +2,8 @@ use std::fmt;
use crate::span::{HasSpan, Span}; use crate::span::{HasSpan, Span};
use super::{TableConstr, TableConstrElem, TableLit, TableLitElem};
#[derive(Clone)] #[derive(Clone)]
pub enum Line { pub enum Line {
Empty, Empty,
@ -30,12 +32,30 @@ impl HasSpan for Space {
} }
} }
impl Space {
pub fn empty(span: Span) -> Self {
Self {
lines: vec![],
span,
}
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct Ident { pub struct Ident {
pub name: String, pub name: String,
pub span: Span, pub span: Span,
} }
impl Ident {
pub fn new<S: ToString>(name: S, span: Span) -> Self {
Self {
name: name.to_string(),
span,
}
}
}
impl fmt::Debug for Ident { impl fmt::Debug for Ident {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "i#{}", self.name) write!(f, "i#{}", self.name)
@ -47,3 +67,78 @@ impl HasSpan for Ident {
self.span self.span
} }
} }
#[derive(Debug, Clone)]
pub struct BoundedSeparated<E> {
pub elems: Vec<(Space, E, Space)>,
pub trailing: Option<Space>,
pub span: Span,
}
impl<E> HasSpan for BoundedSeparated<E> {
fn span(&self) -> Span {
self.span
}
}
impl<E> BoundedSeparated<E> {
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<E2>(self, f: impl Fn(E) -> E2) -> BoundedSeparated<E2> {
let elems = self
.elems
.into_iter()
.map(|(s0, e, s1)| (s0, f(e), s1))
.collect::<Vec<_>>();
BoundedSeparated {
elems,
trailing: self.trailing,
span: self.span,
}
}
pub fn remove_map<E1, E2>(
self,
f: impl Fn(E) -> Result<E1, E2>,
) -> (BoundedSeparated<E1>, 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<TableLitElem> {
pub fn table_lit(self) -> TableLit {
TableLit(self)
}
}
impl BoundedSeparated<TableConstrElem> {
pub fn table_constr(self) -> TableConstr {
TableConstr(self)
}
}

View file

@ -40,9 +40,44 @@ pub enum Call {
impl HasSpan for Call { impl HasSpan for Call {
fn span(&self) -> Span { fn span(&self) -> Span {
match self { match self {
Call::Arg { span, .. } => *span, Self::Arg { span, .. } => *span,
Call::NoArg { span, .. } => *span, Self::NoArg { span, .. } => *span,
Call::Constr { span, .. } => *span, Self::Constr { span, .. } => *span,
} }
} }
} }
impl Call {
pub fn arg(base: Box<Expr>, arg: Box<Expr>, 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<Expr>, span: Span) -> Self {
Self::NoArg {
expr: base,
s0: Space::empty(span),
s1: Space::empty(span),
span,
}
}
pub fn constr(base: Box<Expr>, constr: TableConstr, span: Span) -> Self {
Self::Constr {
expr: base,
s0: Space::empty(span),
constr,
span,
}
}
pub fn expr(self) -> Expr {
Expr::Call(self)
}
}

View file

@ -4,18 +4,30 @@ use crate::span::{HasSpan, Span};
use super::{Call, Field, FuncDef, Lit, Space, TableConstr, TableDestr, Var}; 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)] #[derive(Debug, Clone, Copy)]
pub enum BinOp { pub enum BinOp {
/// `+`
Add,
/// `-`
Sub,
/// `*` /// `*`
Mul, Mul,
/// `/` /// `/`
Div, Div,
/// `%` /// `%`
Mod, Mod,
/// `+`
Add,
/// `-`
Sub,
/// `==` /// `==`
Eq, Eq,
/// `!=` /// `!=`
@ -34,6 +46,36 @@ pub enum BinOp {
Or, 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)] #[derive(Clone)]
pub enum Expr { pub enum Expr {
Lit(Lit), Lit(Lit),
@ -186,17 +228,23 @@ impl fmt::Debug for Expr {
impl HasSpan for Expr { impl HasSpan for Expr {
fn span(&self) -> Span { fn span(&self) -> Span {
match self { match self {
Expr::Lit(lit) => lit.span(), Self::Lit(lit) => lit.span(),
Expr::Call(call) => call.span(), Self::Call(call) => call.span(),
Expr::Field(field) => field.span(), Self::Field(field) => field.span(),
Expr::Var(var) => var.span(), Self::Var(var) => var.span(),
Expr::TableConstr(constr) => constr.span(), Self::TableConstr(constr) => constr.span(),
Expr::TableDestr(destr) => destr.span(), Self::TableDestr(destr) => destr.span(),
Expr::FuncDef(def) => def.span(), Self::FuncDef(def) => def.span(),
Expr::Paren { span, .. } => *span, Self::Paren { span, .. } => *span,
Expr::Neg { span, .. } => *span, Self::Neg { span, .. } => *span,
Expr::Not { span, .. } => *span, Self::Not { span, .. } => *span,
Expr::BinOp { span, .. } => *span, Self::BinOp { span, .. } => *span,
} }
} }
} }
impl Expr {
pub fn boxed(self) -> Box<Self> {
Box::new(self)
}
}

View file

@ -60,10 +60,41 @@ pub enum Field {
impl HasSpan for Field { impl HasSpan for Field {
fn span(&self) -> Span { fn span(&self) -> Span {
match self { match self {
Field::Access { span, .. } => *span, Self::Access { span, .. } => *span,
Field::Assign { span, .. } => *span, Self::Assign { span, .. } => *span,
Field::AccessIdent { span, .. } => *span, Self::AccessIdent { span, .. } => *span,
Field::AssignIdent { span, .. } => *span, Self::AssignIdent { span, .. } => *span,
} }
} }
} }
impl Field {
pub fn access(base: Box<Expr>, index: Box<Expr>, 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<Expr>, index: Box<Expr>, value: Box<Expr>, 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)
}
}

View file

@ -90,12 +90,50 @@ pub enum FuncDef {
impl HasSpan for FuncDef { impl HasSpan for FuncDef {
fn span(&self) -> Span { fn span(&self) -> Span {
match self { match self {
FuncDef::AnonNoArg { span, .. } => *span, Self::AnonNoArg { span, .. } => *span,
FuncDef::AnonArg { span, .. } => *span, Self::AnonArg { span, .. } => *span,
FuncDef::AnonDestr { span, .. } => *span, Self::AnonDestr { span, .. } => *span,
FuncDef::NamedNoArg { span, .. } => *span, Self::NamedNoArg { span, .. } => *span,
FuncDef::NamedArg { span, .. } => *span, Self::NamedArg { span, .. } => *span,
FuncDef::NamedDestr { span, .. } => *span, Self::NamedDestr { span, .. } => *span,
} }
} }
} }
impl FuncDef {
pub fn anon_no_arg(body: Box<Expr>, 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<Expr>, 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<Expr>, span: Span) -> Self {
Self::AnonDestr {
s0: Space::empty(span),
pattern,
s1: Space::empty(span),
body,
span,
}
}
pub fn expr(self) -> Expr {
Expr::FuncDef(self)
}
}

View file

@ -3,7 +3,7 @@ use std::fmt;
use crate::builtin::Builtin; use crate::builtin::Builtin;
use crate::span::{HasSpan, Span}; use crate::span::{HasSpan, Span};
use super::{Expr, Ident, Space}; use super::{BoundedSeparated, Expr, Ident, Space};
#[derive(Clone)] #[derive(Clone)]
pub enum NumLitStr { pub enum NumLitStr {
@ -93,6 +93,19 @@ pub struct StringLit {
pub span: Span, 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 { impl HasSpan for StringLit {
fn span(&self) -> Span { fn span(&self) -> Span {
self.span self.span
@ -119,24 +132,37 @@ pub enum TableLitElem {
impl HasSpan for TableLitElem { impl HasSpan for TableLitElem {
fn span(&self) -> Span { fn span(&self) -> Span {
match self { match self {
TableLitElem::Positional(value) => value.span(), Self::Positional(value) => value.span(),
TableLitElem::Named { span, .. } => *span, Self::Named { span, .. } => *span,
}
}
}
impl TableLitElem {
pub fn named(name: Ident, value: Box<Expr>, span: Span) -> Self {
Self::Named {
name,
s0: Space::empty(span),
s1: Space::empty(span),
value,
span,
} }
} }
} }
/// `'{ a, foo: b }` /// `'{ a, foo: b }`
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TableLit { pub struct TableLit(pub BoundedSeparated<TableLitElem>);
pub elems: Vec<(Space, TableLitElem, Space)>,
/// `Some` if there is a trailing comma, `None` otherwise.
pub trailing_comma: Option<Space>,
pub span: Span,
}
impl HasSpan for TableLit { impl HasSpan for TableLit {
fn span(&self) -> Span { 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 { impl HasSpan for Lit {
fn span(&self) -> Span { fn span(&self) -> Span {
match self { match self {
Lit::Nil(span) => *span, Self::Nil(span) => *span,
Lit::Bool(_, span) => *span, Self::Bool(_, span) => *span,
Lit::Builtin(_, span) => *span, Self::Builtin(_, span) => *span,
Lit::Num(n) => n.span(), Self::Num(n) => n.span(),
Lit::String(s) => s.span(), Self::String(s) => s.span(),
Lit::Table(t) => t.span(), Self::Table(t) => t.span(),
} }
} }
} }
impl Lit {
pub fn expr(self) -> Expr {
Expr::Lit(self)
}
}

View file

@ -1,6 +1,6 @@
use crate::span::{HasSpan, Span}; use crate::span::{HasSpan, Span};
use super::{Expr, Space, TableLitElem}; use super::{BoundedSeparated, Expr, Space, TableLitElem};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Program { pub enum Program {
@ -12,12 +12,10 @@ pub enum Program {
span: Span, span: Span,
}, },
/// Structure: `s0 module elems trailing_comma` /// Structure: `s0 module elems`
Module { Module {
s0: Space, s0: Space,
elems: Vec<(Space, TableLitElem, Space)>, elems: BoundedSeparated<TableLitElem>,
/// `Some` if there is a trailing comma, `None` otherwise.
trailing_comma: Option<Space>,
span: Span, span: Span,
}, },
} }
@ -25,8 +23,8 @@ pub enum Program {
impl HasSpan for Program { impl HasSpan for Program {
fn span(&self) -> Span { fn span(&self) -> Span {
match self { match self {
Program::Expr { span, .. } => *span, Self::Expr { span, .. } => *span,
Program::Module { span, .. } => *span, Self::Module { span, .. } => *span,
} }
} }
} }

View file

@ -1,6 +1,6 @@
use crate::span::{HasSpan, Span}; use crate::span::{HasSpan, Span};
use super::{Expr, Space, TableLitElem}; use super::{BoundedSeparated, Expr, Ident, Space, TableLitElem};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum TableConstrElem { pub enum TableConstrElem {
@ -24,23 +24,34 @@ pub enum TableConstrElem {
impl HasSpan for TableConstrElem { impl HasSpan for TableConstrElem {
fn span(&self) -> Span { fn span(&self) -> Span {
match self { match self {
TableConstrElem::Lit(lit) => lit.span(), Self::Lit(lit) => lit.span(),
TableConstrElem::Indexed { span, .. } => *span, Self::Indexed { span, .. } => *span,
} }
} }
} }
impl TableConstrElem {
pub fn positional(value: Box<Expr>) -> Self {
Self::Lit(TableLitElem::Positional(value))
}
pub fn named(name: Ident, value: Box<Expr>, span: Span) -> Self {
Self::Lit(TableLitElem::named(name, value, span))
}
}
/// `{ a, b, foo: c, [d]: e }` /// `{ a, b, foo: c, [d]: e }`
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TableConstr { pub struct TableConstr(pub BoundedSeparated<TableConstrElem>);
pub elems: Vec<(Space, TableConstrElem, Space)>,
/// `Some` if there is a trailing comma, `None` otherwise.
pub trailing_comma: Option<Space>,
pub span: Span,
}
impl HasSpan for TableConstr { impl HasSpan for TableConstr {
fn span(&self) -> Span { fn span(&self) -> Span {
self.span self.0.span()
}
}
impl TableConstr {
pub fn expr(self) -> Expr {
Expr::TableConstr(self)
} }
} }

View file

@ -1,6 +1,6 @@
use crate::span::{HasSpan, Span}; use crate::span::{HasSpan, Span};
use super::{Expr, Ident, Space}; use super::{BoundedSeparated, Expr, Ident, Space};
// TODO Make table patterns recursive // TODO Make table patterns recursive
@ -24,24 +24,21 @@ pub enum TablePatternElem {
impl HasSpan for TablePatternElem { impl HasSpan for TablePatternElem {
fn span(&self) -> Span { fn span(&self) -> Span {
match self { match self {
TablePatternElem::Positional(ident) => ident.span(), Self::Positional(ident) => ident.span(),
TablePatternElem::Named { span, .. } => *span, Self::Named { span, .. } => *span,
} }
} }
} }
/// `'{ foo, bar: baz }` /// `{ foo, bar: baz }`
///
/// Structure: `{ s0 elems s1 }`
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TablePattern { pub struct TablePattern(pub BoundedSeparated<TablePatternElem>);
pub elems: Vec<(Space, TablePatternElem, Space)>,
/// `Some` if there is a trailing comma, `None` otherwise.
pub trailing_comma: Option<Space>,
pub span: Span,
}
impl HasSpan for TablePattern { impl HasSpan for TablePattern {
fn span(&self) -> Span { fn span(&self) -> Span {
self.span self.0.span()
} }
} }
@ -64,3 +61,26 @@ impl HasSpan for TableDestr {
self.span self.span
} }
} }
impl TableDestr {
pub fn new(local: bool, pattern: TablePattern, value: Box<Expr>, 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)
}
}

View file

@ -49,10 +49,61 @@ pub enum Var {
impl HasSpan for Var { impl HasSpan for Var {
fn span(&self) -> Span { fn span(&self) -> Span {
match self { match self {
Var::Access { span, .. } => *span, Self::Access { span, .. } => *span,
Var::Assign { span, .. } => *span, Self::Assign { span, .. } => *span,
Var::AccessIdent(ident) => ident.span(), Self::AccessIdent(ident) => ident.span(),
Var::AssignIdent { span, .. } => *span, Self::AssignIdent { span, .. } => *span,
} }
} }
} }
impl Var {
pub fn access(index: Box<Expr>, span: Span) -> Self {
Self::Access {
s0: Space::empty(span),
index,
s1: Space::empty(span),
span,
}
}
pub fn assign(local: bool, index: Box<Expr>, value: Box<Expr>, 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<Expr>, 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)
}
}

View file

@ -12,6 +12,21 @@ pub enum Builtin {
Scope, Scope,
Arg, Arg,
Destructure, Destructure,
Neg,
Not,
Mul,
Div,
Mod,
Add,
Sub,
Eq,
Ne,
Gt,
Ge,
Lt,
Le,
And,
Or,
} }
impl fmt::Debug for Builtin { impl fmt::Debug for Builtin {
@ -26,6 +41,21 @@ impl fmt::Debug for Builtin {
Self::Scope => write!(f, "'scope"), Self::Scope => write!(f, "'scope"),
Self::Arg => write!(f, "'arg"), Self::Arg => write!(f, "'arg"),
Self::Destructure => write!(f, "'destructure"), 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"),
} }
} }
} }

10
src/desugar.rs Normal file
View file

@ -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;

24
src/desugar/basic.rs Normal file
View file

@ -0,0 +1,24 @@
use crate::ast::BoundedSeparated;
impl<E> BoundedSeparated<E> {
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)
}
}

44
src/desugar/call.rs Normal file
View file

@ -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)
}
}
}
}

78
src/desugar/expr.rs Normal file
View file

@ -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)
}
}
}
}

86
src/desugar/field.rs Normal file
View file

@ -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)
}
}
}
}

128
src/desugar/func_def.rs Normal file
View file

@ -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)
}
}
}
}

60
src/desugar/lit.rs Normal file
View file

@ -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),
}
}
}

25
src/desugar/program.rs Normal file
View file

@ -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)
}
}
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

82
src/desugar/var.rs Normal file
View file

@ -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)
}
}
}
}

View file

@ -1,12 +1,30 @@
use std::fs; #![deny(unsafe_code)]
use std::path::PathBuf; // 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 chumsky::Parser as _;
use clap::Parser; use clap::Parser;
mod ast; mod ast;
mod builtin; mod builtin;
mod desugar;
mod parser; mod parser;
mod pretty; mod pretty;
mod span; mod span;
@ -15,8 +33,19 @@ mod value;
#[derive(Parser)] #[derive(Parser)]
enum Command { enum Command {
Parse { file: PathBuf }, Parse {
Pretty { file: PathBuf }, file: PathBuf,
},
Pretty {
file: PathBuf,
},
Desugar {
file: PathBuf,
#[arg(long, short, default_value = "diff")]
difftool: String,
#[arg(long, short = 'a')]
diffarg: Vec<String>,
},
} }
#[derive(Parser)] #[derive(Parser)]
@ -43,24 +72,54 @@ fn main() -> anyhow::Result<()> {
} }
} }
} }
Command::Pretty { file } => { Command::Pretty { file } => {
let content = fs::read_to_string(&file)?; let content = fs::read_to_string(&file)?;
let stream = span::stream_from_str(&content); let stream = span::stream_from_str(&content);
match parser::parser().parse(stream) { let program = parser::parser()
Ok(program) => { .parse(stream)
println!("Successful parse"); .map_err(|e| anyhow!("{e:?}"))?;
let doc = program.pretty(&RcAllocator);
let mut out = vec![]; print!("{}", pretty::pretty_to_string(program, 100));
doc.render(100, &mut out)?;
let str = String::from_utf8(out)?;
println!("{str}");
}
Err(errs) => {
println!("Parsing failed");
for err in errs {
println!("{err:?}");
} }
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;
} }
} }
} }

View file

@ -2,6 +2,7 @@
//! //!
//! # Rules //! # Rules
//! //!
//! - Parsers must not consume surrounding whitespace.
//! - Public parser functions must return [`basic::EParser`]. //! - Public parser functions must return [`basic::EParser`].
//! - Public parser functions must receive public subparsers via their arguments. //! - Public parser functions must receive public subparsers via their arguments.
//! - Each public parser function must be called exactly once, inside this file. //! - Each public parser function must be called exactly once, inside this file.
@ -17,7 +18,7 @@
mod basic; mod basic;
mod expr; mod expr;
mod func_defs; mod func_def;
mod lit; mod lit;
mod prefix; mod prefix;
mod program; mod program;
@ -51,7 +52,7 @@ pub fn parser() -> impl Parser<char, Program, Error = Error> {
table_pattern.clone(), table_pattern.clone(),
expr.clone(), expr.clone(),
); );
let func_def = func_defs::func_def( let func_def = func_def::func_def(
space.clone(), space.clone(),
ident.clone(), ident.clone(),
local, local,

View file

@ -3,7 +3,7 @@
use chumsky::prelude::*; use chumsky::prelude::*;
use chumsky::text::Character; use chumsky::text::Character;
use crate::ast::{Ident, Line, Space}; use crate::ast::{BoundedSeparated, Ident, Line, Space};
use crate::span::Span; use crate::span::Span;
pub type Error = Simple<char, Span>; pub type Error = Simple<char, Span>;
@ -58,3 +58,46 @@ pub fn ident() -> EParser<Ident> {
pub fn local(space: EParser<Space>) -> EParser<Option<Space>> { pub fn local(space: EParser<Space>) -> EParser<Option<Space>> {
text::keyword("local").ignore_then(space).or_not().boxed() 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<E: 'static>(
space: impl Parser<char, Space, Error = Error> + Clone + 'static,
start: impl Parser<char, (), Error = Error> + 'static,
end: impl Parser<char, (), Error = Error> + 'static,
separator: impl Parser<char, (), Error = Error> + 'static,
elem: impl Parser<char, E, Error = Error> + Clone + 'static,
) -> EParser<BoundedSeparated<E>> {
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()
}

View file

@ -18,7 +18,7 @@ fn atom_paren(
.then_ignore(just(')')) .then_ignore(just(')'))
.map_with_span(|((s0, inner), s1), span| Expr::Paren { .map_with_span(|((s0, inner), s1), span| Expr::Paren {
s0, s0,
inner: Box::new(inner), inner: inner.boxed(),
s1, s1,
span, span,
}) })
@ -63,11 +63,11 @@ fn left_assoc(
over.then(op_over.repeated()) over.then(op_over.repeated())
.foldl(|left, (s0, op, s1, right)| Expr::BinOp { .foldl(|left, (s0, op, s1, right)| Expr::BinOp {
span: left.span().join(right.span()), span: left.span().join(right.span()),
left: Box::new(left), left: left.boxed(),
s0, s0,
op, op,
s1, s1,
right: Box::new(right), right: right.boxed(),
}) })
.boxed() .boxed()
} }
@ -89,11 +89,11 @@ fn right_assoc(
.then(over) .then(over)
.foldr(|(left, s0, op, s1), right| Expr::BinOp { .foldr(|(left, s0, op, s1), right| Expr::BinOp {
span: left.span().join(right.span()), span: left.span().join(right.span()),
left: Box::new(left), left: left.boxed(),
s0, s0,
op, op,
s1, s1,
right: Box::new(right), right: right.boxed(),
}) })
.boxed() .boxed()
} }

View file

@ -1,5 +1,3 @@
// TODO Rename this module to func_def for consistency
use chumsky::prelude::*; use chumsky::prelude::*;
use crate::ast::{Expr, FuncDef, Ident, Space, TablePattern}; use crate::ast::{Expr, FuncDef, Ident, Space, TablePattern};
@ -21,7 +19,7 @@ fn func_def_anon_no_arg(
s0, s0,
s1, s1,
s2, s2,
body: Box::new(body), body: body.boxed(),
span, span,
}) })
} }
@ -47,7 +45,7 @@ fn func_def_anon_arg(
arg, arg,
s2, s2,
s3, s3,
body: Box::new(body), body: body.boxed(),
span, span,
}, },
) )
@ -67,7 +65,7 @@ fn func_def_anon_destr(
s0, s0,
pattern, pattern,
s1, s1,
body: Box::new(body), body: body.boxed(),
span, span,
}) })
} }
@ -96,7 +94,7 @@ fn func_def_named_no_arg(
s1, s1,
s2, s2,
s3, s3,
body: Box::new(body), body: body.boxed(),
span, span,
}, },
) )
@ -130,7 +128,7 @@ fn func_def_named_arg(
arg, arg,
s3, s3,
s4, s4,
body: Box::new(body), body: body.boxed(),
span, span,
}, },
) )
@ -159,7 +157,7 @@ fn func_def_named_destr(
s1, s1,
pattern, pattern,
s2, s2,
body: Box::new(body), body: body.boxed(),
span, span,
} }
}) })

View file

@ -7,7 +7,7 @@ use crate::ast::{
}; };
use crate::builtin::Builtin; use crate::builtin::Builtin;
use super::basic::{EParser, Error}; use super::basic::{bounded_separated, EParser, Error};
fn builtin_lit() -> impl Parser<char, Builtin, Error = Error> { fn builtin_lit() -> impl Parser<char, Builtin, Error = Error> {
just('\'').ignore_then(choice(( just('\'').ignore_then(choice((
@ -132,7 +132,7 @@ pub fn table_lit_elem(
) -> EParser<TableLitElem> { ) -> EParser<TableLitElem> {
let positional = expr let positional = expr
.clone() .clone()
.map(|value| TableLitElem::Positional(Box::new(value))); .map(|value| TableLitElem::Positional(value.boxed()));
let named = ident let named = ident
.then(space.clone()) .then(space.clone())
@ -143,7 +143,7 @@ pub fn table_lit_elem(
name, name,
s0, s0,
s1, s1,
value: Box::new(value), value: value.boxed(),
span, span,
}); });
@ -154,24 +154,14 @@ fn table_lit(
space: EParser<Space>, space: EParser<Space>,
table_lit_elem: EParser<TableLitElem>, table_lit_elem: EParser<TableLitElem>,
) -> impl Parser<char, TableLit, Error = Error> { ) -> impl Parser<char, TableLit, Error = Error> {
let elem = space bounded_separated(
.clone() space,
.then(table_lit_elem) just("'{").to(()),
.then(space.clone()) just('}').to(()),
.map(|((s0, elem), s1)| (s0, elem, s1)); just(',').to(()),
table_lit_elem,
let trailing_comma = just(',').ignore_then(space).or_not(); )
.map(TableLit)
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,
})
} }
pub fn lit(space: EParser<Space>, table_lit_elem: EParser<TableLitElem>) -> EParser<Lit> { pub fn lit(space: EParser<Space>, table_lit_elem: EParser<TableLitElem>) -> EParser<Lit> {

View file

@ -17,7 +17,7 @@ enum Prefix {
impl Prefix { impl Prefix {
fn into_expr(self, span: Span, expr: Expr) -> Expr { fn into_expr(self, span: Span, expr: Expr) -> Expr {
let expr = Box::new(expr); let expr = expr.boxed();
match self { match self {
Self::Neg { minus, s0 } => Expr::Neg { Self::Neg { minus, s0 } => Expr::Neg {
minus, minus,

View file

@ -4,7 +4,7 @@ use chumsky::prelude::*;
use crate::ast::{Expr, Program, Space, TableLitElem}; use crate::ast::{Expr, Program, Space, TableLitElem};
use super::basic::EParser; use super::basic::{bounded_separated, EParser};
pub fn program( pub fn program(
space: EParser<Space>, space: EParser<Space>,
@ -17,22 +17,17 @@ pub fn program(
.then(space.clone()) .then(space.clone())
.map_with_span(|((s0, expr), s1), span| Program::Expr { s0, expr, s1, span }); .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 let module = space
.clone()
.then_ignore(text::keyword("module")) .then_ignore(text::keyword("module"))
.then(elem.separated_by(just(','))) .then(bounded_separated(
.then(trailing_comma) space,
.map_with_span(|((s0, elems), trailing_comma), span| Program::Module { empty(),
s0, empty(),
elems, just(',').to(()),
trailing_comma, table_lit_elem,
span, ))
}); .map_with_span(|(s0, elems), span| Program::Module { s0, elems, span });
module.or(lit).boxed() module.or(lit).boxed()
} }

View file

@ -57,32 +57,39 @@ enum Suffix {
impl Suffix { impl Suffix {
fn into_expr(self, span: Span, expr: Expr) -> Expr { fn into_expr(self, span: Span, expr: Expr) -> Expr {
let expr = Box::new(expr); let expr = expr.boxed();
match self { match self {
Suffix::CallArg { s0, s1, arg, s2 } => Expr::Call(Call::Arg { Self::CallArg { s0, s1, arg, s2 } => Call::Arg {
expr, expr,
s0, s0,
s1, s1,
arg, arg,
s2, s2,
span, span,
}), }
Suffix::CallNoArg { s0, s1 } => Expr::Call(Call::NoArg { expr, s0, s1, span }), .expr(),
Suffix::CallConstr { s0, constr } => Expr::Call(Call::Constr {
Self::CallNoArg { s0, s1 } => Call::NoArg { expr, s0, s1, span }.expr(),
Self::CallConstr { s0, constr } => Call::Constr {
expr, expr,
s0, s0,
constr, constr,
span, span,
}), }
Suffix::FieldAccess { s0, s1, index, s2 } => Expr::Field(Field::Access { .expr(),
Self::FieldAccess { s0, s1, index, s2 } => Field::Access {
expr, expr,
s0, s0,
s1, s1,
index, index,
s2, s2,
span, span,
}), }
Suffix::FieldAssign { .expr(),
Self::FieldAssign {
s0, s0,
s1, s1,
index, index,
@ -90,7 +97,7 @@ impl Suffix {
s3, s3,
s4, s4,
value, value,
} => Expr::Field(Field::Assign { } => Field::Assign {
expr, expr,
s0, s0,
s1, s1,
@ -100,22 +107,26 @@ impl Suffix {
s4, s4,
value, value,
span, span,
}), }
Suffix::FieldAccessIdent { s0, s1, ident } => Expr::Field(Field::AccessIdent { .expr(),
Self::FieldAccessIdent { s0, s1, ident } => Field::AccessIdent {
expr, expr,
s0, s0,
s1, s1,
ident, ident,
span, span,
}), }
Suffix::FieldAssignIdent { .expr(),
Self::FieldAssignIdent {
s0, s0,
s1, s1,
ident, ident,
s2, s2,
s3, s3,
value, value,
} => Expr::Field(Field::AssignIdent { } => Field::AssignIdent {
expr, expr,
s0, s0,
s1, s1,
@ -124,7 +135,8 @@ impl Suffix {
s3, s3,
value, value,
span, span,
}), }
.expr(),
} }
} }
} }
@ -143,7 +155,7 @@ fn suffix_call_arg(
.map(|(((s0, s1), arg), s2)| Suffix::CallArg { .map(|(((s0, s1), arg), s2)| Suffix::CallArg {
s0, s0,
s1, s1,
arg: Box::new(arg), arg: arg.boxed(),
s2, s2,
}) })
} }
@ -180,7 +192,7 @@ fn suffix_field_access(
.map(|(((s0, s1), index), s2)| Suffix::FieldAccess { .map(|(((s0, s1), index), s2)| Suffix::FieldAccess {
s0, s0,
s1, s1,
index: Box::new(index), index: index.boxed(),
s2, s2,
}) })
} }
@ -204,11 +216,11 @@ fn suffix_field_assign(
|((((((s0, s1), index), s2), s3), s4), value)| Suffix::FieldAssign { |((((((s0, s1), index), s2), s3), s4), value)| Suffix::FieldAssign {
s0, s0,
s1, s1,
index: Box::new(index), index: index.boxed(),
s2, s2,
s3, s3,
s4, s4,
value: Box::new(value), value: value.boxed(),
}, },
) )
} }
@ -246,7 +258,7 @@ fn suffix_field_assign_ident(
ident, ident,
s2, s2,
s3, s3,
value: Box::new(value), value: value.boxed(),
}, },
) )
} }

View file

@ -4,13 +4,13 @@ use chumsky::prelude::*;
use crate::ast::{Expr, Space, TableConstr, TableConstrElem, TableLitElem}; use crate::ast::{Expr, Space, TableConstr, TableConstrElem, TableLitElem};
use super::basic::{EParser, Error}; use super::basic::{bounded_separated, EParser, Error};
fn table_constr_elem( fn table_constr_elem(
space: EParser<Space>, space: EParser<Space>,
table_lit_elem: EParser<TableLitElem>, table_lit_elem: EParser<TableLitElem>,
expr: EParser<Expr>, expr: EParser<Expr>,
) -> impl Parser<char, TableConstrElem, Error = Error> { ) -> impl Parser<char, TableConstrElem, Error = Error> + Clone {
let lit = table_lit_elem.map(TableConstrElem::Lit); let lit = table_lit_elem.map(TableConstrElem::Lit);
let indexed = just('[') let indexed = just('[')
@ -25,11 +25,11 @@ fn table_constr_elem(
.map_with_span( .map_with_span(
|(((((s0, index), s1), s2), s3), value), span| TableConstrElem::Indexed { |(((((s0, index), s1), s2), s3), value), span| TableConstrElem::Indexed {
s0, s0,
index: Box::new(index), index: index.boxed(),
s1, s1,
s2, s2,
s3, s3,
value: Box::new(value), value: value.boxed(),
span, span,
}, },
); );
@ -42,23 +42,14 @@ pub fn table_constr(
table_lit_elem: EParser<TableLitElem>, table_lit_elem: EParser<TableLitElem>,
expr: EParser<Expr>, expr: EParser<Expr>,
) -> EParser<TableConstr> { ) -> EParser<TableConstr> {
let elem = space let elem = table_constr_elem(space.clone(), table_lit_elem, expr);
.clone() bounded_separated(
.then(table_constr_elem(space.clone(), table_lit_elem, expr)) space,
.then(space.clone()) just('{').to(()),
.map(|((s0, elem), s1)| (s0, elem, s1)); just('}').to(()),
just(',').to(()),
let trailing_comma = just(',').ignore_then(space).or_not(); elem,
)
let elems = elem.separated_by(just(',')).then(trailing_comma); .map(TableConstr)
just('{')
.ignore_then(elems)
.then_ignore(just('}'))
.map_with_span(|(elems, trailing_comma), span| TableConstr {
elems,
trailing_comma,
span,
})
.boxed() .boxed()
} }

View file

@ -4,12 +4,12 @@ use chumsky::prelude::*;
use crate::ast::{Expr, Ident, Space, TableDestr, TablePattern, TablePatternElem}; 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( fn table_pattern_elem(
space: EParser<Space>, space: EParser<Space>,
ident: EParser<Ident>, ident: EParser<Ident>,
) -> impl Parser<char, TablePatternElem, Error = Error> { ) -> impl Parser<char, TablePatternElem, Error = Error> + Clone {
let positional = ident.clone().map(TablePatternElem::Positional); let positional = ident.clone().map(TablePatternElem::Positional);
let named = ident let named = ident
@ -30,24 +30,15 @@ fn table_pattern_elem(
} }
pub fn table_pattern(space: EParser<Space>, ident: EParser<Ident>) -> EParser<TablePattern> { pub fn table_pattern(space: EParser<Space>, ident: EParser<Ident>) -> EParser<TablePattern> {
let elem = space let elem = table_pattern_elem(space.clone(), ident);
.clone() bounded_separated(
.then(table_pattern_elem(space.clone(), ident)) space,
.then(space.clone()) just('{').to(()),
.map(|((s0, elem), s1)| (s0, elem, s1)); just('}').to(()),
just(',').to(()),
let trailing_comma = just(',').ignore_then(space).or_not(); elem,
)
let elems = elem.separated_by(just(',')).then(trailing_comma); .map(TablePattern)
just('{')
.ignore_then(elems)
.then_ignore(just('}'))
.map_with_span(|(elems, trailing_comma), span| TablePattern {
elems,
trailing_comma,
span,
})
.boxed() .boxed()
} }
@ -68,7 +59,7 @@ pub fn table_destr(
pattern, pattern,
s0, s0,
s1, s1,
value: Box::new(value), value: value.boxed(),
span, span,
}) })
.boxed() .boxed()

View file

@ -14,7 +14,7 @@ fn var_access(space: EParser<Space>, expr: EParser<Expr>) -> impl Parser<char, V
.then_ignore(just(']')) .then_ignore(just(']'))
.map_with_span(|((s0, index), s1), span| Var::Access { .map_with_span(|((s0, index), s1), span| Var::Access {
s0, s0,
index: Box::new(index), index: index.boxed(),
s1, s1,
span, span,
}) })
@ -39,11 +39,11 @@ fn var_assign(
|((((((local, s0), index), s1), s2), s3), value), span| Var::Assign { |((((((local, s0), index), s1), s2), s3), value), span| Var::Assign {
local, local,
s0, s0,
index: Box::new(index), index: index.boxed(),
s1, s1,
s2, s2,
s3, s3,
value: Box::new(value), value: value.boxed(),
span, span,
}, },
) )
@ -67,7 +67,7 @@ fn var_assign_ident(
name, name,
s0, s0,
s1, s1,
value: Box::new(value), value: value.boxed(),
span, span,
}, },
) )

View file

@ -1,9 +1,27 @@
use pretty::{DocAllocator, DocBuilder, Pretty}; // TODO Remove this and print whitespace and comments properly
#![allow(unused_variables)]
use crate::ast::Program; use pretty::{Pretty, RcAllocator};
impl<'a, A: DocAllocator<'a>> Pretty<'a, A> for Program { mod basic;
fn pretty(self, allocator: &'a A) -> DocBuilder<'a, A, ()> { mod call;
allocator.text("Hello world") 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: Pretty<'static, RcAllocator>>(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
} }

41
src/pretty/basic.rs Normal file
View file

@ -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<E> BoundedSeparated<E> {
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()
}
}

36
src/pretty/call.rs Normal file
View file

@ -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)),
}
}
}

131
src/pretty/expr.rs Normal file
View file

@ -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)
}
}
}
}

68
src/pretty/field.rs Normal file
View file

@ -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(),
}
}
}

102
src/pretty/func_def.rs Normal file
View file

@ -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)),
}
}
}

87
src/pretty/lit.rs Normal file
View file

@ -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),
}
}
}

33
src/pretty/program.rs Normal file
View file

@ -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(","))),
)
}
}
}
}

View file

@ -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),
)
}
}

53
src/pretty/table_destr.rs Normal file
View file

@ -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))
}
}

49
src/pretty/var.rs Normal file
View file

@ -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)),
}
}
}