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
tag-name = "v{{version}}"
[workspace.dependencies]
insta = { version = "1.30.0", features = ["yaml"] }
[profile.dev.package.insta]
opt-level = 3

View File

@ -7,9 +7,9 @@ repository = "https://github.com/aiken-lang/aiken"
homepage = "https://github.com/aiken-lang/aiken"
license = "Apache-2.0"
authors = [
"Lucas Rosa <x@rvcas.dev>",
"Kasey White <kwhitemsg@gmail.com>",
"KtorZ <matthias.benkort@gmail.com>",
"Lucas Rosa <x@rvcas.dev>",
"Kasey White <kwhitemsg@gmail.com>",
"KtorZ <matthias.benkort@gmail.com>",
]
rust-version = "1.66.1"
@ -30,15 +30,12 @@ num-bigint = "0.4.3"
[target.'cfg(not(target_family="wasm"))'.dependencies]
chumsky = "0.9.2"
[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]
indoc = "2.0.1"
insta = { version = "1.30.0", features = ["yaml"] }
insta.workspace = true
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 pattern::parser as pattern;
use crate::ast::{self, BinOp, Span};
use crate::ast::{self, Span};
use chumsky::{chain::Chain, prelude::*};
use error::ParseError;
use extra::ModuleExtra;
@ -96,119 +96,3 @@ pub fn module(
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},
};
pub fn parser<'a>(
seq_r: Recursive<'a, Token, UntypedExpr, ParseError>,
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + 'a {
pub fn parser(
seq_r: Recursive<'_, Token, UntypedExpr, ParseError>,
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
just(Token::Fn)
.ignore_then(
params()

View File

@ -1,6 +1,7 @@
use chumsky::prelude::*;
use vec1::{vec1, Vec1};
use vec1::Vec1;
mod anonymous_binop;
pub mod anonymous_function;
pub mod assignment;
mod block;
@ -14,7 +15,9 @@ mod sequence;
pub mod string;
mod tuple;
mod var;
pub mod when;
use anonymous_binop::parser as anonymous_binop;
pub use anonymous_function::parser as anonymous_function;
pub use block::parser as block;
pub use bytearray::parser as bytearray;
@ -27,10 +30,11 @@ pub use sequence::parser as sequence;
pub use string::parser as string;
pub use tuple::parser as tuple;
pub use var::parser as var;
pub use when::parser as when;
use crate::{
ast::{self, Span},
expr::UntypedExpr,
expr::{FnStyle, UntypedExpr},
};
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((
string(),
int(),
record_update(r),
record(r),
record_update(r.clone()),
record(r.clone()),
field_access_constructor,
var(),
tuple(r),
tuple(r.clone()),
bytearray(),
list(r),
anonymous_function(seq_r),
anon_binop_parser,
block(seq_r),
when_parser,
assignment::let_(r),
assignment::expect(r),
if_else(seq_r, r),
list(r.clone()),
anonymous_function(seq_r.clone()),
anonymous_binop(),
block(seq_r.clone()),
when(r.clone()),
assignment::let_(r.clone()),
assignment::expect(r.clone()),
if_else(seq_r, r.clone()),
));
// Parsing a function call into the appropriate structure
@ -262,7 +118,7 @@ pub fn parser(
select! { Token::Name { name } => name }
.then_ignore(just(Token::Colon))
.or_not()
.then(r.clone())
.then(r)
.map_with_span(|(label, value), span| {
ParserArg::Arg(Box::new(ast::CallArg {
label,
@ -298,7 +154,8 @@ pub fn parser(
.map(|(index, a)| match a {
ParserArg::Arg(arg) => *arg,
ParserArg::Hole { location, label } => {
let name = format!("{CAPTURE_VARIABLE}__{index}");
let name = format!("{}__{index}", ast::CAPTURE_VARIABLE);
holes.push(ast::Arg {
location: Span::empty(),
annotation: None,
@ -316,7 +173,7 @@ pub fn parser(
location,
value: UntypedExpr::Var {
location,
name: format!("{CAPTURE_VARIABLE}__{index}"),
name: format!("{}__{index}", ast::CAPTURE_VARIABLE),
},
}
}
@ -367,7 +224,7 @@ pub fn parser(
// Negate
let op = choice((
just(Token::Bang).to(UnOp::Not),
just(Token::Bang).to(ast::UnOp::Not),
just(Token::Minus)
// NOTE: Prevent conflict with usage for '-' as a standalone binary op.
// This will make '-' parse when used as standalone binop in a function call.
@ -381,7 +238,7 @@ pub fn parser(
//
// which seems acceptable.
.then_ignore(just(Token::Comma).not().rewind())
.to(UnOp::Negate),
.to(ast::UnOp::Negate),
));
let unary = op
@ -397,9 +254,9 @@ pub fn parser(
// Product
let op = choice((
just(Token::Star).to(BinOp::MultInt),
just(Token::Slash).to(BinOp::DivInt),
just(Token::Percent).to(BinOp::ModInt),
just(Token::Star).to(ast::BinOp::MultInt),
just(Token::Slash).to(ast::BinOp::DivInt),
just(Token::Percent).to(ast::BinOp::ModInt),
));
let product = unary
@ -415,8 +272,8 @@ pub fn parser(
// Sum
let op = choice((
just(Token::Plus).to(BinOp::AddInt),
just(Token::Minus).to(BinOp::SubInt),
just(Token::Plus).to(ast::BinOp::AddInt),
just(Token::Minus).to(ast::BinOp::SubInt),
));
let sum = product
@ -432,12 +289,12 @@ pub fn parser(
// Comparison
let 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),
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 = sum
@ -452,7 +309,7 @@ pub fn parser(
.boxed();
// Conjunction
let op = just(Token::AmperAmper).to(BinOp::And);
let op = just(Token::AmperAmper).to(ast::BinOp::And);
let conjunction = comparison
.clone()
.then(op.then(comparison).repeated())
@ -465,7 +322,7 @@ pub fn parser(
.boxed();
// Disjunction
let op = just(Token::VbarVbar).to(BinOp::Or);
let op = just(Token::VbarVbar).to(ast::BinOp::Or);
let disjunction = conjunction
.clone()
.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,
})
}