diff --git a/Cargo.toml b/Cargo.toml index 8de2641b..84a62eeb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 diff --git a/crates/aiken-lang/Cargo.toml b/crates/aiken-lang/Cargo.toml index 365e8386..936dddf3 100644 --- a/crates/aiken-lang/Cargo.toml +++ b/crates/aiken-lang/Cargo.toml @@ -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 ", - "Kasey White ", - "KtorZ ", + "Lucas Rosa ", + "Kasey White ", + "KtorZ ", ] 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 diff --git a/crates/aiken-lang/src/parser.rs b/crates/aiken-lang/src/parser.rs index 84ef1809..2a7b628d 100644 --- a/crates/aiken-lang/src/parser.rs +++ b/crates/aiken-lang/src/parser.rs @@ -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, 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, - } - }) - }) -} diff --git a/crates/aiken-lang/src/parser/expr/anonymous_binop.rs b/crates/aiken-lang/src/parser/expr/anonymous_binop.rs new file mode 100644 index 00000000..7eaa8ae4 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/anonymous_binop.rs @@ -0,0 +1,89 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + expr::{FnStyle, UntypedExpr}, + parser::{error::ParseError, token::Token}, +}; + +pub fn parser() -> impl Parser { + 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, + } + }) +} diff --git a/crates/aiken-lang/src/parser/expr/anonymous_function.rs b/crates/aiken-lang/src/parser/expr/anonymous_function.rs index 9e8ae29d..6c25ab9f 100644 --- a/crates/aiken-lang/src/parser/expr/anonymous_function.rs +++ b/crates/aiken-lang/src/parser/expr/anonymous_function.rs @@ -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 + 'a { +pub fn parser( + seq_r: Recursive<'_, Token, UntypedExpr, ParseError>, +) -> impl Parser + '_ { just(Token::Fn) .ignore_then( params() diff --git a/crates/aiken-lang/src/parser/expr/mod.rs b/crates/aiken-lang/src/parser/expr/mod.rs index df8ac0fe..d6e09842 100644 --- a/crates/aiken-lang/src/parser/expr/mod.rs +++ b/crates/aiken-lang/src/parser/expr/mod.rs @@ -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()) diff --git a/crates/aiken-lang/src/parser/expr/when/clause.rs b/crates/aiken-lang/src/parser/expr/when/clause.rs new file mode 100644 index 00000000..474a470f --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/when/clause.rs @@ -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 + '_ { + 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, + } + }, + ) +} diff --git a/crates/aiken-lang/src/parser/expr/when/guard.rs b/crates/aiken-lang/src/parser/expr/when/guard.rs new file mode 100644 index 00000000..afcc118f --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/when/guard.rs @@ -0,0 +1,122 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + parser::{definitions, error::ParseError, token::Token}, +}; + +pub fn parser() -> impl Parser { + 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, + } + }) + }) +} diff --git a/crates/aiken-lang/src/parser/expr/when/mod.rs b/crates/aiken-lang/src/parser/expr/when/mod.rs new file mode 100644 index 00000000..13c2ed91 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/when/mod.rs @@ -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 + '_ { + 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, + }) +}