Compare commits

..

4 commits

Author SHA1 Message Date
a18532a23a Start pretty printing comments 2022-11-20 20:33:32 +01:00
b21619dabd Pretty print programs partially 2022-11-20 20:32:21 +01:00
969570fc4b Make rule about consuming surrounding whitespace 2022-11-20 20:30:40 +01:00
b009a9c4ec 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 20:25:41 +01:00
47 changed files with 317 additions and 2002 deletions

51
Cargo.lock generated
View file

@ -120,15 +120,6 @@ 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"
@ -155,15 +146,6 @@ 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"
@ -251,24 +233,6 @@ 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"
@ -294,21 +258,6 @@ 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]]

View file

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

View file

@ -2,8 +2,6 @@ use std::fmt;
use crate::span::{HasSpan, Span};
use super::{TableConstr, TableConstrElem, TableLit, TableLitElem};
#[derive(Clone)]
pub enum Line {
Empty,
@ -32,30 +30,12 @@ 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<S: ToString>(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)
@ -69,76 +49,21 @@ impl HasSpan for Ident {
}
#[derive(Debug, Clone)]
pub struct BoundedSeparated<E> {
pub elems: Vec<(Space, E, Space)>,
pub trailing: Option<Space>,
pub span: Span,
pub enum Separated<E, S1, S2> {
Empty(Span),
NonEmpty {
first_elem: E,
last_elems: Vec<(S1, E)>,
trailing: Option<S2>,
span: Span,
},
}
impl<E> HasSpan for BoundedSeparated<E> {
impl<E, S1, S2> HasSpan for Separated<E, S1, S2> {
fn span(&self) -> Span {
self.span
}
}
impl<E> BoundedSeparated<E> {
pub fn new(span: Span) -> Self {
Self {
elems: vec![],
trailing: None,
span,
match self {
Separated::Empty(span) => *span,
Separated::NonEmpty { span, .. } => *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,44 +40,9 @@ pub enum Call {
impl HasSpan for Call {
fn span(&self) -> Span {
match self {
Self::Arg { span, .. } => *span,
Self::NoArg { span, .. } => *span,
Self::Constr { span, .. } => *span,
Call::Arg { span, .. } => *span,
Call::NoArg { span, .. } => *span,
Call::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,30 +4,18 @@ 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,
/// `!=`
@ -46,36 +34,6 @@ 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),
@ -228,23 +186,17 @@ impl fmt::Debug for Expr {
impl HasSpan for Expr {
fn span(&self) -> Span {
match self {
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,
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,
}
}
}
impl Expr {
pub fn boxed(self) -> Box<Self> {
Box::new(self)
}
}

View file

@ -60,41 +60,10 @@ pub enum Field {
impl HasSpan for Field {
fn span(&self) -> Span {
match self {
Self::Access { span, .. } => *span,
Self::Assign { span, .. } => *span,
Self::AccessIdent { span, .. } => *span,
Self::AssignIdent { span, .. } => *span,
Field::Access { span, .. } => *span,
Field::Assign { span, .. } => *span,
Field::AccessIdent { span, .. } => *span,
Field::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,50 +90,12 @@ pub enum FuncDef {
impl HasSpan for FuncDef {
fn span(&self) -> Span {
match self {
Self::AnonNoArg { span, .. } => *span,
Self::AnonArg { span, .. } => *span,
Self::AnonDestr { span, .. } => *span,
Self::NamedNoArg { span, .. } => *span,
Self::NamedArg { span, .. } => *span,
Self::NamedDestr { span, .. } => *span,
FuncDef::AnonNoArg { span, .. } => *span,
FuncDef::AnonArg { span, .. } => *span,
FuncDef::AnonDestr { span, .. } => *span,
FuncDef::NamedNoArg { span, .. } => *span,
FuncDef::NamedArg { span, .. } => *span,
FuncDef::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::span::{HasSpan, Span};
use super::{BoundedSeparated, Expr, Ident, Space};
use super::{Expr, Ident, Separated, Space};
#[derive(Clone)]
pub enum NumLitStr {
@ -93,19 +93,6 @@ 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
@ -132,37 +119,26 @@ pub enum TableLitElem {
impl HasSpan for TableLitElem {
fn span(&self) -> Span {
match self {
Self::Positional(value) => value.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,
TableLitElem::Positional(value) => value.span(),
TableLitElem::Named { span, .. } => *span,
}
}
}
/// `'{ a, foo: b }`
///
/// Structure: `'{ s0 elems s1 }`
#[derive(Debug, Clone)]
pub struct TableLit(pub BoundedSeparated<TableLitElem>);
pub struct TableLit {
pub s0: Space,
pub elems: Separated<TableLitElem, (Space, Space), Space>,
pub s1: Space,
pub span: Span,
}
impl HasSpan for TableLit {
fn span(&self) -> Span {
self.0.span()
}
}
impl TableLit {
pub fn lit(self) -> Lit {
Lit::Table(self)
self.span
}
}
@ -211,18 +187,12 @@ impl fmt::Debug for Lit {
impl HasSpan for Lit {
fn span(&self) -> Span {
match self {
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(),
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(),
}
}
}
impl Lit {
pub fn expr(self) -> Expr {
Expr::Lit(self)
}
}

View file

@ -1,6 +1,6 @@
use crate::span::{HasSpan, Span};
use super::{BoundedSeparated, Expr, Space, TableLitElem};
use super::{Expr, Separated, Space, TableLitElem};
#[derive(Debug, Clone)]
pub enum Program {
@ -12,10 +12,12 @@ pub enum Program {
span: Span,
},
/// Structure: `s0 module elems`
/// Structure: `s0 module s1 elems s2`
Module {
s0: Space,
elems: BoundedSeparated<TableLitElem>,
s1: Space,
elems: Separated<TableLitElem, (Space, Space), Space>,
s2: Space,
span: Span,
},
}
@ -23,8 +25,8 @@ pub enum Program {
impl HasSpan for Program {
fn span(&self) -> Span {
match self {
Self::Expr { span, .. } => *span,
Self::Module { span, .. } => *span,
Program::Expr { span, .. } => *span,
Program::Module { span, .. } => *span,
}
}
}

View file

@ -1,6 +1,6 @@
use crate::span::{HasSpan, Span};
use super::{BoundedSeparated, Expr, Ident, Space, TableLitElem};
use super::{Expr, Separated, Space, TableLitElem};
#[derive(Debug, Clone)]
pub enum TableConstrElem {
@ -24,34 +24,25 @@ pub enum TableConstrElem {
impl HasSpan for TableConstrElem {
fn span(&self) -> Span {
match self {
Self::Lit(lit) => lit.span(),
Self::Indexed { span, .. } => *span,
TableConstrElem::Lit(lit) => lit.span(),
TableConstrElem::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 }`
///
/// Structure: `{ s0 elems s1 }`
#[derive(Debug, Clone)]
pub struct TableConstr(pub BoundedSeparated<TableConstrElem>);
pub struct TableConstr {
pub s0: Space,
pub elems: Separated<TableConstrElem, (Space, Space), Space>,
pub s1: Space,
pub span: Span,
}
impl HasSpan for TableConstr {
fn span(&self) -> Span {
self.0.span()
}
}
impl TableConstr {
pub fn expr(self) -> Expr {
Expr::TableConstr(self)
self.span
}
}

View file

@ -1,6 +1,6 @@
use crate::span::{HasSpan, Span};
use super::{BoundedSeparated, Expr, Ident, Space};
use super::{Expr, Ident, Separated, Space};
// TODO Make table patterns recursive
@ -24,8 +24,8 @@ pub enum TablePatternElem {
impl HasSpan for TablePatternElem {
fn span(&self) -> Span {
match self {
Self::Positional(ident) => ident.span(),
Self::Named { span, .. } => *span,
TablePatternElem::Positional(ident) => ident.span(),
TablePatternElem::Named { span, .. } => *span,
}
}
}
@ -34,11 +34,16 @@ impl HasSpan for TablePatternElem {
///
/// Structure: `{ s0 elems s1 }`
#[derive(Debug, Clone)]
pub struct TablePattern(pub BoundedSeparated<TablePatternElem>);
pub struct TablePattern {
pub s0: Space,
pub elems: Separated<TablePatternElem, (Space, Space), Space>,
pub s1: Space,
pub span: Span,
}
impl HasSpan for TablePattern {
fn span(&self) -> Span {
self.0.span()
self.span
}
}
@ -61,26 +66,3 @@ impl HasSpan for TableDestr {
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,61 +49,10 @@ pub enum Var {
impl HasSpan for Var {
fn span(&self) -> Span {
match self {
Self::Access { span, .. } => *span,
Self::Assign { span, .. } => *span,
Self::AccessIdent(ident) => ident.span(),
Self::AssignIdent { span, .. } => *span,
Var::Access { span, .. } => *span,
Var::Assign { span, .. } => *span,
Var::AccessIdent(ident) => ident.span(),
Var::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,21 +12,6 @@ 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 {
@ -41,21 +26,6 @@ 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"),
}
}
}

View file

@ -1,10 +0,0 @@
mod basic;
mod call;
mod expr;
mod field;
mod func_def;
mod lit;
mod program;
mod table_constr;
mod table_destr;
mod var;

View file

@ -1,24 +0,0 @@
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)
}
}

View file

@ -1,44 +0,0 @@
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)
}
}
}
}

View file

@ -1,78 +0,0 @@
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)
}
}
}
}

View file

@ -1,86 +0,0 @@
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)
}
}
}
}

View file

@ -1,128 +0,0 @@
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)
}
}
}
}

View file

@ -1,60 +0,0 @@
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),
}
}
}

View file

@ -1,25 +0,0 @@
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

@ -1,39 +0,0 @@
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

@ -1,63 +0,0 @@
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)
}
}

View file

@ -1,82 +0,0 @@
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,30 +1,11 @@
#![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 std::io::Write;
use std::fs;
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;
@ -33,19 +14,8 @@ mod value;
#[derive(Parser)]
enum Command {
Parse {
file: PathBuf,
},
Pretty {
file: PathBuf,
},
Desugar {
file: PathBuf,
#[arg(long, short, default_value = "diff")]
difftool: String,
#[arg(long, short = 'a')]
diffarg: Vec<String>,
},
Parse { file: PathBuf },
Pretty { file: PathBuf },
}
#[derive(Parser)]
@ -72,54 +42,21 @@ fn main() -> anyhow::Result<()> {
}
}
}
Command::Pretty { file } => {
let content = fs::read_to_string(&file)?;
let stream = span::stream_from_str(&content);
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;
match parser::parser().parse(stream) {
Ok(program) => {
let mut out = vec![];
program.to_doc().render(100, &mut out)?;
println!("{}", String::from_utf8(out)?);
}
Err(errs) => {
eprintln!("Parsing failed");
for err in errs {
eprintln!("{err:?}");
}
}
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

@ -18,7 +18,7 @@
mod basic;
mod expr;
mod func_def;
mod func_defs;
mod lit;
mod prefix;
mod program;
@ -52,7 +52,7 @@ pub fn parser() -> impl Parser<char, Program, Error = Error> {
table_pattern.clone(),
expr.clone(),
);
let func_def = func_def::func_def(
let func_def = func_defs::func_def(
space.clone(),
ident.clone(),
local,

View file

@ -3,7 +3,7 @@
use chumsky::prelude::*;
use chumsky::text::Character;
use crate::ast::{BoundedSeparated, Ident, Line, Space};
use crate::ast::{Ident, Line, Separated, Space};
use crate::span::Span;
pub type Error = Simple<char, Span>;
@ -62,42 +62,23 @@ pub fn local(space: EParser<Space>) -> EParser<Option<Space>> {
// 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,
pub fn separated_by<E: 'static, S1: 'static, S2: '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,
separator: impl Parser<char, S1, Error = Error> + 'static,
trailing_separator: impl Parser<char, S2, Error = Error> + 'static,
) -> EParser<Separated<E, S1, S2>> {
elem.clone()
.then(separator.then(elem).repeated())
.then(trailing_separator.or_not())
.or_not()
.map_with_span(|s, span| match s {
Some(((first_elem, last_elems), trailing)) => Separated::NonEmpty {
first_elem,
last_elems,
trailing,
span,
}
},
None => Separated::Empty(span),
})
.boxed()
}

View file

@ -18,7 +18,7 @@ fn atom_paren(
.then_ignore(just(')'))
.map_with_span(|((s0, inner), s1), span| Expr::Paren {
s0,
inner: inner.boxed(),
inner: Box::new(inner),
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: left.boxed(),
left: Box::new(left),
s0,
op,
s1,
right: right.boxed(),
right: Box::new(right),
})
.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: left.boxed(),
left: Box::new(left),
s0,
op,
s1,
right: right.boxed(),
right: Box::new(right),
})
.boxed()
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@ use chumsky::prelude::*;
use crate::ast::{Expr, Space, TableConstr, TableConstrElem, TableLitElem};
use super::basic::{bounded_separated, EParser, Error};
use super::basic::{separated_by, EParser, Error};
fn table_constr_elem(
space: EParser<Space>,
@ -25,11 +25,11 @@ fn table_constr_elem(
.map_with_span(
|(((((s0, index), s1), s2), s3), value), span| TableConstrElem::Indexed {
s0,
index: index.boxed(),
index: Box::new(index),
s1,
s2,
s3,
value: value.boxed(),
value: Box::new(value),
span,
},
);
@ -43,13 +43,19 @@ pub fn table_constr(
expr: EParser<Expr>,
) -> EParser<TableConstr> {
let elem = table_constr_elem(space.clone(), table_lit_elem, expr);
bounded_separated(
space,
just('{').to(()),
just('}').to(()),
just(',').to(()),
elem,
)
.map(TableConstr)
.boxed()
let separator = space.clone().then_ignore(just(',')).then(space.clone());
let trailing_separator = space.clone().then_ignore(just(','));
space
.clone()
.then(separated_by(elem, separator, trailing_separator))
.then(space)
.delimited_by(just('{'), just('}'))
.map_with_span(|((s0, elems), s1), span| TableConstr {
s0,
elems,
s1,
span,
})
.boxed()
}

View file

@ -4,7 +4,7 @@ use chumsky::prelude::*;
use crate::ast::{Expr, Ident, Space, TableDestr, TablePattern, TablePatternElem};
use super::basic::{bounded_separated, EParser, Error};
use super::basic::{separated_by, EParser, Error};
fn table_pattern_elem(
space: EParser<Space>,
@ -31,15 +31,21 @@ fn table_pattern_elem(
pub fn table_pattern(space: EParser<Space>, ident: EParser<Ident>) -> EParser<TablePattern> {
let elem = table_pattern_elem(space.clone(), ident);
bounded_separated(
space,
just('{').to(()),
just('}').to(()),
just(',').to(()),
elem,
)
.map(TablePattern)
.boxed()
let separator = space.clone().then_ignore(just(',')).then(space.clone());
let trailing_separator = space.clone().then_ignore(just(','));
space
.clone()
.then(separated_by(elem, separator, trailing_separator))
.then(space)
.delimited_by(just('{'), just('}'))
.map_with_span(|((s0, elems), s1), span| TablePattern {
s0,
elems,
s1,
span,
})
.boxed()
}
pub fn table_destr(
@ -59,7 +65,7 @@ pub fn table_destr(
pattern,
s0,
s1,
value: value.boxed(),
value: Box::new(value),
span,
})
.boxed()

View file

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

View file

@ -1,27 +1 @@
// TODO Remove this and print whitespace and comments properly
#![allow(unused_variables)]
use pretty::{Pretty, RcAllocator};
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: 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
}

View file

@ -1,41 +1,86 @@
use pretty::{DocAllocator, DocBuilder, Pretty};
use std::mem;
use crate::ast::{BoundedSeparated, Ident};
use pretty::{DocAllocator, DocBuilder, Pretty, RcAllocator, RcDoc};
use super::NEST_DEPTH;
use crate::ast::{Ident, Line, Space};
impl<'a, D: DocAllocator<'a>> Pretty<'a, D> for Ident {
fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> {
allocator.text(self.name)
pub enum Group<'a> {
EmptyLine,
CommentBlock(Vec<&'a str>),
}
/// Group comments and deduplicate empty lines
fn group_comments(lines: &[Line]) -> Vec<Group> {
let mut result = vec![];
let mut current_block = vec![];
let mut last_line_was_empty = false;
for line in lines {
match line {
Line::Empty if last_line_was_empty => {}
Line::Empty => {
last_line_was_empty = true;
if !current_block.is_empty() {
result.push(Group::CommentBlock(mem::take(&mut current_block)));
}
result.push(Group::EmptyLine);
}
Line::Comment(comment) => {
last_line_was_empty = false;
current_block.push(comment);
}
}
}
if !current_block.is_empty() {
result.push(Group::CommentBlock(current_block));
}
result
}
fn remove_leading_empty_line(groups: &mut Vec<Group>) {
if let Some(Group::EmptyLine) = groups.first() {
groups.remove(0);
}
}
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()
fn remove_trailing_empty_line(groups: &mut Vec<Group>) {
if let Some(Group::EmptyLine) = groups.last() {
groups.pop();
}
}
fn comment_group_to_doc(comment: Vec<&str>) -> RcDoc {
RcAllocator
.concat(comment.into_iter().map(|c| {
RcDoc::text("# ")
.append(RcDoc::text(c))
.append(RcDoc::hardline())
}))
.indent(0)
.into_doc()
}
impl Space {
/// Format as document for inline use, prefering nil if possible.
pub fn to_doc_inline_nospace(&self) -> RcDoc {
RcDoc::nil()
}
/// Format as document, prefering a single space if possible.
pub fn to_doc_inline_space(&self) -> RcDoc {
RcDoc::space()
}
/// Format as document, prefering a newline if possible.
pub fn to_doc_newline(&self) -> RcDoc {
RcDoc::line()
}
}
impl Ident {
pub fn to_doc(&self) -> RcDoc {
RcDoc::text(&self.name)
}
}

View file

@ -1,36 +0,0 @@
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)),
}
}
}

View file

@ -1,131 +0,0 @@
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)
}
}
}
}

View file

@ -1,68 +0,0 @@
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(),
}
}
}

View file

@ -1,102 +0,0 @@
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)),
}
}
}

View file

@ -1,87 +0,0 @@
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),
}
}
}

View file

@ -1,33 +1,23 @@
use pretty::{DocAllocator, DocBuilder, Pretty};
use pretty::RcDoc;
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> {
impl Program {
pub fn to_doc(&self) -> RcDoc {
match self {
Self::Expr {
Program::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(","))),
)
}
} => RcDoc::nil(),
Program::Module {
s0,
s1,
elems,
s2,
span: _,
} => RcDoc::text("module"),
}
}
}

View file

@ -1,44 +0,0 @@
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),
)
}
}

View file

@ -1,53 +0,0 @@
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))
}
}

View file

@ -1,49 +0,0 @@
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)),
}
}
}