feat: finish splitting up parsers

This commit is contained in:
rvcas 2023-06-30 13:40:53 -04:00
parent 63cdb8aa09
commit 2226747dc1
No known key found for this signature in database
GPG Key ID: C09B64E263F7D68C
9 changed files with 351 additions and 310 deletions

View File

@ -8,6 +8,9 @@ strip = true
shared-version = true shared-version = true
tag-name = "v{{version}}" tag-name = "v{{version}}"
[workspace.dependencies]
insta = { version = "1.30.0", features = ["yaml"] }
[profile.dev.package.insta] [profile.dev.package.insta]
opt-level = 3 opt-level = 3

View File

@ -30,15 +30,12 @@ num-bigint = "0.4.3"
[target.'cfg(not(target_family="wasm"))'.dependencies] [target.'cfg(not(target_family="wasm"))'.dependencies]
chumsky = "0.9.2" chumsky = "0.9.2"
[target.'cfg(target_family="wasm")'.dependencies] [target.'cfg(target_family="wasm")'.dependencies]
chumsky = { version = "0.9.2", features = ["ahash", "std"], default-features = false } chumsky = { version = "0.9.2", features = [
"ahash",
"std",
], default-features = false }
[dev-dependencies] [dev-dependencies]
indoc = "2.0.1" indoc = "2.0.1"
insta = { version = "1.30.0", features = ["yaml"] } insta.workspace = true
pretty_assertions = "1.3.0" pretty_assertions = "1.3.0"
[profile.dev.package.insta]
opt-level = 3
[profile.dev.package.similar]
opt-level = 3

View File

@ -13,7 +13,7 @@ pub use definitions::parser as definitions;
pub use expr::parser as expression; pub use expr::parser as expression;
pub use pattern::parser as pattern; pub use pattern::parser as pattern;
use crate::ast::{self, BinOp, Span}; use crate::ast::{self, Span};
use chumsky::{chain::Chain, prelude::*}; use chumsky::{chain::Chain, prelude::*};
use error::ParseError; use error::ParseError;
use extra::ModuleExtra; use extra::ModuleExtra;
@ -96,119 +96,3 @@ pub fn module(
Ok((module, extra)) Ok((module, extra))
} }
pub fn when_clause_guard_parser() -> impl Parser<Token, ast::ClauseGuard<()>, Error = ParseError> {
recursive(|r| {
let var_parser = select! {
Token::Name { name } => name,
Token::UpName { name } => name,
}
.map_with_span(|name, span| ast::ClauseGuard::Var {
name,
tipo: (),
location: span,
});
let constant_parser = definitions::constant::value().map(ast::ClauseGuard::Constant);
let block_parser = r
.clone()
.delimited_by(just(Token::LeftParen), just(Token::RightParen));
let leaf_parser = choice((var_parser, constant_parser, block_parser)).boxed();
let unary_op = just(Token::Bang);
let unary = unary_op
.map_with_span(|op, span| (op, span))
.repeated()
.then(leaf_parser)
.foldr(|(_, span), value| ast::ClauseGuard::Not {
location: span.union(value.location()),
value: Box::new(value),
})
.boxed();
let comparison_op = choice((
just(Token::EqualEqual).to(BinOp::Eq),
just(Token::NotEqual).to(BinOp::NotEq),
just(Token::Less).to(BinOp::LtInt),
just(Token::Greater).to(BinOp::GtInt),
just(Token::LessEqual).to(BinOp::LtEqInt),
just(Token::GreaterEqual).to(BinOp::GtEqInt),
));
let comparison = unary
.clone()
.then(comparison_op.then(unary).repeated())
.foldl(|left, (op, right)| {
let location = left.location().union(right.location());
let left = Box::new(left);
let right = Box::new(right);
match op {
BinOp::Eq => ast::ClauseGuard::Equals {
location,
left,
right,
},
BinOp::NotEq => ast::ClauseGuard::NotEquals {
location,
left,
right,
},
BinOp::LtInt => ast::ClauseGuard::LtInt {
location,
left,
right,
},
BinOp::GtInt => ast::ClauseGuard::GtInt {
location,
left,
right,
},
BinOp::LtEqInt => ast::ClauseGuard::LtEqInt {
location,
left,
right,
},
BinOp::GtEqInt => ast::ClauseGuard::GtEqInt {
location,
left,
right,
},
_ => unreachable!(),
}
})
.boxed();
let and_op = just(Token::AmperAmper);
let conjunction = comparison
.clone()
.then(and_op.then(comparison).repeated())
.foldl(|left, (_tok, right)| {
let location = left.location().union(right.location());
let left = Box::new(left);
let right = Box::new(right);
ast::ClauseGuard::And {
location,
left,
right,
}
});
let or_op = just(Token::VbarVbar);
conjunction
.clone()
.then(or_op.then(conjunction).repeated())
.foldl(|left, (_tok, right)| {
let location = left.location().union(right.location());
let left = Box::new(left);
let right = Box::new(right);
ast::ClauseGuard::Or {
location,
left,
right,
}
})
})
}

View File

@ -0,0 +1,89 @@
use chumsky::prelude::*;
use crate::{
ast,
expr::{FnStyle, UntypedExpr},
parser::{error::ParseError, token::Token},
};
pub fn parser() -> impl Parser<Token, UntypedExpr, Error = ParseError> {
select! {
Token::EqualEqual => ast::BinOp::Eq,
Token::NotEqual => ast::BinOp::NotEq,
Token::Less => ast::BinOp::LtInt,
Token::LessEqual => ast::BinOp::LtEqInt,
Token::Greater => ast::BinOp::GtInt,
Token::GreaterEqual => ast::BinOp::GtEqInt,
Token::VbarVbar => ast::BinOp::Or,
Token::AmperAmper => ast::BinOp::And,
Token::Plus => ast::BinOp::AddInt,
Token::Minus => ast::BinOp::SubInt,
Token::Slash => ast::BinOp::DivInt,
Token::Star => ast::BinOp::MultInt,
Token::Percent => ast::BinOp::ModInt,
}
.map_with_span(|name, location| {
use ast::BinOp::*;
let arg_annotation = match name {
Or | And => Some(ast::Annotation::boolean(location)),
Eq | NotEq => None,
LtInt | LtEqInt | GtInt | GtEqInt | AddInt | SubInt | MultInt | DivInt | ModInt => {
Some(ast::Annotation::int(location))
}
};
let return_annotation = match name {
Or | And | Eq | NotEq | LtInt | LtEqInt | GtInt | GtEqInt => {
Some(ast::Annotation::boolean(location))
}
AddInt | SubInt | MultInt | DivInt | ModInt => Some(ast::Annotation::int(location)),
};
let arguments = vec![
ast::Arg {
arg_name: ast::ArgName::Named {
name: "left".to_string(),
label: "left".to_string(),
location,
is_validator_param: false,
},
annotation: arg_annotation.clone(),
location,
tipo: (),
},
ast::Arg {
arg_name: ast::ArgName::Named {
name: "right".to_string(),
label: "right".to_string(),
location,
is_validator_param: false,
},
annotation: arg_annotation,
location,
tipo: (),
},
];
let body = UntypedExpr::BinOp {
location,
name,
left: Box::new(UntypedExpr::Var {
location,
name: "left".to_string(),
}),
right: Box::new(UntypedExpr::Var {
location,
name: "right".to_string(),
}),
};
UntypedExpr::Fn {
arguments,
body: Box::new(body),
return_annotation,
fn_style: FnStyle::BinOp(name),
location,
}
})
}

View File

@ -6,9 +6,9 @@ use crate::{
parser::{annotation, error::ParseError, token::Token}, parser::{annotation, error::ParseError, token::Token},
}; };
pub fn parser<'a>( pub fn parser(
seq_r: Recursive<'a, Token, UntypedExpr, ParseError>, seq_r: Recursive<'_, Token, UntypedExpr, ParseError>,
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + 'a { ) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
just(Token::Fn) just(Token::Fn)
.ignore_then( .ignore_then(
params() params()

View File

@ -1,6 +1,7 @@
use chumsky::prelude::*; use chumsky::prelude::*;
use vec1::{vec1, Vec1}; use vec1::Vec1;
mod anonymous_binop;
pub mod anonymous_function; pub mod anonymous_function;
pub mod assignment; pub mod assignment;
mod block; mod block;
@ -14,7 +15,9 @@ mod sequence;
pub mod string; pub mod string;
mod tuple; mod tuple;
mod var; mod var;
pub mod when;
use anonymous_binop::parser as anonymous_binop;
pub use anonymous_function::parser as anonymous_function; pub use anonymous_function::parser as anonymous_function;
pub use block::parser as block; pub use block::parser as block;
pub use bytearray::parser as bytearray; pub use bytearray::parser as bytearray;
@ -27,10 +30,11 @@ pub use sequence::parser as sequence;
pub use string::parser as string; pub use string::parser as string;
pub use tuple::parser as tuple; pub use tuple::parser as tuple;
pub use var::parser as var; pub use var::parser as var;
pub use when::parser as when;
use crate::{ use crate::{
ast::{self, Span}, ast::{self, Span},
expr::UntypedExpr, expr::{FnStyle, UntypedExpr},
}; };
use super::{error::ParseError, token::Token}; use super::{error::ParseError, token::Token};
@ -52,171 +56,23 @@ pub fn parser(
}), }),
}); });
let anon_binop_parser = select! {
Token::EqualEqual => BinOp::Eq,
Token::NotEqual => BinOp::NotEq,
Token::Less => BinOp::LtInt,
Token::LessEqual => BinOp::LtEqInt,
Token::Greater => BinOp::GtInt,
Token::GreaterEqual => BinOp::GtEqInt,
Token::VbarVbar => BinOp::Or,
Token::AmperAmper => BinOp::And,
Token::Plus => BinOp::AddInt,
Token::Minus => BinOp::SubInt,
Token::Slash => BinOp::DivInt,
Token::Star => BinOp::MultInt,
Token::Percent => BinOp::ModInt,
}
.map_with_span(|name, location| {
use BinOp::*;
let arg_annotation = match name {
Or | And => Some(ast::Annotation::boolean(location)),
Eq | NotEq => None,
LtInt | LtEqInt | GtInt | GtEqInt | AddInt | SubInt | MultInt | DivInt | ModInt => {
Some(ast::Annotation::int(location))
}
};
let return_annotation = match name {
Or | And | Eq | NotEq | LtInt | LtEqInt | GtInt | GtEqInt => {
Some(ast::Annotation::boolean(location))
}
AddInt | SubInt | MultInt | DivInt | ModInt => Some(ast::Annotation::int(location)),
};
let arguments = vec![
ast::Arg {
arg_name: ast::ArgName::Named {
name: "left".to_string(),
label: "left".to_string(),
location,
is_validator_param: false,
},
annotation: arg_annotation.clone(),
location,
tipo: (),
},
ast::Arg {
arg_name: ast::ArgName::Named {
name: "right".to_string(),
label: "right".to_string(),
location,
is_validator_param: false,
},
annotation: arg_annotation,
location,
tipo: (),
},
];
let body = UntypedExpr::BinOp {
location,
name,
left: Box::new(UntypedExpr::Var {
location,
name: "left".to_string(),
}),
right: Box::new(UntypedExpr::Var {
location,
name: "right".to_string(),
}),
};
UntypedExpr::Fn {
arguments,
body: Box::new(body),
return_annotation,
fn_style: FnStyle::BinOp(name),
location,
}
});
let when_clause_parser = pattern_parser()
.then(
just(Token::Vbar)
.ignore_then(pattern_parser())
.repeated()
.or_not(),
)
.then(choice((
just(Token::If)
.ignore_then(when_clause_guard_parser())
.or_not()
.then_ignore(just(Token::RArrow)),
just(Token::If)
.ignore_then(take_until(just(Token::RArrow)))
.validate(|_value, span, emit| {
emit(ParseError::invalid_when_clause_guard(span));
None
}),
)))
// TODO: add hint "Did you mean to wrap a multi line clause in curly braces?"
.then(choice((
r.clone(),
just(Token::Todo)
.ignore_then(
r.clone()
.then_ignore(one_of(Token::RArrow).not().rewind())
.or_not(),
)
.map_with_span(|reason, span| {
UntypedExpr::todo(span, reason.map(flexible_string_literal))
}),
just(Token::ErrorTerm)
.ignore_then(
r.clone()
.then_ignore(just(Token::RArrow).not().rewind())
.or_not(),
)
.map_with_span(|reason, span| {
UntypedExpr::error(span, reason.map(flexible_string_literal))
}),
)))
.map_with_span(
|(((pattern, alternative_patterns_opt), guard), then), span| {
let mut patterns = vec1![pattern];
patterns.append(&mut alternative_patterns_opt.unwrap_or_default());
ast::UntypedClause {
location: span,
patterns,
guard,
then,
}
},
);
let when_parser = just(Token::When)
// TODO: If subject is empty we should return ParseErrorType::ExpectedExpr,
.ignore_then(r.clone().map(Box::new))
.then_ignore(just(Token::Is))
.then_ignore(just(Token::LeftBrace))
// TODO: If clauses are empty we should return ParseErrorType::NoCaseClause
.then(when_clause_parser.repeated())
.then_ignore(just(Token::RightBrace))
.map_with_span(|(subject, clauses), span| UntypedExpr::When {
location: span,
subject,
clauses,
});
let expr_unit_parser = choice(( let expr_unit_parser = choice((
string(), string(),
int(), int(),
record_update(r), record_update(r.clone()),
record(r), record(r.clone()),
field_access_constructor, field_access_constructor,
var(), var(),
tuple(r), tuple(r.clone()),
bytearray(), bytearray(),
list(r), list(r.clone()),
anonymous_function(seq_r), anonymous_function(seq_r.clone()),
anon_binop_parser, anonymous_binop(),
block(seq_r), block(seq_r.clone()),
when_parser, when(r.clone()),
assignment::let_(r), assignment::let_(r.clone()),
assignment::expect(r), assignment::expect(r.clone()),
if_else(seq_r, r), if_else(seq_r, r.clone()),
)); ));
// Parsing a function call into the appropriate structure // Parsing a function call into the appropriate structure
@ -262,7 +118,7 @@ pub fn parser(
select! { Token::Name { name } => name } select! { Token::Name { name } => name }
.then_ignore(just(Token::Colon)) .then_ignore(just(Token::Colon))
.or_not() .or_not()
.then(r.clone()) .then(r)
.map_with_span(|(label, value), span| { .map_with_span(|(label, value), span| {
ParserArg::Arg(Box::new(ast::CallArg { ParserArg::Arg(Box::new(ast::CallArg {
label, label,
@ -298,7 +154,8 @@ pub fn parser(
.map(|(index, a)| match a { .map(|(index, a)| match a {
ParserArg::Arg(arg) => *arg, ParserArg::Arg(arg) => *arg,
ParserArg::Hole { location, label } => { ParserArg::Hole { location, label } => {
let name = format!("{CAPTURE_VARIABLE}__{index}"); let name = format!("{}__{index}", ast::CAPTURE_VARIABLE);
holes.push(ast::Arg { holes.push(ast::Arg {
location: Span::empty(), location: Span::empty(),
annotation: None, annotation: None,
@ -316,7 +173,7 @@ pub fn parser(
location, location,
value: UntypedExpr::Var { value: UntypedExpr::Var {
location, location,
name: format!("{CAPTURE_VARIABLE}__{index}"), name: format!("{}__{index}", ast::CAPTURE_VARIABLE),
}, },
} }
} }
@ -367,7 +224,7 @@ pub fn parser(
// Negate // Negate
let op = choice(( let op = choice((
just(Token::Bang).to(UnOp::Not), just(Token::Bang).to(ast::UnOp::Not),
just(Token::Minus) just(Token::Minus)
// NOTE: Prevent conflict with usage for '-' as a standalone binary op. // NOTE: Prevent conflict with usage for '-' as a standalone binary op.
// This will make '-' parse when used as standalone binop in a function call. // This will make '-' parse when used as standalone binop in a function call.
@ -381,7 +238,7 @@ pub fn parser(
// //
// which seems acceptable. // which seems acceptable.
.then_ignore(just(Token::Comma).not().rewind()) .then_ignore(just(Token::Comma).not().rewind())
.to(UnOp::Negate), .to(ast::UnOp::Negate),
)); ));
let unary = op let unary = op
@ -397,9 +254,9 @@ pub fn parser(
// Product // Product
let op = choice(( let op = choice((
just(Token::Star).to(BinOp::MultInt), just(Token::Star).to(ast::BinOp::MultInt),
just(Token::Slash).to(BinOp::DivInt), just(Token::Slash).to(ast::BinOp::DivInt),
just(Token::Percent).to(BinOp::ModInt), just(Token::Percent).to(ast::BinOp::ModInt),
)); ));
let product = unary let product = unary
@ -415,8 +272,8 @@ pub fn parser(
// Sum // Sum
let op = choice(( let op = choice((
just(Token::Plus).to(BinOp::AddInt), just(Token::Plus).to(ast::BinOp::AddInt),
just(Token::Minus).to(BinOp::SubInt), just(Token::Minus).to(ast::BinOp::SubInt),
)); ));
let sum = product let sum = product
@ -432,12 +289,12 @@ pub fn parser(
// Comparison // Comparison
let op = choice(( let op = choice((
just(Token::EqualEqual).to(BinOp::Eq), just(Token::EqualEqual).to(ast::BinOp::Eq),
just(Token::NotEqual).to(BinOp::NotEq), just(Token::NotEqual).to(ast::BinOp::NotEq),
just(Token::Less).to(BinOp::LtInt), just(Token::Less).to(ast::BinOp::LtInt),
just(Token::Greater).to(BinOp::GtInt), just(Token::Greater).to(ast::BinOp::GtInt),
just(Token::LessEqual).to(BinOp::LtEqInt), just(Token::LessEqual).to(ast::BinOp::LtEqInt),
just(Token::GreaterEqual).to(BinOp::GtEqInt), just(Token::GreaterEqual).to(ast::BinOp::GtEqInt),
)); ));
let comparison = sum let comparison = sum
@ -452,7 +309,7 @@ pub fn parser(
.boxed(); .boxed();
// Conjunction // Conjunction
let op = just(Token::AmperAmper).to(BinOp::And); let op = just(Token::AmperAmper).to(ast::BinOp::And);
let conjunction = comparison let conjunction = comparison
.clone() .clone()
.then(op.then(comparison).repeated()) .then(op.then(comparison).repeated())
@ -465,7 +322,7 @@ pub fn parser(
.boxed(); .boxed();
// Disjunction // Disjunction
let op = just(Token::VbarVbar).to(BinOp::Or); let op = just(Token::VbarVbar).to(ast::BinOp::Or);
let disjunction = conjunction let disjunction = conjunction
.clone() .clone()
.then(op.then(conjunction).repeated()) .then(op.then(conjunction).repeated())

View File

@ -0,0 +1,59 @@
use chumsky::prelude::*;
use vec1::vec1;
use crate::{
ast,
expr::UntypedExpr,
parser::{error::ParseError, expr::string::flexible, pattern, token::Token},
};
use super::guard;
pub fn parser(
r: Recursive<'_, Token, UntypedExpr, ParseError>,
) -> impl Parser<Token, ast::UntypedClause, Error = ParseError> + '_ {
pattern()
.then(just(Token::Vbar).ignore_then(pattern()).repeated().or_not())
.then(choice((
just(Token::If)
.ignore_then(guard())
.or_not()
.then_ignore(just(Token::RArrow)),
just(Token::If)
.ignore_then(take_until(just(Token::RArrow)))
.validate(|_value, span, emit| {
emit(ParseError::invalid_when_clause_guard(span));
None
}),
)))
// TODO: add hint "Did you mean to wrap a multi line clause in curly braces?"
.then(choice((
r.clone(),
just(Token::Todo)
.ignore_then(
r.clone()
.then_ignore(one_of(Token::RArrow).not().rewind())
.or_not(),
)
.map_with_span(|reason, span| UntypedExpr::todo(span, reason.map(flexible))),
just(Token::ErrorTerm)
.ignore_then(
r.clone()
.then_ignore(just(Token::RArrow).not().rewind())
.or_not(),
)
.map_with_span(|reason, span| UntypedExpr::error(span, reason.map(flexible))),
)))
.map_with_span(
|(((pattern, alternative_patterns_opt), guard), then), span| {
let mut patterns = vec1![pattern];
patterns.append(&mut alternative_patterns_opt.unwrap_or_default());
ast::UntypedClause {
location: span,
patterns,
guard,
then,
}
},
)
}

View File

@ -0,0 +1,122 @@
use chumsky::prelude::*;
use crate::{
ast,
parser::{definitions, error::ParseError, token::Token},
};
pub fn parser() -> impl Parser<Token, ast::UntypedClauseGuard, Error = ParseError> {
recursive(|r| {
let var_parser = select! {
Token::Name { name } => name,
Token::UpName { name } => name,
}
.map_with_span(|name, span| ast::ClauseGuard::Var {
name,
tipo: (),
location: span,
});
let constant_parser = definitions::constant::value().map(ast::ClauseGuard::Constant);
let block_parser = r
.clone()
.delimited_by(just(Token::LeftParen), just(Token::RightParen));
let leaf_parser = choice((var_parser, constant_parser, block_parser)).boxed();
let unary_op = just(Token::Bang);
let unary = unary_op
.map_with_span(|op, span| (op, span))
.repeated()
.then(leaf_parser)
.foldr(|(_, span), value| ast::ClauseGuard::Not {
location: span.union(value.location()),
value: Box::new(value),
})
.boxed();
let comparison_op = choice((
just(Token::EqualEqual).to(ast::BinOp::Eq),
just(Token::NotEqual).to(ast::BinOp::NotEq),
just(Token::Less).to(ast::BinOp::LtInt),
just(Token::Greater).to(ast::BinOp::GtInt),
just(Token::LessEqual).to(ast::BinOp::LtEqInt),
just(Token::GreaterEqual).to(ast::BinOp::GtEqInt),
));
let comparison = unary
.clone()
.then(comparison_op.then(unary).repeated())
.foldl(|left, (op, right)| {
let location = left.location().union(right.location());
let left = Box::new(left);
let right = Box::new(right);
match op {
ast::BinOp::Eq => ast::ClauseGuard::Equals {
location,
left,
right,
},
ast::BinOp::NotEq => ast::ClauseGuard::NotEquals {
location,
left,
right,
},
ast::BinOp::LtInt => ast::ClauseGuard::LtInt {
location,
left,
right,
},
ast::BinOp::GtInt => ast::ClauseGuard::GtInt {
location,
left,
right,
},
ast::BinOp::LtEqInt => ast::ClauseGuard::LtEqInt {
location,
left,
right,
},
ast::BinOp::GtEqInt => ast::ClauseGuard::GtEqInt {
location,
left,
right,
},
_ => unreachable!(),
}
})
.boxed();
let and_op = just(Token::AmperAmper);
let conjunction = comparison
.clone()
.then(and_op.then(comparison).repeated())
.foldl(|left, (_tok, right)| {
let location = left.location().union(right.location());
let left = Box::new(left);
let right = Box::new(right);
ast::ClauseGuard::And {
location,
left,
right,
}
});
let or_op = just(Token::VbarVbar);
conjunction
.clone()
.then(or_op.then(conjunction).repeated())
.foldl(|left, (_tok, right)| {
let location = left.location().union(right.location());
let left = Box::new(left);
let right = Box::new(right);
ast::ClauseGuard::Or {
location,
left,
right,
}
})
})
}

View File

@ -0,0 +1,30 @@
use chumsky::prelude::*;
mod clause;
mod guard;
pub use clause::parser as clause;
pub use guard::parser as guard;
use crate::{
expr::UntypedExpr,
parser::{error::ParseError, token::Token},
};
pub fn parser(
r: Recursive<'_, Token, UntypedExpr, ParseError>,
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
just(Token::When)
// TODO: If subject is empty we should return ParseErrorType::ExpectedExpr,
.ignore_then(r.clone().map(Box::new))
.then_ignore(just(Token::Is))
.then_ignore(just(Token::LeftBrace))
// TODO: If clauses are empty we should return ParseErrorType::NoCaseClause
.then(clause(r).repeated())
.then_ignore(just(Token::RightBrace))
.map_with_span(|(subject, clauses), span| UntypedExpr::When {
location: span,
subject,
clauses,
})
}