feat: parser improvements

- record creation with punning
- disambiguate if condition from record creation with punning
- split parser tests up into many smaller ones
This commit is contained in:
rvcas 2022-12-04 16:02:26 -05:00 committed by Lucas
parent 375499930a
commit 391849bf37
7 changed files with 1386 additions and 1064 deletions

1
Cargo.lock generated
View File

@ -75,6 +75,7 @@ version = "0.0.26"
dependencies = [ dependencies = [
"chumsky", "chumsky",
"indexmap", "indexmap",
"indoc",
"itertools", "itertools",
"miette", "miette",
"pretty_assertions", "pretty_assertions",

View File

@ -21,4 +21,5 @@ uplc = { path = '../uplc', version = "0.0.25" }
vec1 = "1.8.0" vec1 = "1.8.0"
[dev-dependencies] [dev-dependencies]
indoc = "1.0.7"
pretty_assertions = "1.3.0" pretty_assertions = "1.3.0"

View File

@ -562,6 +562,168 @@ pub fn expr_parser(
} }
}); });
let record_update_parser = select! {Token::Name { name } => name}
.map_with_span(|module, span: Span| (module, span))
.then_ignore(just(Token::Dot))
.or_not()
.then(select! {Token::UpName { name } => name}.map_with_span(|name, span| (name, span)))
.then(
just(Token::DotDot)
.ignore_then(r.clone())
.then(
just(Token::Comma)
.ignore_then(
select! { Token::Name {name} => name }
.then_ignore(just(Token::Colon))
.then(r.clone())
.map_with_span(|(label, value), span| {
ast::UntypedRecordUpdateArg {
label,
value,
location: span,
}
})
.separated_by(just(Token::Comma))
.allow_trailing(),
)
.or_not(),
)
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace))
.map_with_span(|a, span: Span| (a, span)),
)
.map(|((module, (name, n_span)), ((spread, opt_args), span))| {
let constructor = if let Some((module, m_span)) = module {
expr::UntypedExpr::FieldAccess {
location: m_span.union(n_span),
label: name,
container: Box::new(expr::UntypedExpr::Var {
location: m_span,
name: module,
}),
}
} else {
expr::UntypedExpr::Var {
location: n_span,
name,
}
};
let spread_span = spread.location();
let location = Span::new((), spread_span.start - 2..spread_span.end);
let spread = ast::RecordUpdateSpread {
base: Box::new(spread),
location,
};
expr::UntypedExpr::RecordUpdate {
location: constructor.location().union(span),
constructor: Box::new(constructor),
spread,
arguments: opt_args.unwrap_or_default(),
}
});
let record_parser = choice((
select! {Token::Name { name } => name}
.map_with_span(|module, span| (module, span))
.then_ignore(just(Token::Dot))
.or_not()
.then(
select! {Token::UpName { name } => name}
.map_with_span(|name, span| (name, span)),
)
.then(
select! {Token::Name {name} => name}
.then_ignore(just(Token::Colon))
.or_not()
.then(r.clone())
.validate(|(label_opt, value), span, emit| {
dbg!(&label_opt);
let label = if label_opt.is_some() {
label_opt
} else if let expr::UntypedExpr::Var { name, .. } = &value {
Some(name.clone())
} else {
emit(ParseError::expected_input_found(
value.location(),
None,
Some(error::Pattern::RecordPunning),
));
None
};
ast::CallArg {
location: span,
value,
label,
}
})
.separated_by(just(Token::Comma))
.allow_trailing()
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace)),
),
select! {Token::Name { name } => name}
.map_with_span(|module, span| (module, span))
.then_ignore(just(Token::Dot))
.or_not()
.then(
select! {Token::UpName { name } => name}
.map_with_span(|name, span| (name, span)),
)
.then(
r.clone()
.map_with_span(|value, span| ast::CallArg {
location: span,
value,
label: None,
})
.separated_by(just(Token::Comma))
.allow_trailing()
.delimited_by(just(Token::LeftParen), just(Token::RightParen)),
),
))
.map_with_span(|((module, (name, n_span)), arguments), span| {
let fun = if let Some((module, m_span)) = module {
expr::UntypedExpr::FieldAccess {
location: m_span.union(n_span),
label: name,
container: Box::new(expr::UntypedExpr::Var {
location: m_span,
name: module,
}),
}
} else {
expr::UntypedExpr::Var {
location: n_span,
name,
}
};
expr::UntypedExpr::Call {
arguments,
fun: Box::new(fun),
location: span,
}
});
let field_access_constructor = select! {Token::Name { name } => name}
.map_with_span(|module, span| (module, span))
.then_ignore(just(Token::Dot))
.then(select! {Token::UpName { name } => name})
.map_with_span(
|((module, m_span), name), span| expr::UntypedExpr::FieldAccess {
location: span,
label: name,
container: Box::new(expr::UntypedExpr::Var {
location: m_span,
name: module,
}),
},
);
let var_parser = select! { let var_parser = select! {
Token::Name { name } => name, Token::Name { name } => name,
Token::UpName { name } => name, Token::UpName { name } => name,
@ -756,23 +918,29 @@ pub fn expr_parser(
); );
let if_parser = just(Token::If) let if_parser = just(Token::If)
.ignore_then(r.clone().then(block_parser.clone()).map_with_span( .ignore_then(
|(condition, body), span| ast::IfBranch { r.clone()
condition, .then_ignore(just(Token::Then))
body, .then(block_parser.clone())
location: span, .map_with_span(|(condition, body), span| ast::IfBranch {
}, condition,
)) body,
location: span,
}),
)
.then( .then(
just(Token::Else) just(Token::Else)
.ignore_then(just(Token::If)) .ignore_then(just(Token::If))
.ignore_then(r.clone().then(block_parser.clone()).map_with_span( .ignore_then(
|(condition, body), span| ast::IfBranch { r.clone()
condition, .then_ignore(just(Token::Then))
body, .then(block_parser.clone())
location: span, .map_with_span(|(condition, body), span| ast::IfBranch {
}, condition,
)) body,
location: span,
}),
)
.repeated(), .repeated(),
) )
.then_ignore(just(Token::Else)) .then_ignore(just(Token::Else))
@ -792,6 +960,9 @@ pub fn expr_parser(
let expr_unit_parser = choice(( let expr_unit_parser = choice((
string_parser, string_parser,
int_parser, int_parser,
record_update_parser,
record_parser,
field_access_constructor,
var_parser, var_parser,
todo_parser, todo_parser,
tuple, tuple,
@ -819,42 +990,14 @@ pub fn expr_parser(
enum Chain { enum Chain {
Call(Vec<ParserArg>, Span), Call(Vec<ParserArg>, Span),
FieldAccess(String, Span), FieldAccess(String, Span),
RecordUpdate(
Box<(expr::UntypedExpr, Vec<ast::UntypedRecordUpdateArg>)>,
Span,
),
} }
let field_access_parser = just(Token::Dot) let field_access_parser = just(Token::Dot)
.ignore_then(select! { .ignore_then(select! {
Token::Name { name } => name, Token::Name { name } => name,
Token::UpName { name } => name
}) })
.map_with_span(Chain::FieldAccess); .map_with_span(Chain::FieldAccess);
let record_update_parser = just(Token::DotDot)
.ignore_then(r.clone())
.then(
just(Token::Comma)
.ignore_then(
select! { Token::Name {name} => name }
.then_ignore(just(Token::Colon))
.then(r.clone())
.map_with_span(|(label, value), span| ast::UntypedRecordUpdateArg {
label,
value,
location: span,
})
.separated_by(just(Token::Comma))
.allow_trailing(),
)
.or_not(),
)
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace))
.map_with_span(|(spread, args_opt), span| {
Chain::RecordUpdate(Box::new((spread, args_opt.unwrap_or_default())), span)
});
let call_parser = choice(( let call_parser = choice((
select! { Token::Name { name } => name } select! { Token::Name { name } => name }
.then_ignore(just(Token::Colon)) .then_ignore(just(Token::Colon))
@ -881,7 +1024,7 @@ pub fn expr_parser(
.delimited_by(just(Token::LeftParen), just(Token::RightParen)) .delimited_by(just(Token::LeftParen), just(Token::RightParen))
.map_with_span(Chain::Call); .map_with_span(Chain::Call);
let chain = choice((field_access_parser, record_update_parser, call_parser)); let chain = choice((field_access_parser, call_parser));
let chained = expr_unit_parser let chained = expr_unit_parser
.then(chain.repeated()) .then(chain.repeated())
@ -941,24 +1084,6 @@ pub fn expr_parser(
label, label,
container: Box::new(e), container: Box::new(e),
}, },
Chain::RecordUpdate(data, span) => {
let (spread, arguments) = *data;
let location = span.union(spread.location());
let spread = ast::RecordUpdateSpread {
base: Box::new(spread),
location,
};
expr::UntypedExpr::RecordUpdate {
location: e.location().union(span),
constructor: Box::new(e),
spread,
arguments,
}
}
}); });
// Negate // Negate

View File

@ -69,6 +69,7 @@ pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = ParseError> {
"fn" => Token::Fn, "fn" => Token::Fn,
"if" => Token::If, "if" => Token::If,
"else" => Token::Else, "else" => Token::Else,
"then" => Token::Then,
"is" => Token::Is, "is" => Token::Is,
"let" => Token::Let, "let" => Token::Let,
"opaque" => Token::Opaque, "opaque" => Token::Opaque,

View File

@ -72,6 +72,7 @@ pub enum Token {
Trace, Trace,
Type, Type,
When, When,
Then,
} }
impl fmt::Display for Token { impl fmt::Display for Token {
@ -145,6 +146,7 @@ impl fmt::Display for Token {
Token::Todo => "todo", Token::Todo => "todo",
Token::Trace => "try", Token::Trace => "try",
Token::Type => "type", Token::Type => "type",
Token::Then => "then",
}; };
write!(f, "\"{}\"", s) write!(f, "\"{}\"", s)
} }

File diff suppressed because it is too large Load Diff

View File

@ -32,7 +32,7 @@ pub fn final_check(z: Int) {
} }
pub fn incrementor(counter: Int, target: Int) -> Int { pub fn incrementor(counter: Int, target: Int) -> Int {
if counter == target { if counter == target then {
target target
} else { } else {
incrementor(counter + 1, target) incrementor(counter + 1, target)