diff --git a/Cargo.lock b/Cargo.lock index 25a29a3c8..d6a09b89d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,6 +83,7 @@ dependencies = [ "hex", "indexmap", "indoc", + "insta", "itertools", "miette", "num-bigint", @@ -525,6 +526,18 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys 0.45.0", +] + [[package]] name = "const-oid" version = "0.9.2" @@ -738,6 +751,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.32" @@ -1208,6 +1227,20 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f2cb48b81b1dc9f39676bf99f5499babfec7cd8fe14307f7b3d747208fb5690" +[[package]] +name = "insta" +version = "1.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28491f7753051e5704d4d0ae7860d45fae3238d7d235bc4289dcd45c48d3cec3" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "serde", + "similar", + "yaml-rust", +] + [[package]] name = "instant" version = "0.1.12" @@ -1341,6 +1374,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.3.1" @@ -2385,6 +2424,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "similar" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" + [[package]] name = "slab" version = "0.4.8" @@ -3257,6 +3302,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 6a22a2d40..84a62eeb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,12 @@ strip = true [workspace.metadata.release] shared-version = true tag-name = "v{{version}}" + +[workspace.dependencies] +insta = { version = "1.30.0", features = ["yaml"] } + +[profile.dev.package.insta] +opt-level = 3 + +[profile.dev.package.similar] +opt-level = 3 diff --git a/crates/aiken-lang/Cargo.toml b/crates/aiken-lang/Cargo.toml index 6a079f21b..936dddf3a 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,8 +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.workspace = true pretty_assertions = "1.3.0" diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 9f98f97c1..a2b1ad895 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -1167,6 +1167,12 @@ impl Span { Self::new((), 0..0) } + pub fn create(i: usize, n: usize) -> Self { + use chumsky::Span; + + Self::new((), i..i + n) + } + pub fn range(&self) -> Range { use chumsky::Span; diff --git a/crates/aiken-lang/src/parser.rs b/crates/aiken-lang/src/parser.rs index 408511f96..a3813a82c 100644 --- a/crates/aiken-lang/src/parser.rs +++ b/crates/aiken-lang/src/parser.rs @@ -1,85 +1,34 @@ +mod annotation; +pub mod chain; +pub mod definition; pub mod error; +pub mod expr; pub mod extra; pub mod lexer; +pub mod literal; +pub mod pattern; pub mod token; +mod utils; -use crate::{ - ast::{ - self, BinOp, ByteArrayFormatPreference, Span, TraceKind, UnOp, UntypedDefinition, - CAPTURE_VARIABLE, - }, - expr, -}; -use chumsky::{chain::Chain, prelude::*}; +pub use annotation::parser as annotation; +pub use definition::parser as definition; +pub use expr::parser as expression; +pub use pattern::parser as pattern; + +use crate::ast; +use chumsky::prelude::*; use error::ParseError; use extra::ModuleExtra; -use token::{Base, Token}; -use vec1::{vec1, Vec1}; pub fn module( src: &str, kind: ast::ModuleKind, ) -> Result<(ast::UntypedModule, ModuleExtra), Vec> { - let len = src.as_bytes().len(); - - let span = |i, n| Span::new((), i..i + n); - - let tokens = lexer::lexer().parse(chumsky::Stream::from_iter( - span(len, 1), - src.chars().scan(0, |i, c| { - let start = *i; - let offset = c.len_utf8(); - *i = start + offset; - Some((c, span(start, offset))) - }), - ))?; - - let mut extra = ModuleExtra::new(); - - let mut previous_is_newline = false; + let lexer::LexInfo { tokens, extra } = lexer::run(src)?; - let tokens = tokens.into_iter().filter_map(|(token, ref span)| { - let current_is_newline = token == Token::NewLine || token == Token::EmptyLine; - let result = match token { - Token::ModuleComment => { - extra.module_comments.push(*span); - None - } - Token::DocComment => { - extra.doc_comments.push(*span); - None - } - Token::Comment => { - extra.comments.push(*span); - None - } - Token::EmptyLine => { - extra.empty_lines.push(span.start); - None - } - Token::LeftParen => { - if previous_is_newline { - Some((Token::NewLineLeftParen, *span)) - } else { - Some((Token::LeftParen, *span)) - } - } - Token::Pipe => { - if previous_is_newline { - Some((Token::NewLinePipe, *span)) - } else { - Some((Token::Pipe, *span)) - } - } - Token::NewLine => None, - _ => Some((token, *span)), - }; - previous_is_newline = current_is_newline; - result - }); + let stream = chumsky::Stream::from_iter(ast::Span::create(tokens.len(), 1), tokens.into_iter()); - let definitions = - module_parser().parse(chumsky::Stream::from_iter(span(tokens.len(), 1), tokens))?; + let definitions = definition().repeated().then_ignore(end()).parse(stream)?; let module = ast::UntypedModule { kind, @@ -92,1798 +41,73 @@ pub fn module( Ok((module, extra)) } -fn module_parser() -> impl Parser, Error = ParseError> { - choice(( - import_parser(), - data_parser(), - type_alias_parser(), - validator_parser(), - fn_parser(), - test_parser(), - constant_parser(), - )) - .repeated() - .then_ignore(end()) -} - -pub fn import_parser() -> impl Parser { - let unqualified_import = choice(( - select! {Token::Name { name } => name}.then( - just(Token::As) - .ignore_then(select! {Token::Name { name } => name}) - .or_not(), - ), - select! {Token::UpName { name } => name}.then( - just(Token::As) - .ignore_then(select! {Token::UpName { name } => name}) - .or_not(), - ), - )) - .map_with_span(|(name, as_name), span| ast::UnqualifiedImport { - name, - location: span, - as_name, - layer: Default::default(), - }); - - let unqualified_imports = just(Token::Dot) - .ignore_then( - unqualified_import - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), - ) - .or_not(); - - let as_name = just(Token::As) - .ignore_then(select! {Token::Name { name } => name}) - .or_not(); - - let module_path = select! {Token::Name { name } => name} - .separated_by(just(Token::Slash)) - .then(unqualified_imports) - .then(as_name); - - just(Token::Use).ignore_then(module_path).map_with_span( - |((module, unqualified), as_name), span| { - ast::UntypedDefinition::Use(ast::Use { - module, - as_name, - unqualified: unqualified.unwrap_or_default(), - package: (), - location: span, - }) - }, - ) -} - -pub fn data_parser() -> impl Parser { - let unlabeled_constructor_type_args = type_parser() - .map_with_span(|annotation, span| ast::RecordConstructorArg { - label: None, - annotation, - tipo: (), - doc: None, - location: span, - }) - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::LeftParen), just(Token::RightParen)); - - let constructors = select! {Token::UpName { name } => name} - .then( - choice(( - labeled_constructor_type_args(), - unlabeled_constructor_type_args, - )) - .or_not(), - ) - .map_with_span(|(name, arguments), span| ast::RecordConstructor { - location: span, - arguments: arguments.unwrap_or_default(), - name, - doc: None, - sugar: false, - }) - .repeated() - .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)); - - let record_sugar = labeled_constructor_type_args().map_with_span(|arguments, span| { - vec![ast::RecordConstructor { - location: span, - arguments, - doc: None, - name: String::from("_replace"), - sugar: true, - }] - }); - - pub_parser() - .then(just(Token::Opaque).ignored().or_not()) - .or_not() - .then(type_name_with_args()) - .then(choice((constructors, record_sugar))) - .map_with_span(|((pub_opaque, (name, parameters)), constructors), span| { - ast::UntypedDefinition::DataType(ast::DataType { - location: span, - constructors: constructors - .into_iter() - .map(|mut constructor| { - if constructor.sugar { - constructor.name = name.clone(); - } - - constructor - }) - .collect(), - doc: None, - name, - opaque: pub_opaque - .map(|(_, opt_opaque)| opt_opaque.is_some()) - .unwrap_or(false), - parameters: parameters.unwrap_or_default(), - public: pub_opaque.is_some(), - typed_parameters: vec![], - }) - }) -} - -pub fn type_alias_parser() -> impl Parser { - pub_parser() - .or_not() - .then(type_name_with_args()) - .then_ignore(just(Token::Equal)) - .then(type_parser()) - .map_with_span(|((opt_pub, (alias, parameters)), annotation), span| { - ast::UntypedDefinition::TypeAlias(ast::TypeAlias { - alias, - annotation, - doc: None, - location: span, - parameters: parameters.unwrap_or_default(), - public: opt_pub.is_some(), - tipo: (), - }) - }) -} - -pub fn validator_parser() -> impl Parser { - just(Token::Validator) - .ignore_then( - fn_param_parser(true) - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::LeftParen), just(Token::RightParen)) - .map_with_span(|arguments, span| (arguments, span)) - .or_not(), - ) - .then( - fn_parser() - .repeated() - .at_least(1) - .at_most(2) - .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)) - .map(|defs| { - defs.into_iter().map(|def| { - let ast::UntypedDefinition::Fn(fun) = def else { - unreachable!("It should be a fn definition"); - }; +#[cfg(test)] +mod tests { + use crate::assert_module; - fun - }) - }), - ) - .map_with_span(|(opt_extra_params, mut functions), span| { - let (params, params_span) = opt_extra_params.unwrap_or(( - vec![], - Span { - start: 0, - end: span.start + "validator".len(), - }, - )); - - ast::UntypedDefinition::Validator(ast::Validator { - doc: None, - fun: functions - .next() - .expect("unwrapping safe because there's 'at_least(1)' function"), - other_fun: functions.next(), - location: Span { - start: span.start, - // capture the span from the optional params - end: params_span.end, - }, - params, - end_position: span.end - 1, - }) - }) -} - -pub fn fn_parser() -> impl Parser { - pub_parser() - .or_not() - .then_ignore(just(Token::Fn)) - .then(select! {Token::Name {name} => name}) - .then( - fn_param_parser(false) - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::LeftParen), just(Token::RightParen)) - .map_with_span(|arguments, span| (arguments, span)), - ) - .then(just(Token::RArrow).ignore_then(type_parser()).or_not()) - .then( - expr_seq_parser() - .or_not() - .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), - ) - .map_with_span( - |((((opt_pub, name), (arguments, args_span)), return_annotation), body), span| { - ast::UntypedDefinition::Fn(ast::Function { - arguments, - body: body.unwrap_or_else(|| expr::UntypedExpr::todo(span, None)), - doc: None, - location: Span { - start: span.start, - end: return_annotation - .as_ref() - .map(|l| l.location().end) - .unwrap_or_else(|| args_span.end), - }, - end_position: span.end - 1, - name, - public: opt_pub.is_some(), - return_annotation, - return_type: (), - can_error: true, - }) - }, - ) -} - -pub fn test_parser() -> impl Parser { - just(Token::Bang) - .ignored() - .or_not() - .then_ignore(just(Token::Test)) - .then(select! {Token::Name {name} => name}) - .then_ignore(just(Token::LeftParen)) - .then_ignore(just(Token::RightParen)) - .map_with_span(|name, span| (name, span)) - .then( - expr_seq_parser() - .or_not() - .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), - ) - .map_with_span(|(((fail, name), span_end), body), span| { - ast::UntypedDefinition::Test(ast::Function { - arguments: vec![], - body: body.unwrap_or_else(|| expr::UntypedExpr::todo(span, None)), - doc: None, - location: span_end, - end_position: span.end - 1, - name, - public: false, - return_annotation: None, - return_type: (), - can_error: fail.is_some(), - }) - }) -} + #[test] + fn windows_newline() { + assert_module!("use aiken/list\r\n"); + } -fn constant_parser() -> impl Parser { - pub_parser() - .or_not() - .then_ignore(just(Token::Const)) - .then(select! {Token::Name{name} => name}) - .then(just(Token::Colon).ignore_then(type_parser()).or_not()) - .then_ignore(just(Token::Equal)) - .then(constant_value_parser()) - .map_with_span(|(((public, name), annotation), value), span| { - ast::UntypedDefinition::ModuleConstant(ast::ModuleConstant { - doc: None, - location: span, - public: public.is_some(), - name, - annotation, - value: Box::new(value), - tipo: (), - }) - }) -} + #[test] + fn can_handle_comments_at_end_of_file() { + assert_module!( + r#" + use aiken -fn constant_value_parser() -> impl Parser { - let constant_string_parser = - select! {Token::String {value} => value}.map_with_span(|value, span| { - ast::Constant::String { - location: span, - value, - } - }); + // some comment + // more comments"# + ); + } - let constant_int_parser = - select! {Token::Int {value, base} => (value, base)}.map_with_span(|(value, base), span| { - ast::Constant::Int { - location: span, - value, - base, + #[test] + fn function_ambiguous_sequence() { + assert_module!( + r#" + fn foo_1() { + let a = bar + (40) } - }); - let constant_bytearray_parser = - bytearray_parser().map_with_span(|(preferred_format, bytes), span| { - ast::Constant::ByteArray { - location: span, - bytes, - preferred_format, + fn foo_2() { + let a = bar + {40} } - }); - - choice(( - constant_string_parser, - constant_int_parser, - constant_bytearray_parser, - )) -} - -pub fn bytearray_parser( -) -> impl Parser), Error = ParseError> { - let bytearray_list_parser = just(Token::Hash) - .ignore_then( - select! {Token::Int {value, base, ..} => (value, base)} - .validate(|(value, base), span, emit| { - let byte: u8 = match value.parse() { - Ok(b) => b, - Err(_) => { - emit(ParseError::expected_input_found( - span, - None, - Some(error::Pattern::Byte), - )); - 0 - } - }; - (byte, base) - }) - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::LeftSquare), just(Token::RightSquare)), - ) - .validate(|bytes, span, emit| { - let base = bytes.iter().fold(Ok(None), |acc, (_, base)| match acc { - Ok(None) => Ok(Some(base)), - Ok(Some(previous_base)) if previous_base == base => Ok(Some(base)), - _ => Err(()), - }); - - let base = match base { - Err(()) => { - emit(ParseError::hybrid_notation_in_bytearray(span)); - Base::Decimal { - numeric_underscore: false, - } - } - Ok(None) => Base::Decimal { - numeric_underscore: false, - }, - Ok(Some(base)) => *base, - }; - - (bytes.into_iter().map(|(b, _)| b).collect::>(), base) - }) - .map(|(bytes, base)| (ByteArrayFormatPreference::ArrayOfBytes(base), bytes)); - - let bytearray_hexstring_parser = - just(Token::Hash) - .ignore_then(select! {Token::ByteString {value} => value}.validate( - |value, span, emit| match hex::decode(value) { - Ok(bytes) => bytes, - Err(_) => { - emit(ParseError::malformed_base16_string_literal(span)); - vec![] - } - }, - )) - .map(|token| (ByteArrayFormatPreference::HexadecimalString, token)); - - let bytearray_utf8_parser = select! {Token::ByteString {value} => value.into_bytes() } - .map(|token| (ByteArrayFormatPreference::Utf8String, token)); - choice(( - bytearray_list_parser, - bytearray_hexstring_parser, - bytearray_utf8_parser, - )) -} - -pub fn fn_param_parser( - is_validator_param: bool, -) -> impl Parser { - choice(( - select! {Token::Name {name} => name} - .then(select! {Token::DiscardName {name} => name}) - .map_with_span(|(label, name), span| ast::ArgName::Discarded { - label, - name, - location: span, - }), - select! {Token::DiscardName {name} => name}.map_with_span(|name, span| { - ast::ArgName::Discarded { - label: name.clone(), - name, - location: span, + fn foo_3() { + let a = (40+2) } - }), - select! {Token::Name {name} => name} - .then(select! {Token::Name {name} => name}) - .map_with_span(move |(label, name), span| ast::ArgName::Named { - label, - name, - location: span, - is_validator_param, - }), - select! {Token::Name {name} => name}.map_with_span(move |name, span| ast::ArgName::Named { - label: name.clone(), - name, - location: span, - is_validator_param, - }), - )) - .then(just(Token::Colon).ignore_then(type_parser()).or_not()) - .map_with_span(|(arg_name, annotation), span| ast::Arg { - location: span, - annotation, - tipo: (), - arg_name, - }) -} -pub fn anon_fn_param_parser() -> impl Parser { - // TODO: return a better error when a label is provided `UnexpectedLabel` - choice(( - select! {Token::DiscardName {name} => name}.map_with_span(|name, span| { - ast::ArgName::Discarded { - label: name.clone(), - name, - location: span, + fn foo_4() { + let a = bar(42) + (a + 14) * 42 } - }), - select! {Token::Name {name} => name}.map_with_span(|name, span| ast::ArgName::Named { - label: name.clone(), - name, - location: span, - is_validator_param: false, - }), - )) - .then(just(Token::Colon).ignore_then(type_parser()).or_not()) - .map_with_span(|(arg_name, annotation), span| ast::Arg { - location: span, - annotation, - tipo: (), - arg_name, - }) -} - -// Interpret bytearray string literals written as utf-8 strings, as strings. -// -// This is mostly convenient so that todo & error works with either @"..." or plain "...". -// In this particular context, there's actually no ambiguity about the right-hand-side, so -// we can provide this syntactic sugar. -fn flexible_string_literal(expr: expr::UntypedExpr) -> expr::UntypedExpr { - match expr { - expr::UntypedExpr::ByteArray { - preferred_format: ByteArrayFormatPreference::Utf8String, - bytes, - location, - } => expr::UntypedExpr::String { - location, - value: String::from_utf8(bytes).unwrap(), - }, - _ => expr, - } -} - -pub fn expr_seq_parser() -> impl Parser { - recursive(|r| { - choice(( - just(Token::Trace) - .ignore_then(expr_parser(r.clone())) - .then(r.clone()) - .map_with_span(|(text, then_), span| expr::UntypedExpr::Trace { - kind: TraceKind::Trace, - location: span, - then: Box::new(then_), - text: Box::new(flexible_string_literal(text)), - }), - just(Token::ErrorTerm) - .ignore_then(expr_parser(r.clone()).or_not()) - .map_with_span(|reason, span| { - expr::UntypedExpr::error(span, reason.map(flexible_string_literal)) - }), - just(Token::Todo) - .ignore_then(expr_parser(r.clone()).or_not()) - .map_with_span(|reason, span| { - expr::UntypedExpr::todo(span, reason.map(flexible_string_literal)) - }), - expr_parser(r.clone()) - .then(r.repeated()) - .foldl(|current, next| current.append_in_sequence(next)), - )) - }) -} - -pub fn expr_parser( - seq_r: Recursive<'_, Token, expr::UntypedExpr, ParseError>, -) -> impl Parser + '_ { - recursive(|r| { - let string_parser = - select! {Token::String {value} => value}.map_with_span(|value, span| { - expr::UntypedExpr::String { - location: span, - value, - } - }); - - let int_parser = select! { Token::Int {value, base} => (value, base)}.map_with_span( - |(value, base), span| expr::UntypedExpr::Int { - location: span, - value, - base, - }, + "# ); + } - 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( - choice(( - 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, - } - }), - select! {Token::Name {name} => name}.map_with_span( - |name, span| ast::UntypedRecordUpdateArg { - location: span, - value: expr::UntypedExpr::Var { - name: name.clone(), - location: span, - }, - label: name, - }, - ), - )) - .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: Span| (module, span)) - .then_ignore(just(Token::Dot)) - .or_not() - .then( - select! {Token::UpName { name } => name} - .map_with_span(|name, span| (name, span)), - ) - .then( - choice(( - select! {Token::Name {name} => name} - .then_ignore(just(Token::Colon)) - .then(choice(( - r.clone(), - select! {Token::DiscardName {name} => name }.validate( - |_name, span, emit| { - emit(ParseError::expected_input_found( - span, - None, - Some(error::Pattern::Discard), - )); - - expr::UntypedExpr::Var { - location: span, - name: CAPTURE_VARIABLE.to_string(), - } - }, - ), - ))) - .map_with_span(|(label, value), span| ast::CallArg { - location: span, - value, - label: Some(label), - }), - choice(( - select! {Token::Name {name} => name}.map_with_span(|name, span| { - ( - expr::UntypedExpr::Var { - name: name.clone(), - location: span, - }, - name, - ) - }), - select! {Token::DiscardName {name} => name }.validate( - |name, span, emit| { - emit(ParseError::expected_input_found( - span, - None, - Some(error::Pattern::Discard), - )); - - ( - expr::UntypedExpr::Var { - location: span, - name: CAPTURE_VARIABLE.to_string(), - }, - name, - ) - }, - ), - )) - .map(|(value, name)| ast::CallArg { - location: value.location(), - value, - label: Some(name), - }), - )) - .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( - select! {Token::Name {name} => name} - .ignored() - .then_ignore(just(Token::Colon)) - .validate(|_label, span, emit| { - emit(ParseError::expected_input_found( - span, - None, - Some(error::Pattern::Label), - )) - }) - .or_not() - .then(choice(( - r.clone(), - select! {Token::DiscardName {name} => name }.validate( - |_name, span, emit| { - emit(ParseError::expected_input_found( - span, - None, - Some(error::Pattern::Discard), - )); - - expr::UntypedExpr::Var { - location: span, - name: CAPTURE_VARIABLE.to_string(), - } - }, - ), - ))) - .map(|(_label, value)| ast::CallArg { - location: value.location(), - 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! { - Token::Name { name } => name, - Token::UpName { name } => name, - } - .map_with_span(|name, span| expr::UntypedExpr::Var { - location: span, - name, - }); - - let tuple = r - .clone() - .separated_by(just(Token::Comma)) - .at_least(2) - .allow_trailing() - .delimited_by( - choice((just(Token::LeftParen), just(Token::NewLineLeftParen))), - just(Token::RightParen), - ) - .map_with_span(|elems, span| expr::UntypedExpr::Tuple { - location: span, - elems, - }); - - let bytearray = bytearray_parser().map_with_span(|(preferred_format, bytes), span| { - expr::UntypedExpr::ByteArray { - location: span, - bytes, - preferred_format, - } - }); - - let list_parser = just(Token::LeftSquare) - .ignore_then(r.clone().separated_by(just(Token::Comma))) - .then(choice(( - just(Token::Comma).ignore_then( - just(Token::DotDot) - .ignore_then(r.clone()) - .map(Box::new) - .or_not(), - ), - just(Token::Comma).ignored().or_not().map(|_| None), - ))) - .then_ignore(just(Token::RightSquare)) - // TODO: check if tail.is_some and elements.is_empty then return ListSpreadWithoutElements error - .map_with_span(|(elements, tail), span| expr::UntypedExpr::List { - location: span, - elements, - tail, - }); - - let block_parser = choice(( - seq_r - .clone() - .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), - seq_r.clone().delimited_by( - choice((just(Token::LeftParen), just(Token::NewLineLeftParen))), - just(Token::RightParen), - ), - )); - - let anon_fn_parser = just(Token::Fn) - .ignore_then( - anon_fn_param_parser() - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::LeftParen), just(Token::RightParen)), - ) - .then(just(Token::RArrow).ignore_then(type_parser()).or_not()) - .then(seq_r.delimited_by(just(Token::LeftBrace), just(Token::RightBrace))) - .map_with_span( - |((arguments, return_annotation), body), span| expr::UntypedExpr::Fn { - arguments, - body: Box::new(body), - location: span, - fn_style: expr::FnStyle::Plain, - return_annotation, - }, - ); - - 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 = expr::UntypedExpr::BinOp { - location, - name, - left: Box::new(expr::UntypedExpr::Var { - location, - name: "left".to_string(), - }), - right: Box::new(expr::UntypedExpr::Var { - location, - name: "right".to_string(), - }), - }; - - expr::UntypedExpr::Fn { - arguments, - body: Box::new(body), - return_annotation, - fn_style: expr::FnStyle::BinOp(name), - location, + #[test] + fn parse_unicode_offset_1() { + assert_module!( + r#" + fn foo() { + let x = "★" + x } - }); - - 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| { - expr::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| { - expr::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| expr::UntypedExpr::When { - location: span, - subject, - clauses, - }); - - let let_parser = just(Token::Let) - .ignore_then(pattern_parser()) - .then(just(Token::Colon).ignore_then(type_parser()).or_not()) - .then_ignore(just(Token::Equal)) - .then(r.clone()) - .map_with_span( - |((pattern, annotation), value), span| expr::UntypedExpr::Assignment { - location: span, - value: Box::new(value), - pattern, - kind: ast::AssignmentKind::Let, - annotation, - }, - ); - - let expect_parser = just(Token::Expect) - .ignore_then(pattern_parser()) - .then(just(Token::Colon).ignore_then(type_parser()).or_not()) - .then_ignore(just(Token::Equal)) - .then(r.clone()) - .map_with_span( - |((pattern, annotation), value), span| expr::UntypedExpr::Assignment { - location: span, - value: Box::new(value), - pattern, - kind: ast::AssignmentKind::Expect, - annotation, - }, - ); - - let if_parser = just(Token::If) - .ignore_then(r.clone().then(block_parser.clone()).map_with_span( - |(condition, body), span| ast::IfBranch { - condition, - body, - location: span, - }, - )) - .then( - just(Token::Else) - .ignore_then(just(Token::If)) - .ignore_then(r.clone().then(block_parser.clone()).map_with_span( - |(condition, body), span| ast::IfBranch { - condition, - body, - location: span, - }, - )) - .repeated(), - ) - .then_ignore(just(Token::Else)) - .then(block_parser.clone()) - .map_with_span(|((first, alternative_branches), final_else), span| { - let mut branches = vec1::vec1![first]; - - branches.extend(alternative_branches); - - expr::UntypedExpr::If { - location: span, - branches, - final_else: Box::new(final_else), - } - }); - - let expr_unit_parser = choice(( - string_parser, - int_parser, - record_update_parser, - record_parser, - field_access_constructor, - var_parser, - tuple, - bytearray, - list_parser, - anon_fn_parser, - anon_binop_parser, - block_parser, - when_parser, - let_parser, - expect_parser, - if_parser, - )); - - // Parsing a function call into the appropriate structure - #[derive(Debug)] - enum ParserArg { - Arg(Box>), - Hole { - location: Span, - label: Option, - }, - } - - enum Chain { - Call(Vec, Span), - FieldAccess(String, Span), - TupleIndex(usize, Span), - } - - let field_access_parser = just(Token::Dot) - .ignore_then(select! { - Token::Name { name } => name, - }) - .map_with_span(Chain::FieldAccess); - - let tuple_index_parser = just(Token::Dot) - .ignore_then(select! { - Token::Ordinal { index } => index, - }) - .validate(|index, span, emit| { - if index < 1 { - emit(ParseError::invalid_tuple_index( - span, - index.to_string(), - None, - )); - Chain::TupleIndex(0, span) - } else { - Chain::TupleIndex(index as usize - 1, span) - } - }); - - let call_parser = choice(( - select! { Token::Name { name } => name } - .then_ignore(just(Token::Colon)) - .or_not() - .then(r.clone()) - .map_with_span(|(label, value), span| { - ParserArg::Arg(Box::new(ast::CallArg { - label, - location: span, - value, - })) - }), - select! { Token::Name { name } => name } - .then_ignore(just(Token::Colon)) - .or_not() - .then_ignore(select! {Token::DiscardName {name} => name }) - .map_with_span(|label, span| ParserArg::Hole { - location: span, - label, - }), - )) - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::LeftParen), just(Token::RightParen)) - .map_with_span(Chain::Call); - - let chain = choice((tuple_index_parser, field_access_parser, call_parser)); - - let chained = expr_unit_parser - .then(chain.repeated()) - .foldl(|expr, chain| match chain { - Chain::Call(args, span) => { - let mut holes = Vec::new(); - - let args = args - .into_iter() - .enumerate() - .map(|(index, a)| match a { - ParserArg::Arg(arg) => *arg, - ParserArg::Hole { location, label } => { - let name = format!("{CAPTURE_VARIABLE}__{index}"); - holes.push(ast::Arg { - location: Span::empty(), - annotation: None, - arg_name: ast::ArgName::Named { - label: name.clone(), - name, - location: Span::empty(), - is_validator_param: false, - }, - tipo: (), - }); - - ast::CallArg { - label, - location, - value: expr::UntypedExpr::Var { - location, - name: format!("{CAPTURE_VARIABLE}__{index}"), - }, - } - } - }) - .collect(); - - let call = expr::UntypedExpr::Call { - location: expr.location().union(span), - fun: Box::new(expr), - arguments: args, - }; - - if holes.is_empty() { - call - } else { - expr::UntypedExpr::Fn { - location: call.location(), - fn_style: expr::FnStyle::Capture, - arguments: holes, - body: Box::new(call), - return_annotation: None, - } - } - } - - Chain::FieldAccess(label, span) => expr::UntypedExpr::FieldAccess { - location: expr.location().union(span), - label, - container: Box::new(expr), - }, - - Chain::TupleIndex(index, span) => expr::UntypedExpr::TupleIndex { - location: expr.location().union(span), - index, - tuple: Box::new(expr), - }, - }); - - let debug = chained.then(just(Token::Question).or_not()).map_with_span( - |(value, token), location| match token { - Some(_) => expr::UntypedExpr::TraceIfFalse { - value: Box::new(value), - location, - }, - None => value, - }, + "# ); + } - // Negate - let op = choice(( - just(Token::Bang).to(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. - // For example: - // - // foo(a, -, b) - // - // but it'll fail in a let-binding: - // - // let foo = - - // - // which seems acceptable. - .then_ignore(just(Token::Comma).not().rewind()) - .to(UnOp::Negate), - )); - - let unary = op - .map_with_span(|op, span| (op, span)) - .repeated() - .then(debug) - .foldr(|(un_op, span), value| expr::UntypedExpr::UnOp { - op: un_op, - location: span.union(value.location()), - value: Box::new(value), - }) - .boxed(); - - // Product - let op = choice(( - just(Token::Star).to(BinOp::MultInt), - just(Token::Slash).to(BinOp::DivInt), - just(Token::Percent).to(BinOp::ModInt), - )); - - let product = unary - .clone() - .then(op.then(unary).repeated()) - .foldl(|a, (op, b)| expr::UntypedExpr::BinOp { - location: a.location().union(b.location()), - name: op, - left: Box::new(a), - right: Box::new(b), - }) - .boxed(); - - // Sum - let op = choice(( - just(Token::Plus).to(BinOp::AddInt), - just(Token::Minus).to(BinOp::SubInt), - )); - - let sum = product - .clone() - .then(op.then(product).repeated()) - .foldl(|a, (op, b)| expr::UntypedExpr::BinOp { - location: a.location().union(b.location()), - name: op, - left: Box::new(a), - right: Box::new(b), - }) - .boxed(); - - // 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), - )); - - let comparison = sum - .clone() - .then(op.then(sum).repeated()) - .foldl(|a, (op, b)| expr::UntypedExpr::BinOp { - location: a.location().union(b.location()), - name: op, - left: Box::new(a), - right: Box::new(b), - }) - .boxed(); - - // Conjunction - let op = just(Token::AmperAmper).to(BinOp::And); - let conjunction = comparison - .clone() - .then(op.then(comparison).repeated()) - .foldl(|a, (op, b)| expr::UntypedExpr::BinOp { - location: a.location().union(b.location()), - name: op, - left: Box::new(a), - right: Box::new(b), - }) - .boxed(); - - // Disjunction - let op = just(Token::VbarVbar).to(BinOp::Or); - let disjunction = conjunction - .clone() - .then(op.then(conjunction).repeated()) - .foldl(|a, (op, b)| expr::UntypedExpr::BinOp { - location: a.location().union(b.location()), - name: op, - left: Box::new(a), - right: Box::new(b), - }) - .boxed(); - - // Pipeline - disjunction - .clone() - .then( - choice((just(Token::Pipe), just(Token::NewLinePipe))) - .then(disjunction) - .repeated(), - ) - .foldl(|l, (pipe, r)| { - if let expr::UntypedExpr::PipeLine { - mut expressions, - one_liner, - } = l - { - expressions.push(r); - return expr::UntypedExpr::PipeLine { - expressions, - one_liner, - }; - } - - let mut expressions = Vec1::new(l); - expressions.push(r); - expr::UntypedExpr::PipeLine { - expressions, - one_liner: pipe != Token::NewLinePipe, - } - }) - }) -} - -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 = constant_value_parser().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, - } - }) - }) -} - -pub fn type_parser() -> impl Parser { - recursive(|r| { - choice(( - // Type hole - select! {Token::DiscardName { name } => name}.map_with_span(|name, span| { - ast::Annotation::Hole { - location: span, - name, - } - }), - // Tuple - r.clone() - .separated_by(just(Token::Comma)) - .at_least(2) - .allow_trailing() - .delimited_by( - choice((just(Token::LeftParen), just(Token::NewLineLeftParen))), - just(Token::RightParen), - ) - .map_with_span(|elems, span| ast::Annotation::Tuple { - location: span, - elems, - }), - // Function - just(Token::Fn) - .ignore_then( - r.clone() - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::LeftParen), just(Token::RightParen)), - ) - .then_ignore(just(Token::RArrow)) - .then(r.clone()) - .map_with_span(|(arguments, ret), span| ast::Annotation::Fn { - location: span, - arguments, - ret: Box::new(ret), - }), - // Constructor function - select! {Token::UpName { name } => name} - .then( - r.clone() - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::Less), just(Token::Greater)) - .or_not(), - ) - .map_with_span(|(name, arguments), span| ast::Annotation::Constructor { - location: span, - module: None, - name, - arguments: arguments.unwrap_or_default(), - }), - // Constructor Module or type Variable - select! {Token::Name { name } => name} - .then( - just(Token::Dot) - .ignore_then(select! {Token::UpName {name} => name}) - .then( - r.separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::Less), just(Token::Greater)) - .or_not(), - ) - .or_not(), - ) - .map_with_span(|(mod_name, opt_dot), span| { - if let Some((name, arguments)) = opt_dot { - ast::Annotation::Constructor { - location: span, - module: Some(mod_name), - name, - arguments: arguments.unwrap_or_default(), - } - } else { - // TODO: parse_error(ParseErrorType::NotConstType, SrcSpan { start, end }) - ast::Annotation::Var { - location: span, - name: mod_name, - } - } - }), - )) - }) -} - -pub fn labeled_constructor_type_args( -) -> impl Parser>, Error = ParseError> { - select! {Token::Name {name} => name} - .then_ignore(just(Token::Colon)) - .then(type_parser()) - .map_with_span(|(name, annotation), span| ast::RecordConstructorArg { - label: Some(name), - annotation, - tipo: (), - doc: None, - location: span, - }) - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)) -} - -pub fn type_name_with_args() -> impl Parser>), Error = ParseError> -{ - just(Token::Type).ignore_then( - select! {Token::UpName { name } => name}.then( - select! {Token::Name { name } => name} - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::Less), just(Token::Greater)) - .or_not(), - ), - ) -} - -pub fn pub_parser() -> impl Parser { - just(Token::Pub).ignored() -} - -pub fn pattern_parser() -> impl Parser { - recursive(|r| { - let record_constructor_pattern_arg_parser = choice(( - select! {Token::Name {name} => name} - .then_ignore(just(Token::Colon)) - .then(r.clone()) - .map_with_span(|(name, pattern), span| ast::CallArg { - location: span, - label: Some(name), - value: pattern, - }), - select! {Token::Name{name} => name}.map_with_span(|name, span| ast::CallArg { - location: span, - value: ast::UntypedPattern::Var { - name: name.clone(), - location: span, - }, - label: Some(name), - }), - )) - .separated_by(just(Token::Comma)) - .allow_trailing() - .then( - just(Token::DotDot) - .then_ignore(just(Token::Comma).or_not()) - .ignored() - .or_not(), - ) - .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)); - - let tuple_constructor_pattern_arg_parser = r - .clone() - .map(|pattern| ast::CallArg { - location: pattern.location(), - value: pattern, - label: None, - }) - .separated_by(just(Token::Comma)) - .allow_trailing() - .then( - just(Token::DotDot) - .then_ignore(just(Token::Comma).or_not()) - .ignored() - .or_not(), - ) - .delimited_by(just(Token::LeftParen), just(Token::RightParen)); - - let constructor_pattern_args_parser = choice(( - record_constructor_pattern_arg_parser.map(|a| (a, true)), - tuple_constructor_pattern_arg_parser.map(|a| (a, false)), - )) - .or_not() - .map(|opt_args| { - opt_args - .map(|((a, b), c)| (a, b.is_some(), c)) - .unwrap_or_else(|| (vec![], false, false)) - }); - - let constructor_pattern_parser = - select! {Token::UpName { name } => name}.then(constructor_pattern_args_parser); - - choice(( - select! { Token::Name {name} => name } - .then( - just(Token::Dot) - .ignore_then(constructor_pattern_parser.clone()) - .or_not(), - ) - .map_with_span(|(name, opt_pattern), span| { - if let Some((c_name, (arguments, with_spread, is_record))) = opt_pattern { - ast::UntypedPattern::Constructor { - is_record, - location: span, - name: c_name, - arguments, - module: Some(name), - constructor: (), - with_spread, - tipo: (), - } - } else { - ast::UntypedPattern::Var { - location: span, - name, - } - } - }), - constructor_pattern_parser.map_with_span( - |(name, (arguments, with_spread, is_record)), span| { - ast::UntypedPattern::Constructor { - is_record, - location: span, - name, - arguments, - module: None, - constructor: (), - with_spread, - tipo: (), - } - }, - ), - select! {Token::DiscardName {name} => name}.map_with_span(|name, span| { - ast::UntypedPattern::Discard { - name, - location: span, - } - }), - select! {Token::Int {value, base} => (value, base)}.map_with_span( - |(value, base), span| ast::UntypedPattern::Int { - location: span, - value, - base, - }, - ), - r.clone() - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by( - choice((just(Token::LeftParen), just(Token::NewLineLeftParen))), - just(Token::RightParen), - ) - .map_with_span(|elems, span| ast::UntypedPattern::Tuple { - location: span, - elems, - }), - just(Token::LeftSquare) - .ignore_then(r.clone().separated_by(just(Token::Comma))) - .then(choice(( - just(Token::Comma) - .ignore_then(just(Token::DotDot).ignore_then(r.clone().or_not()).or_not()), - just(Token::Comma).ignored().or_not().map(|_| None), - ))) - .then_ignore(just(Token::RightSquare)) - .validate(|(elements, tail), span: Span, emit| { - let tail = match tail { - // There is a tail and it has a Pattern::Var or Pattern::Discard - Some(Some( - pat @ (ast::UntypedPattern::Var { .. } - | ast::UntypedPattern::Discard { .. }), - )) => Some(pat), - Some(Some(pat)) => { - emit(ParseError::expected_input_found( - pat.location(), - None, - Some(error::Pattern::Match), - )); - - Some(pat) - } - // There is a tail but it has no content, implicit discard - Some(None) => Some(ast::UntypedPattern::Discard { - location: Span { - start: span.end - 1, - end: span.end, - }, - name: "_".to_string(), - }), - // No tail specified - None => None, - }; - - ast::UntypedPattern::List { - location: span, - elements, - tail: tail.map(Box::new), - } - }), - )) - .then( - just(Token::As) - .ignore_then(select! { Token::Name {name} => name}) - .or_not(), - ) - .map_with_span(|(pattern, opt_as), span| { - if let Some(name) = opt_as { - ast::UntypedPattern::Assign { - name, - location: span, - pattern: Box::new(pattern), - } - } else { - pattern + #[test] + fn parse_unicode_offset_2() { + assert_module!( + r#" + fn foo() { + let x = "*" + x } - }) - }) + "# + ); + } } diff --git a/crates/aiken-lang/src/parser/annotation.rs b/crates/aiken-lang/src/parser/annotation.rs new file mode 100644 index 000000000..8afdac558 --- /dev/null +++ b/crates/aiken-lang/src/parser/annotation.rs @@ -0,0 +1,105 @@ +use chumsky::prelude::*; + +use crate::ast; + +use super::{error::ParseError, token::Token}; + +pub fn parser() -> impl Parser { + recursive(|expression| { + choice(( + // Type hole + select! {Token::DiscardName { name } => name}.map_with_span(|name, span| { + ast::Annotation::Hole { + location: span, + name, + } + }), + // Tuple + expression + .clone() + .separated_by(just(Token::Comma)) + .at_least(2) + .allow_trailing() + .delimited_by( + choice((just(Token::LeftParen), just(Token::NewLineLeftParen))), + just(Token::RightParen), + ) + .map_with_span(|elems, span| ast::Annotation::Tuple { + location: span, + elems, + }), + // Function + just(Token::Fn) + .ignore_then( + expression + .clone() + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftParen), just(Token::RightParen)), + ) + .then_ignore(just(Token::RArrow)) + .then(expression.clone()) + .map_with_span(|(arguments, ret), span| ast::Annotation::Fn { + location: span, + arguments, + ret: Box::new(ret), + }), + // Constructor function + select! {Token::UpName { name } => name} + .then( + expression + .clone() + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::Less), just(Token::Greater)) + .or_not(), + ) + .map_with_span(|(name, arguments), span| ast::Annotation::Constructor { + location: span, + module: None, + name, + arguments: arguments.unwrap_or_default(), + }), + // Constructor Module or type Variable + select! {Token::Name { name } => name} + .then( + just(Token::Dot) + .ignore_then(select! {Token::UpName {name} => name}) + .then( + expression + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::Less), just(Token::Greater)) + .or_not(), + ) + .or_not(), + ) + .map_with_span(|(mod_name, opt_dot), span| { + if let Some((name, arguments)) = opt_dot { + ast::Annotation::Constructor { + location: span, + module: Some(mod_name), + name, + arguments: arguments.unwrap_or_default(), + } + } else { + // TODO: parse_error(ParseErrorType::NotConstType, SrcSpan { start, end }) + ast::Annotation::Var { + location: span, + name: mod_name, + } + } + }), + )) + }) +} + +#[cfg(test)] +mod tests { + use crate::assert_annotation; + + #[test] + fn type_annotation_with_module_prefix() { + assert_annotation!("aiken.Option"); + } +} diff --git a/crates/aiken-lang/src/parser/chain/call.rs b/crates/aiken-lang/src/parser/chain/call.rs new file mode 100644 index 000000000..42e7b2226 --- /dev/null +++ b/crates/aiken-lang/src/parser/chain/call.rs @@ -0,0 +1,38 @@ +use chumsky::prelude::*; + +use super::{Chain, ParserArg}; +use crate::{ + ast, + expr::UntypedExpr, + parser::{token::Token, ParseError}, +}; + +pub(crate) fn parser( + expression: Recursive<'_, Token, UntypedExpr, ParseError>, +) -> impl Parser + '_ { + choice(( + select! { Token::Name { name } => name } + .then_ignore(just(Token::Colon)) + .or_not() + .then(expression) + .map_with_span(|(label, value), span| { + ParserArg::Arg(Box::new(ast::CallArg { + label, + location: span, + value, + })) + }), + select! { Token::Name { name } => name } + .then_ignore(just(Token::Colon)) + .or_not() + .then_ignore(select! {Token::DiscardName {name} => name }) + .map_with_span(|label, span| ParserArg::Hole { + location: span, + label, + }), + )) + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftParen), just(Token::RightParen)) + .map_with_span(Chain::Call) +} diff --git a/crates/aiken-lang/src/parser/chain/field_access.rs b/crates/aiken-lang/src/parser/chain/field_access.rs new file mode 100644 index 000000000..14f84bce5 --- /dev/null +++ b/crates/aiken-lang/src/parser/chain/field_access.rs @@ -0,0 +1,30 @@ +use chumsky::prelude::*; + +use super::Chain; +use crate::{ + expr::UntypedExpr, + parser::{token::Token, ParseError}, +}; + +pub(crate) fn parser() -> impl Parser { + just(Token::Dot) + .ignore_then(select! { + Token::Name { name } => name, + }) + .map_with_span(Chain::FieldAccess) +} + +pub(crate) fn constructor() -> impl Parser { + 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| UntypedExpr::FieldAccess { + location: span, + label: name, + container: Box::new(UntypedExpr::Var { + location: m_span, + name: module, + }), + }) +} diff --git a/crates/aiken-lang/src/parser/chain/mod.rs b/crates/aiken-lang/src/parser/chain/mod.rs new file mode 100644 index 000000000..baad45165 --- /dev/null +++ b/crates/aiken-lang/src/parser/chain/mod.rs @@ -0,0 +1,22 @@ +use crate::ast::{self, Span}; +use crate::expr::UntypedExpr; + +pub(crate) mod call; +pub(crate) mod field_access; +pub(crate) mod tuple_index; + +pub(crate) enum Chain { + Call(Vec, Span), + FieldAccess(String, Span), + TupleIndex(usize, Span), +} + +// Parsing a function call into the appropriate structure +#[derive(Debug)] +pub(crate) enum ParserArg { + Arg(Box>), + Hole { + location: Span, + label: Option, + }, +} diff --git a/crates/aiken-lang/src/parser/chain/tuple_index.rs b/crates/aiken-lang/src/parser/chain/tuple_index.rs new file mode 100644 index 000000000..e3ce7f24b --- /dev/null +++ b/crates/aiken-lang/src/parser/chain/tuple_index.rs @@ -0,0 +1,23 @@ +use chumsky::prelude::*; + +use super::Chain; +use crate::parser::{token::Token, ParseError}; + +pub(crate) fn parser() -> impl Parser { + just(Token::Dot) + .ignore_then(select! { + Token::Ordinal { index } => index, + }) + .validate(|index, span, emit| { + if index < 1 { + emit(ParseError::invalid_tuple_index( + span, + index.to_string(), + None, + )); + Chain::TupleIndex(0, span) + } else { + Chain::TupleIndex(index as usize - 1, span) + } + }) +} diff --git a/crates/aiken-lang/src/parser/definition/constant.rs b/crates/aiken-lang/src/parser/definition/constant.rs new file mode 100644 index 000000000..1e2629ed5 --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/constant.rs @@ -0,0 +1,64 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + parser::{ + annotation, error::ParseError, literal::bytearray::parser as bytearray, token::Token, utils, + }, +}; + +pub fn parser() -> impl Parser { + utils::optional_flag(Token::Pub) + .then_ignore(just(Token::Const)) + .then(select! {Token::Name{name} => name}) + .then( + just(Token::Colon) + .ignore_then(annotation::parser()) + .or_not(), + ) + .then_ignore(just(Token::Equal)) + .then(value()) + .map_with_span(|(((public, name), annotation), value), span| { + ast::UntypedDefinition::ModuleConstant(ast::ModuleConstant { + doc: None, + location: span, + public, + name, + annotation, + value: Box::new(value), + tipo: (), + }) + }) +} + +pub fn value() -> impl Parser { + let constant_string_parser = + select! {Token::String {value} => value}.map_with_span(|value, span| { + ast::Constant::String { + location: span, + value, + } + }); + + let constant_int_parser = + select! {Token::Int {value, base} => (value, base)}.map_with_span(|(value, base), span| { + ast::Constant::Int { + location: span, + value, + base, + } + }); + + let constant_bytearray_parser = + bytearray(|bytes, preferred_format, span| ast::Constant::ByteArray { + location: span, + bytes, + preferred_format, + }); + + choice(( + constant_string_parser, + constant_int_parser, + constant_bytearray_parser, + )) +} diff --git a/crates/aiken-lang/src/parser/definition/data_type.rs b/crates/aiken-lang/src/parser/definition/data_type.rs new file mode 100644 index 000000000..dfea21d33 --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/data_type.rs @@ -0,0 +1,122 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + parser::{annotation, error::ParseError, token::Token, utils}, +}; + +pub fn parser() -> impl Parser { + let unlabeled_constructor_type_args = annotation() + .map_with_span(|annotation, span| ast::RecordConstructorArg { + label: None, + annotation, + tipo: (), + doc: None, + location: span, + }) + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftParen), just(Token::RightParen)); + + let constructors = select! {Token::UpName { name } => name} + .then( + choice(( + labeled_constructor_type_args(), + unlabeled_constructor_type_args, + )) + .or_not(), + ) + .map_with_span(|(name, arguments), span| ast::RecordConstructor { + location: span, + arguments: arguments.unwrap_or_default(), + name, + doc: None, + sugar: false, + }) + .repeated() + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)); + + let record_sugar = labeled_constructor_type_args().map_with_span(|arguments, span| { + vec![ast::RecordConstructor { + location: span, + arguments, + doc: None, + name: String::from("_replace"), + sugar: true, + }] + }); + + utils::optional_flag(Token::Pub) + .then(utils::optional_flag(Token::Opaque)) + .then(utils::type_name_with_args()) + .then(choice((constructors, record_sugar))) + .map_with_span( + |(((public, opaque), (name, parameters)), constructors), span| { + ast::UntypedDefinition::DataType(ast::DataType { + location: span, + constructors: constructors + .into_iter() + .map(|mut constructor| { + if constructor.sugar { + constructor.name = name.clone(); + } + + constructor + }) + .collect(), + doc: None, + name, + opaque, + parameters: parameters.unwrap_or_default(), + public, + typed_parameters: vec![], + }) + }, + ) +} + +fn labeled_constructor_type_args( +) -> impl Parser>, Error = ParseError> { + select! {Token::Name {name} => name} + .then_ignore(just(Token::Colon)) + .then(annotation()) + .map_with_span(|(name, annotation), span| ast::RecordConstructorArg { + label: Some(name), + annotation, + tipo: (), + doc: None, + location: span, + }) + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)) +} + +#[cfg(test)] +mod tests { + use crate::assert_definition; + + #[test] + fn custom_type() { + assert_definition!( + r#" + type Option { + Some(a, Int) + None + Wow { name: Int, age: Int } + } + "# + ); + } + + #[test] + fn opaque_type() { + assert_definition!( + r#" + pub opaque type User { + name: _w + } + "# + ); + } +} diff --git a/crates/aiken-lang/src/parser/definition/function.rs b/crates/aiken-lang/src/parser/definition/function.rs new file mode 100644 index 000000000..e0a615b3b --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/function.rs @@ -0,0 +1,111 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + expr::UntypedExpr, + parser::{annotation, error::ParseError, expr, token::Token, utils}, +}; + +pub fn parser() -> impl Parser { + utils::optional_flag(Token::Pub) + .then_ignore(just(Token::Fn)) + .then(select! {Token::Name {name} => name}) + .then( + param(false) + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftParen), just(Token::RightParen)) + .map_with_span(|arguments, span| (arguments, span)), + ) + .then(just(Token::RArrow).ignore_then(annotation()).or_not()) + .then( + expr::sequence() + .or_not() + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), + ) + .map_with_span( + |((((public, name), (arguments, args_span)), return_annotation), body), span| { + ast::UntypedDefinition::Fn(ast::Function { + arguments, + body: body.unwrap_or_else(|| UntypedExpr::todo(span, None)), + doc: None, + location: ast::Span { + start: span.start, + end: return_annotation + .as_ref() + .map(|l| l.location().end) + .unwrap_or_else(|| args_span.end), + }, + end_position: span.end - 1, + name, + public, + return_annotation, + return_type: (), + can_error: true, + }) + }, + ) +} + +pub fn param(is_validator_param: bool) -> impl Parser { + choice(( + select! {Token::Name {name} => name} + .then(select! {Token::DiscardName {name} => name}) + .map_with_span(|(label, name), span| ast::ArgName::Discarded { + label, + name, + location: span, + }), + select! {Token::DiscardName {name} => name}.map_with_span(|name, span| { + ast::ArgName::Discarded { + label: name.clone(), + name, + location: span, + } + }), + select! {Token::Name {name} => name} + .then(select! {Token::Name {name} => name}) + .map_with_span(move |(label, name), span| ast::ArgName::Named { + label, + name, + location: span, + is_validator_param, + }), + select! {Token::Name {name} => name}.map_with_span(move |name, span| ast::ArgName::Named { + label: name.clone(), + name, + location: span, + is_validator_param, + }), + )) + .then(just(Token::Colon).ignore_then(annotation()).or_not()) + .map_with_span(|(arg_name, annotation), span| ast::Arg { + location: span, + annotation, + tipo: (), + arg_name, + }) +} + +#[cfg(test)] +mod tests { + use crate::assert_definition; + + #[test] + fn function_empty() { + assert_definition!( + r#" + pub fn run() {} + "# + ); + } + + #[test] + fn function_non_public() { + assert_definition!( + r#" + fn run() {} + "# + ); + } +} diff --git a/crates/aiken-lang/src/parser/definition/import.rs b/crates/aiken-lang/src/parser/definition/import.rs new file mode 100644 index 000000000..2b1d959d7 --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/import.rs @@ -0,0 +1,81 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + parser::{error::ParseError, token::Token}, +}; + +pub fn parser() -> impl Parser { + let unqualified_import = choice(( + select! {Token::Name { name } => name}.then( + just(Token::As) + .ignore_then(select! {Token::Name { name } => name}) + .or_not(), + ), + select! {Token::UpName { name } => name}.then( + just(Token::As) + .ignore_then(select! {Token::UpName { name } => name}) + .or_not(), + ), + )) + .map_with_span(|(name, as_name), span| ast::UnqualifiedImport { + name, + location: span, + as_name, + layer: Default::default(), + }); + + let unqualified_imports = just(Token::Dot) + .ignore_then( + unqualified_import + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), + ) + .or_not(); + + let as_name = just(Token::As) + .ignore_then(select! {Token::Name { name } => name}) + .or_not(); + + let module_path = select! {Token::Name { name } => name} + .separated_by(just(Token::Slash)) + .then(unqualified_imports) + .then(as_name); + + just(Token::Use).ignore_then(module_path).map_with_span( + |((module, unqualified), as_name), span| { + ast::UntypedDefinition::Use(ast::Use { + module, + as_name, + unqualified: unqualified.unwrap_or_default(), + package: (), + location: span, + }) + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::assert_definition; + + #[test] + fn import_basic() { + assert_definition!("use aiken/list"); + } + + #[test] + fn import_unqualified() { + assert_definition!( + r#" + use std/address.{Address as A, thing as w} + "# + ); + } + + #[test] + fn import_alias() { + assert_definition!("use aiken/list as foo"); + } +} diff --git a/crates/aiken-lang/src/parser/definition/mod.rs b/crates/aiken-lang/src/parser/definition/mod.rs new file mode 100644 index 000000000..937e07fae --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/mod.rs @@ -0,0 +1,32 @@ +use chumsky::prelude::*; + +pub mod constant; +mod data_type; +mod function; +mod import; +mod test; +mod type_alias; +mod validator; + +pub use constant::parser as constant; +pub use data_type::parser as data_type; +pub use function::parser as function; +pub use import::parser as import; +pub use test::parser as test; +pub use type_alias::parser as type_alias; +pub use validator::parser as validator; + +use super::{error::ParseError, token::Token}; +use crate::ast; + +pub fn parser() -> impl Parser { + choice(( + import(), + data_type(), + type_alias(), + validator(), + function(), + test(), + constant(), + )) +} diff --git a/crates/aiken-lang/src/parser/definition/snapshots/custom_type.snap b/crates/aiken-lang/src/parser/definition/snapshots/custom_type.snap new file mode 100644 index 000000000..53760f6ce --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/custom_type.snap @@ -0,0 +1,92 @@ +--- +source: crates/aiken-lang/src/parser/definition/data_type.rs +description: "Code:\n\ntype Option {\n Some(a, Int)\n None\n Wow { name: Int, age: Int }\n}\n" +--- +DataType( + DataType { + constructors: [ + RecordConstructor { + location: 19..31, + name: "Some", + arguments: [ + RecordConstructorArg { + label: None, + annotation: Var { + location: 24..25, + name: "a", + }, + location: 24..25, + tipo: (), + doc: None, + }, + RecordConstructorArg { + label: None, + annotation: Constructor { + location: 27..30, + module: None, + name: "Int", + arguments: [], + }, + location: 27..30, + tipo: (), + doc: None, + }, + ], + doc: None, + sugar: false, + }, + RecordConstructor { + location: 34..38, + name: "None", + arguments: [], + doc: None, + sugar: false, + }, + RecordConstructor { + location: 41..68, + name: "Wow", + arguments: [ + RecordConstructorArg { + label: Some( + "name", + ), + annotation: Constructor { + location: 53..56, + module: None, + name: "Int", + arguments: [], + }, + location: 47..56, + tipo: (), + doc: None, + }, + RecordConstructorArg { + label: Some( + "age", + ), + annotation: Constructor { + location: 63..66, + module: None, + name: "Int", + arguments: [], + }, + location: 58..66, + tipo: (), + doc: None, + }, + ], + doc: None, + sugar: false, + }, + ], + doc: None, + location: 0..70, + name: "Option", + opaque: false, + parameters: [ + "a", + ], + public: false, + typed_parameters: [], + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/double_validator.snap b/crates/aiken-lang/src/parser/definition/snapshots/double_validator.snap new file mode 100644 index 000000000..447b271c7 --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/double_validator.snap @@ -0,0 +1,101 @@ +--- +source: crates/aiken-lang/src/parser/definition/validator.rs +description: "Code:\n\nvalidator {\n fn foo(datum, rdmr, ctx) {\n True\n }\n\n fn bar(rdmr, ctx) {\n True\n }\n}\n" +--- +Validator( + Validator { + doc: None, + end_position: 90, + fun: Function { + arguments: [ + Arg { + arg_name: Named { + name: "datum", + label: "datum", + location: 21..26, + is_validator_param: false, + }, + location: 21..26, + annotation: None, + tipo: (), + }, + Arg { + arg_name: Named { + name: "rdmr", + label: "rdmr", + location: 28..32, + is_validator_param: false, + }, + location: 28..32, + annotation: None, + tipo: (), + }, + Arg { + arg_name: Named { + name: "ctx", + label: "ctx", + location: 34..37, + is_validator_param: false, + }, + location: 34..37, + annotation: None, + tipo: (), + }, + ], + body: Var { + location: 45..49, + name: "True", + }, + doc: None, + location: 14..38, + name: "foo", + public: false, + return_annotation: None, + return_type: (), + end_position: 52, + can_error: true, + }, + other_fun: Some( + Function { + arguments: [ + Arg { + arg_name: Named { + name: "rdmr", + label: "rdmr", + location: 64..68, + is_validator_param: false, + }, + location: 64..68, + annotation: None, + tipo: (), + }, + Arg { + arg_name: Named { + name: "ctx", + label: "ctx", + location: 70..73, + is_validator_param: false, + }, + location: 70..73, + annotation: None, + tipo: (), + }, + ], + body: Var { + location: 81..85, + name: "True", + }, + doc: None, + location: 57..74, + name: "bar", + public: false, + return_annotation: None, + return_type: (), + end_position: 88, + can_error: true, + }, + ), + location: 0..9, + params: [], + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/fail.snap b/crates/aiken-lang/src/parser/definition/snapshots/fail.snap new file mode 100644 index 000000000..efa48064c --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/fail.snap @@ -0,0 +1,45 @@ +--- +source: crates/aiken-lang/src/parser/definition/test.rs +description: "Code:\n\n!test invalid_inputs() {\n expect True = False\n\n False\n}\n" +--- +Test( + Function { + arguments: [], + body: Sequence { + location: 27..55, + expressions: [ + Assignment { + location: 27..46, + value: Var { + location: 41..46, + name: "False", + }, + pattern: Constructor { + is_record: false, + location: 34..38, + name: "True", + arguments: [], + module: None, + constructor: (), + with_spread: false, + tipo: (), + }, + kind: Expect, + annotation: None, + }, + Var { + location: 50..55, + name: "False", + }, + ], + }, + doc: None, + location: 0..22, + name: "invalid_inputs", + public: false, + return_annotation: None, + return_type: (), + end_position: 56, + can_error: true, + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/function_empty.snap b/crates/aiken-lang/src/parser/definition/snapshots/function_empty.snap new file mode 100644 index 000000000..41150298d --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/function_empty.snap @@ -0,0 +1,28 @@ +--- +source: crates/aiken-lang/src/parser/definition/function.rs +description: "Code:\n\npub fn run() {}\n" +--- +Fn( + Function { + arguments: [], + body: Trace { + kind: Todo, + location: 0..15, + then: ErrorTerm { + location: 0..15, + }, + text: String { + location: 0..15, + value: "aiken::todo", + }, + }, + doc: None, + location: 0..12, + name: "run", + public: true, + return_annotation: None, + return_type: (), + end_position: 14, + can_error: true, + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/function_non_public.snap b/crates/aiken-lang/src/parser/definition/snapshots/function_non_public.snap new file mode 100644 index 000000000..2904baa4e --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/function_non_public.snap @@ -0,0 +1,28 @@ +--- +source: crates/aiken-lang/src/parser/definition/function.rs +description: "Code:\n\nfn run() {}\n" +--- +Fn( + Function { + arguments: [], + body: Trace { + kind: Todo, + location: 0..11, + then: ErrorTerm { + location: 0..11, + }, + text: String { + location: 0..11, + value: "aiken::todo", + }, + }, + doc: None, + location: 0..8, + name: "run", + public: false, + return_annotation: None, + return_type: (), + end_position: 10, + can_error: true, + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/import_alias.snap b/crates/aiken-lang/src/parser/definition/snapshots/import_alias.snap new file mode 100644 index 000000000..72220ba3f --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/import_alias.snap @@ -0,0 +1,18 @@ +--- +source: crates/aiken-lang/src/parser/definition/import.rs +description: "Code:\n\nuse aiken/list as foo" +--- +Use( + Use { + as_name: Some( + "foo", + ), + location: 0..21, + module: [ + "aiken", + "list", + ], + package: (), + unqualified: [], + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/import_basic.snap b/crates/aiken-lang/src/parser/definition/snapshots/import_basic.snap new file mode 100644 index 000000000..49015b8f2 --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/import_basic.snap @@ -0,0 +1,16 @@ +--- +source: crates/aiken-lang/src/parser/definition/import.rs +description: "Code:\n\nuse aiken/list" +--- +Use( + Use { + as_name: None, + location: 0..14, + module: [ + "aiken", + "list", + ], + package: (), + unqualified: [], + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/import_unqualified.snap b/crates/aiken-lang/src/parser/definition/snapshots/import_unqualified.snap new file mode 100644 index 000000000..d001f3bc6 --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/import_unqualified.snap @@ -0,0 +1,33 @@ +--- +source: crates/aiken-lang/src/parser/definition/import.rs +description: "Code:\n\nuse std/address.{Address as A, thing as w}\n" +--- +Use( + Use { + as_name: None, + location: 0..42, + module: [ + "std", + "address", + ], + package: (), + unqualified: [ + UnqualifiedImport { + location: 17..29, + name: "Address", + as_name: Some( + "A", + ), + layer: Value, + }, + UnqualifiedImport { + location: 31..41, + name: "thing", + as_name: Some( + "w", + ), + layer: Value, + }, + ], + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/opaque_type.snap b/crates/aiken-lang/src/parser/definition/snapshots/opaque_type.snap new file mode 100644 index 000000000..2ed29bba1 --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/opaque_type.snap @@ -0,0 +1,37 @@ +--- +source: crates/aiken-lang/src/parser/definition/data_type.rs +description: "Code:\n\npub opaque type User {\n name: _w\n}\n" +--- +DataType( + DataType { + constructors: [ + RecordConstructor { + location: 21..35, + name: "User", + arguments: [ + RecordConstructorArg { + label: Some( + "name", + ), + annotation: Hole { + location: 31..33, + name: "_w", + }, + location: 25..33, + tipo: (), + doc: None, + }, + ], + doc: None, + sugar: true, + }, + ], + doc: None, + location: 0..35, + name: "User", + opaque: true, + parameters: [], + public: true, + typed_parameters: [], + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/type_alias_basic.snap b/crates/aiken-lang/src/parser/definition/snapshots/type_alias_basic.snap new file mode 100644 index 000000000..2f6e7729c --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/type_alias_basic.snap @@ -0,0 +1,20 @@ +--- +source: crates/aiken-lang/src/parser/definition/type_alias.rs +description: "Code:\n\ntype Thing = Int" +--- +TypeAlias( + TypeAlias { + alias: "Thing", + annotation: Constructor { + location: 13..16, + module: None, + name: "Int", + arguments: [], + }, + doc: None, + location: 0..16, + parameters: [], + public: false, + tipo: (), + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/type_alias_pub.snap b/crates/aiken-lang/src/parser/definition/snapshots/type_alias_pub.snap new file mode 100644 index 000000000..d40a61ef0 --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/type_alias_pub.snap @@ -0,0 +1,20 @@ +--- +source: crates/aiken-lang/src/parser/definition/type_alias.rs +description: "Code:\n\npub type Me = String" +--- +TypeAlias( + TypeAlias { + alias: "Me", + annotation: Constructor { + location: 14..20, + module: None, + name: "String", + arguments: [], + }, + doc: None, + location: 0..20, + parameters: [], + public: true, + tipo: (), + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/type_alias_tuple.snap b/crates/aiken-lang/src/parser/definition/snapshots/type_alias_tuple.snap new file mode 100644 index 000000000..b3b2e0f38 --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/type_alias_tuple.snap @@ -0,0 +1,31 @@ +--- +source: crates/aiken-lang/src/parser/definition/type_alias.rs +description: "Code:\n\ntype RoyaltyToken = (PolicyId, AssetName)" +--- +TypeAlias( + TypeAlias { + alias: "RoyaltyToken", + annotation: Tuple { + location: 20..41, + elems: [ + Constructor { + location: 21..29, + module: None, + name: "PolicyId", + arguments: [], + }, + Constructor { + location: 31..40, + module: None, + name: "AssetName", + arguments: [], + }, + ], + }, + doc: None, + location: 0..41, + parameters: [], + public: false, + tipo: (), + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/validator.snap b/crates/aiken-lang/src/parser/definition/snapshots/validator.snap new file mode 100644 index 000000000..81332987c --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/validator.snap @@ -0,0 +1,62 @@ +--- +source: crates/aiken-lang/src/parser/definition/validator.rs +description: "Code:\n\nvalidator {\n fn foo(datum, rdmr, ctx) {\n True\n }\n}\n" +--- +Validator( + Validator { + doc: None, + end_position: 54, + fun: Function { + arguments: [ + Arg { + arg_name: Named { + name: "datum", + label: "datum", + location: 21..26, + is_validator_param: false, + }, + location: 21..26, + annotation: None, + tipo: (), + }, + Arg { + arg_name: Named { + name: "rdmr", + label: "rdmr", + location: 28..32, + is_validator_param: false, + }, + location: 28..32, + annotation: None, + tipo: (), + }, + Arg { + arg_name: Named { + name: "ctx", + label: "ctx", + location: 34..37, + is_validator_param: false, + }, + location: 34..37, + annotation: None, + tipo: (), + }, + ], + body: Var { + location: 45..49, + name: "True", + }, + doc: None, + location: 14..38, + name: "foo", + public: false, + return_annotation: None, + return_type: (), + end_position: 52, + can_error: true, + }, + other_fun: None, + location: 0..9, + params: [], + }, +) diff --git a/crates/aiken-lang/src/parser/definition/test.rs b/crates/aiken-lang/src/parser/definition/test.rs new file mode 100644 index 000000000..8d11e5064 --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/test.rs @@ -0,0 +1,55 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + expr::UntypedExpr, + parser::{error::ParseError, expr, token::Token}, +}; + +pub fn parser() -> impl Parser { + just(Token::Bang) + .ignored() + .or_not() + .then_ignore(just(Token::Test)) + .then(select! {Token::Name {name} => name}) + .then_ignore(just(Token::LeftParen)) + .then_ignore(just(Token::RightParen)) + .map_with_span(|name, span| (name, span)) + .then( + expr::sequence() + .or_not() + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), + ) + .map_with_span(|(((fail, name), span_end), body), span| { + ast::UntypedDefinition::Test(ast::Function { + arguments: vec![], + body: body.unwrap_or_else(|| UntypedExpr::todo(span, None)), + doc: None, + location: span_end, + end_position: span.end - 1, + name, + public: false, + return_annotation: None, + return_type: (), + can_error: fail.is_some(), + }) + }) +} + +#[cfg(test)] +mod tests { + use crate::assert_definition; + + #[test] + fn test_fail() { + assert_definition!( + r#" + !test invalid_inputs() { + expect True = False + + False + } + "# + ); + } +} diff --git a/crates/aiken-lang/src/parser/definition/type_alias.rs b/crates/aiken-lang/src/parser/definition/type_alias.rs new file mode 100644 index 000000000..b1a6f3c7d --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/type_alias.rs @@ -0,0 +1,53 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + parser::{annotation, error::ParseError, token::Token, utils}, +}; + +pub fn parser() -> impl Parser { + utils::optional_flag(Token::Pub) + .then(utils::type_name_with_args()) + .then_ignore(just(Token::Equal)) + .then(annotation()) + .map_with_span(|((public, (alias, parameters)), annotation), span| { + ast::UntypedDefinition::TypeAlias(ast::TypeAlias { + alias, + annotation, + doc: None, + location: span, + parameters: parameters.unwrap_or_default(), + public, + tipo: (), + }) + }) +} + +#[cfg(test)] +mod tests { + use crate::assert_definition; + + #[test] + fn type_alias_tuple() { + assert_definition!( + r#" + type RoyaltyToken = (PolicyId, AssetName)"# + ); + } + + #[test] + fn type_alias_basic() { + assert_definition!( + r#" + type Thing = Int"# + ); + } + + #[test] + fn type_alias_pub() { + assert_definition!( + r#" + pub type Me = String"# + ); + } +} diff --git a/crates/aiken-lang/src/parser/definition/validator.rs b/crates/aiken-lang/src/parser/definition/validator.rs new file mode 100644 index 000000000..ea7adf6e5 --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/validator.rs @@ -0,0 +1,95 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + parser::{error::ParseError, token::Token}, +}; + +use super::function; + +pub fn parser() -> impl Parser { + just(Token::Validator) + .ignore_then( + function::param(true) + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftParen), just(Token::RightParen)) + .map_with_span(|arguments, span| (arguments, span)) + .or_not(), + ) + .then( + function() + .repeated() + .at_least(1) + .at_most(2) + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)) + .map(|defs| { + defs.into_iter().map(|def| { + let ast::UntypedDefinition::Fn(fun) = def else { + unreachable!("It should be a fn definition"); + }; + + fun + }) + }), + ) + .map_with_span(|(opt_extra_params, mut functions), span| { + let (params, params_span) = opt_extra_params.unwrap_or(( + vec![], + ast::Span { + start: 0, + end: span.start + "validator".len(), + }, + )); + + ast::UntypedDefinition::Validator(ast::Validator { + doc: None, + fun: functions + .next() + .expect("unwrapping safe because there's 'at_least(1)' function"), + other_fun: functions.next(), + location: ast::Span { + start: span.start, + // capture the span from the optional params + end: params_span.end, + }, + params, + end_position: span.end - 1, + }) + }) +} + +#[cfg(test)] +mod tests { + use crate::assert_definition; + + #[test] + fn validator() { + assert_definition!( + r#" + validator { + fn foo(datum, rdmr, ctx) { + True + } + } + "# + ); + } + + #[test] + fn double_validator() { + assert_definition!( + r#" + validator { + fn foo(datum, rdmr, ctx) { + True + } + + fn bar(rdmr, ctx) { + True + } + } + "# + ); + } +} 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 000000000..e55da029a --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/anonymous_binop.rs @@ -0,0 +1,114 @@ +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, + } + }) +} + +#[cfg(test)] +mod tests { + use crate::assert_expr; + + #[test] + fn first_class_binop() { + assert_expr!( + r#" + compare_with(a, >, b) + compare_with(a, >=, b) + compare_with(a, <, b) + compare_with(a, <=, b) + compare_with(a, ==, b) + compare_with(a, !=, b) + combine_with(a, &&, b) + combine_with(a, ||, b) + compute_with(a, +, b) + compute_with(a, -, b) + compute_with(a, /, b) + compute_with(a, *, b) + compute_with(a, %, b)"# + ); + } +} diff --git a/crates/aiken-lang/src/parser/expr/anonymous_function.rs b/crates/aiken-lang/src/parser/expr/anonymous_function.rs new file mode 100644 index 000000000..39bdb0448 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/anonymous_function.rs @@ -0,0 +1,66 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + expr::{FnStyle, UntypedExpr}, + parser::{annotation, error::ParseError, token::Token}, +}; + +pub fn parser( + sequence: Recursive<'_, Token, UntypedExpr, ParseError>, +) -> impl Parser + '_ { + just(Token::Fn) + .ignore_then( + params() + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftParen), just(Token::RightParen)), + ) + .then(just(Token::RArrow).ignore_then(annotation()).or_not()) + .then(sequence.delimited_by(just(Token::LeftBrace), just(Token::RightBrace))) + .map_with_span( + |((arguments, return_annotation), body), span| UntypedExpr::Fn { + arguments, + body: Box::new(body), + location: span, + fn_style: FnStyle::Plain, + return_annotation, + }, + ) +} + +pub fn params() -> impl Parser { + // TODO: return a better error when a label is provided `UnexpectedLabel` + choice(( + select! {Token::DiscardName {name} => name}.map_with_span(|name, span| { + ast::ArgName::Discarded { + label: name.clone(), + name, + location: span, + } + }), + select! {Token::Name {name} => name}.map_with_span(|name, span| ast::ArgName::Named { + label: name.clone(), + name, + location: span, + is_validator_param: false, + }), + )) + .then(just(Token::Colon).ignore_then(annotation()).or_not()) + .map_with_span(|(arg_name, annotation), span| ast::Arg { + location: span, + annotation, + tipo: (), + arg_name, + }) +} + +#[cfg(test)] +mod tests { + use crate::assert_expr; + + #[test] + fn anonymous_function_basic() { + assert_expr!(r#"fn (a: Int) -> Int { a + 1 }"#); + } +} diff --git a/crates/aiken-lang/src/parser/expr/assignment.rs b/crates/aiken-lang/src/parser/expr/assignment.rs new file mode 100644 index 000000000..8e86ae16e --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/assignment.rs @@ -0,0 +1,59 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + expr::UntypedExpr, + parser::{annotation, error::ParseError, pattern, token::Token}, +}; + +pub fn let_( + r: Recursive<'_, Token, UntypedExpr, ParseError>, +) -> impl Parser + '_ { + assignment(r, ast::AssignmentKind::Let) +} + +pub fn expect( + r: Recursive<'_, Token, UntypedExpr, ParseError>, +) -> impl Parser + '_ { + assignment(r, ast::AssignmentKind::Expect) +} + +fn assignment( + r: Recursive<'_, Token, UntypedExpr, ParseError>, + kind: ast::AssignmentKind, +) -> impl Parser + '_ { + let keyword = match kind { + ast::AssignmentKind::Let => Token::Let, + ast::AssignmentKind::Expect => Token::Expect, + }; + + just(keyword) + .ignore_then(pattern()) + .then(just(Token::Colon).ignore_then(annotation()).or_not()) + .then_ignore(just(Token::Equal)) + .then(r.clone()) + .map_with_span( + move |((pattern, annotation), value), span| UntypedExpr::Assignment { + location: span, + value: Box::new(value), + pattern, + kind, + annotation, + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::assert_expr; + + #[test] + fn let_bindings() { + assert_expr!("let thing = [ 1, 2, a ]"); + } + + #[test] + fn expect() { + assert_expr!("expect Some(x) = something.field"); + } +} diff --git a/crates/aiken-lang/src/parser/expr/block.rs b/crates/aiken-lang/src/parser/expr/block.rs new file mode 100644 index 000000000..f40424e74 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/block.rs @@ -0,0 +1,38 @@ +use chumsky::prelude::*; + +use crate::{ + expr::UntypedExpr, + parser::{error::ParseError, token::Token}, +}; + +pub fn parser( + sequence: Recursive<'_, Token, UntypedExpr, ParseError>, +) -> impl Parser + '_ { + choice(( + sequence + .clone() + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), + sequence.clone().delimited_by( + choice((just(Token::LeftParen), just(Token::NewLineLeftParen))), + just(Token::RightParen), + ), + )) +} + +#[cfg(test)] +mod tests { + use crate::assert_expr; + + #[test] + fn block_basic() { + assert_expr!( + r#" + let b = { + let x = 4 + + x + 5 + } + "# + ); + } +} diff --git a/crates/aiken-lang/src/parser/expr/bytearray.rs b/crates/aiken-lang/src/parser/expr/bytearray.rs new file mode 100644 index 000000000..2829b8dc3 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/bytearray.rs @@ -0,0 +1,33 @@ +use chumsky::prelude::*; + +use crate::parser::{ + error::ParseError, expr::UntypedExpr, literal::bytearray::parser as bytearray, token::Token, +}; + +pub fn parser() -> impl Parser { + bytearray(|bytes, preferred_format, location| UntypedExpr::ByteArray { + location, + bytes, + preferred_format, + }) +} + +#[cfg(test)] +mod tests { + use crate::assert_expr; + + #[test] + fn bytearray_basic() { + assert_expr!("#[0, 170, 255]"); + } + + #[test] + fn bytearray_base16() { + assert_expr!("#\"00aaff\""); + } + + #[test] + fn bytearray_utf8_encoded() { + assert_expr!("\"aiken\""); + } +} diff --git a/crates/aiken-lang/src/parser/expr/chained.rs b/crates/aiken-lang/src/parser/expr/chained.rs new file mode 100644 index 000000000..2a12e96da --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/chained.rs @@ -0,0 +1,133 @@ +use chumsky::prelude::*; + +use super::anonymous_binop::parser as anonymous_binop; +use super::anonymous_function::parser as anonymous_function; +use super::assignment; +use super::block::parser as block; +use super::bytearray::parser as bytearray; +use super::if_else::parser as if_else; +use super::int::parser as int; +use super::list::parser as list; +use super::record::parser as record; +use super::record_update::parser as record_update; +use super::string::parser as string; +use super::tuple::parser as tuple; +use super::var::parser as var; +use super::when::parser as when; + +use crate::{ + ast::{self, Span}, + expr::{FnStyle, UntypedExpr}, + parser::{ + chain::{ + call::parser as call, field_access, tuple_index::parser as tuple_index, Chain, + ParserArg, + }, + error::ParseError, + token::Token, + }, +}; + +pub fn parser<'a>( + sequence: Recursive<'a, Token, UntypedExpr, ParseError>, + expression: Recursive<'a, Token, UntypedExpr, ParseError>, +) -> impl Parser + 'a { + let chain = choice(( + tuple_index(), + field_access::parser(), + call(expression.clone()), + )); + chain_start(sequence, expression) + .then(chain.repeated()) + .foldl(|expr, chain| match chain { + Chain::Call(args, span) => { + let mut holes = Vec::new(); + + let args = args + .into_iter() + .enumerate() + .map(|(index, a)| match a { + ParserArg::Arg(arg) => *arg, + ParserArg::Hole { location, label } => { + let name = format!("{}__{index}", ast::CAPTURE_VARIABLE); + + holes.push(ast::Arg { + location: Span::empty(), + annotation: None, + arg_name: ast::ArgName::Named { + label: name.clone(), + name, + location: Span::empty(), + is_validator_param: false, + }, + tipo: (), + }); + + ast::CallArg { + label, + location, + value: UntypedExpr::Var { + location, + name: format!("{}__{index}", ast::CAPTURE_VARIABLE), + }, + } + } + }) + .collect(); + + let call = UntypedExpr::Call { + location: expr.location().union(span), + fun: Box::new(expr), + arguments: args, + }; + + if holes.is_empty() { + call + } else { + UntypedExpr::Fn { + location: call.location(), + fn_style: FnStyle::Capture, + arguments: holes, + body: Box::new(call), + return_annotation: None, + } + } + } + + Chain::FieldAccess(label, span) => UntypedExpr::FieldAccess { + location: expr.location().union(span), + label, + container: Box::new(expr), + }, + + Chain::TupleIndex(index, span) => UntypedExpr::TupleIndex { + location: expr.location().union(span), + index, + tuple: Box::new(expr), + }, + }) +} + +pub fn chain_start<'a>( + sequence: Recursive<'a, Token, UntypedExpr, ParseError>, + expression: Recursive<'a, Token, UntypedExpr, ParseError>, +) -> impl Parser + 'a { + choice(( + string(), + int(), + record_update(expression.clone()), + record(expression.clone()), + field_access::constructor(), + var(), + tuple(expression.clone()), + bytearray(), + list(expression.clone()), + anonymous_function(sequence.clone()), + anonymous_binop(), + block(sequence.clone()), + when(expression.clone()), + assignment::let_(expression.clone()), + assignment::expect(expression.clone()), + if_else(sequence, expression.clone()), + )) +} diff --git a/crates/aiken-lang/src/parser/expr/if_else.rs b/crates/aiken-lang/src/parser/expr/if_else.rs new file mode 100644 index 000000000..3d9c5deb9 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/if_else.rs @@ -0,0 +1,74 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + expr::UntypedExpr, + parser::{error::ParseError, token::Token}, +}; + +use super::block; + +pub fn parser<'a>( + sequence: Recursive<'a, Token, UntypedExpr, ParseError>, + expression: Recursive<'a, Token, UntypedExpr, ParseError>, +) -> impl Parser + 'a { + just(Token::If) + .ignore_then( + expression + .clone() + .then(block(sequence.clone())) + .map_with_span(|(condition, body), span| ast::IfBranch { + condition, + body, + location: span, + }), + ) + .then( + just(Token::Else) + .ignore_then(just(Token::If)) + .ignore_then( + expression + .clone() + .then(block(sequence.clone())) + .map_with_span(|(condition, body), span| ast::IfBranch { + condition, + body, + location: span, + }), + ) + .repeated(), + ) + .then_ignore(just(Token::Else)) + .then(block(sequence)) + .map_with_span(|((first, alternative_branches), final_else), span| { + let mut branches = vec1::vec1![first]; + + branches.extend(alternative_branches); + + UntypedExpr::If { + location: span, + branches, + final_else: Box::new(final_else), + } + }) +} + +#[cfg(test)] +mod tests { + use crate::assert_expr; + + #[test] + fn if_else_basic() { + assert_expr!( + r#" + if True { + 1 + 1 + } else if a < 1 { + 3 + } else { + 4 + } + "# + ); + } +} diff --git a/crates/aiken-lang/src/parser/expr/int.rs b/crates/aiken-lang/src/parser/expr/int.rs new file mode 100644 index 000000000..5ed56f13d --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/int.rs @@ -0,0 +1,47 @@ +use chumsky::prelude::*; + +use crate::{ + expr::UntypedExpr, + parser::{error::ParseError, literal::int::parser as int, token::Token}, +}; + +pub fn parser() -> impl Parser { + int().map_with_span(|(value, base), span| UntypedExpr::Int { + location: span, + value, + base, + }) +} + +#[cfg(test)] +mod tests { + use crate::assert_expr; + + #[test] + fn int_literal() { + assert_expr!("1"); + } + + #[test] + fn int_negative() { + assert_expr!("-1"); + } + + #[test] + fn int_numeric_underscore() { + assert_expr!( + r#" + { + let i = 1_234_567 + let j = 1_000_000 + let k = -10_000 + } + "# + ); + } + + #[test] + fn int_hex_bytes() { + assert_expr!(r#"0x01"#); + } +} diff --git a/crates/aiken-lang/src/parser/expr/list.rs b/crates/aiken-lang/src/parser/expr/list.rs new file mode 100644 index 000000000..d11af859d --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/list.rs @@ -0,0 +1,49 @@ +use chumsky::prelude::*; + +use crate::{ + expr::UntypedExpr, + parser::{error::ParseError, token::Token}, +}; + +pub fn parser( + r: Recursive<'_, Token, UntypedExpr, ParseError>, +) -> impl Parser + '_ { + just(Token::LeftSquare) + .ignore_then(r.clone().separated_by(just(Token::Comma))) + .then(choice(( + just(Token::Comma).ignore_then( + just(Token::DotDot) + .ignore_then(r.clone()) + .map(Box::new) + .or_not(), + ), + just(Token::Comma).ignored().or_not().map(|_| None), + ))) + .then_ignore(just(Token::RightSquare)) + // TODO: check if tail.is_some and elements.is_empty then return ListSpreadWithoutElements error + .map_with_span(|(elements, tail), span| UntypedExpr::List { + location: span, + elements, + tail, + }) +} + +#[cfg(test)] +mod tests { + use crate::assert_expr; + + #[test] + fn empty_list() { + assert_expr!("[]"); + } + + #[test] + fn int_list() { + assert_expr!("[1, 2, 3]"); + } + + #[test] + fn list_spread() { + assert_expr!("[1, 2, ..[]]"); + } +} diff --git a/crates/aiken-lang/src/parser/expr/mod.rs b/crates/aiken-lang/src/parser/expr/mod.rs new file mode 100644 index 000000000..96d361f2f --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/mod.rs @@ -0,0 +1,233 @@ +use chumsky::prelude::*; +use vec1::Vec1; + +mod anonymous_binop; +pub mod anonymous_function; +pub mod assignment; +mod block; +pub(crate) mod bytearray; +mod chained; +mod if_else; +mod int; +mod list; +mod record; +mod record_update; +mod sequence; +pub mod string; +mod tuple; +mod var; +pub mod when; + +pub use anonymous_function::parser as anonymous_function; +pub use block::parser as block; +pub use bytearray::parser as bytearray; +pub use chained::parser as chained; +pub use if_else::parser as if_else; +pub use int::parser as int; +pub use list::parser as list; +pub use record::parser as record; +pub use record_update::parser as record_update; +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 super::{error::ParseError, token::Token}; +use crate::{ast, expr::UntypedExpr}; + +pub fn parser( + sequence: Recursive<'_, Token, UntypedExpr, ParseError>, +) -> impl Parser + '_ { + recursive(|expression| { + let chained_debugged = chained(sequence, expression) + .then(just(Token::Question).or_not()) + .map_with_span(|(value, token), location| match token { + Some(_) => UntypedExpr::TraceIfFalse { + value: Box::new(value), + location, + }, + None => value, + }); + + // Negate + let op = choice(( + 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. + // For example: + // + // foo(a, -, b) + // + // but it'll fail in a let-binding: + // + // let foo = - + // + // which seems acceptable. + .then_ignore(just(Token::Comma).not().rewind()) + .to(ast::UnOp::Negate), + )); + + let unary = op + .map_with_span(|op, span| (op, span)) + .repeated() + .then(chained_debugged) + .foldr(|(un_op, span), value| UntypedExpr::UnOp { + op: un_op, + location: span.union(value.location()), + value: Box::new(value), + }) + .boxed(); + + // Product + let op = choice(( + just(Token::Star).to(ast::BinOp::MultInt), + just(Token::Slash).to(ast::BinOp::DivInt), + just(Token::Percent).to(ast::BinOp::ModInt), + )); + + let product = unary + .clone() + .then(op.then(unary).repeated()) + .foldl(|a, (op, b)| UntypedExpr::BinOp { + location: a.location().union(b.location()), + name: op, + left: Box::new(a), + right: Box::new(b), + }) + .boxed(); + + // Sum + let op = choice(( + just(Token::Plus).to(ast::BinOp::AddInt), + just(Token::Minus).to(ast::BinOp::SubInt), + )); + + let sum = product + .clone() + .then(op.then(product).repeated()) + .foldl(|a, (op, b)| UntypedExpr::BinOp { + location: a.location().union(b.location()), + name: op, + left: Box::new(a), + right: Box::new(b), + }) + .boxed(); + + // Comparison + let 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 = sum + .clone() + .then(op.then(sum).repeated()) + .foldl(|a, (op, b)| UntypedExpr::BinOp { + location: a.location().union(b.location()), + name: op, + left: Box::new(a), + right: Box::new(b), + }) + .boxed(); + + // Conjunction + let op = just(Token::AmperAmper).to(ast::BinOp::And); + let conjunction = comparison + .clone() + .then(op.then(comparison).repeated()) + .foldl(|a, (op, b)| UntypedExpr::BinOp { + location: a.location().union(b.location()), + name: op, + left: Box::new(a), + right: Box::new(b), + }) + .boxed(); + + // Disjunction + let op = just(Token::VbarVbar).to(ast::BinOp::Or); + let disjunction = conjunction + .clone() + .then(op.then(conjunction).repeated()) + .foldl(|a, (op, b)| UntypedExpr::BinOp { + location: a.location().union(b.location()), + name: op, + left: Box::new(a), + right: Box::new(b), + }) + .boxed(); + + // Pipeline + disjunction + .clone() + .then( + choice((just(Token::Pipe), just(Token::NewLinePipe))) + .then(disjunction) + .repeated(), + ) + .foldl(|l, (pipe, r)| { + if let UntypedExpr::PipeLine { + mut expressions, + one_liner, + } = l + { + expressions.push(r); + return UntypedExpr::PipeLine { + expressions, + one_liner, + }; + } + + let mut expressions = Vec1::new(l); + expressions.push(r); + UntypedExpr::PipeLine { + expressions, + one_liner: pipe != Token::NewLinePipe, + } + }) + }) +} + +#[cfg(test)] +mod tests { + use crate::assert_expr; + + #[test] + fn plus_binop() { + assert_expr!("a + 1"); + } + + #[test] + fn pipeline() { + assert_expr!( + r#" + a + 2 + |> add_one + |> add_one + "# + ); + } + + #[test] + fn field_access() { + assert_expr!("user.name"); + } + + #[test] + fn function_invoke() { + assert_expr!( + r#" + let x = add_one(3) + + let map_add_x = list.map(_, fn (y) { x + y }) + + map_add_x([ 1, 2, 3 ]) + "# + ); + } +} diff --git a/crates/aiken-lang/src/parser/expr/record.rs b/crates/aiken-lang/src/parser/expr/record.rs new file mode 100644 index 000000000..e8473a125 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/record.rs @@ -0,0 +1,172 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + expr::UntypedExpr, + parser::{ + error::{self, ParseError}, + token::Token, + }, +}; + +pub fn parser( + r: Recursive<'_, Token, UntypedExpr, ParseError>, +) -> impl Parser + '_ { + choice(( + select! {Token::Name { name } => name} + .map_with_span(|module, span: ast::Span| (module, span)) + .then_ignore(just(Token::Dot)) + .or_not() + .then(select! {Token::UpName { name } => name}.map_with_span(|name, span| (name, span))) + .then( + choice(( + select! {Token::Name {name} => name} + .then_ignore(just(Token::Colon)) + .then(choice(( + r.clone(), + select! {Token::DiscardName {name} => name }.validate( + |_name, span, emit| { + emit(ParseError::expected_input_found( + span, + None, + Some(error::Pattern::Discard), + )); + + UntypedExpr::Var { + location: span, + name: ast::CAPTURE_VARIABLE.to_string(), + } + }, + ), + ))) + .map_with_span(|(label, value), span| ast::CallArg { + location: span, + value, + label: Some(label), + }), + choice(( + select! {Token::Name {name} => name}.map_with_span(|name, span| { + ( + UntypedExpr::Var { + name: name.clone(), + location: span, + }, + name, + ) + }), + select! {Token::DiscardName {name} => name }.validate( + |name, span, emit| { + emit(ParseError::expected_input_found( + span, + None, + Some(error::Pattern::Discard), + )); + + ( + UntypedExpr::Var { + location: span, + name: ast::CAPTURE_VARIABLE.to_string(), + }, + name, + ) + }, + ), + )) + .map(|(value, name)| ast::CallArg { + location: value.location(), + value, + label: Some(name), + }), + )) + .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( + select! {Token::Name {name} => name} + .ignored() + .then_ignore(just(Token::Colon)) + .validate(|_label, span, emit| { + emit(ParseError::expected_input_found( + span, + None, + Some(error::Pattern::Label), + )) + }) + .or_not() + .then(choice(( + r.clone(), + select! {Token::DiscardName {name} => name }.validate( + |_name, span, emit| { + emit(ParseError::expected_input_found( + span, + None, + Some(error::Pattern::Discard), + )); + + UntypedExpr::Var { + location: span, + name: ast::CAPTURE_VARIABLE.to_string(), + } + }, + ), + ))) + .map(|(_label, value)| ast::CallArg { + location: value.location(), + 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 { + UntypedExpr::FieldAccess { + location: m_span.union(n_span), + label: name, + container: Box::new(UntypedExpr::Var { + location: m_span, + name: module, + }), + } + } else { + UntypedExpr::Var { + location: n_span, + name, + } + }; + + UntypedExpr::Call { + arguments, + fun: Box::new(fun), + location: span, + } + }) +} + +#[cfg(test)] +mod tests { + use crate::assert_expr; + + #[test] + fn record_create_labeled() { + assert_expr!(r#"User { name: "Aiken", age, thing: 2 }"#); + } + + #[test] + fn record_create_labeled_with_field_access() { + assert_expr!(r#"some_module.User { name: "Aiken", age, thing: 2 }"#); + } + + #[test] + fn record_create_unlabeled() { + assert_expr!(r#"some_module.Thing(1, a)"#); + } +} diff --git a/crates/aiken-lang/src/parser/expr/record_update.rs b/crates/aiken-lang/src/parser/expr/record_update.rs new file mode 100644 index 000000000..4565be127 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/record_update.rs @@ -0,0 +1,96 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + expr::UntypedExpr, + parser::{error::ParseError, token::Token}, +}; + +pub fn parser( + r: Recursive<'_, Token, UntypedExpr, ParseError>, +) -> impl Parser + '_ { + select! {Token::Name { name } => name} + .map_with_span(|module, span: ast::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( + choice(( + 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, + } + }), + select! {Token::Name {name} => name}.map_with_span(|name, span| { + ast::UntypedRecordUpdateArg { + location: span, + value: UntypedExpr::Var { + name: name.clone(), + location: span, + }, + label: name, + } + }), + )) + .separated_by(just(Token::Comma)) + .allow_trailing(), + ) + .or_not(), + ) + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)) + .map_with_span(|a, span: ast::Span| (a, span)), + ) + .map(|((module, (name, n_span)), ((spread, opt_args), span))| { + let constructor = if let Some((module, m_span)) = module { + UntypedExpr::FieldAccess { + location: m_span.union(n_span), + label: name, + container: Box::new(UntypedExpr::Var { + location: m_span, + name: module, + }), + } + } else { + UntypedExpr::Var { + location: n_span, + name, + } + }; + + let spread_span = spread.location(); + + let location = ast::Span::new((), spread_span.start - 2..spread_span.end); + + let spread = ast::RecordUpdateSpread { + base: Box::new(spread), + location, + }; + + UntypedExpr::RecordUpdate { + location: constructor.location().union(span), + constructor: Box::new(constructor), + spread, + arguments: opt_args.unwrap_or_default(), + } + }) +} + +#[cfg(test)] +mod tests { + use crate::assert_expr; + + #[test] + fn record_update_basic() { + assert_expr!(r#"User { ..user, name: "Aiken", age }"#); + } +} diff --git a/crates/aiken-lang/src/parser/expr/sequence.rs b/crates/aiken-lang/src/parser/expr/sequence.rs new file mode 100644 index 000000000..68e5c0758 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/sequence.rs @@ -0,0 +1,36 @@ +use chumsky::prelude::*; + +use crate::{ + ast::TraceKind, + expr::UntypedExpr, + parser::{error::ParseError, token::Token}, +}; + +pub fn parser() -> impl Parser { + recursive(|expression| { + choice(( + just(Token::Trace) + .ignore_then(super::parser(expression.clone())) + .then(expression.clone()) + .map_with_span(|(text, then_), span| UntypedExpr::Trace { + kind: TraceKind::Trace, + location: span, + then: Box::new(then_), + text: Box::new(super::string::flexible(text)), + }), + just(Token::ErrorTerm) + .ignore_then(super::parser(expression.clone()).or_not()) + .map_with_span(|reason, span| { + UntypedExpr::error(span, reason.map(super::string::flexible)) + }), + just(Token::Todo) + .ignore_then(super::parser(expression.clone()).or_not()) + .map_with_span(|reason, span| { + UntypedExpr::todo(span, reason.map(super::string::flexible)) + }), + super::parser(expression.clone()) + .then(expression.repeated()) + .foldl(|current, next| current.append_in_sequence(next)), + )) + }) +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_basic.snap b/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_basic.snap new file mode 100644 index 000000000..da64b10fb --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_basic.snap @@ -0,0 +1,51 @@ +--- +source: crates/aiken-lang/src/parser/expr/anonymous_function.rs +description: "Code:\n\nfn (a: Int) -> Int { a + 1 }" +--- +Fn { + location: 0..28, + fn_style: Plain, + arguments: [ + Arg { + arg_name: Named { + name: "a", + label: "a", + location: 4..5, + is_validator_param: false, + }, + location: 4..10, + annotation: Some( + Constructor { + location: 7..10, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + ], + body: BinOp { + location: 21..26, + name: AddInt, + left: Var { + location: 21..22, + name: "a", + }, + right: Int { + location: 25..26, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + return_annotation: Some( + Constructor { + location: 15..18, + module: None, + name: "Int", + arguments: [], + }, + ), +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/block_basic.snap b/crates/aiken-lang/src/parser/expr/snapshots/block_basic.snap new file mode 100644 index 000000000..7d07f7180 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/block_basic.snap @@ -0,0 +1,49 @@ +--- +source: crates/aiken-lang/src/parser/expr/block.rs +description: "Code:\n\nlet b = {\n let x = 4\n\n x + 5\n}\n" +--- +Assignment { + location: 0..32, + value: Sequence { + location: 12..30, + expressions: [ + Assignment { + location: 12..21, + value: Int { + location: 20..21, + value: "4", + base: Decimal { + numeric_underscore: false, + }, + }, + pattern: Var { + location: 16..17, + name: "x", + }, + kind: Let, + annotation: None, + }, + BinOp { + location: 25..30, + name: AddInt, + left: Var { + location: 25..26, + name: "x", + }, + right: Int { + location: 29..30, + value: "5", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + ], + }, + pattern: Var { + location: 4..5, + name: "b", + }, + kind: Let, + annotation: None, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/bytearray_base16.snap b/crates/aiken-lang/src/parser/expr/snapshots/bytearray_base16.snap new file mode 100644 index 000000000..36d36a2a9 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/bytearray_base16.snap @@ -0,0 +1,13 @@ +--- +source: crates/aiken-lang/src/parser/expr/bytearray.rs +description: "Code:\n\n#\"00aaff\"" +--- +ByteArray { + location: 0..9, + bytes: [ + 0, + 170, + 255, + ], + preferred_format: HexadecimalString, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/bytearray_basic.snap b/crates/aiken-lang/src/parser/expr/snapshots/bytearray_basic.snap new file mode 100644 index 000000000..58c6b407f --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/bytearray_basic.snap @@ -0,0 +1,17 @@ +--- +source: crates/aiken-lang/src/parser/expr/bytearray.rs +description: "Code:\n\n#[0, 170, 255]" +--- +ByteArray { + location: 0..14, + bytes: [ + 0, + 170, + 255, + ], + preferred_format: ArrayOfBytes( + Decimal { + numeric_underscore: false, + }, + ), +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/bytearray_utf8_encoded.snap b/crates/aiken-lang/src/parser/expr/snapshots/bytearray_utf8_encoded.snap new file mode 100644 index 000000000..611eefcf8 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/bytearray_utf8_encoded.snap @@ -0,0 +1,15 @@ +--- +source: crates/aiken-lang/src/parser/expr/bytearray.rs +description: "Code:\n\n\"aiken\"" +--- +ByteArray { + location: 0..7, + bytes: [ + 97, + 105, + 107, + 101, + 110, + ], + preferred_format: Utf8String, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/empty_list.snap b/crates/aiken-lang/src/parser/expr/snapshots/empty_list.snap new file mode 100644 index 000000000..8874c6b64 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/empty_list.snap @@ -0,0 +1,9 @@ +--- +source: crates/aiken-lang/src/parser/expr/list.rs +description: "Code:\n\n[]" +--- +List { + location: 0..2, + elements: [], + tail: None, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/expect.snap b/crates/aiken-lang/src/parser/expr/snapshots/expect.snap new file mode 100644 index 000000000..440e0855c --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/expect.snap @@ -0,0 +1,36 @@ +--- +source: crates/aiken-lang/src/parser/expr/assignment.rs +description: "Code:\n\nexpect Some(x) = something.field" +--- +Assignment { + location: 0..32, + value: FieldAccess { + location: 17..32, + label: "field", + container: Var { + location: 17..26, + name: "something", + }, + }, + pattern: Constructor { + is_record: false, + location: 7..14, + name: "Some", + arguments: [ + CallArg { + label: None, + location: 12..13, + value: Var { + location: 12..13, + name: "x", + }, + }, + ], + module: None, + constructor: (), + with_spread: false, + tipo: (), + }, + kind: Expect, + annotation: None, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/field_access.snap b/crates/aiken-lang/src/parser/expr/snapshots/field_access.snap new file mode 100644 index 000000000..f429b58ed --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/field_access.snap @@ -0,0 +1,12 @@ +--- +source: crates/aiken-lang/src/parser/expr/mod.rs +description: "Code:\n\nuser.name" +--- +FieldAccess { + location: 0..9, + label: "name", + container: Var { + location: 0..4, + name: "user", + }, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/first_class_binop.snap b/crates/aiken-lang/src/parser/expr/snapshots/first_class_binop.snap new file mode 100644 index 000000000..45a675bd0 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/first_class_binop.snap @@ -0,0 +1,1190 @@ +--- +source: crates/aiken-lang/src/parser/expr/anonymous_binop.rs +description: "Code:\n\ncompare_with(a, >, b)\ncompare_with(a, >=, b)\ncompare_with(a, <, b)\ncompare_with(a, <=, b)\ncompare_with(a, ==, b)\ncompare_with(a, !=, b)\ncombine_with(a, &&, b)\ncombine_with(a, ||, b)\ncompute_with(a, +, b)\ncompute_with(a, -, b)\ncompute_with(a, /, b)\ncompute_with(a, *, b)\ncompute_with(a, %, b)" +--- +Sequence { + location: 0..291, + expressions: [ + Call { + arguments: [ + CallArg { + label: None, + location: 13..14, + value: Var { + location: 13..14, + name: "a", + }, + }, + CallArg { + label: None, + location: 16..17, + value: Fn { + location: 16..17, + fn_style: BinOp( + GtInt, + ), + arguments: [ + Arg { + arg_name: Named { + name: "left", + label: "left", + location: 16..17, + is_validator_param: false, + }, + location: 16..17, + annotation: Some( + Constructor { + location: 16..17, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + Arg { + arg_name: Named { + name: "right", + label: "right", + location: 16..17, + is_validator_param: false, + }, + location: 16..17, + annotation: Some( + Constructor { + location: 16..17, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + ], + body: BinOp { + location: 16..17, + name: GtInt, + left: Var { + location: 16..17, + name: "left", + }, + right: Var { + location: 16..17, + name: "right", + }, + }, + return_annotation: Some( + Constructor { + location: 16..17, + module: None, + name: "Bool", + arguments: [], + }, + ), + }, + }, + CallArg { + label: None, + location: 19..20, + value: Var { + location: 19..20, + name: "b", + }, + }, + ], + fun: Var { + location: 0..12, + name: "compare_with", + }, + location: 0..21, + }, + Call { + arguments: [ + CallArg { + label: None, + location: 35..36, + value: Var { + location: 35..36, + name: "a", + }, + }, + CallArg { + label: None, + location: 38..40, + value: Fn { + location: 38..40, + fn_style: BinOp( + GtEqInt, + ), + arguments: [ + Arg { + arg_name: Named { + name: "left", + label: "left", + location: 38..40, + is_validator_param: false, + }, + location: 38..40, + annotation: Some( + Constructor { + location: 38..40, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + Arg { + arg_name: Named { + name: "right", + label: "right", + location: 38..40, + is_validator_param: false, + }, + location: 38..40, + annotation: Some( + Constructor { + location: 38..40, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + ], + body: BinOp { + location: 38..40, + name: GtEqInt, + left: Var { + location: 38..40, + name: "left", + }, + right: Var { + location: 38..40, + name: "right", + }, + }, + return_annotation: Some( + Constructor { + location: 38..40, + module: None, + name: "Bool", + arguments: [], + }, + ), + }, + }, + CallArg { + label: None, + location: 42..43, + value: Var { + location: 42..43, + name: "b", + }, + }, + ], + fun: Var { + location: 22..34, + name: "compare_with", + }, + location: 22..44, + }, + Call { + arguments: [ + CallArg { + label: None, + location: 58..59, + value: Var { + location: 58..59, + name: "a", + }, + }, + CallArg { + label: None, + location: 61..62, + value: Fn { + location: 61..62, + fn_style: BinOp( + LtInt, + ), + arguments: [ + Arg { + arg_name: Named { + name: "left", + label: "left", + location: 61..62, + is_validator_param: false, + }, + location: 61..62, + annotation: Some( + Constructor { + location: 61..62, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + Arg { + arg_name: Named { + name: "right", + label: "right", + location: 61..62, + is_validator_param: false, + }, + location: 61..62, + annotation: Some( + Constructor { + location: 61..62, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + ], + body: BinOp { + location: 61..62, + name: LtInt, + left: Var { + location: 61..62, + name: "left", + }, + right: Var { + location: 61..62, + name: "right", + }, + }, + return_annotation: Some( + Constructor { + location: 61..62, + module: None, + name: "Bool", + arguments: [], + }, + ), + }, + }, + CallArg { + label: None, + location: 64..65, + value: Var { + location: 64..65, + name: "b", + }, + }, + ], + fun: Var { + location: 45..57, + name: "compare_with", + }, + location: 45..66, + }, + Call { + arguments: [ + CallArg { + label: None, + location: 80..81, + value: Var { + location: 80..81, + name: "a", + }, + }, + CallArg { + label: None, + location: 83..85, + value: Fn { + location: 83..85, + fn_style: BinOp( + LtEqInt, + ), + arguments: [ + Arg { + arg_name: Named { + name: "left", + label: "left", + location: 83..85, + is_validator_param: false, + }, + location: 83..85, + annotation: Some( + Constructor { + location: 83..85, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + Arg { + arg_name: Named { + name: "right", + label: "right", + location: 83..85, + is_validator_param: false, + }, + location: 83..85, + annotation: Some( + Constructor { + location: 83..85, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + ], + body: BinOp { + location: 83..85, + name: LtEqInt, + left: Var { + location: 83..85, + name: "left", + }, + right: Var { + location: 83..85, + name: "right", + }, + }, + return_annotation: Some( + Constructor { + location: 83..85, + module: None, + name: "Bool", + arguments: [], + }, + ), + }, + }, + CallArg { + label: None, + location: 87..88, + value: Var { + location: 87..88, + name: "b", + }, + }, + ], + fun: Var { + location: 67..79, + name: "compare_with", + }, + location: 67..89, + }, + Call { + arguments: [ + CallArg { + label: None, + location: 103..104, + value: Var { + location: 103..104, + name: "a", + }, + }, + CallArg { + label: None, + location: 106..108, + value: Fn { + location: 106..108, + fn_style: BinOp( + Eq, + ), + arguments: [ + Arg { + arg_name: Named { + name: "left", + label: "left", + location: 106..108, + is_validator_param: false, + }, + location: 106..108, + annotation: None, + tipo: (), + }, + Arg { + arg_name: Named { + name: "right", + label: "right", + location: 106..108, + is_validator_param: false, + }, + location: 106..108, + annotation: None, + tipo: (), + }, + ], + body: BinOp { + location: 106..108, + name: Eq, + left: Var { + location: 106..108, + name: "left", + }, + right: Var { + location: 106..108, + name: "right", + }, + }, + return_annotation: Some( + Constructor { + location: 106..108, + module: None, + name: "Bool", + arguments: [], + }, + ), + }, + }, + CallArg { + label: None, + location: 110..111, + value: Var { + location: 110..111, + name: "b", + }, + }, + ], + fun: Var { + location: 90..102, + name: "compare_with", + }, + location: 90..112, + }, + Call { + arguments: [ + CallArg { + label: None, + location: 126..127, + value: Var { + location: 126..127, + name: "a", + }, + }, + CallArg { + label: None, + location: 129..131, + value: Fn { + location: 129..131, + fn_style: BinOp( + NotEq, + ), + arguments: [ + Arg { + arg_name: Named { + name: "left", + label: "left", + location: 129..131, + is_validator_param: false, + }, + location: 129..131, + annotation: None, + tipo: (), + }, + Arg { + arg_name: Named { + name: "right", + label: "right", + location: 129..131, + is_validator_param: false, + }, + location: 129..131, + annotation: None, + tipo: (), + }, + ], + body: BinOp { + location: 129..131, + name: NotEq, + left: Var { + location: 129..131, + name: "left", + }, + right: Var { + location: 129..131, + name: "right", + }, + }, + return_annotation: Some( + Constructor { + location: 129..131, + module: None, + name: "Bool", + arguments: [], + }, + ), + }, + }, + CallArg { + label: None, + location: 133..134, + value: Var { + location: 133..134, + name: "b", + }, + }, + ], + fun: Var { + location: 113..125, + name: "compare_with", + }, + location: 113..135, + }, + Call { + arguments: [ + CallArg { + label: None, + location: 149..150, + value: Var { + location: 149..150, + name: "a", + }, + }, + CallArg { + label: None, + location: 152..154, + value: Fn { + location: 152..154, + fn_style: BinOp( + And, + ), + arguments: [ + Arg { + arg_name: Named { + name: "left", + label: "left", + location: 152..154, + is_validator_param: false, + }, + location: 152..154, + annotation: Some( + Constructor { + location: 152..154, + module: None, + name: "Bool", + arguments: [], + }, + ), + tipo: (), + }, + Arg { + arg_name: Named { + name: "right", + label: "right", + location: 152..154, + is_validator_param: false, + }, + location: 152..154, + annotation: Some( + Constructor { + location: 152..154, + module: None, + name: "Bool", + arguments: [], + }, + ), + tipo: (), + }, + ], + body: BinOp { + location: 152..154, + name: And, + left: Var { + location: 152..154, + name: "left", + }, + right: Var { + location: 152..154, + name: "right", + }, + }, + return_annotation: Some( + Constructor { + location: 152..154, + module: None, + name: "Bool", + arguments: [], + }, + ), + }, + }, + CallArg { + label: None, + location: 156..157, + value: Var { + location: 156..157, + name: "b", + }, + }, + ], + fun: Var { + location: 136..148, + name: "combine_with", + }, + location: 136..158, + }, + Call { + arguments: [ + CallArg { + label: None, + location: 172..173, + value: Var { + location: 172..173, + name: "a", + }, + }, + CallArg { + label: None, + location: 175..177, + value: Fn { + location: 175..177, + fn_style: BinOp( + Or, + ), + arguments: [ + Arg { + arg_name: Named { + name: "left", + label: "left", + location: 175..177, + is_validator_param: false, + }, + location: 175..177, + annotation: Some( + Constructor { + location: 175..177, + module: None, + name: "Bool", + arguments: [], + }, + ), + tipo: (), + }, + Arg { + arg_name: Named { + name: "right", + label: "right", + location: 175..177, + is_validator_param: false, + }, + location: 175..177, + annotation: Some( + Constructor { + location: 175..177, + module: None, + name: "Bool", + arguments: [], + }, + ), + tipo: (), + }, + ], + body: BinOp { + location: 175..177, + name: Or, + left: Var { + location: 175..177, + name: "left", + }, + right: Var { + location: 175..177, + name: "right", + }, + }, + return_annotation: Some( + Constructor { + location: 175..177, + module: None, + name: "Bool", + arguments: [], + }, + ), + }, + }, + CallArg { + label: None, + location: 179..180, + value: Var { + location: 179..180, + name: "b", + }, + }, + ], + fun: Var { + location: 159..171, + name: "combine_with", + }, + location: 159..181, + }, + Call { + arguments: [ + CallArg { + label: None, + location: 195..196, + value: Var { + location: 195..196, + name: "a", + }, + }, + CallArg { + label: None, + location: 198..199, + value: Fn { + location: 198..199, + fn_style: BinOp( + AddInt, + ), + arguments: [ + Arg { + arg_name: Named { + name: "left", + label: "left", + location: 198..199, + is_validator_param: false, + }, + location: 198..199, + annotation: Some( + Constructor { + location: 198..199, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + Arg { + arg_name: Named { + name: "right", + label: "right", + location: 198..199, + is_validator_param: false, + }, + location: 198..199, + annotation: Some( + Constructor { + location: 198..199, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + ], + body: BinOp { + location: 198..199, + name: AddInt, + left: Var { + location: 198..199, + name: "left", + }, + right: Var { + location: 198..199, + name: "right", + }, + }, + return_annotation: Some( + Constructor { + location: 198..199, + module: None, + name: "Int", + arguments: [], + }, + ), + }, + }, + CallArg { + label: None, + location: 201..202, + value: Var { + location: 201..202, + name: "b", + }, + }, + ], + fun: Var { + location: 182..194, + name: "compute_with", + }, + location: 182..203, + }, + Call { + arguments: [ + CallArg { + label: None, + location: 217..218, + value: Var { + location: 217..218, + name: "a", + }, + }, + CallArg { + label: None, + location: 220..221, + value: Fn { + location: 220..221, + fn_style: BinOp( + SubInt, + ), + arguments: [ + Arg { + arg_name: Named { + name: "left", + label: "left", + location: 220..221, + is_validator_param: false, + }, + location: 220..221, + annotation: Some( + Constructor { + location: 220..221, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + Arg { + arg_name: Named { + name: "right", + label: "right", + location: 220..221, + is_validator_param: false, + }, + location: 220..221, + annotation: Some( + Constructor { + location: 220..221, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + ], + body: BinOp { + location: 220..221, + name: SubInt, + left: Var { + location: 220..221, + name: "left", + }, + right: Var { + location: 220..221, + name: "right", + }, + }, + return_annotation: Some( + Constructor { + location: 220..221, + module: None, + name: "Int", + arguments: [], + }, + ), + }, + }, + CallArg { + label: None, + location: 223..224, + value: Var { + location: 223..224, + name: "b", + }, + }, + ], + fun: Var { + location: 204..216, + name: "compute_with", + }, + location: 204..225, + }, + Call { + arguments: [ + CallArg { + label: None, + location: 239..240, + value: Var { + location: 239..240, + name: "a", + }, + }, + CallArg { + label: None, + location: 242..243, + value: Fn { + location: 242..243, + fn_style: BinOp( + DivInt, + ), + arguments: [ + Arg { + arg_name: Named { + name: "left", + label: "left", + location: 242..243, + is_validator_param: false, + }, + location: 242..243, + annotation: Some( + Constructor { + location: 242..243, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + Arg { + arg_name: Named { + name: "right", + label: "right", + location: 242..243, + is_validator_param: false, + }, + location: 242..243, + annotation: Some( + Constructor { + location: 242..243, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + ], + body: BinOp { + location: 242..243, + name: DivInt, + left: Var { + location: 242..243, + name: "left", + }, + right: Var { + location: 242..243, + name: "right", + }, + }, + return_annotation: Some( + Constructor { + location: 242..243, + module: None, + name: "Int", + arguments: [], + }, + ), + }, + }, + CallArg { + label: None, + location: 245..246, + value: Var { + location: 245..246, + name: "b", + }, + }, + ], + fun: Var { + location: 226..238, + name: "compute_with", + }, + location: 226..247, + }, + Call { + arguments: [ + CallArg { + label: None, + location: 261..262, + value: Var { + location: 261..262, + name: "a", + }, + }, + CallArg { + label: None, + location: 264..265, + value: Fn { + location: 264..265, + fn_style: BinOp( + MultInt, + ), + arguments: [ + Arg { + arg_name: Named { + name: "left", + label: "left", + location: 264..265, + is_validator_param: false, + }, + location: 264..265, + annotation: Some( + Constructor { + location: 264..265, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + Arg { + arg_name: Named { + name: "right", + label: "right", + location: 264..265, + is_validator_param: false, + }, + location: 264..265, + annotation: Some( + Constructor { + location: 264..265, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + ], + body: BinOp { + location: 264..265, + name: MultInt, + left: Var { + location: 264..265, + name: "left", + }, + right: Var { + location: 264..265, + name: "right", + }, + }, + return_annotation: Some( + Constructor { + location: 264..265, + module: None, + name: "Int", + arguments: [], + }, + ), + }, + }, + CallArg { + label: None, + location: 267..268, + value: Var { + location: 267..268, + name: "b", + }, + }, + ], + fun: Var { + location: 248..260, + name: "compute_with", + }, + location: 248..269, + }, + Call { + arguments: [ + CallArg { + label: None, + location: 283..284, + value: Var { + location: 283..284, + name: "a", + }, + }, + CallArg { + label: None, + location: 286..287, + value: Fn { + location: 286..287, + fn_style: BinOp( + ModInt, + ), + arguments: [ + Arg { + arg_name: Named { + name: "left", + label: "left", + location: 286..287, + is_validator_param: false, + }, + location: 286..287, + annotation: Some( + Constructor { + location: 286..287, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + Arg { + arg_name: Named { + name: "right", + label: "right", + location: 286..287, + is_validator_param: false, + }, + location: 286..287, + annotation: Some( + Constructor { + location: 286..287, + module: None, + name: "Int", + arguments: [], + }, + ), + tipo: (), + }, + ], + body: BinOp { + location: 286..287, + name: ModInt, + left: Var { + location: 286..287, + name: "left", + }, + right: Var { + location: 286..287, + name: "right", + }, + }, + return_annotation: Some( + Constructor { + location: 286..287, + module: None, + name: "Int", + arguments: [], + }, + ), + }, + }, + CallArg { + label: None, + location: 289..290, + value: Var { + location: 289..290, + name: "b", + }, + }, + ], + fun: Var { + location: 270..282, + name: "compute_with", + }, + location: 270..291, + }, + ], +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/function_invoke.snap b/crates/aiken-lang/src/parser/expr/snapshots/function_invoke.snap new file mode 100644 index 000000000..411a7f4f0 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/function_invoke.snap @@ -0,0 +1,160 @@ +--- +source: crates/aiken-lang/src/parser/expr/mod.rs +description: "Code:\n\nlet x = add_one(3)\n\nlet map_add_x = list.map(_, fn (y) { x + y })\n\nmap_add_x([ 1, 2, 3 ])\n" +--- +Sequence { + location: 0..89, + expressions: [ + Assignment { + location: 0..18, + value: Call { + arguments: [ + CallArg { + label: None, + location: 16..17, + value: Int { + location: 16..17, + value: "3", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + ], + fun: Var { + location: 8..15, + name: "add_one", + }, + location: 8..18, + }, + pattern: Var { + location: 4..5, + name: "x", + }, + kind: Let, + annotation: None, + }, + Assignment { + location: 20..65, + value: Fn { + location: 36..65, + fn_style: Capture, + arguments: [ + Arg { + arg_name: Named { + name: "_capture__0", + label: "_capture__0", + location: 0..0, + is_validator_param: false, + }, + location: 0..0, + annotation: None, + tipo: (), + }, + ], + body: Call { + arguments: [ + CallArg { + label: None, + location: 45..46, + value: Var { + location: 45..46, + name: "_capture__0", + }, + }, + CallArg { + label: None, + location: 48..64, + value: Fn { + location: 48..64, + fn_style: Plain, + arguments: [ + Arg { + arg_name: Named { + name: "y", + label: "y", + location: 52..53, + is_validator_param: false, + }, + location: 52..53, + annotation: None, + tipo: (), + }, + ], + body: BinOp { + location: 57..62, + name: AddInt, + left: Var { + location: 57..58, + name: "x", + }, + right: Var { + location: 61..62, + name: "y", + }, + }, + return_annotation: None, + }, + }, + ], + fun: FieldAccess { + location: 36..44, + label: "map", + container: Var { + location: 36..40, + name: "list", + }, + }, + location: 36..65, + }, + return_annotation: None, + }, + pattern: Var { + location: 24..33, + name: "map_add_x", + }, + kind: Let, + annotation: None, + }, + Call { + arguments: [ + CallArg { + label: None, + location: 77..88, + value: List { + location: 77..88, + elements: [ + Int { + location: 79..80, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, + Int { + location: 82..83, + value: "2", + base: Decimal { + numeric_underscore: false, + }, + }, + Int { + location: 85..86, + value: "3", + base: Decimal { + numeric_underscore: false, + }, + }, + ], + tail: None, + }, + }, + ], + fun: Var { + location: 67..76, + name: "map_add_x", + }, + location: 67..89, + }, + ], +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/if_else_basic.snap b/crates/aiken-lang/src/parser/expr/snapshots/if_else_basic.snap new file mode 100644 index 000000000..32cbd0343 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/if_else_basic.snap @@ -0,0 +1,66 @@ +--- +source: crates/aiken-lang/src/parser/expr/if_else.rs +description: "Code:\n\nif True {\n 1 + 1\n} else if a < 1 {\n 3\n} else {\n 4\n}\n" +--- +If { + location: 0..54, + branches: [ + IfBranch { + condition: Var { + location: 3..7, + name: "True", + }, + body: BinOp { + location: 12..17, + name: AddInt, + left: Int { + location: 12..13, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, + right: Int { + location: 16..17, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + location: 3..19, + }, + IfBranch { + condition: BinOp { + location: 28..33, + name: LtInt, + left: Var { + location: 28..29, + name: "a", + }, + right: Int { + location: 32..33, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + body: Int { + location: 38..39, + value: "3", + base: Decimal { + numeric_underscore: false, + }, + }, + location: 28..41, + }, + ], + final_else: Int { + location: 51..52, + value: "4", + base: Decimal { + numeric_underscore: false, + }, + }, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/int_hex_bytes.snap b/crates/aiken-lang/src/parser/expr/snapshots/int_hex_bytes.snap new file mode 100644 index 000000000..56ab77ece --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/int_hex_bytes.snap @@ -0,0 +1,9 @@ +--- +source: crates/aiken-lang/src/parser/expr/int.rs +description: "Code:\n\n0x01" +--- +Int { + location: 0..4, + value: "1", + base: Hexadecimal, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/int_list.snap b/crates/aiken-lang/src/parser/expr/snapshots/int_list.snap new file mode 100644 index 000000000..a2040224b --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/int_list.snap @@ -0,0 +1,31 @@ +--- +source: crates/aiken-lang/src/parser/expr/list.rs +description: "Code:\n\n[1, 2, 3]" +--- +List { + location: 0..9, + elements: [ + Int { + location: 1..2, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, + Int { + location: 4..5, + value: "2", + base: Decimal { + numeric_underscore: false, + }, + }, + Int { + location: 7..8, + value: "3", + base: Decimal { + numeric_underscore: false, + }, + }, + ], + tail: None, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/int_literal.snap b/crates/aiken-lang/src/parser/expr/snapshots/int_literal.snap new file mode 100644 index 000000000..da7cb54b4 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/int_literal.snap @@ -0,0 +1,11 @@ +--- +source: crates/aiken-lang/src/parser/expr/int.rs +description: "Code:\n\n1" +--- +Int { + location: 0..1, + value: "1", + base: Decimal { + numeric_underscore: false, + }, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/int_negative.snap b/crates/aiken-lang/src/parser/expr/snapshots/int_negative.snap new file mode 100644 index 000000000..fcf40a1d8 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/int_negative.snap @@ -0,0 +1,15 @@ +--- +source: crates/aiken-lang/src/parser/expr/int.rs +description: "Code:\n\n-1" +--- +UnOp { + op: Negate, + location: 0..2, + value: Int { + location: 1..2, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/int_numeric_underscore.snap b/crates/aiken-lang/src/parser/expr/snapshots/int_numeric_underscore.snap new file mode 100644 index 000000000..566e732ec --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/int_numeric_underscore.snap @@ -0,0 +1,61 @@ +--- +source: crates/aiken-lang/src/parser/expr/int.rs +description: "Code:\n\n{\n let i = 1_234_567\n let j = 1_000_000\n let k = -10_000\n}\n" +--- +Sequence { + location: 4..59, + expressions: [ + Assignment { + location: 4..21, + value: Int { + location: 12..21, + value: "1234567", + base: Decimal { + numeric_underscore: true, + }, + }, + pattern: Var { + location: 8..9, + name: "i", + }, + kind: Let, + annotation: None, + }, + Assignment { + location: 24..41, + value: Int { + location: 32..41, + value: "1000000", + base: Decimal { + numeric_underscore: true, + }, + }, + pattern: Var { + location: 28..29, + name: "j", + }, + kind: Let, + annotation: None, + }, + Assignment { + location: 44..59, + value: UnOp { + op: Negate, + location: 52..59, + value: Int { + location: 53..59, + value: "10000", + base: Decimal { + numeric_underscore: true, + }, + }, + }, + pattern: Var { + location: 48..49, + name: "k", + }, + kind: Let, + annotation: None, + }, + ], +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/let_bindings.snap b/crates/aiken-lang/src/parser/expr/snapshots/let_bindings.snap new file mode 100644 index 000000000..71e556c84 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/let_bindings.snap @@ -0,0 +1,37 @@ +--- +source: crates/aiken-lang/src/parser/expr/assignment.rs +description: "Code:\n\nlet thing = [ 1, 2, a ]" +--- +Assignment { + location: 0..23, + value: List { + location: 12..23, + elements: [ + Int { + location: 14..15, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, + Int { + location: 17..18, + value: "2", + base: Decimal { + numeric_underscore: false, + }, + }, + Var { + location: 20..21, + name: "a", + }, + ], + tail: None, + }, + pattern: Var { + location: 4..9, + name: "thing", + }, + kind: Let, + annotation: None, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/list_spread.snap b/crates/aiken-lang/src/parser/expr/snapshots/list_spread.snap new file mode 100644 index 000000000..2009bc420 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/list_spread.snap @@ -0,0 +1,30 @@ +--- +source: crates/aiken-lang/src/parser/expr/list.rs +description: "Code:\n\n[1, 2, ..[]]" +--- +List { + location: 0..12, + elements: [ + Int { + location: 1..2, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, + Int { + location: 4..5, + value: "2", + base: Decimal { + numeric_underscore: false, + }, + }, + ], + tail: Some( + List { + location: 9..11, + elements: [], + tail: None, + }, + ), +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple.snap b/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple.snap new file mode 100644 index 000000000..857616c86 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple.snap @@ -0,0 +1,95 @@ +--- +source: crates/aiken-lang/src/parser/expr/tuple.rs +description: "Code:\n\nlet tuple = (1, 2, 3, 4)\ntuple.1st + tuple.2nd + tuple.3rd + tuple.4th\n" +--- +Sequence { + location: 0..70, + expressions: [ + Assignment { + location: 0..24, + value: Tuple { + location: 12..24, + elems: [ + Int { + location: 13..14, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, + Int { + location: 16..17, + value: "2", + base: Decimal { + numeric_underscore: false, + }, + }, + Int { + location: 19..20, + value: "3", + base: Decimal { + numeric_underscore: false, + }, + }, + Int { + location: 22..23, + value: "4", + base: Decimal { + numeric_underscore: false, + }, + }, + ], + }, + pattern: Var { + location: 4..9, + name: "tuple", + }, + kind: Let, + annotation: None, + }, + BinOp { + location: 25..70, + name: AddInt, + left: BinOp { + location: 25..58, + name: AddInt, + left: BinOp { + location: 25..46, + name: AddInt, + left: TupleIndex { + location: 25..34, + index: 0, + tuple: Var { + location: 25..30, + name: "tuple", + }, + }, + right: TupleIndex { + location: 37..46, + index: 1, + tuple: Var { + location: 37..42, + name: "tuple", + }, + }, + }, + right: TupleIndex { + location: 49..58, + index: 2, + tuple: Var { + location: 49..54, + name: "tuple", + }, + }, + }, + right: TupleIndex { + location: 61..70, + index: 3, + tuple: Var { + location: 61..66, + name: "tuple", + }, + }, + }, + ], +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple2.snap b/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple2.snap new file mode 100644 index 000000000..634d61840 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple2.snap @@ -0,0 +1,54 @@ +--- +source: crates/aiken-lang/src/parser/expr/tuple.rs +description: "Code:\n\nlet a = foo(14)\n(a, 42)\n" +--- +Sequence { + location: 0..23, + expressions: [ + Assignment { + location: 0..15, + value: Call { + arguments: [ + CallArg { + label: None, + location: 12..14, + value: Int { + location: 12..14, + value: "14", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + ], + fun: Var { + location: 8..11, + name: "foo", + }, + location: 8..15, + }, + pattern: Var { + location: 4..5, + name: "a", + }, + kind: Let, + annotation: None, + }, + Tuple { + location: 16..23, + elems: [ + Var { + location: 17..18, + name: "a", + }, + Int { + location: 20..22, + value: "42", + base: Decimal { + numeric_underscore: false, + }, + }, + ], + }, + ], +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/pipeline.snap b/crates/aiken-lang/src/parser/expr/snapshots/pipeline.snap new file mode 100644 index 000000000..f98b6a8ce --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/pipeline.snap @@ -0,0 +1,32 @@ +--- +source: crates/aiken-lang/src/parser/expr/mod.rs +description: "Code:\n\na + 2\n|> add_one\n|> add_one\n" +--- +PipeLine { + expressions: [ + BinOp { + location: 0..5, + name: AddInt, + left: Var { + location: 0..1, + name: "a", + }, + right: Int { + location: 4..5, + value: "2", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + Var { + location: 9..16, + name: "add_one", + }, + Var { + location: 20..27, + name: "add_one", + }, + ], + one_liner: false, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/plus_binop.snap b/crates/aiken-lang/src/parser/expr/snapshots/plus_binop.snap new file mode 100644 index 000000000..397597ec9 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/plus_binop.snap @@ -0,0 +1,19 @@ +--- +source: crates/aiken-lang/src/parser/expr/mod.rs +description: "Code:\n\na + 1" +--- +BinOp { + location: 0..5, + name: AddInt, + left: Var { + location: 0..1, + name: "a", + }, + right: Int { + location: 4..5, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/record_create_labeled.snap b/crates/aiken-lang/src/parser/expr/snapshots/record_create_labeled.snap new file mode 100644 index 000000000..13c2c4337 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/record_create_labeled.snap @@ -0,0 +1,53 @@ +--- +source: crates/aiken-lang/src/parser/expr/record.rs +description: "Code:\n\nUser { name: \"Aiken\", age, thing: 2 }" +--- +Call { + arguments: [ + CallArg { + label: Some( + "name", + ), + location: 7..20, + value: ByteArray { + location: 13..20, + bytes: [ + 65, + 105, + 107, + 101, + 110, + ], + preferred_format: Utf8String, + }, + }, + CallArg { + label: Some( + "age", + ), + location: 22..25, + value: Var { + location: 22..25, + name: "age", + }, + }, + CallArg { + label: Some( + "thing", + ), + location: 27..35, + value: Int { + location: 34..35, + value: "2", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + ], + fun: Var { + location: 0..4, + name: "User", + }, + location: 0..37, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/record_create_labeled_with_field_access.snap b/crates/aiken-lang/src/parser/expr/snapshots/record_create_labeled_with_field_access.snap new file mode 100644 index 000000000..f1f46e9bf --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/record_create_labeled_with_field_access.snap @@ -0,0 +1,57 @@ +--- +source: crates/aiken-lang/src/parser/expr/record.rs +description: "Code:\n\nsome_module.User { name: \"Aiken\", age, thing: 2 }" +--- +Call { + arguments: [ + CallArg { + label: Some( + "name", + ), + location: 19..32, + value: ByteArray { + location: 25..32, + bytes: [ + 65, + 105, + 107, + 101, + 110, + ], + preferred_format: Utf8String, + }, + }, + CallArg { + label: Some( + "age", + ), + location: 34..37, + value: Var { + location: 34..37, + name: "age", + }, + }, + CallArg { + label: Some( + "thing", + ), + location: 39..47, + value: Int { + location: 46..47, + value: "2", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + ], + fun: FieldAccess { + location: 0..16, + label: "User", + container: Var { + location: 0..11, + name: "some_module", + }, + }, + location: 0..49, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/record_create_unlabeled.snap b/crates/aiken-lang/src/parser/expr/snapshots/record_create_unlabeled.snap new file mode 100644 index 000000000..2f9f629c5 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/record_create_unlabeled.snap @@ -0,0 +1,36 @@ +--- +source: crates/aiken-lang/src/parser/expr/record.rs +description: "Code:\n\nsome_module.Thing(1, a)" +--- +Call { + arguments: [ + CallArg { + label: None, + location: 18..19, + value: Int { + location: 18..19, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + CallArg { + label: None, + location: 21..22, + value: Var { + location: 21..22, + name: "a", + }, + }, + ], + fun: FieldAccess { + location: 0..17, + label: "Thing", + container: Var { + location: 0..11, + name: "some_module", + }, + }, + location: 0..23, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/record_update_basic.snap b/crates/aiken-lang/src/parser/expr/snapshots/record_update_basic.snap new file mode 100644 index 000000000..69e8ebb80 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/record_update_basic.snap @@ -0,0 +1,43 @@ +--- +source: crates/aiken-lang/src/parser/expr/record_update.rs +description: "Code:\n\nUser { ..user, name: \"Aiken\", age }" +--- +RecordUpdate { + location: 0..35, + constructor: Var { + location: 0..4, + name: "User", + }, + spread: RecordUpdateSpread { + base: Var { + location: 9..13, + name: "user", + }, + location: 7..13, + }, + arguments: [ + UntypedRecordUpdateArg { + label: "name", + location: 15..28, + value: ByteArray { + location: 21..28, + bytes: [ + 65, + 105, + 107, + 101, + 110, + ], + preferred_format: Utf8String, + }, + }, + UntypedRecordUpdateArg { + label: "age", + location: 30..33, + value: Var { + location: 30..33, + name: "age", + }, + }, + ], +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/string_basic.snap b/crates/aiken-lang/src/parser/expr/snapshots/string_basic.snap new file mode 100644 index 000000000..1cdd1ba91 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/string_basic.snap @@ -0,0 +1,8 @@ +--- +source: crates/aiken-lang/src/parser/expr/string.rs +description: "Code:\n\n@\"aiken\"" +--- +String { + location: 0..8, + value: "aiken", +} diff --git a/crates/aiken-lang/src/parser/expr/string.rs b/crates/aiken-lang/src/parser/expr/string.rs new file mode 100644 index 000000000..f1361d646 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/string.rs @@ -0,0 +1,43 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + expr::UntypedExpr, + parser::{error::ParseError, literal::string::parser as string, token::Token}, +}; + +pub fn parser() -> impl Parser { + string().map_with_span(|value, span| UntypedExpr::String { + location: span, + value, + }) +} + +/// Interpret bytearray string literals written as utf-8 strings, as strings. +/// +/// This is mostly convenient so that todo & error works with either @"..." or plain "...". +/// In this particular context, there's actually no ambiguity about the right-hand-side, so +/// we can provide this syntactic sugar. +pub fn flexible(expr: UntypedExpr) -> UntypedExpr { + match expr { + UntypedExpr::ByteArray { + preferred_format: ast::ByteArrayFormatPreference::Utf8String, + bytes, + location, + } => UntypedExpr::String { + location, + value: String::from_utf8(bytes).unwrap(), + }, + _ => expr, + } +} + +#[cfg(test)] +mod tests { + use crate::assert_expr; + + #[test] + fn string_basic() { + assert_expr!("@\"aiken\""); + } +} diff --git a/crates/aiken-lang/src/parser/expr/tuple.rs b/crates/aiken-lang/src/parser/expr/tuple.rs new file mode 100644 index 000000000..3b02f9479 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/tuple.rs @@ -0,0 +1,48 @@ +use chumsky::prelude::*; + +use crate::{ + expr::UntypedExpr, + parser::{error::ParseError, token::Token}, +}; + +pub fn parser( + r: Recursive<'_, Token, UntypedExpr, ParseError>, +) -> impl Parser + '_ { + r.clone() + .separated_by(just(Token::Comma)) + .at_least(2) + .allow_trailing() + .delimited_by( + choice((just(Token::LeftParen), just(Token::NewLineLeftParen))), + just(Token::RightParen), + ) + .map_with_span(|elems, span| UntypedExpr::Tuple { + location: span, + elems, + }) +} + +#[cfg(test)] +mod tests { + use crate::assert_expr; + + #[test] + fn parse_tuple() { + assert_expr!( + r#" + let tuple = (1, 2, 3, 4) + tuple.1st + tuple.2nd + tuple.3rd + tuple.4th + "# + ); + } + + #[test] + fn parse_tuple2() { + assert_expr!( + r#" + let a = foo(14) + (a, 42) + "# + ); + } +} diff --git a/crates/aiken-lang/src/parser/expr/var.rs b/crates/aiken-lang/src/parser/expr/var.rs new file mode 100644 index 000000000..b80c1da8a --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/var.rs @@ -0,0 +1,17 @@ +use chumsky::prelude::*; + +use crate::{ + expr::UntypedExpr, + parser::{error::ParseError, token::Token}, +}; + +pub fn parser() -> impl Parser { + select! { + Token::Name { name } => name, + Token::UpName { name } => name, + } + .map_with_span(|name, span| UntypedExpr::Var { + location: span, + name, + }) +} 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 000000000..474a470f1 --- /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 000000000..c7b445dd8 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/when/guard.rs @@ -0,0 +1,122 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + parser::{definition, error::ParseError, token::Token}, +}; + +pub fn parser() -> impl Parser { + recursive(|expression| { + 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 = definition::constant::value().map(ast::ClauseGuard::Constant); + + let block_parser = expression + .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 000000000..53b476278 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/when/mod.rs @@ -0,0 +1,52 @@ +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, + }) +} + +#[cfg(test)] +mod tests { + use crate::assert_expr; + + #[test] + fn when_basic() { + assert_expr!( + r#" + when a is { + 2 if x > 1 -> 3 + 1 | 4 | 5 -> { + let amazing = 5 + amazing + } + 3 -> 9 + _ -> 4 + } + "# + ); + } +} diff --git a/crates/aiken-lang/src/parser/expr/when/snapshots/when_basic.snap b/crates/aiken-lang/src/parser/expr/when/snapshots/when_basic.snap new file mode 100644 index 000000000..7d5056b6f --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/when/snapshots/when_basic.snap @@ -0,0 +1,140 @@ +--- +source: crates/aiken-lang/src/parser/expr/when/mod.rs +description: "Code:\n\nwhen a is {\n 2 if x > 1 -> 3\n 1 | 4 | 5 -> {\n let amazing = 5\n amazing\n }\n 3 -> 9\n _ -> 4\n}\n" +--- +When { + location: 0..102, + subject: Var { + location: 5..6, + name: "a", + }, + clauses: [ + UntypedClause { + location: 14..29, + patterns: [ + Int { + location: 14..15, + value: "2", + base: Decimal { + numeric_underscore: false, + }, + }, + ], + guard: Some( + GtInt { + location: 19..24, + left: Var { + location: 19..20, + tipo: (), + name: "x", + }, + right: Constant( + Int { + location: 23..24, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, + ), + }, + ), + then: Int { + location: 28..29, + value: "3", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + UntypedClause { + location: 32..82, + patterns: [ + Int { + location: 32..33, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, + Int { + location: 36..37, + value: "4", + base: Decimal { + numeric_underscore: false, + }, + }, + Int { + location: 40..41, + value: "5", + base: Decimal { + numeric_underscore: false, + }, + }, + ], + guard: None, + then: Sequence { + location: 51..78, + expressions: [ + Assignment { + location: 51..66, + value: Int { + location: 65..66, + value: "5", + base: Decimal { + numeric_underscore: false, + }, + }, + pattern: Var { + location: 55..62, + name: "amazing", + }, + kind: Let, + annotation: None, + }, + Var { + location: 71..78, + name: "amazing", + }, + ], + }, + }, + UntypedClause { + location: 85..91, + patterns: [ + Int { + location: 85..86, + value: "3", + base: Decimal { + numeric_underscore: false, + }, + }, + ], + guard: None, + then: Int { + location: 90..91, + value: "9", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + UntypedClause { + location: 94..100, + patterns: [ + Discard { + name: "_", + location: 94..95, + }, + ], + guard: None, + then: Int { + location: 99..100, + value: "4", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + ], +} diff --git a/crates/aiken-lang/src/parser/lexer.rs b/crates/aiken-lang/src/parser/lexer.rs index 110d0335f..f81e82076 100644 --- a/crates/aiken-lang/src/parser/lexer.rs +++ b/crates/aiken-lang/src/parser/lexer.rs @@ -1,11 +1,83 @@ +use chumsky::prelude::*; +use num_bigint::BigInt; +use ordinal::Ordinal; + use super::{ error::ParseError, + extra::ModuleExtra, token::{Base, Token}, }; use crate::ast::Span; -use chumsky::prelude::*; -use num_bigint::BigInt; -use ordinal::Ordinal; + +pub struct LexInfo { + pub tokens: Vec<(Token, Span)>, + pub extra: ModuleExtra, +} + +pub fn run(src: &str) -> Result> { + let len = src.as_bytes().len(); + + let tokens = lexer().parse(chumsky::Stream::from_iter( + Span::create(len, 1), + src.chars().scan(0, |i, c| { + let start = *i; + let offset = c.len_utf8(); + *i = start + offset; + Some((c, Span::create(start, offset))) + }), + ))?; + + let mut extra = ModuleExtra::new(); + + let mut previous_is_newline = false; + + let tokens = tokens + .into_iter() + .filter_map(|(token, ref span)| { + let current_is_newline = token == Token::NewLine || token == Token::EmptyLine; + let result = match token { + Token::ModuleComment => { + extra.module_comments.push(*span); + None + } + Token::DocComment => { + extra.doc_comments.push(*span); + None + } + Token::Comment => { + extra.comments.push(*span); + None + } + Token::EmptyLine => { + extra.empty_lines.push(span.start); + None + } + Token::LeftParen => { + if previous_is_newline { + Some((Token::NewLineLeftParen, *span)) + } else { + Some((Token::LeftParen, *span)) + } + } + Token::Pipe => { + if previous_is_newline { + Some((Token::NewLinePipe, *span)) + } else { + Some((Token::Pipe, *span)) + } + } + Token::NewLine => None, + _ => Some((token, *span)), + }; + + previous_is_newline = current_is_newline; + + result + }) + .collect::>(); + + Ok(LexInfo { tokens, extra }) +} pub fn lexer() -> impl Parser, Error = ParseError> { let base10 = text::int(10).map(|value| Token::Int { diff --git a/crates/aiken-lang/src/parser/literal/bytearray.rs b/crates/aiken-lang/src/parser/literal/bytearray.rs new file mode 100644 index 000000000..5516b9819 --- /dev/null +++ b/crates/aiken-lang/src/parser/literal/bytearray.rs @@ -0,0 +1,87 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + parser::{ + error::{self, ParseError}, + token::{Base, Token}, + }, +}; + +pub fn parser( + into: impl Fn(Vec, ast::ByteArrayFormatPreference, ast::Span) -> A, +) -> impl Parser { + choice((array_of_bytes(), hex_string(), utf8_string())) + .map_with_span(move |(preferred_format, bytes), span| into(bytes, preferred_format, span)) +} + +pub fn array_of_bytes( +) -> impl Parser), Error = ParseError> { + just(Token::Hash) + .ignore_then( + select! {Token::Int {value, base, ..} => (value, base)} + .validate(|(value, base), span, emit| { + let byte: u8 = match value.parse() { + Ok(b) => b, + Err(_) => { + emit(ParseError::expected_input_found( + span, + None, + Some(error::Pattern::Byte), + )); + 0 + } + }; + (byte, base) + }) + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftSquare), just(Token::RightSquare)), + ) + .validate(|bytes, span, emit| { + let base = bytes.iter().fold(Ok(None), |acc, (_, base)| match acc { + Ok(None) => Ok(Some(base)), + Ok(Some(previous_base)) if previous_base == base => Ok(Some(base)), + _ => Err(()), + }); + + let base = match base { + Err(()) => { + emit(ParseError::hybrid_notation_in_bytearray(span)); + Base::Decimal { + numeric_underscore: false, + } + } + Ok(None) => Base::Decimal { + numeric_underscore: false, + }, + Ok(Some(base)) => *base, + }; + + (bytes.into_iter().map(|(b, _)| b).collect::>(), base) + }) + .map(|(bytes, base)| (ast::ByteArrayFormatPreference::ArrayOfBytes(base), bytes)) +} + +pub fn hex_string( +) -> impl Parser), Error = ParseError> { + just(Token::Hash) + .ignore_then( + select! {Token::ByteString {value} => value}.validate(|value, span, emit| { + match hex::decode(value) { + Ok(bytes) => bytes, + Err(_) => { + emit(ParseError::malformed_base16_string_literal(span)); + vec![] + } + } + }), + ) + .map(|token| (ast::ByteArrayFormatPreference::HexadecimalString, token)) +} + +pub fn utf8_string( +) -> impl Parser), Error = ParseError> { + select! {Token::ByteString {value} => value.into_bytes() } + .map(|token| (ast::ByteArrayFormatPreference::Utf8String, token)) +} diff --git a/crates/aiken-lang/src/parser/literal/int.rs b/crates/aiken-lang/src/parser/literal/int.rs new file mode 100644 index 000000000..05e6cdb1c --- /dev/null +++ b/crates/aiken-lang/src/parser/literal/int.rs @@ -0,0 +1,10 @@ +use chumsky::prelude::*; + +use crate::parser::{ + error::ParseError, + token::{Base, Token}, +}; + +pub fn parser() -> impl Parser { + select! { Token::Int {value, base} => (value, base)} +} diff --git a/crates/aiken-lang/src/parser/literal/mod.rs b/crates/aiken-lang/src/parser/literal/mod.rs new file mode 100644 index 000000000..6a67a32e4 --- /dev/null +++ b/crates/aiken-lang/src/parser/literal/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod bytearray; +pub(crate) mod int; +pub(crate) mod string; diff --git a/crates/aiken-lang/src/parser/literal/string.rs b/crates/aiken-lang/src/parser/literal/string.rs new file mode 100644 index 000000000..d1d88cf85 --- /dev/null +++ b/crates/aiken-lang/src/parser/literal/string.rs @@ -0,0 +1,7 @@ +use chumsky::prelude::*; + +use crate::parser::{error::ParseError, token::Token}; + +pub fn parser() -> impl Parser { + select! {Token::String {value} => value} +} diff --git a/crates/aiken-lang/src/parser/pattern/mod.rs b/crates/aiken-lang/src/parser/pattern/mod.rs new file mode 100644 index 000000000..17e72d338 --- /dev/null +++ b/crates/aiken-lang/src/parser/pattern/mod.rs @@ -0,0 +1,199 @@ +use chumsky::prelude::*; + +use crate::ast; + +use super::{ + error::{self, ParseError}, + token::Token, +}; + +pub fn parser() -> impl Parser { + recursive(|expression| { + let record_constructor_pattern_arg_parser = choice(( + select! {Token::Name {name} => name} + .then_ignore(just(Token::Colon)) + .then(expression.clone()) + .map_with_span(|(name, pattern), span| ast::CallArg { + location: span, + label: Some(name), + value: pattern, + }), + select! {Token::Name{name} => name}.map_with_span(|name, span| ast::CallArg { + location: span, + value: ast::UntypedPattern::Var { + name: name.clone(), + location: span, + }, + label: Some(name), + }), + )) + .separated_by(just(Token::Comma)) + .allow_trailing() + .then( + just(Token::DotDot) + .then_ignore(just(Token::Comma).or_not()) + .ignored() + .or_not(), + ) + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)); + + let tuple_constructor_pattern_arg_parser = expression + .clone() + .map(|pattern| ast::CallArg { + location: pattern.location(), + value: pattern, + label: None, + }) + .separated_by(just(Token::Comma)) + .allow_trailing() + .then( + just(Token::DotDot) + .then_ignore(just(Token::Comma).or_not()) + .ignored() + .or_not(), + ) + .delimited_by(just(Token::LeftParen), just(Token::RightParen)); + + let constructor_pattern_args_parser = choice(( + record_constructor_pattern_arg_parser.map(|a| (a, true)), + tuple_constructor_pattern_arg_parser.map(|a| (a, false)), + )) + .or_not() + .map(|opt_args| { + opt_args + .map(|((a, b), c)| (a, b.is_some(), c)) + .unwrap_or_else(|| (vec![], false, false)) + }); + + let constructor_pattern_parser = + select! {Token::UpName { name } => name}.then(constructor_pattern_args_parser); + + choice(( + select! { Token::Name {name} => name } + .then( + just(Token::Dot) + .ignore_then(constructor_pattern_parser.clone()) + .or_not(), + ) + .map_with_span(|(name, opt_pattern), span| { + if let Some((c_name, (arguments, with_spread, is_record))) = opt_pattern { + ast::UntypedPattern::Constructor { + is_record, + location: span, + name: c_name, + arguments, + module: Some(name), + constructor: (), + with_spread, + tipo: (), + } + } else { + ast::UntypedPattern::Var { + location: span, + name, + } + } + }), + constructor_pattern_parser.map_with_span( + |(name, (arguments, with_spread, is_record)), span| { + ast::UntypedPattern::Constructor { + is_record, + location: span, + name, + arguments, + module: None, + constructor: (), + with_spread, + tipo: (), + } + }, + ), + select! {Token::DiscardName {name} => name}.map_with_span(|name, span| { + ast::UntypedPattern::Discard { + name, + location: span, + } + }), + select! {Token::Int {value, base} => (value, base)}.map_with_span( + |(value, base), span| ast::UntypedPattern::Int { + location: span, + value, + base, + }, + ), + expression + .clone() + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by( + choice((just(Token::LeftParen), just(Token::NewLineLeftParen))), + just(Token::RightParen), + ) + .map_with_span(|elems, span| ast::UntypedPattern::Tuple { + location: span, + elems, + }), + just(Token::LeftSquare) + .ignore_then(expression.clone().separated_by(just(Token::Comma))) + .then(choice(( + just(Token::Comma).ignore_then( + just(Token::DotDot) + .ignore_then(expression.clone().or_not()) + .or_not(), + ), + just(Token::Comma).ignored().or_not().map(|_| None), + ))) + .then_ignore(just(Token::RightSquare)) + .validate(|(elements, tail), span: ast::Span, emit| { + let tail = match tail { + // There is a tail and it has a Pattern::Var or Pattern::Discard + Some(Some( + pat @ (ast::UntypedPattern::Var { .. } + | ast::UntypedPattern::Discard { .. }), + )) => Some(pat), + Some(Some(pat)) => { + emit(ParseError::expected_input_found( + pat.location(), + None, + Some(error::Pattern::Match), + )); + + Some(pat) + } + // There is a tail but it has no content, implicit discard + Some(None) => Some(ast::UntypedPattern::Discard { + location: ast::Span { + start: span.end - 1, + end: span.end, + }, + name: "_".to_string(), + }), + // No tail specified + None => None, + }; + + ast::UntypedPattern::List { + location: span, + elements, + tail: tail.map(Box::new), + } + }), + )) + .then( + just(Token::As) + .ignore_then(select! { Token::Name {name} => name}) + .or_not(), + ) + .map_with_span(|(pattern, opt_as), span| { + if let Some(name) = opt_as { + ast::UntypedPattern::Assign { + name, + location: span, + pattern: Box::new(pattern), + } + } else { + pattern + } + }) + }) +} diff --git a/crates/aiken-lang/src/parser/snapshots/type_annotation_with_module_prefix.snap b/crates/aiken-lang/src/parser/snapshots/type_annotation_with_module_prefix.snap new file mode 100644 index 000000000..44243da96 --- /dev/null +++ b/crates/aiken-lang/src/parser/snapshots/type_annotation_with_module_prefix.snap @@ -0,0 +1,19 @@ +--- +source: crates/aiken-lang/src/parser/annotation.rs +description: "Code:\n\naiken.Option" +--- +Constructor { + location: 0..17, + module: Some( + "aiken", + ), + name: "Option", + arguments: [ + Constructor { + location: 13..16, + module: None, + name: "Int", + arguments: [], + }, + ], +} diff --git a/crates/aiken-lang/src/parser/utils.rs b/crates/aiken-lang/src/parser/utils.rs new file mode 100644 index 000000000..f45a639e1 --- /dev/null +++ b/crates/aiken-lang/src/parser/utils.rs @@ -0,0 +1,99 @@ +use chumsky::prelude::*; + +use super::{error::ParseError, token::Token}; + +pub fn optional_flag(token: Token) -> impl Parser { + just(token).ignored().or_not().map(|v| v.is_some()) +} + +pub fn type_name_with_args() -> impl Parser>), Error = ParseError> +{ + just(Token::Type).ignore_then( + select! {Token::UpName { name } => name}.then( + select! {Token::Name { name } => name} + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::Less), just(Token::Greater)) + .or_not(), + ), + ) +} + +#[macro_export] +macro_rules! assert_expr { + ($code:expr) => { + use chumsky::Parser; + + let $crate::parser::lexer::LexInfo { tokens, .. } = $crate::parser::lexer::run(indoc::indoc! { $code }).unwrap(); + + let stream = chumsky::Stream::from_iter($crate::ast::Span::create(tokens.len(), 1), tokens.into_iter()); + + let result = $crate::parser::expr::sequence().parse(stream).unwrap(); + + insta::with_settings!({ + description => concat!("Code:\n\n", indoc::indoc! { $code }), + prepend_module_to_snapshot => false, + omit_expression => true + }, { + insta::assert_debug_snapshot!(result); + }); + }; +} + +#[macro_export] +macro_rules! assert_annotation { + ($code:expr) => { + use chumsky::Parser; + + let $crate::parser::lexer::LexInfo { tokens, .. } = $crate::parser::lexer::run(indoc::indoc! { $code }).unwrap(); + + let stream = chumsky::Stream::from_iter($crate::ast::Span::create(tokens.len(), 1), tokens.into_iter()); + + let result = $crate::parser::annotation().parse(stream).unwrap(); + + insta::with_settings!({ + description => concat!("Code:\n\n", indoc::indoc! { $code }), + prepend_module_to_snapshot => false, + omit_expression => true + }, { + insta::assert_debug_snapshot!(result); + }); + }; +} + +#[macro_export] +macro_rules! assert_module { + ($code:expr) => { + let (module, _) = + $crate::parser::module(indoc::indoc!{ $code }, $crate::ast::ModuleKind::Validator).expect("Failed to parse code"); + + insta::with_settings!({ + description => concat!("Code:\n\n", indoc::indoc! { $code }), + prepend_module_to_snapshot => false, + omit_expression => true + }, { + insta::assert_debug_snapshot!(module); + }); + }; +} + +#[macro_export] +macro_rules! assert_definition { + ($code:expr) => { + use chumsky::Parser; + + let $crate::parser::lexer::LexInfo { tokens, .. } = $crate::parser::lexer::run(indoc::indoc! { $code }).unwrap(); + + let stream = chumsky::Stream::from_iter($crate::ast::Span::create(tokens.len(), 1), tokens.into_iter()); + + let result = $crate::parser::definition().parse(stream).unwrap(); + + insta::with_settings!({ + description => concat!("Code:\n\n", indoc::indoc! { $code }), + prepend_module_to_snapshot => false, + omit_expression => true + }, { + insta::assert_debug_snapshot!(result); + }); + }; +} diff --git a/crates/aiken-lang/src/snapshots/can_handle_comments_at_end_of_file.snap b/crates/aiken-lang/src/snapshots/can_handle_comments_at_end_of_file.snap new file mode 100644 index 000000000..c4b7b173b --- /dev/null +++ b/crates/aiken-lang/src/snapshots/can_handle_comments_at_end_of_file.snap @@ -0,0 +1,23 @@ +--- +source: crates/aiken-lang/src/parser.rs +description: "Code:\n\nuse aiken\n\n// some comment\n// more comments" +--- +Module { + name: "", + docs: [], + type_info: (), + definitions: [ + Use( + Use { + as_name: None, + location: 0..9, + module: [ + "aiken", + ], + package: (), + unqualified: [], + }, + ), + ], + kind: Validator, +} diff --git a/crates/aiken-lang/src/snapshots/function_ambiguous_sequence.snap b/crates/aiken-lang/src/snapshots/function_ambiguous_sequence.snap new file mode 100644 index 000000000..0f6292b05 --- /dev/null +++ b/crates/aiken-lang/src/snapshots/function_ambiguous_sequence.snap @@ -0,0 +1,201 @@ +--- +source: crates/aiken-lang/src/parser.rs +description: "Code:\n\nfn foo_1() {\n let a = bar\n (40)\n}\n\nfn foo_2() {\n let a = bar\n {40}\n}\n\nfn foo_3() {\n let a = (40+2)\n}\n\nfn foo_4() {\n let a = bar(42)\n (a + 14) * 42\n}\n" +--- +Module { + name: "", + docs: [], + type_info: (), + definitions: [ + Fn( + Function { + arguments: [], + body: Sequence { + location: 15..32, + expressions: [ + Assignment { + location: 15..26, + value: Var { + location: 23..26, + name: "bar", + }, + pattern: Var { + location: 19..20, + name: "a", + }, + kind: Let, + annotation: None, + }, + Int { + location: 30..32, + value: "40", + base: Decimal { + numeric_underscore: false, + }, + }, + ], + }, + doc: None, + location: 0..10, + name: "foo_1", + public: false, + return_annotation: None, + return_type: (), + end_position: 34, + can_error: true, + }, + ), + Fn( + Function { + arguments: [], + body: Sequence { + location: 52..69, + expressions: [ + Assignment { + location: 52..63, + value: Var { + location: 60..63, + name: "bar", + }, + pattern: Var { + location: 56..57, + name: "a", + }, + kind: Let, + annotation: None, + }, + Int { + location: 67..69, + value: "40", + base: Decimal { + numeric_underscore: false, + }, + }, + ], + }, + doc: None, + location: 37..47, + name: "foo_2", + public: false, + return_annotation: None, + return_type: (), + end_position: 71, + can_error: true, + }, + ), + Fn( + Function { + arguments: [], + body: Assignment { + location: 89..103, + value: BinOp { + location: 98..102, + name: AddInt, + left: Int { + location: 98..100, + value: "40", + base: Decimal { + numeric_underscore: false, + }, + }, + right: Int { + location: 101..102, + value: "2", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + pattern: Var { + location: 93..94, + name: "a", + }, + kind: Let, + annotation: None, + }, + doc: None, + location: 74..84, + name: "foo_3", + public: false, + return_annotation: None, + return_type: (), + end_position: 104, + can_error: true, + }, + ), + Fn( + Function { + arguments: [], + body: Sequence { + location: 122..153, + expressions: [ + Assignment { + location: 122..137, + value: Call { + arguments: [ + CallArg { + label: None, + location: 134..136, + value: Int { + location: 134..136, + value: "42", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + ], + fun: Var { + location: 130..133, + name: "bar", + }, + location: 130..137, + }, + pattern: Var { + location: 126..127, + name: "a", + }, + kind: Let, + annotation: None, + }, + BinOp { + location: 141..153, + name: MultInt, + left: BinOp { + location: 141..147, + name: AddInt, + left: Var { + location: 141..142, + name: "a", + }, + right: Int { + location: 145..147, + value: "14", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + right: Int { + location: 151..153, + value: "42", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + ], + }, + doc: None, + location: 107..117, + name: "foo_4", + public: false, + return_annotation: None, + return_type: (), + end_position: 154, + can_error: true, + }, + ), + ], + kind: Validator, +} diff --git a/crates/aiken-lang/src/snapshots/parse_unicode_offset_1.snap b/crates/aiken-lang/src/snapshots/parse_unicode_offset_1.snap new file mode 100644 index 000000000..b7e862150 --- /dev/null +++ b/crates/aiken-lang/src/snapshots/parse_unicode_offset_1.snap @@ -0,0 +1,52 @@ +--- +source: crates/aiken-lang/src/parser.rs +description: "Code:\n\nfn foo() {\n let x = \"★\"\n x\n}\n" +--- +Module { + name: "", + docs: [], + type_info: (), + definitions: [ + Fn( + Function { + arguments: [], + body: Sequence { + location: 13..30, + expressions: [ + Assignment { + location: 13..26, + value: ByteArray { + location: 21..26, + bytes: [ + 226, + 152, + 133, + ], + preferred_format: Utf8String, + }, + pattern: Var { + location: 17..18, + name: "x", + }, + kind: Let, + annotation: None, + }, + Var { + location: 29..30, + name: "x", + }, + ], + }, + doc: None, + location: 0..8, + name: "foo", + public: false, + return_annotation: None, + return_type: (), + end_position: 31, + can_error: true, + }, + ), + ], + kind: Validator, +} diff --git a/crates/aiken-lang/src/snapshots/parse_unicode_offset_2.snap b/crates/aiken-lang/src/snapshots/parse_unicode_offset_2.snap new file mode 100644 index 000000000..b8bb738fd --- /dev/null +++ b/crates/aiken-lang/src/snapshots/parse_unicode_offset_2.snap @@ -0,0 +1,50 @@ +--- +source: crates/aiken-lang/src/parser.rs +description: "Code:\n\nfn foo() {\n let x = \"*\"\n x\n}\n" +--- +Module { + name: "", + docs: [], + type_info: (), + definitions: [ + Fn( + Function { + arguments: [], + body: Sequence { + location: 13..28, + expressions: [ + Assignment { + location: 13..24, + value: ByteArray { + location: 21..24, + bytes: [ + 42, + ], + preferred_format: Utf8String, + }, + pattern: Var { + location: 17..18, + name: "x", + }, + kind: Let, + annotation: None, + }, + Var { + location: 27..28, + name: "x", + }, + ], + }, + doc: None, + location: 0..8, + name: "foo", + public: false, + return_annotation: None, + return_type: (), + end_position: 29, + can_error: true, + }, + ), + ], + kind: Validator, +} diff --git a/crates/aiken-lang/src/snapshots/windows_newline.snap b/crates/aiken-lang/src/snapshots/windows_newline.snap new file mode 100644 index 000000000..70e7d0e62 --- /dev/null +++ b/crates/aiken-lang/src/snapshots/windows_newline.snap @@ -0,0 +1,24 @@ +--- +source: crates/aiken-lang/src/parser.rs +description: "Code:\n\nuse aiken/list\r\n" +--- +Module { + name: "", + docs: [], + type_info: (), + definitions: [ + Use( + Use { + as_name: None, + location: 0..14, + module: [ + "aiken", + "list", + ], + package: (), + unqualified: [], + }, + ), + ], + kind: Validator, +} diff --git a/crates/aiken-lang/src/tests/mod.rs b/crates/aiken-lang/src/tests/mod.rs index 0b33bec12..18af907b8 100644 --- a/crates/aiken-lang/src/tests/mod.rs +++ b/crates/aiken-lang/src/tests/mod.rs @@ -1,4 +1,3 @@ mod check; mod format; mod lexer; -mod parser; diff --git a/crates/aiken-lang/src/tests/parser.rs b/crates/aiken-lang/src/tests/parser.rs deleted file mode 100644 index 902bec422..000000000 --- a/crates/aiken-lang/src/tests/parser.rs +++ /dev/null @@ -1,4898 +0,0 @@ -use crate::{ - ast::{self, Constant, DataType, Function, ModuleConstant, Span, TypeAlias, Use}, - expr, - parser::{self, token::Base}, -}; -use chumsky::prelude::*; -use indoc::indoc; -use pretty_assertions::assert_eq; -use vec1::vec1; - -fn assert_definitions(code: &str, definitions: Vec) { - let (module, _extra) = parser::module(code, ast::ModuleKind::Validator).unwrap(); - - assert_eq!( - ast::UntypedModule { - docs: vec![], - kind: ast::ModuleKind::Validator, - name: "".to_string(), - type_info: (), - definitions, - }, - module - ) -} - -#[test] -fn windows_newline() { - let code = "use aiken/list\r\n"; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Use(Use { - location: Span::new((), 0..14), - module: vec!["aiken".to_string(), "list".to_string()], - as_name: None, - unqualified: vec![], - package: (), - })], - ) -} - -#[test] -fn can_handle_comments_at_end_of_file() { - let code = indoc! {r#" - use aiken - - // some comment - // more comments"#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Use(Use { - location: Span::new((), 0..9), - module: vec!["aiken".to_string()], - as_name: None, - unqualified: vec![], - package: (), - })], - ) -} - -#[test] -fn type_annotation_with_module_prefix() { - let code = indoc! {r#" - use aiken - - pub fn go() -> aiken.Option { - False - } - "#}; - - assert_definitions( - code, - vec![ - ast::UntypedDefinition::Use(ast::Use { - as_name: None, - location: Span::new((), 0..9), - module: vec!["aiken".to_string()], - package: (), - unqualified: vec![], - }), - ast::UntypedDefinition::Fn(ast::Function { - arguments: vec![], - body: expr::UntypedExpr::Var { - location: Span::new((), 48..53), - name: "False".to_string(), - }, - doc: None, - location: Span::new((), 11..43), - name: "go".to_string(), - public: true, - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 26..43), - module: Some("aiken".to_string()), - name: "Option".to_string(), - arguments: vec![ast::Annotation::Constructor { - location: Span::new((), 39..42), - module: None, - name: "Int".to_string(), - arguments: vec![], - }], - }), - return_type: (), - end_position: 54, - can_error: true, - }), - ], - ) -} - -#[test] -fn test_fail() { - let code = indoc! {r#" - !test invalid_inputs() { - expect True = False - - False - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Test(ast::Function { - arguments: vec![], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 27..55), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 27..46), - value: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 41..46), - name: "False".to_string(), - }), - pattern: ast::UntypedPattern::Constructor { - is_record: false, - location: Span::new((), 34..38), - name: "True".to_string(), - arguments: vec![], - module: None, - constructor: (), - with_spread: false, - tipo: (), - }, - kind: ast::AssignmentKind::Expect, - annotation: None, - }, - expr::UntypedExpr::Var { - location: Span::new((), 50..55), - name: "False".to_string(), - }, - ], - }, - doc: None, - location: Span::new((), 0..22), - name: "invalid_inputs".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 56, - can_error: true, - })], - ); -} - -#[test] -fn validator() { - let code = indoc! {r#" - validator { - fn foo(datum, rdmr, ctx) { - True - } - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Validator(ast::Validator { - doc: None, - end_position: 54, - fun: Function { - can_error: true, - arguments: vec![ - ast::Arg { - arg_name: ast::ArgName::Named { - name: "datum".to_string(), - label: "datum".to_string(), - location: Span::new((), 21..26), - is_validator_param: false, - }, - location: Span::new((), 21..26), - annotation: None, - tipo: (), - }, - ast::Arg { - arg_name: ast::ArgName::Named { - name: "rdmr".to_string(), - label: "rdmr".to_string(), - location: Span::new((), 28..32), - is_validator_param: false, - }, - location: Span::new((), 28..32), - annotation: None, - tipo: (), - }, - ast::Arg { - arg_name: ast::ArgName::Named { - name: "ctx".to_string(), - label: "ctx".to_string(), - location: Span::new((), 34..37), - is_validator_param: false, - }, - location: Span::new((), 34..37), - annotation: None, - tipo: (), - }, - ], - body: expr::UntypedExpr::Var { - location: Span::new((), 45..49), - name: "True".to_string(), - }, - doc: None, - location: Span::new((), 14..38), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 52, - }, - other_fun: None, - location: Span::new((), 0..9), - params: vec![], - })], - ) -} - -#[test] -fn double_validator() { - let code = indoc! {r#" - validator { - fn foo(datum, rdmr, ctx) { - True - } - - fn bar(rdmr, ctx) { - True - } - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Validator(ast::Validator { - doc: None, - end_position: 90, - fun: Function { - can_error: true, - arguments: vec![ - ast::Arg { - arg_name: ast::ArgName::Named { - name: "datum".to_string(), - label: "datum".to_string(), - location: Span::new((), 21..26), - is_validator_param: false, - }, - location: Span::new((), 21..26), - annotation: None, - tipo: (), - }, - ast::Arg { - arg_name: ast::ArgName::Named { - name: "rdmr".to_string(), - label: "rdmr".to_string(), - location: Span::new((), 28..32), - is_validator_param: false, - }, - location: Span::new((), 28..32), - annotation: None, - tipo: (), - }, - ast::Arg { - arg_name: ast::ArgName::Named { - name: "ctx".to_string(), - label: "ctx".to_string(), - location: Span::new((), 34..37), - is_validator_param: false, - }, - location: Span::new((), 34..37), - annotation: None, - tipo: (), - }, - ], - body: expr::UntypedExpr::Var { - location: Span::new((), 45..49), - name: "True".to_string(), - }, - doc: None, - location: Span::new((), 14..38), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 52, - }, - other_fun: Some(Function { - can_error: true, - arguments: vec![ - ast::Arg { - arg_name: ast::ArgName::Named { - name: "rdmr".to_string(), - label: "rdmr".to_string(), - location: Span::new((), 64..68), - is_validator_param: false, - }, - location: Span::new((), 64..68), - annotation: None, - tipo: (), - }, - ast::Arg { - arg_name: ast::ArgName::Named { - name: "ctx".to_string(), - label: "ctx".to_string(), - location: Span::new((), 70..73), - is_validator_param: false, - }, - location: Span::new((), 70..73), - annotation: None, - tipo: (), - }, - ], - body: expr::UntypedExpr::Var { - location: Span::new((), 81..85), - name: "True".to_string(), - }, - doc: None, - location: Span::new((), 57..74), - name: "bar".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 88, - }), - location: Span::new((), 0..9), - params: vec![], - })], - ) -} - -#[test] -fn import() { - let code = indoc! {r#" - use std/list - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Use(Use { - location: Span::new((), 0..12), - module: vec!["std".to_string(), "list".to_string()], - as_name: None, - unqualified: vec![], - package: (), - })], - ) -} - -#[test] -fn unqualified_imports() { - let code = indoc! {r#" - use std/address.{Address as A, thing as w} - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Use(Use { - location: Span::new((), 0..42), - module: vec!["std".to_string(), "address".to_string()], - as_name: None, - unqualified: vec![ - ast::UnqualifiedImport { - as_name: Some("A".to_string()), - location: Span::new((), 17..29), - layer: Default::default(), - name: "Address".to_string(), - }, - ast::UnqualifiedImport { - as_name: Some("w".to_string()), - location: Span::new((), 31..41), - layer: Default::default(), - name: "thing".to_string(), - }, - ], - package: (), - })], - ) -} - -#[test] -fn import_alias() { - let code = indoc! {r#" - use std/tx as t - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Use(Use { - location: Span::new((), 0..15), - module: vec!["std".to_string(), "tx".to_string()], - as_name: Some("t".to_string()), - unqualified: vec![], - package: (), - })], - ) -} - -#[test] -fn custom_type() { - let code = indoc! {r#" - type Option { - Some(a, Int) - None - Wow { name: Int, age: Int } - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::DataType(DataType { - constructors: vec![ - ast::RecordConstructor { - location: Span::new((), 19..31), - name: "Some".to_string(), - arguments: vec![ - ast::RecordConstructorArg { - label: None, - annotation: ast::Annotation::Var { - location: Span::new((), 24..25), - name: "a".to_string(), - }, - location: Span::new((), 24..25), - tipo: (), - doc: None, - }, - ast::RecordConstructorArg { - label: None, - annotation: ast::Annotation::Constructor { - location: Span::new((), 27..30), - module: None, - name: "Int".to_string(), - arguments: vec![], - }, - location: Span::new((), 27..30), - tipo: (), - doc: None, - }, - ], - doc: None, - sugar: false, - }, - ast::RecordConstructor { - location: Span::new((), 34..38), - name: "None".to_string(), - arguments: vec![], - doc: None, - sugar: false, - }, - ast::RecordConstructor { - location: Span::new((), 41..68), - name: "Wow".to_string(), - arguments: vec![ - ast::RecordConstructorArg { - label: Some("name".to_string()), - annotation: ast::Annotation::Constructor { - location: Span::new((), 53..56), - module: None, - name: "Int".to_string(), - arguments: vec![], - }, - location: Span::new((), 47..56), - tipo: (), - doc: None, - }, - ast::RecordConstructorArg { - label: Some("age".to_string()), - annotation: ast::Annotation::Constructor { - location: Span::new((), 63..66), - module: None, - name: "Int".to_string(), - arguments: vec![], - }, - location: Span::new((), 58..66), - tipo: (), - doc: None, - }, - ], - doc: None, - sugar: false, - }, - ], - doc: None, - location: Span::new((), 0..70), - name: "Option".to_string(), - opaque: false, - parameters: vec!["a".to_string()], - public: false, - typed_parameters: vec![], - })], - ) -} - -#[test] -fn opaque_type() { - let code = indoc! {r#" - pub opaque type User { - name: _w - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::DataType(DataType { - constructors: vec![ast::RecordConstructor { - location: Span::new((), 21..35), - name: "User".to_string(), - arguments: vec![ast::RecordConstructorArg { - label: Some("name".to_string()), - annotation: ast::Annotation::Hole { - location: Span::new((), 31..33), - name: "_w".to_string(), - }, - location: Span::new((), 25..33), - tipo: (), - doc: None, - }], - doc: None, - sugar: true, - }], - doc: None, - location: Span::new((), 0..35), - name: "User".to_string(), - opaque: true, - parameters: vec![], - public: true, - typed_parameters: vec![], - })], - ) -} - -#[test] -fn type_alias() { - let code = indoc! {r#" - type Thing = Option - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::TypeAlias(TypeAlias { - alias: "Thing".to_string(), - annotation: ast::Annotation::Constructor { - location: Span::new((), 13..24), - module: None, - name: "Option".to_string(), - arguments: vec![ast::Annotation::Constructor { - location: Span::new((), 20..23), - module: None, - name: "Int".to_string(), - arguments: vec![], - }], - }, - doc: None, - location: Span::new((), 0..24), - parameters: vec![], - public: false, - tipo: (), - })], - ) -} - -#[test] -fn pub_type_alias() { - let code = indoc! {r#" - pub type Me = Option - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::TypeAlias(TypeAlias { - alias: "Me".to_string(), - annotation: ast::Annotation::Constructor { - location: Span::new((), 14..28), - module: None, - name: "Option".to_string(), - arguments: vec![ast::Annotation::Constructor { - location: Span::new((), 21..27), - module: None, - name: "String".to_string(), - arguments: vec![], - }], - }, - doc: None, - location: Span::new((), 0..28), - parameters: vec![], - public: true, - tipo: (), - })], - ) -} - -#[test] -fn empty_function() { - let code = indoc! {r#" - pub fn run() {} - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Trace { - kind: ast::TraceKind::Todo, - location: Span::new((), 0..15), - text: Box::new(expr::UntypedExpr::String { - value: "aiken::todo".to_string(), - location: Span::new((), 0..15), - }), - then: Box::new(expr::UntypedExpr::ErrorTerm { - location: Span::new((), 0..15), - }), - }, - doc: None, - location: Span::new((), 0..12), - name: "run".to_string(), - public: true, - return_annotation: None, - return_type: (), - end_position: 14, - })], - ) -} - -#[test] -fn expect() { - let code = indoc! {r#" - pub fn run() { - expect Some(x) = something.field - x.other_field - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 19..69), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 19..51), - value: expr::UntypedExpr::FieldAccess { - location: Span::new((), 36..51), - label: "field".to_string(), - container: expr::UntypedExpr::Var { - location: Span::new((), 36..45), - name: "something".to_string(), - } - .into(), - } - .into(), - pattern: ast::Pattern::Constructor { - is_record: false, - location: Span::new((), 26..33), - name: "Some".to_string(), - arguments: vec![ast::CallArg { - label: None, - location: Span::new((), 31..32), - value: ast::Pattern::Var { - location: Span::new((), 31..32), - name: "x".to_string(), - }, - }], - module: None, - constructor: (), - with_spread: false, - tipo: (), - }, - kind: ast::AssignmentKind::Expect, - annotation: None, - }, - expr::UntypedExpr::FieldAccess { - location: Span::new((), 56..69), - label: "other_field".to_string(), - container: expr::UntypedExpr::Var { - location: Span::new((), 56..57), - name: "x".to_string(), - } - .into(), - }, - ], - }, - doc: None, - - location: Span::new((), 0..12), - name: "run".to_string(), - public: true, - return_annotation: None, - return_type: (), - end_position: 70, - })], - ) -} - -#[test] -fn plus_binop() { - let code = indoc! {r#" - pub fn add_one(a) -> Int { - a + 1 - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![ast::Arg { - arg_name: ast::ArgName::Named { - label: "a".to_string(), - name: "a".to_string(), - location: Span::new((), 15..16), - is_validator_param: false, - }, - location: Span::new((), 15..16), - annotation: None, - tipo: (), - }], - body: expr::UntypedExpr::BinOp { - location: Span::new((), 29..34), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 29..30), - name: "a".to_string(), - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 33..34), - value: "1".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }, - doc: None, - location: Span::new((), 0..24), - name: "add_one".to_string(), - public: true, - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 21..24), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - return_type: (), - end_position: 35, - })], - ) -} - -#[test] -fn pipeline() { - let code = indoc! {r#" - pub fn thing(thing a: Int) { - a + 2 - |> add_one - |> add_one - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![ast::Arg { - arg_name: ast::ArgName::Named { - name: "a".to_string(), - label: "thing".to_string(), - location: Span::new((), 13..20), - is_validator_param: false, - }, - location: Span::new((), 13..25), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 22..25), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }], - body: expr::UntypedExpr::PipeLine { - one_liner: false, - expressions: vec1::vec1![ - expr::UntypedExpr::BinOp { - location: Span::new((), 31..36), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 31..32), - name: "a".to_string(), - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 35..36), - value: "2".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }, - expr::UntypedExpr::Var { - location: Span::new((), 42..49), - name: "add_one".to_string(), - }, - expr::UntypedExpr::Var { - location: Span::new((), 55..62), - name: "add_one".to_string(), - }, - ], - }, - doc: None, - location: Span::new((), 0..26), - name: "thing".to_string(), - public: true, - return_annotation: None, - return_type: (), - end_position: 63, - })], - ) -} - -#[test] -fn if_expression() { - let code = indoc! {r#" - fn ifs() { - if True { - 1 + 1 - } else if a < 4 { - 5 - } else if a || b { - 6 - } else { - 3 - } - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::If { - location: Span::new((), 13..106), - branches: vec1::vec1![ - ast::IfBranch { - condition: expr::UntypedExpr::Var { - location: Span::new((), 16..20), - name: "True".to_string(), - }, - body: expr::UntypedExpr::BinOp { - location: Span::new((), 27..32), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 27..28), - value: "1".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 31..32), - value: "1".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }, - location: Span::new((), 16..36), - }, - ast::IfBranch { - condition: expr::UntypedExpr::BinOp { - location: Span::new((), 45..50), - name: ast::BinOp::LtInt, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 45..46), - name: "a".to_string(), - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 49..50), - value: "4".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }, - body: expr::UntypedExpr::Int { - location: Span::new((), 57..58), - value: "5".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - location: Span::new((), 45..62), - }, - ast::IfBranch { - condition: expr::UntypedExpr::BinOp { - location: Span::new((), 71..77), - name: ast::BinOp::Or, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 71..72), - name: "a".to_string(), - }), - right: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 76..77), - name: "b".to_string(), - }), - }, - body: expr::UntypedExpr::Int { - location: Span::new((), 84..85), - value: "6".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - location: Span::new((), 71..89), - }, - ], - final_else: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 101..102), - value: "3".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }, - doc: None, - location: Span::new((), 0..8), - name: "ifs".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 107, - })], - ) -} - -#[test] -fn let_bindings() { - let code = indoc! {r#" - pub fn wow(a: Int) { - let x = - a + 2 - |> add_one - |> add_one - - let thing = [ 1, 2, a ] - - let idk = thing - - y - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![ast::Arg { - arg_name: ast::ArgName::Named { - label: "a".to_string(), - name: "a".to_string(), - location: Span::new((), 11..12), - is_validator_param: false, - }, - location: Span::new((), 11..17), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 14..17), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 23..121), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 23..70), - value: Box::new(expr::UntypedExpr::PipeLine { - one_liner: false, - expressions: vec1::vec1![ - expr::UntypedExpr::BinOp { - location: Span::new((), 35..40), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 35..36), - name: "a".to_string(), - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 39..40), - value: "2".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }, - expr::UntypedExpr::Var { - location: Span::new((), 48..55), - name: "add_one".to_string(), - }, - expr::UntypedExpr::Var { - location: Span::new((), 63..70), - name: "add_one".to_string(), - }, - ], - }), - pattern: ast::Pattern::Var { - location: Span::new((), 27..28), - name: "x".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Assignment { - location: Span::new((), 74..97), - value: Box::new(expr::UntypedExpr::List { - location: Span::new((), 86..97), - elements: vec![ - expr::UntypedExpr::Int { - location: Span::new((), 88..89), - value: "1".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - expr::UntypedExpr::Int { - location: Span::new((), 91..92), - value: "2".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - expr::UntypedExpr::Var { - location: Span::new((), 94..95), - name: "a".to_string(), - }, - ], - tail: None, - }), - pattern: ast::Pattern::Var { - location: Span::new((), 78..83), - name: "thing".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Assignment { - location: Span::new((), 101..116), - value: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 111..116), - name: "thing".to_string(), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 105..108), - name: "idk".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Var { - location: Span::new((), 120..121), - name: "y".to_string(), - }, - ], - }, - doc: None, - location: Span::new((), 0..18), - name: "wow".to_string(), - public: true, - return_annotation: None, - return_type: (), - end_position: 122, - })], - ) -} - -#[test] -fn block() { - let code = indoc! {r#" - pub fn wow2(a: Int){ - let b = { - let x = 4 - - x + 5 - } - - b - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![ast::Arg { - arg_name: ast::ArgName::Named { - label: "a".to_string(), - name: "a".to_string(), - location: Span::new((), 12..13), - is_validator_param: false, - }, - location: Span::new((), 12..18), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 15..18), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 23..66), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 23..61), - value: Box::new(expr::UntypedExpr::Sequence { - location: Span::new((), 37..57), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 37..46), - value: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 45..46), - value: "4".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - pattern: ast::Pattern::Var { - location: Span::new((), 41..42), - name: "x".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::BinOp { - location: Span::new((), 52..57), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 52..53), - name: "x".to_string(), - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 56..57), - value: "5".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }, - ], - }), - pattern: ast::Pattern::Var { - location: Span::new((), 27..28), - name: "b".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Var { - location: Span::new((), 65..66), - name: "b".to_string(), - }, - ], - }, - doc: None, - location: Span::new((), 0..19), - name: "wow2".to_string(), - public: true, - return_annotation: None, - return_type: (), - end_position: 67, - })], - ) -} - -#[test] -fn when() { - let code = indoc! {r#" - pub fn wow2(a: Int){ - when a is { - 2 -> 3 - 1 | 4 | 5 -> { - let amazing = 5 - amazing - } - 3 -> 9 - _ -> 4 - } - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![ast::Arg { - arg_name: ast::ArgName::Named { - label: "a".to_string(), - name: "a".to_string(), - location: Span::new((), 12..13), - is_validator_param: false, - }, - location: Span::new((), 12..18), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 15..18), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }], - body: expr::UntypedExpr::When { - location: Span::new((), 23..132), - subject: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 28..29), - name: "a".to_string(), - }), - clauses: vec![ - ast::UntypedClause { - location: Span::new((), 39..45), - patterns: vec1![ast::Pattern::Int { - location: Span::new((), 39..40), - value: "2".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }], - guard: None, - then: expr::UntypedExpr::Int { - location: Span::new((), 44..45), - value: "3".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - }, - ast::UntypedClause { - location: Span::new((), 50..106), - patterns: vec1![ - ast::Pattern::Int { - location: Span::new((), 50..51), - value: "1".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - ast::Pattern::Int { - location: Span::new((), 54..55), - value: "4".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - ast::Pattern::Int { - location: Span::new((), 58..59), - value: "5".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - ], - guard: None, - then: expr::UntypedExpr::Sequence { - location: Span::new((), 71..100), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 71..86), - value: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 85..86), - value: "5".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - pattern: ast::Pattern::Var { - location: Span::new((), 75..82), - name: "amazing".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Var { - location: Span::new((), 93..100), - name: "amazing".to_string(), - }, - ], - }, - }, - ast::UntypedClause { - location: Span::new((), 111..117), - patterns: vec1![ast::Pattern::Int { - location: Span::new((), 111..112), - value: "3".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }], - guard: None, - then: expr::UntypedExpr::Int { - location: Span::new((), 116..117), - value: "9".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - }, - ast::UntypedClause { - location: Span::new((), 122..128), - patterns: vec1![ast::Pattern::Discard { - name: "_".to_string(), - location: Span::new((), 122..123), - }], - guard: None, - then: expr::UntypedExpr::Int { - location: Span::new((), 127..128), - value: "4".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - }, - ], - }, - doc: None, - location: Span::new((), 0..19), - name: "wow2".to_string(), - public: true, - return_annotation: None, - return_type: (), - end_position: 133, - })], - ) -} - -#[test] -fn anonymous_function() { - let code = indoc! {r#" - pub fn such() -> Int { - let add_one = fn (a: Int) -> Int { a + 1 } - - 2 |> add_one - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 25..83), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 25..67), - value: Box::new(expr::UntypedExpr::Fn { - location: Span::new((), 39..67), - fn_style: expr::FnStyle::Plain, - arguments: vec![ast::Arg { - arg_name: ast::ArgName::Named { - label: "a".to_string(), - name: "a".to_string(), - location: Span::new((), 43..44), - is_validator_param: false, - }, - location: Span::new((), 43..49), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 46..49), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }], - body: Box::new(expr::UntypedExpr::BinOp { - location: Span::new((), 60..65), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 60..61), - name: "a".to_string(), - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 64..65), - value: "1".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }), - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 54..57), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 29..36), - name: "add_one".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::PipeLine { - one_liner: true, - expressions: vec1::vec1![ - expr::UntypedExpr::Int { - location: Span::new((), 71..72), - value: "2".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - expr::UntypedExpr::Var { - location: Span::new((), 76..83), - name: "add_one".to_string(), - }, - ], - }, - ], - }, - doc: None, - location: Span::new((), 0..20), - name: "such".to_string(), - public: true, - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 17..20), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - return_type: (), - end_position: 84, - })], - ) -} - -#[test] -fn field_access() { - let code = indoc! {r#" - fn name(user: User) { - user.name - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![ast::Arg { - arg_name: ast::ArgName::Named { - label: "user".to_string(), - name: "user".to_string(), - location: Span::new((), 8..12), - is_validator_param: false, - }, - location: Span::new((), 8..18), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 14..18), - module: None, - name: "User".to_string(), - arguments: vec![], - }), - tipo: (), - }], - body: expr::UntypedExpr::FieldAccess { - location: Span::new((), 24..33), - label: "name".to_string(), - container: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 24..28), - name: "user".to_string(), - }), - }, - doc: None, - location: Span::new((), 0..19), - name: "name".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 34, - })], - ) -} - -#[test] -fn call() { - let code = indoc! {r#" - fn calls() { - let x = add_one(3) - - let map_add_x = list.map(_, fn (y) { x + y }) - - map_add_x([ 1, 2, 3 ]) - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 15..108), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 15..33), - value: Box::new(expr::UntypedExpr::Call { - arguments: vec![ast::CallArg { - label: None, - location: Span::new((), 31..32), - value: expr::UntypedExpr::Int { - location: Span::new((), 31..32), - value: "3".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - }], - fun: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 23..30), - name: "add_one".to_string(), - }), - location: Span::new((), 23..33), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 19..20), - name: "x".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Assignment { - location: Span::new((), 37..82), - value: Box::new(expr::UntypedExpr::Fn { - location: Span::new((), 53..82), - fn_style: expr::FnStyle::Capture, - arguments: vec![ast::Arg { - arg_name: ast::ArgName::Named { - label: "_capture__0".to_string(), - name: "_capture__0".to_string(), - location: Span::new((), 0..0), - is_validator_param: false, - }, - location: Span::new((), 0..0), - annotation: None, - tipo: (), - }], - body: Box::new(expr::UntypedExpr::Call { - arguments: vec![ - ast::CallArg { - label: None, - location: Span::new((), 62..63), - value: expr::UntypedExpr::Var { - location: Span::new((), 62..63), - name: "_capture__0".to_string(), - }, - }, - ast::CallArg { - label: None, - location: Span::new((), 65..81), - value: expr::UntypedExpr::Fn { - location: Span::new((), 65..81), - fn_style: expr::FnStyle::Plain, - arguments: vec![ast::Arg { - arg_name: ast::ArgName::Named { - label: "y".to_string(), - name: "y".to_string(), - location: Span::new((), 69..70), - is_validator_param: false, - }, - location: Span::new((), 69..70), - annotation: None, - tipo: (), - }], - body: Box::new(expr::UntypedExpr::BinOp { - location: Span::new((), 74..79), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 74..75), - name: "x".to_string(), - }), - right: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 78..79), - name: "y".to_string(), - }), - }), - return_annotation: None, - }, - }, - ], - fun: Box::new(expr::UntypedExpr::FieldAccess { - location: Span::new((), 53..61), - label: "map".to_string(), - container: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 53..57), - name: "list".to_string(), - }), - }), - location: Span::new((), 53..82), - }), - return_annotation: None, - }), - pattern: ast::Pattern::Var { - location: Span::new((), 41..50), - name: "map_add_x".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Call { - arguments: vec![ast::CallArg { - label: None, - location: Span::new((), 96..107), - value: expr::UntypedExpr::List { - location: Span::new((), 96..107), - elements: vec![ - expr::UntypedExpr::Int { - location: Span::new((), 98..99), - value: "1".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - expr::UntypedExpr::Int { - location: Span::new((), 101..102), - value: "2".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - expr::UntypedExpr::Int { - location: Span::new((), 104..105), - value: "3".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - ], - tail: None, - }, - }], - fun: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 86..95), - name: "map_add_x".to_string(), - }), - location: Span::new((), 86..108), - }, - ], - }, - doc: None, - location: Span::new((), 0..10), - name: "calls".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 109, - })], - ) -} - -#[test] -fn record_update() { - let code = indoc! {r#" - fn update_name(user: User, name: ByteArray) -> User { - User { ..user, name: "Aiken", age } - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![ - ast::Arg { - arg_name: ast::ArgName::Named { - label: "user".to_string(), - name: "user".to_string(), - location: Span::new((), 15..19), - is_validator_param: false, - }, - location: Span::new((), 15..25), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 21..25), - module: None, - name: "User".to_string(), - arguments: vec![], - }), - tipo: (), - }, - ast::Arg { - arg_name: ast::ArgName::Named { - label: "name".to_string(), - name: "name".to_string(), - location: Span::new((), 27..31), - is_validator_param: false, - }, - location: Span::new((), 27..42), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 33..42), - module: None, - name: "ByteArray".to_string(), - arguments: vec![], - }), - tipo: (), - }, - ], - body: expr::UntypedExpr::RecordUpdate { - location: Span::new((), 56..91), - constructor: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 56..60), - name: "User".to_string(), - }), - spread: ast::RecordUpdateSpread { - base: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 65..69), - name: "user".to_string(), - }), - location: Span::new((), 63..69), - }, - arguments: vec![ - ast::UntypedRecordUpdateArg { - label: "name".to_string(), - location: Span::new((), 71..84), - value: expr::UntypedExpr::ByteArray { - location: Span::new((), 77..84), - bytes: String::from("Aiken").into_bytes(), - preferred_format: ast::ByteArrayFormatPreference::Utf8String, - }, - }, - ast::UntypedRecordUpdateArg { - label: "age".to_string(), - location: Span::new((), 86..89), - value: expr::UntypedExpr::Var { - location: Span::new((), 86..89), - name: "age".to_string(), - }, - }, - ], - }, - doc: None, - location: Span::new((), 0..51), - name: "update_name".to_string(), - public: false, - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 47..51), - module: None, - name: "User".to_string(), - arguments: vec![], - }), - return_type: (), - end_position: 92, - })], - ) -} - -#[test] -fn record_create_labeled() { - let code = indoc! {r#" - fn create() { - User { name: "Aiken", age, thing: 2 } - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(ast::Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Call { - arguments: vec![ - ast::CallArg { - label: Some("name".to_string()), - location: Span::new((), 23..36), - value: expr::UntypedExpr::ByteArray { - location: Span::new((), 29..36), - bytes: String::from("Aiken").into_bytes(), - preferred_format: ast::ByteArrayFormatPreference::Utf8String, - }, - }, - ast::CallArg { - label: Some("age".to_string()), - location: Span::new((), 38..41), - value: expr::UntypedExpr::Var { - location: Span::new((), 38..41), - name: "age".to_string(), - }, - }, - ast::CallArg { - label: Some("thing".to_string()), - location: Span::new((), 43..51), - value: expr::UntypedExpr::Int { - location: Span::new((), 50..51), - value: "2".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - }, - ], - fun: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 16..20), - name: "User".to_string(), - }), - location: Span::new((), 16..53), - }, - doc: None, - location: Span::new((), 0..11), - name: "create".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 54, - })], - ) -} - -#[test] -fn record_create_labeled_with_field_access() { - let code = indoc! {r#" - fn create() { - some_module.User { name: "Aiken", age, thing: 2 } - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(ast::Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Call { - arguments: vec![ - ast::CallArg { - label: Some("name".to_string()), - location: Span::new((), 35..48), - value: expr::UntypedExpr::ByteArray { - location: Span::new((), 41..48), - bytes: String::from("Aiken").into_bytes(), - preferred_format: ast::ByteArrayFormatPreference::Utf8String, - }, - }, - ast::CallArg { - label: Some("age".to_string()), - location: Span::new((), 50..53), - value: expr::UntypedExpr::Var { - location: Span::new((), 50..53), - name: "age".to_string(), - }, - }, - ast::CallArg { - label: Some("thing".to_string()), - location: Span::new((), 55..63), - value: expr::UntypedExpr::Int { - location: Span::new((), 62..63), - value: "2".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - }, - ], - fun: Box::new(expr::UntypedExpr::FieldAccess { - location: Span::new((), 16..32), - label: "User".to_string(), - container: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 16..27), - name: "some_module".to_string(), - }), - }), - location: Span::new((), 16..65), - }, - doc: None, - location: Span::new((), 0..11), - name: "create".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 66, - })], - ) -} - -#[test] -fn record_create_unlabeled() { - let code = indoc! {r#" - fn create() { - some_module.Thing(1, a) - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(ast::Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Call { - arguments: vec![ - ast::CallArg { - label: None, - location: Span::new((), 34..35), - value: expr::UntypedExpr::Int { - location: Span::new((), 34..35), - value: "1".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - }, - ast::CallArg { - label: None, - location: Span::new((), 37..38), - value: expr::UntypedExpr::Var { - location: Span::new((), 37..38), - name: "a".to_string(), - }, - }, - ], - fun: Box::new(expr::UntypedExpr::FieldAccess { - location: Span::new((), 16..33), - label: "Thing".to_string(), - container: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 16..27), - name: "some_module".to_string(), - }), - }), - location: Span::new((), 16..39), - }, - doc: None, - location: Span::new((), 0..11), - name: "create".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 40, - })], - ) -} - -#[test] -fn parse_tuple() { - let code = indoc! {r#" - fn foo() { - let tuple = (1, 2, 3, 4) - tuple.1st + tuple.2nd + tuple.3rd + tuple.4th - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 13..85), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 13..37), - value: Box::new(expr::UntypedExpr::Tuple { - location: Span::new((), 25..37), - elems: vec![ - expr::UntypedExpr::Int { - location: Span::new((), 26..27), - value: "1".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - expr::UntypedExpr::Int { - location: Span::new((), 29..30), - value: "2".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - expr::UntypedExpr::Int { - location: Span::new((), 32..33), - value: "3".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - expr::UntypedExpr::Int { - location: Span::new((), 35..36), - value: "4".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - ], - }), - pattern: ast::Pattern::Var { - location: Span::new((), 17..22), - name: "tuple".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::BinOp { - location: Span::new((), 40..85), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::BinOp { - location: Span::new((), 40..73), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::BinOp { - location: Span::new((), 40..61), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::TupleIndex { - location: Span::new((), 40..49), - index: 0, - tuple: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 40..45), - name: "tuple".to_string(), - }), - }), - right: Box::new(expr::UntypedExpr::TupleIndex { - location: Span::new((), 52..61), - index: 1, - tuple: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 52..57), - name: "tuple".to_string(), - }), - }), - }), - right: Box::new(expr::UntypedExpr::TupleIndex { - location: Span::new((), 64..73), - index: 2, - tuple: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 64..69), - name: "tuple".to_string(), - }), - }), - }), - right: Box::new(expr::UntypedExpr::TupleIndex { - location: Span::new((), 76..85), - index: 3, - tuple: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 76..81), - name: "tuple".to_string(), - }), - }), - }, - ], - }, - doc: None, - location: Span::new((), 0..8), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 86, - })], - ) -} - -#[test] -fn parse_tuple2() { - let code = indoc! {r#" - fn foo() { - let a = foo(14) - (a, 42) - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 13..38), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 13..28), - value: Box::new(expr::UntypedExpr::Call { - arguments: vec![ast::CallArg { - label: None, - location: Span::new((), 25..27), - value: expr::UntypedExpr::Int { - location: Span::new((), 25..27), - value: "14".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - }], - fun: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 21..24), - name: "foo".to_string(), - }), - location: Span::new((), 21..28), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 17..18), - name: "a".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Tuple { - location: Span::new((), 31..38), - elems: vec![ - expr::UntypedExpr::Var { - location: Span::new((), 32..33), - name: "a".to_string(), - }, - expr::UntypedExpr::Int { - location: Span::new((), 35..37), - value: "42".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - ], - }, - ], - }, - doc: None, - location: Span::new((), 0..8), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 39, - })], - ); -} - -#[test] -fn large_integer_constants() { - let code = indoc! {r#" - pub const my_big_int = 999999999999999999999999 - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::ModuleConstant(ModuleConstant { - doc: None, - location: Span::new((), 0..47), - public: true, - name: "my_big_int".to_string(), - annotation: None, - value: Box::new(ast::Constant::Int { - location: Span::new((), 23..47), - value: "999999999999999999999999".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - tipo: (), - })], - ) -} - -#[test] -fn plain_bytearray_literals() { - let code = indoc! {r#" - pub const my_policy_id = #[0, 170, 255] - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::ModuleConstant(ModuleConstant { - doc: None, - location: Span::new((), 0..39), - public: true, - name: "my_policy_id".to_string(), - annotation: None, - value: Box::new(Constant::ByteArray { - location: Span::new((), 25..39), - bytes: vec![0, 170, 255], - preferred_format: ast::ByteArrayFormatPreference::ArrayOfBytes(Base::Decimal { - numeric_underscore: false, - }), - }), - tipo: (), - })], - ) -} - -#[test] -fn base16_bytearray_literals() { - let code = indoc! {r#" - pub const my_policy_id = #"00aaff" - - pub fn foo() { - my_policy_id == #"00aaff" - } - "#}; - - assert_definitions( - code, - vec![ - ast::UntypedDefinition::ModuleConstant(ModuleConstant { - doc: None, - location: Span::new((), 0..34), - public: true, - name: "my_policy_id".to_string(), - annotation: None, - value: Box::new(Constant::ByteArray { - location: Span::new((), 25..34), - bytes: vec![0, 170, 255], - preferred_format: ast::ByteArrayFormatPreference::HexadecimalString, - }), - tipo: (), - }), - ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::BinOp { - location: Span::new((), 55..80), - name: ast::BinOp::Eq, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 55..67), - name: "my_policy_id".to_string(), - }), - right: Box::new(expr::UntypedExpr::ByteArray { - location: Span::new((), 71..80), - bytes: vec![0, 170, 255], - preferred_format: ast::ByteArrayFormatPreference::HexadecimalString, - }), - }, - doc: None, - location: Span::new((), 36..48), - name: "foo".to_string(), - public: true, - return_annotation: None, - return_type: (), - end_position: 81, - }), - ], - ) -} - -#[test] -fn function_def() { - let code = indoc! {r#" - fn foo() {} - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(Function { - can_error: true, - doc: None, - arguments: vec![], - body: expr::UntypedExpr::Trace { - kind: ast::TraceKind::Todo, - location: Span::new((), 0..11), - text: Box::new(expr::UntypedExpr::String { - value: "aiken::todo".to_string(), - location: Span::new((), 0..11), - }), - then: Box::new(expr::UntypedExpr::ErrorTerm { - location: Span::new((), 0..11), - }), - }, - location: Span::new((), 0..8), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 10, - })], - ) -} - -#[test] -fn function_invoke() { - let code = indoc! {r#" - fn foo() { - let a = bar(42) - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(Function { - can_error: true, - doc: None, - arguments: vec![], - body: expr::UntypedExpr::Assignment { - location: Span::new((), 13..28), - kind: ast::AssignmentKind::Let, - annotation: None, - pattern: ast::Pattern::Var { - location: Span::new((), 17..18), - name: "a".to_string(), - }, - value: Box::new(expr::UntypedExpr::Call { - location: Span::new((), 21..28), - fun: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 21..24), - name: "bar".to_string(), - }), - arguments: vec![ast::CallArg { - label: None, - location: Span::new((), 25..27), - value: expr::UntypedExpr::Int { - location: Span::new((), 25..27), - value: "42".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - }], - }), - }, - location: Span::new((), 0..8), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 29, - })], - ) -} - -#[test] -fn function_ambiguous_sequence() { - let code = indoc! {r#" - fn foo_1() { - let a = bar - (40) - } - - fn foo_2() { - let a = bar - {40} - } - - fn foo_3() { - let a = (40+2) - } - - fn foo_4() { - let a = bar(42) - (a + 14) * 42 - } - "#}; - - assert_definitions( - code, - vec![ - ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 15..32), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 15..26), - value: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 23..26), - name: "bar".to_string(), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 19..20), - name: "a".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Int { - location: Span::new((), 30..32), - value: "40".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - ], - }, - doc: None, - location: Span::new((), 0..10), - name: "foo_1".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 34, - }), - ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 52..69), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 52..63), - value: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 60..63), - name: "bar".to_string(), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 56..57), - name: "a".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Int { - location: Span::new((), 67..69), - value: "40".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - ], - }, - doc: None, - location: Span::new((), 37..47), - name: "foo_2".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 71, - }), - ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Assignment { - location: Span::new((), 89..103), - value: Box::new(expr::UntypedExpr::BinOp { - location: Span::new((), 98..102), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 98..100), - value: "40".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 101..102), - value: "2".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 93..94), - name: "a".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - doc: None, - location: Span::new((), 74..84), - name: "foo_3".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 104, - }), - ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 122..153), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 122..137), - value: Box::new(expr::UntypedExpr::Call { - arguments: vec![ast::CallArg { - label: None, - location: Span::new((), 134..136), - value: expr::UntypedExpr::Int { - location: Span::new((), 134..136), - value: "42".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - }], - fun: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 130..133), - name: "bar".to_string(), - }), - location: Span::new((), 130..137), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 126..127), - name: "a".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::BinOp { - location: Span::new((), 141..153), - name: ast::BinOp::MultInt, - left: Box::new(expr::UntypedExpr::BinOp { - location: Span::new((), 141..147), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 141..142), - name: "a".to_string(), - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 145..147), - value: "14".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 151..153), - value: "42".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }, - ], - }, - doc: None, - location: Span::new((), 107..117), - name: "foo_4".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 154, - }), - ], - ) -} - -#[test] -fn tuple_type_alias() { - let code = indoc! {r#" - type RoyaltyToken = - (PolicyId, AssetName) - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::TypeAlias(TypeAlias { - alias: "RoyaltyToken".to_string(), - annotation: ast::Annotation::Tuple { - location: Span::new((), 22..43), - elems: vec![ - ast::Annotation::Constructor { - location: Span::new((), 23..31), - module: None, - name: "PolicyId".to_string(), - arguments: vec![], - }, - ast::Annotation::Constructor { - location: Span::new((), 33..42), - module: None, - name: "AssetName".to_string(), - arguments: vec![], - }, - ], - }, - doc: None, - location: Span::new((), 0..43), - parameters: vec![], - public: false, - tipo: (), - })], - ) -} - -#[test] -fn tuple_pattern() { - let code = indoc! {r#" - fn foo() { - when a is { - (u, dic) -> True - } - } - "#}; - - assert_definitions( - code, - vec![ast::UntypedDefinition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::When { - location: Span::new((), 13..49), - subject: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 18..19), - name: "a".to_string(), - }), - clauses: vec![ast::UntypedClause { - location: Span::new((), 29..45), - patterns: vec1![ast::Pattern::Tuple { - location: Span::new((), 29..37), - elems: vec![ - ast::Pattern::Var { - location: Span::new((), 30..31), - name: "u".to_string(), - }, - ast::Pattern::Var { - location: Span::new((), 33..36), - name: "dic".to_string(), - }, - ], - }], - guard: None, - then: expr::UntypedExpr::Var { - location: Span::new((), 41..45), - name: "True".to_string(), - }, - }], - }, - doc: None, - location: Span::new((), 0..8), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 50, - })], - ); -} - -#[test] -fn subtraction_vs_negate() { - let code = indoc! {r#" - fn foo() { - (1-1) == 0 - let x = -2 - bar()-4 - bar(-1) - 42 - } - "#}; - assert_definitions( - code, - vec![ast::Definition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 14..61), - expressions: vec![ - expr::UntypedExpr::BinOp { - location: Span::new((), 14..23), - name: ast::BinOp::Eq, - left: Box::new(expr::UntypedExpr::BinOp { - location: Span::new((), 14..17), - name: ast::BinOp::SubInt, - left: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 14..15), - value: "1".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 16..17), - value: "1".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 22..23), - value: "0".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }, - expr::UntypedExpr::Assignment { - location: Span::new((), 26..36), - value: Box::new(expr::UntypedExpr::UnOp { - op: ast::UnOp::Negate, - location: Span::new((), 34..36), - value: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 35..36), - value: "2".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 30..31), - name: "x".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::BinOp { - location: Span::new((), 39..46), - name: ast::BinOp::SubInt, - left: Box::new(expr::UntypedExpr::Call { - arguments: vec![], - fun: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 39..42), - name: "bar".to_string(), - }), - location: Span::new((), 39..44), - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 45..46), - value: "4".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }, - expr::UntypedExpr::BinOp { - location: Span::new((), 49..61), - name: ast::BinOp::SubInt, - left: Box::new(expr::UntypedExpr::Call { - arguments: vec![ast::CallArg { - label: None, - location: Span::new((), 53..55), - value: expr::UntypedExpr::UnOp { - op: ast::UnOp::Negate, - location: Span::new((), 53..55), - value: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 54..55), - value: "1".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }, - }], - fun: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 49..52), - name: "bar".to_string(), - }), - location: Span::new((), 49..56), - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 59..61), - value: "42".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }, - ], - }, - doc: None, - location: Span::new((), 0..8), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 62, - })], - ); -} - -#[test] -fn clause_guards() { - let code = indoc! {r#" - fn foo() { - when a is { - _ if 42 -> Void - _ if bar -> Void - _ if True -> Void - _ if a || b && c -> Void - _ if (a || b) && c -> Void - _ if a <= 42 || b > 14 || "str" -> Void - _ if a == 14 && !b -> Void - _ if !!True -> Void - } - } - "#}; - assert_definitions( - code, - vec![ast::Definition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::When { - location: Span::new((), 13..250), - subject: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 18..19), - name: "a".to_string(), - }), - clauses: vec![ - ast::UntypedClause { - location: Span::new((), 29..44), - patterns: vec1![ast::Pattern::Discard { - name: "_".to_string(), - location: Span::new((), 29..30), - }], - guard: Some(ast::UntypedClauseGuard::Constant(ast::Constant::Int { - location: Span::new((), 34..36), - value: "42".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - })), - then: expr::UntypedExpr::Var { - location: Span::new((), 40..44), - name: "Void".to_string(), - }, - }, - ast::UntypedClause { - location: Span::new((), 49..65), - patterns: vec1![ast::Pattern::Discard { - name: "_".to_string(), - location: Span::new((), 49..50), - }], - guard: Some(ast::UntypedClauseGuard::Var { - location: Span::new((), 54..57), - name: "bar".to_string(), - tipo: (), - }), - then: expr::UntypedExpr::Var { - location: Span::new((), 61..65), - name: "Void".to_string(), - }, - }, - ast::UntypedClause { - location: Span::new((), 70..87), - patterns: vec1![ast::Pattern::Discard { - name: "_".to_string(), - location: Span::new((), 70..71), - }], - guard: Some(ast::UntypedClauseGuard::Var { - location: Span::new((), 75..79), - tipo: (), - name: "True".to_string(), - }), - then: expr::UntypedExpr::Var { - location: Span::new((), 83..87), - name: "Void".to_string(), - }, - }, - ast::UntypedClause { - location: Span::new((), 92..116), - patterns: vec1![ast::Pattern::Discard { - name: "_".to_string(), - location: Span::new((), 92..93), - }], - guard: Some(ast::UntypedClauseGuard::Or { - location: Span::new((), 97..108), - left: Box::new(ast::UntypedClauseGuard::Var { - location: Span::new((), 97..98), - name: "a".to_string(), - tipo: (), - }), - right: Box::new(ast::UntypedClauseGuard::And { - location: Span::new((), 102..108), - left: Box::new(ast::UntypedClauseGuard::Var { - location: Span::new((), 102..103), - name: "b".to_string(), - tipo: (), - }), - right: Box::new(ast::UntypedClauseGuard::Var { - location: Span::new((), 107..108), - name: "c".to_string(), - tipo: (), - }), - }), - }), - then: expr::UntypedExpr::Var { - location: Span::new((), 112..116), - name: "Void".to_string(), - }, - }, - ast::UntypedClause { - location: Span::new((), 121..147), - patterns: vec1![ast::Pattern::Discard { - name: "_".to_string(), - location: Span::new((), 121..122), - }], - guard: Some(ast::UntypedClauseGuard::And { - location: Span::new((), 127..139), - left: Box::new(ast::UntypedClauseGuard::Or { - location: Span::new((), 127..133), - left: Box::new(ast::UntypedClauseGuard::Var { - location: Span::new((), 127..128), - name: "a".to_string(), - tipo: (), - }), - right: Box::new(ast::UntypedClauseGuard::Var { - location: Span::new((), 132..133), - name: "b".to_string(), - tipo: (), - }), - }), - right: Box::new(ast::UntypedClauseGuard::Var { - location: Span::new((), 138..139), - name: "c".to_string(), - tipo: (), - }), - }), - then: expr::UntypedExpr::Var { - location: Span::new((), 143..147), - name: "Void".to_string(), - }, - }, - ast::UntypedClause { - location: Span::new((), 152..191), - patterns: vec1![ast::Pattern::Discard { - name: "_".to_string(), - location: Span::new((), 152..153), - }], - guard: Some(ast::UntypedClauseGuard::Or { - location: Span::new((), 157..183), - left: Box::new(ast::UntypedClauseGuard::Or { - location: Span::new((), 157..174), - left: Box::new(ast::UntypedClauseGuard::LtEqInt { - location: Span::new((), 157..164), - left: Box::new(ast::UntypedClauseGuard::Var { - location: Span::new((), 157..158), - name: "a".to_string(), - tipo: (), - }), - right: Box::new(ast::UntypedClauseGuard::Constant( - ast::Constant::Int { - location: Span::new((), 162..164), - value: "42".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - )), - }), - right: Box::new(ast::UntypedClauseGuard::GtInt { - location: Span::new((), 168..174), - left: Box::new(ast::UntypedClauseGuard::Var { - location: Span::new((), 168..169), - name: "b".to_string(), - tipo: (), - }), - right: Box::new(ast::UntypedClauseGuard::Constant( - ast::Constant::Int { - location: Span::new((), 172..174), - value: "14".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - )), - }), - }), - right: Box::new(ast::UntypedClauseGuard::Constant( - ast::Constant::ByteArray { - location: Span::new((), 178..183), - bytes: String::from("str").into_bytes(), - preferred_format: ast::ByteArrayFormatPreference::Utf8String, - }, - )), - }), - then: expr::UntypedExpr::Var { - location: Span::new((), 187..191), - name: "Void".to_string(), - }, - }, - ast::UntypedClause { - location: Span::new((), 196..222), - patterns: vec1![ast::Pattern::Discard { - name: "_".to_string(), - location: Span::new((), 196..197), - }], - guard: Some(ast::UntypedClauseGuard::And { - location: Span::new((), 201..214), - left: Box::new(ast::UntypedClauseGuard::Equals { - location: Span::new((), 201..208), - left: Box::new(ast::UntypedClauseGuard::Var { - location: Span::new((), 201..202), - name: "a".to_string(), - tipo: (), - }), - right: Box::new(ast::UntypedClauseGuard::Constant( - ast::Constant::Int { - location: Span::new((), 206..208), - value: "14".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }, - )), - }), - right: Box::new(ast::UntypedClauseGuard::Not { - location: Span::new((), 212..214), - value: Box::new(ast::UntypedClauseGuard::Var { - location: Span::new((), 213..214), - name: "b".to_string(), - tipo: (), - }), - }), - }), - then: expr::UntypedExpr::Var { - location: Span::new((), 218..222), - name: "Void".to_string(), - }, - }, - ast::UntypedClause { - location: Span::new((), 227..246), - patterns: vec1![ast::Pattern::Discard { - name: "_".to_string(), - location: Span::new((), 227..228), - }], - guard: Some(ast::UntypedClauseGuard::Not { - location: Span::new((), 232..238), - value: Box::new(ast::UntypedClauseGuard::Not { - location: Span::new((), 233..238), - value: Box::new(ast::UntypedClauseGuard::Var { - location: Span::new((), 234..238), - tipo: (), - name: "True".to_string(), - }), - }), - }), - then: expr::UntypedExpr::Var { - location: Span::new((), 242..246), - name: "Void".to_string(), - }, - }, - ], - }, - doc: None, - location: Span::new((), 0..8), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 251, - })], - ); -} - -#[test] -fn scope_logical_expression() { - let code = indoc! {r#" - fn foo() { - let x = !(a && b) - let y = a || b && c || d - x - } - "#}; - assert_definitions( - code, - vec![ast::Definition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 13..61), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 13..30), - value: Box::new(expr::UntypedExpr::UnOp { - op: ast::UnOp::Not, - location: Span::new((), 21..29), - value: Box::new(expr::UntypedExpr::BinOp { - location: Span::new((), 23..29), - name: ast::BinOp::And, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 23..24), - name: "a".to_string(), - }), - right: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 28..29), - name: "b".to_string(), - }), - }), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 17..18), - name: "x".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Assignment { - location: Span::new((), 33..57), - value: Box::new(expr::UntypedExpr::BinOp { - location: Span::new((), 41..57), - name: ast::BinOp::Or, - left: Box::new(expr::UntypedExpr::BinOp { - location: Span::new((), 41..52), - name: ast::BinOp::Or, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 41..42), - name: "a".to_string(), - }), - right: Box::new(expr::UntypedExpr::BinOp { - location: Span::new((), 46..52), - name: ast::BinOp::And, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 46..47), - name: "b".to_string(), - }), - right: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 51..52), - name: "c".to_string(), - }), - }), - }), - right: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 56..57), - name: "d".to_string(), - }), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 37..38), - name: "y".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Var { - location: Span::new((), 60..61), - name: "x".to_string(), - }, - ], - }, - doc: None, - location: Span::new((), 0..8), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 62, - })], - ) -} - -#[test] -fn trace_expressions() { - let code = indoc! {r#" - fn foo() { - let msg1 = @"FOO" - trace @"INLINE" - trace msg1 - trace string.concat(msg1, @"BAR") - trace ( 14 + 42 * 1337 ) - Void - } - "#}; - assert_definitions( - code, - vec![ast::Definition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 13..131), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 13..30), - value: Box::new(expr::UntypedExpr::String { - location: Span::new((), 24..30), - value: "FOO".to_string(), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 17..21), - name: "msg1".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Trace { - kind: ast::TraceKind::Trace, - location: Span::new((), 33..131), - then: Box::new(expr::UntypedExpr::Trace { - kind: ast::TraceKind::Trace, - location: Span::new((), 51..131), - then: Box::new(expr::UntypedExpr::Trace { - kind: ast::TraceKind::Trace, - location: Span::new((), 64..131), - then: Box::new(expr::UntypedExpr::Trace { - kind: ast::TraceKind::Trace, - location: Span::new((), 100..131), - then: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 127..131), - name: "Void".to_string(), - }), - text: Box::new(expr::UntypedExpr::BinOp { - location: Span::new((), 108..122), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 108..110), - value: "14".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - right: Box::new(expr::UntypedExpr::BinOp { - location: Span::new((), 113..122), - name: ast::BinOp::MultInt, - left: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 113..115), - value: "42".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 118..122), - value: "1337".to_string(), - base: Base::Decimal { - numeric_underscore: false, - }, - }), - }), - }), - }), - text: Box::new(expr::UntypedExpr::Call { - arguments: vec![ - ast::CallArg { - label: None, - location: Span::new((), 84..88), - value: expr::UntypedExpr::Var { - location: Span::new((), 84..88), - name: "msg1".to_string(), - }, - }, - ast::CallArg { - label: None, - location: Span::new((), 90..96), - value: expr::UntypedExpr::String { - location: Span::new((), 90..96), - value: "BAR".to_string(), - }, - }, - ], - fun: Box::new(expr::UntypedExpr::FieldAccess { - location: Span::new((), 70..83), - label: "concat".to_string(), - container: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 70..76), - name: "string".to_string(), - }), - }), - location: Span::new((), 70..97), - }), - }), - text: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 57..61), - name: "msg1".to_string(), - }), - }), - text: Box::new(expr::UntypedExpr::String { - location: Span::new((), 39..48), - value: "INLINE".to_string(), - }), - }, - ], - }, - doc: None, - location: Span::new((), 0..8), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 132, - })], - ) -} - -#[test] -fn parse_keyword_error() { - let code = indoc! {r#" - fn foo() { - error @"not implemented" - } - - fn bar() { - when x is { - Something -> Void - _ -> error - } - } - "#}; - assert_definitions( - code, - vec![ - ast::Definition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Trace { - kind: ast::TraceKind::Error, - location: Span::new((), 13..37), - then: Box::new(expr::UntypedExpr::ErrorTerm { - location: Span::new((), 13..37), - }), - text: Box::new(expr::UntypedExpr::String { - location: Span::new((), 19..37), - value: "not implemented".to_string(), - }), - }, - doc: None, - location: Span::new((), 0..8), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 38, - }), - ast::Definition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::When { - location: Span::new((), 54..110), - subject: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 59..60), - name: "x".to_string(), - }), - clauses: vec![ - ast::UntypedClause { - location: Span::new((), 72..89), - patterns: vec1![ast::Pattern::Constructor { - is_record: false, - location: Span::new((), 72..81), - name: "Something".to_string(), - arguments: vec![], - module: None, - constructor: (), - with_spread: false, - tipo: (), - }], - guard: None, - then: expr::UntypedExpr::Var { - location: Span::new((), 85..89), - name: "Void".to_string(), - }, - }, - ast::UntypedClause { - location: Span::new((), 96..106), - patterns: vec1![ast::Pattern::Discard { - name: "_".to_string(), - location: Span::new((), 96..97), - }], - guard: None, - then: expr::UntypedExpr::Trace { - kind: ast::TraceKind::Error, - location: Span::new((), 101..106), - then: Box::new(expr::UntypedExpr::ErrorTerm { - location: Span::new((), 101..106), - }), - text: Box::new(expr::UntypedExpr::String { - location: Span::new((), 101..106), - value: "aiken::error".to_string(), - }), - }, - }, - ], - }, - doc: None, - location: Span::new((), 41..49), - name: "bar".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 111, - }), - ], - ) -} - -#[test] -fn parse_keyword_todo() { - let code = indoc! {r#" - fn foo() { - todo @"not implemented" - } - - fn bar() { - when x is { - Foo -> todo - Bar -> True - _ -> False - } - } - "#}; - assert_definitions( - code, - vec![ - ast::Definition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::Trace { - kind: ast::TraceKind::Todo, - location: Span::new((), 13..36), - then: Box::new(expr::UntypedExpr::ErrorTerm { - location: Span::new((), 13..36), - }), - text: Box::new(expr::UntypedExpr::String { - location: Span::new((), 18..36), - value: "not implemented".to_string(), - }), - }, - doc: None, - location: Span::new((), 0..8), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 37, - }), - ast::Definition::Fn(Function { - can_error: true, - arguments: vec![], - body: expr::UntypedExpr::When { - location: Span::new((), 53..121), - subject: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 58..59), - name: "x".to_string(), - }), - clauses: vec![ - ast::UntypedClause { - location: Span::new((), 71..82), - patterns: vec1![ast::Pattern::Constructor { - is_record: false, - location: Span::new((), 71..74), - name: "Foo".to_string(), - arguments: vec![], - module: None, - constructor: (), - with_spread: false, - tipo: (), - }], - guard: None, - then: expr::UntypedExpr::Trace { - kind: ast::TraceKind::Todo, - location: Span::new((), 78..82), - then: Box::new(expr::UntypedExpr::ErrorTerm { - location: Span::new((), 78..82), - }), - text: Box::new(expr::UntypedExpr::String { - location: Span::new((), 78..82), - value: "aiken::todo".to_string(), - }), - }, - }, - ast::UntypedClause { - location: Span::new((), 89..100), - patterns: vec1![ast::Pattern::Constructor { - is_record: false, - location: Span::new((), 89..92), - name: "Bar".to_string(), - arguments: vec![], - module: None, - constructor: (), - with_spread: false, - tipo: (), - }], - guard: None, - then: expr::UntypedExpr::Var { - location: Span::new((), 96..100), - name: "True".to_string(), - }, - }, - ast::UntypedClause { - location: Span::new((), 107..117), - patterns: vec1![ast::Pattern::Discard { - name: "_".to_string(), - location: Span::new((), 107..108), - }], - guard: None, - then: expr::UntypedExpr::Var { - location: Span::new((), 112..117), - name: "False".to_string(), - }, - }, - ], - }, - doc: None, - location: Span::new((), 40..48), - name: "bar".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 122, - }), - ], - ) -} - -#[test] -fn brackets_followed_by_parenthesis() { - fn assert_sequence(code: &str) { - let (module, _extra) = parser::module(code, ast::ModuleKind::Validator).unwrap(); - assert!( - matches!( - module.definitions[..], - [ast::Definition::Test(Function { - body: expr::UntypedExpr::Sequence { .. }, - .. - })] - ), - "{}", - code.to_string() - ); - } - - assert_sequence(indoc! {r#" - test foo () { - let a = [] - (x |> y) == [] - } - "#}); - - assert_sequence(indoc! {r#" - test foo () { - let a = [] - (x |> y) == [] - } - "#}); - - assert_sequence(indoc! {r#" - test foo () { - let a = [] - // foo - (x |> y) == [] - } - "#}); -} - -#[test] -fn int_parsing_hex() { - let code = indoc! {r#" - fn foo() { - let i = 0xff - } - "#}; - assert_definitions( - code, - vec![ast::Definition::Fn(Function { - arguments: vec![], - body: expr::UntypedExpr::Assignment { - location: Span::new((), 13..25), - value: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 21..25), - value: "255".to_string(), - base: Base::Hexadecimal, - }), - pattern: ast::Pattern::Var { - location: Span::new((), 17..18), - name: "i".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - doc: None, - location: Span::new((), 0..8), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 26, - can_error: true, - })], - ) -} - -#[test] -fn int_parsing_hex_bytes() { - let code = indoc! {r#" - fn foo() { - #[ 0x01, 0xa2, 0x03 ] - } - "#}; - assert_definitions( - code, - vec![ast::Definition::Fn(Function { - arguments: vec![], - body: expr::UntypedExpr::ByteArray { - location: Span::new((), 13..34), - bytes: vec![1, 162, 3], - preferred_format: ast::ByteArrayFormatPreference::ArrayOfBytes(Base::Hexadecimal), - }, - doc: None, - location: Span::new((), 0..8), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 35, - can_error: true, - })], - ) -} - -#[test] -fn int_parsing_numeric_underscore() { - let code = indoc! {r#" - fn foo() { - let i = 1_234_567 - let j = 1_000_000 - let k = -10_000 - } - "#}; - assert_definitions( - code, - vec![ast::Definition::Fn(Function { - arguments: vec![], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 17..76), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 17..34), - value: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 25..34), - value: "1234567".to_string(), - base: Base::Decimal { - numeric_underscore: true, - }, - }), - pattern: ast::Pattern::Var { - location: Span::new((), 21..22), - name: "i".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Assignment { - location: Span::new((), 39..56), - value: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 47..56), - value: "1000000".to_string(), - base: Base::Decimal { - numeric_underscore: true, - }, - }), - pattern: ast::Pattern::Var { - location: Span::new((), 43..44), - name: "j".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Assignment { - location: Span::new((), 61..76), - value: Box::new(expr::UntypedExpr::UnOp { - op: ast::UnOp::Negate, - location: Span::new((), 69..76), - value: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 70..76), - value: "10000".to_string(), - base: Base::Decimal { - numeric_underscore: true, - }, - }), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 65..66), - name: "k".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - ], - }, - doc: None, - location: Span::new((), 2..10), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 77, - can_error: true, - })], - ) -} - -#[test] -fn first_class_binop() { - use ast::{Arg, ArgName::*, BinOp::*, CallArg}; - use expr::UntypedExpr::*; - - let code = indoc! {r#" - fn foo() { - compare_with(a, >, b) - compare_with(a, >=, b) - compare_with(a, <, b) - compare_with(a, <=, b) - compare_with(a, ==, b) - compare_with(a, !=, b) - combine_with(a, &&, b) - combine_with(a, ||, b) - compute_with(a, +, b) - compute_with(a, -, b) - compute_with(a, /, b) - compute_with(a, *, b) - compute_with(a, %, b) - } - "#}; - - assert_definitions( - code, - vec![ast::Definition::Fn(Function { - arguments: vec![], - body: Sequence { - location: Span::new((), 13..328), - expressions: vec![ - Call { - arguments: vec![ - ast::CallArg { - label: None, - location: Span::new((), 26..27), - value: Var { - location: Span::new((), 26..27), - name: "a".to_string(), - }, - }, - ast::CallArg { - label: None, - location: Span::new((), 29..30), - value: Fn { - location: Span::new((), 29..30), - fn_style: expr::FnStyle::BinOp(ast::BinOp::GtInt), - arguments: vec![ - Arg { - arg_name: Named { - name: "left".to_string(), - label: "left".to_string(), - location: Span::new((), 29..30), - is_validator_param: false, - }, - location: Span::new((), 29..30), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 29..30), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - Arg { - arg_name: Named { - name: "right".to_string(), - label: "right".to_string(), - location: Span::new((), 29..30), - is_validator_param: false, - }, - location: Span::new((), 29..30), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 29..30), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - ], - body: Box::new(BinOp { - location: Span::new((), 29..30), - name: GtInt, - left: Box::new(Var { - location: Span::new((), 29..30), - name: "left".to_string(), - }), - right: Box::new(Var { - location: Span::new((), 29..30), - name: "right".to_string(), - }), - }), - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 29..30), - module: None, - name: "Bool".to_string(), - arguments: vec![], - }), - }, - }, - CallArg { - label: None, - location: Span::new((), 32..33), - value: Var { - location: Span::new((), 32..33), - name: "b".to_string(), - }, - }, - ], - fun: Box::new(Var { - location: Span::new((), 13..25), - name: "compare_with".to_string(), - }), - location: Span::new((), 13..34), - }, - Call { - arguments: vec![ - ast::CallArg { - label: None, - location: Span::new((), 50..51), - value: Var { - location: Span::new((), 50..51), - name: "a".to_string(), - }, - }, - ast::CallArg { - label: None, - location: Span::new((), 53..55), - value: Fn { - location: Span::new((), 53..55), - fn_style: expr::FnStyle::BinOp(GtEqInt), - arguments: vec![ - Arg { - arg_name: Named { - name: "left".to_string(), - label: "left".to_string(), - location: Span::new((), 53..55), - is_validator_param: false, - }, - location: Span::new((), 53..55), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 53..55), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - Arg { - arg_name: Named { - name: "right".to_string(), - label: "right".to_string(), - location: Span::new((), 53..55), - is_validator_param: false, - }, - location: Span::new((), 53..55), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 53..55), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - ], - body: Box::new(BinOp { - location: Span::new((), 53..55), - name: GtEqInt, - left: Box::new(Var { - location: Span::new((), 53..55), - name: "left".to_string(), - }), - right: Box::new(Var { - location: Span::new((), 53..55), - name: "right".to_string(), - }), - }), - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 53..55), - module: None, - name: "Bool".to_string(), - arguments: vec![], - }), - }, - }, - ast::CallArg { - label: None, - location: Span::new((), 57..58), - value: Var { - location: Span::new((), 57..58), - name: "b".to_string(), - }, - }, - ], - fun: Box::new(Var { - location: Span::new((), 37..49), - name: "compare_with".to_string(), - }), - location: Span::new((), 37..59), - }, - Call { - arguments: vec![ - CallArg { - label: None, - location: Span::new((), 75..76), - value: Var { - location: Span::new((), 75..76), - name: "a".to_string(), - }, - }, - CallArg { - label: None, - location: Span::new((), 78..79), - value: Fn { - location: Span::new((), 78..79), - fn_style: expr::FnStyle::BinOp(LtInt), - arguments: vec![ - Arg { - arg_name: Named { - name: "left".to_string(), - label: "left".to_string(), - location: Span::new((), 78..79), - is_validator_param: false, - }, - location: Span::new((), 78..79), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 78..79), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - Arg { - arg_name: Named { - name: "right".to_string(), - label: "right".to_string(), - location: Span::new((), 78..79), - is_validator_param: false, - }, - location: Span::new((), 78..79), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 78..79), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - ], - body: Box::new(BinOp { - location: Span::new((), 78..79), - name: LtInt, - left: Box::new(Var { - location: Span::new((), 78..79), - name: "left".to_string(), - }), - right: Box::new(Var { - location: Span::new((), 78..79), - name: "right".to_string(), - }), - }), - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 78..79), - module: None, - name: "Bool".to_string(), - arguments: vec![], - }), - }, - }, - CallArg { - label: None, - location: Span::new((), 81..82), - value: Var { - location: Span::new((), 81..82), - name: "b".to_string(), - }, - }, - ], - fun: Box::new(Var { - location: Span::new((), 62..74), - name: "compare_with".to_string(), - }), - location: Span::new((), 62..83), - }, - Call { - arguments: vec![ - CallArg { - label: None, - location: Span::new((), 99..100), - value: Var { - location: Span::new((), 99..100), - name: "a".to_string(), - }, - }, - CallArg { - label: None, - location: Span::new((), 102..104), - value: Fn { - location: Span::new((), 102..104), - fn_style: expr::FnStyle::BinOp(LtEqInt), - arguments: vec![ - Arg { - arg_name: Named { - name: "left".to_string(), - label: "left".to_string(), - location: Span::new((), 102..104), - is_validator_param: false, - }, - location: Span::new((), 102..104), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 102..104), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - Arg { - arg_name: Named { - name: "right".to_string(), - label: "right".to_string(), - location: Span::new((), 102..104), - is_validator_param: false, - }, - location: Span::new((), 102..104), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 102..104), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - ], - body: Box::new(BinOp { - location: Span::new((), 102..104), - name: LtEqInt, - left: Box::new(Var { - location: Span::new((), 102..104), - name: "left".to_string(), - }), - right: Box::new(Var { - location: Span::new((), 102..104), - name: "right".to_string(), - }), - }), - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 102..104), - module: None, - name: "Bool".to_string(), - arguments: vec![], - }), - }, - }, - CallArg { - label: None, - location: Span::new((), 106..107), - value: Var { - location: Span::new((), 106..107), - name: "b".to_string(), - }, - }, - ], - fun: Box::new(Var { - location: Span::new((), 86..98), - name: "compare_with".to_string(), - }), - location: Span::new((), 86..108), - }, - Call { - arguments: vec![ - CallArg { - label: None, - location: Span::new((), 124..125), - value: Var { - location: Span::new((), 124..125), - name: "a".to_string(), - }, - }, - CallArg { - label: None, - location: Span::new((), 127..129), - value: Fn { - location: Span::new((), 127..129), - fn_style: expr::FnStyle::BinOp(Eq), - arguments: vec![ - Arg { - arg_name: Named { - name: "left".to_string(), - label: "left".to_string(), - location: Span::new((), 127..129), - is_validator_param: false, - }, - location: Span::new((), 127..129), - annotation: None, - tipo: (), - }, - Arg { - arg_name: Named { - name: "right".to_string(), - label: "right".to_string(), - location: Span::new((), 127..129), - is_validator_param: false, - }, - location: Span::new((), 127..129), - annotation: None, - tipo: (), - }, - ], - body: Box::new(BinOp { - location: Span::new((), 127..129), - name: Eq, - left: Box::new(Var { - location: Span::new((), 127..129), - name: "left".to_string(), - }), - right: Box::new(Var { - location: Span::new((), 127..129), - name: "right".to_string(), - }), - }), - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 127..129), - module: None, - name: "Bool".to_string(), - arguments: vec![], - }), - }, - }, - CallArg { - label: None, - location: Span::new((), 131..132), - value: Var { - location: Span::new((), 131..132), - name: "b".to_string(), - }, - }, - ], - fun: Box::new(Var { - location: Span::new((), 111..123), - name: "compare_with".to_string(), - }), - location: Span::new((), 111..133), - }, - Call { - arguments: vec![ - CallArg { - label: None, - location: Span::new((), 149..150), - value: Var { - location: Span::new((), 149..150), - name: "a".to_string(), - }, - }, - CallArg { - label: None, - location: Span::new((), 152..154), - value: Fn { - location: Span::new((), 152..154), - fn_style: expr::FnStyle::BinOp(NotEq), - arguments: vec![ - Arg { - arg_name: Named { - name: "left".to_string(), - label: "left".to_string(), - location: Span::new((), 152..154), - is_validator_param: false, - }, - location: Span::new((), 152..154), - annotation: None, - tipo: (), - }, - Arg { - arg_name: Named { - name: "right".to_string(), - label: "right".to_string(), - location: Span::new((), 152..154), - is_validator_param: false, - }, - location: Span::new((), 152..154), - annotation: None, - tipo: (), - }, - ], - body: Box::new(BinOp { - location: Span::new((), 152..154), - name: NotEq, - left: Box::new(Var { - location: Span::new((), 152..154), - name: "left".to_string(), - }), - right: Box::new(Var { - location: Span::new((), 152..154), - name: "right".to_string(), - }), - }), - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 152..154), - module: None, - name: "Bool".to_string(), - arguments: vec![], - }), - }, - }, - CallArg { - label: None, - location: Span::new((), 156..157), - value: Var { - location: Span::new((), 156..157), - name: "b".to_string(), - }, - }, - ], - fun: Box::new(Var { - location: Span::new((), 136..148), - name: "compare_with".to_string(), - }), - location: Span::new((), 136..158), - }, - Call { - arguments: vec![ - CallArg { - label: None, - location: Span::new((), 174..175), - value: Var { - location: Span::new((), 174..175), - name: "a".to_string(), - }, - }, - CallArg { - label: None, - location: Span::new((), 177..179), - value: Fn { - location: Span::new((), 177..179), - fn_style: expr::FnStyle::BinOp(And), - arguments: vec![ - Arg { - arg_name: Named { - name: "left".to_string(), - label: "left".to_string(), - location: Span::new((), 177..179), - is_validator_param: false, - }, - location: Span::new((), 177..179), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 177..179), - module: None, - name: "Bool".to_string(), - arguments: vec![], - }), - tipo: (), - }, - Arg { - arg_name: Named { - name: "right".to_string(), - label: "right".to_string(), - location: Span::new((), 177..179), - is_validator_param: false, - }, - location: Span::new((), 177..179), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 177..179), - module: None, - name: "Bool".to_string(), - arguments: vec![], - }), - tipo: (), - }, - ], - body: Box::new(BinOp { - location: Span::new((), 177..179), - name: And, - left: Box::new(Var { - location: Span::new((), 177..179), - name: "left".to_string(), - }), - right: Box::new(Var { - location: Span::new((), 177..179), - name: "right".to_string(), - }), - }), - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 177..179), - module: None, - name: "Bool".to_string(), - arguments: vec![], - }), - }, - }, - CallArg { - label: None, - location: Span::new((), 181..182), - value: Var { - location: Span::new((), 181..182), - name: "b".to_string(), - }, - }, - ], - fun: Box::new(Var { - location: Span::new((), 161..173), - name: "combine_with".to_string(), - }), - location: Span::new((), 161..183), - }, - Call { - arguments: vec![ - CallArg { - label: None, - location: Span::new((), 199..200), - value: Var { - location: Span::new((), 199..200), - name: "a".to_string(), - }, - }, - CallArg { - label: None, - location: Span::new((), 202..204), - value: Fn { - location: Span::new((), 202..204), - fn_style: expr::FnStyle::BinOp(Or), - arguments: vec![ - Arg { - arg_name: Named { - name: "left".to_string(), - label: "left".to_string(), - location: Span::new((), 202..204), - is_validator_param: false, - }, - location: Span::new((), 202..204), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 202..204), - module: None, - name: "Bool".to_string(), - arguments: vec![], - }), - tipo: (), - }, - Arg { - arg_name: Named { - name: "right".to_string(), - label: "right".to_string(), - location: Span::new((), 202..204), - is_validator_param: false, - }, - location: Span::new((), 202..204), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 202..204), - module: None, - name: "Bool".to_string(), - arguments: vec![], - }), - tipo: (), - }, - ], - body: Box::new(BinOp { - location: Span::new((), 202..204), - name: Or, - left: Box::new(Var { - location: Span::new((), 202..204), - name: "left".to_string(), - }), - right: Box::new(Var { - location: Span::new((), 202..204), - name: "right".to_string(), - }), - }), - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 202..204), - module: None, - name: "Bool".to_string(), - arguments: vec![], - }), - }, - }, - CallArg { - label: None, - location: Span::new((), 206..207), - value: Var { - location: Span::new((), 206..207), - name: "b".to_string(), - }, - }, - ], - fun: Box::new(Var { - location: Span::new((), 186..198), - name: "combine_with".to_string(), - }), - location: Span::new((), 186..208), - }, - Call { - arguments: vec![ - CallArg { - label: None, - location: Span::new((), 224..225), - value: Var { - location: Span::new((), 224..225), - name: "a".to_string(), - }, - }, - CallArg { - label: None, - location: Span::new((), 227..228), - value: Fn { - location: Span::new((), 227..228), - fn_style: expr::FnStyle::BinOp(AddInt), - arguments: vec![ - Arg { - arg_name: Named { - name: "left".to_string(), - label: "left".to_string(), - location: Span::new((), 227..228), - is_validator_param: false, - }, - location: Span::new((), 227..228), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 227..228), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - Arg { - arg_name: Named { - name: "right".to_string(), - label: "right".to_string(), - location: Span::new((), 227..228), - is_validator_param: false, - }, - location: Span::new((), 227..228), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 227..228), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - ], - body: Box::new(BinOp { - location: Span::new((), 227..228), - name: AddInt, - left: Box::new(Var { - location: Span::new((), 227..228), - name: "left".to_string(), - }), - right: Box::new(Var { - location: Span::new((), 227..228), - name: "right".to_string(), - }), - }), - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 227..228), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - }, - }, - CallArg { - label: None, - location: Span::new((), 230..231), - value: Var { - location: Span::new((), 230..231), - name: "b".to_string(), - }, - }, - ], - fun: Box::new(Var { - location: Span::new((), 211..223), - name: "compute_with".to_string(), - }), - location: Span::new((), 211..232), - }, - Call { - arguments: vec![ - CallArg { - label: None, - location: Span::new((), 248..249), - value: Var { - location: Span::new((), 248..249), - name: "a".to_string(), - }, - }, - CallArg { - label: None, - location: Span::new((), 251..252), - value: Fn { - location: Span::new((), 251..252), - fn_style: expr::FnStyle::BinOp(SubInt), - arguments: vec![ - Arg { - arg_name: Named { - name: "left".to_string(), - label: "left".to_string(), - location: Span::new((), 251..252), - is_validator_param: false, - }, - location: Span::new((), 251..252), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 251..252), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - Arg { - arg_name: Named { - name: "right".to_string(), - label: "right".to_string(), - location: Span::new((), 251..252), - is_validator_param: false, - }, - location: Span::new((), 251..252), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 251..252), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - ], - body: Box::new(BinOp { - location: Span::new((), 251..252), - name: SubInt, - left: Box::new(Var { - location: Span::new((), 251..252), - name: "left".to_string(), - }), - right: Box::new(Var { - location: Span::new((), 251..252), - name: "right".to_string(), - }), - }), - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 251..252), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - }, - }, - CallArg { - label: None, - location: Span::new((), 254..255), - value: Var { - location: Span::new((), 254..255), - name: "b".to_string(), - }, - }, - ], - fun: Box::new(Var { - location: Span::new((), 235..247), - name: "compute_with".to_string(), - }), - location: Span::new((), 235..256), - }, - Call { - arguments: vec![ - CallArg { - label: None, - location: Span::new((), 272..273), - value: Var { - location: Span::new((), 272..273), - name: "a".to_string(), - }, - }, - CallArg { - label: None, - location: Span::new((), 275..276), - value: Fn { - location: Span::new((), 275..276), - fn_style: expr::FnStyle::BinOp(DivInt), - arguments: vec![ - Arg { - arg_name: Named { - name: "left".to_string(), - label: "left".to_string(), - location: Span::new((), 275..276), - is_validator_param: false, - }, - location: Span::new((), 275..276), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 275..276), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - Arg { - arg_name: Named { - name: "right".to_string(), - label: "right".to_string(), - location: Span::new((), 275..276), - is_validator_param: false, - }, - location: Span::new((), 275..276), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 275..276), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - ], - body: Box::new(BinOp { - location: Span::new((), 275..276), - name: DivInt, - left: Box::new(Var { - location: Span::new((), 275..276), - name: "left".to_string(), - }), - right: Box::new(Var { - location: Span::new((), 275..276), - name: "right".to_string(), - }), - }), - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 275..276), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - }, - }, - CallArg { - label: None, - location: Span::new((), 278..279), - value: Var { - location: Span::new((), 278..279), - name: "b".to_string(), - }, - }, - ], - fun: Box::new(Var { - location: Span::new((), 259..271), - name: "compute_with".to_string(), - }), - location: Span::new((), 259..280), - }, - Call { - arguments: vec![ - CallArg { - label: None, - location: Span::new((), 296..297), - value: Var { - location: Span::new((), 296..297), - name: "a".to_string(), - }, - }, - CallArg { - label: None, - location: Span::new((), 299..300), - value: Fn { - location: Span::new((), 299..300), - fn_style: expr::FnStyle::BinOp(MultInt), - arguments: vec![ - Arg { - arg_name: Named { - name: "left".to_string(), - label: "left".to_string(), - location: Span::new((), 299..300), - is_validator_param: false, - }, - location: Span::new((), 299..300), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 299..300), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - Arg { - arg_name: Named { - name: "right".to_string(), - label: "right".to_string(), - location: Span::new((), 299..300), - is_validator_param: false, - }, - location: Span::new((), 299..300), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 299..300), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - ], - body: Box::new(BinOp { - location: Span::new((), 299..300), - name: MultInt, - left: Box::new(Var { - location: Span::new((), 299..300), - name: "left".to_string(), - }), - right: Box::new(Var { - location: Span::new((), 299..300), - name: "right".to_string(), - }), - }), - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 299..300), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - }, - }, - CallArg { - label: None, - location: Span::new((), 302..303), - value: Var { - location: Span::new((), 302..303), - name: "b".to_string(), - }, - }, - ], - fun: Box::new(Var { - location: Span::new((), 283..295), - name: "compute_with".to_string(), - }), - location: Span::new((), 283..304), - }, - Call { - arguments: vec![ - CallArg { - label: None, - location: Span::new((), 320..321), - value: Var { - location: Span::new((), 320..321), - name: "a".to_string(), - }, - }, - CallArg { - label: None, - location: Span::new((), 323..324), - value: Fn { - location: Span::new((), 323..324), - fn_style: expr::FnStyle::BinOp(ModInt), - arguments: vec![ - Arg { - arg_name: Named { - name: "left".to_string(), - label: "left".to_string(), - location: Span::new((), 323..324), - is_validator_param: false, - }, - location: Span::new((), 323..324), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 323..324), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - Arg { - arg_name: Named { - name: "right".to_string(), - label: "right".to_string(), - location: Span::new((), 323..324), - is_validator_param: false, - }, - location: Span::new((), 323..324), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 323..324), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - tipo: (), - }, - ], - body: Box::new(BinOp { - location: Span::new((), 323..324), - name: ModInt, - left: Box::new(Var { - location: Span::new((), 323..324), - name: "left".to_string(), - }), - right: Box::new(Var { - location: Span::new((), 323..324), - name: "right".to_string(), - }), - }), - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 323..324), - module: None, - name: "Int".to_string(), - arguments: vec![], - }), - }, - }, - CallArg { - label: None, - location: Span::new((), 326..327), - value: Var { - location: Span::new((), 326..327), - name: "b".to_string(), - }, - }, - ], - fun: Box::new(Var { - location: Span::new((), 307..319), - name: "compute_with".to_string(), - }), - location: Span::new((), 307..328), - }, - ], - }, - doc: None, - location: Span::new((), 0..8), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 329, - can_error: true, - })], - ); -} - -#[test] -fn parse_unicode_offset_1() { - use expr::UntypedExpr::*; - - let code = indoc! {r#" - fn foo() { - let x = "★" - x - } - "#}; - - assert_definitions( - code, - vec![ast::Definition::Fn(Function { - arguments: vec![], - body: Sequence { - location: Span::new((), 13..30), - expressions: vec![ - Assignment { - location: Span::new((), 13..26), - value: Box::new(ByteArray { - location: Span::new((), 21..26), - bytes: vec![226, 152, 133], - preferred_format: ast::ByteArrayFormatPreference::Utf8String, - }), - pattern: ast::Pattern::Var { - location: Span::new((), 17..18), - name: "x".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - Var { - location: Span::new((), 29..30), - name: "x".to_string(), - }, - ], - }, - doc: None, - location: Span::new((), 0..8), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 31, - can_error: true, - })], - ) -} - -#[test] -fn parse_unicode_offset_2() { - use expr::UntypedExpr::*; - - let code = indoc! {r#" - fn foo() { - let x = "*" - x - } - "#}; - - assert_definitions( - code, - vec![ast::Definition::Fn(Function { - arguments: vec![], - body: Sequence { - location: Span::new((), 13..28), - expressions: vec![ - Assignment { - location: Span::new((), 13..24), - value: Box::new(ByteArray { - location: Span::new((), 21..24), - bytes: vec![42], - preferred_format: ast::ByteArrayFormatPreference::Utf8String, - }), - pattern: ast::Pattern::Var { - location: Span::new((), 17..18), - name: "x".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - Var { - location: Span::new((), 27..28), - name: "x".to_string(), - }, - ], - }, - doc: None, - location: Span::new((), 0..8), - name: "foo".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 29, - can_error: true, - })], - ) -} diff --git a/crates/aiken-lang/src/tipo/pretty.rs b/crates/aiken-lang/src/tipo/pretty.rs index 8dc9d29e1..c851d2199 100644 --- a/crates/aiken-lang/src/tipo/pretty.rs +++ b/crates/aiken-lang/src/tipo/pretty.rs @@ -168,7 +168,7 @@ fn qualify_type_name(module: &String, typ_name: &str) -> Document<'static> { } #[cfg(test)] -mod test { +mod tests { use std::cell::RefCell; use pretty_assertions::assert_eq; diff --git a/crates/aiken-project/src/blueprint/mod.rs b/crates/aiken-project/src/blueprint/mod.rs index 168953c8d..8998ae8dd 100644 --- a/crates/aiken-project/src/blueprint/mod.rs +++ b/crates/aiken-project/src/blueprint/mod.rs @@ -140,7 +140,7 @@ impl From<&Config> for Preamble { } #[cfg(test)] -mod test { +mod tests { use super::*; use aiken_lang::builtins; use schema::{Data, Declaration, Items, Schema}; diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index 4bbfedbbd..16a42e269 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -1033,7 +1033,7 @@ Here's the types I followed and that led me to this problem: } #[cfg(test)] -pub mod test { +pub mod tests { use super::*; use proptest::prelude::*; use serde_json::{self, json, Value}; diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index eb962ae71..656511104 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -177,7 +177,7 @@ impl Validator { } #[cfg(test)] -mod test { +mod tests { use assert_json_diff::assert_json_eq; use serde_json::{self, json}; diff --git a/crates/flat-rs/tests/flat_test.rs b/crates/flat-rs/tests/flat_test.rs index 3faa5b3bd..cee23e052 100644 --- a/crates/flat-rs/tests/flat_test.rs +++ b/crates/flat-rs/tests/flat_test.rs @@ -1,126 +1,123 @@ -#[cfg(test)] -mod test { - use flat_rs::filler::Filler; - use flat_rs::{decode, encode}; - use proptest::prelude::*; - - prop_compose! { - fn arb_big_vec()(size in 255..300, element in any::()) -> Vec { - (0..size).map(|_| element).collect() - } +use flat_rs::filler::Filler; +use flat_rs::{decode, encode}; +use proptest::prelude::*; + +prop_compose! { + fn arb_big_vec()(size in 255..300, element in any::()) -> Vec { + (0..size).map(|_| element).collect() } +} - #[test] - fn encode_bool() { - let bytes = encode(&true).unwrap(); +#[test] +fn encode_bool() { + let bytes = encode(&true).unwrap(); + + assert_eq!(bytes, vec![0b10000001]); + + let decoded: bool = decode(bytes.as_slice()).unwrap(); + + assert!(decoded); + + let bytes = encode(&false).unwrap(); - assert_eq!(bytes, vec![0b10000001]); + assert_eq!(bytes, vec![0b00000001]); - let decoded: bool = decode(bytes.as_slice()).unwrap(); + let decoded: bool = decode(bytes.as_slice()).unwrap(); - assert!(decoded); + assert!(!decoded); +} + +#[test] +fn encode_u8() { + let bytes = encode(&3_u8).unwrap(); - let bytes = encode(&false).unwrap(); + assert_eq!(bytes, vec![0b00000011, 0b00000001]); - assert_eq!(bytes, vec![0b00000001]); + let decoded: u8 = decode(bytes.as_slice()).unwrap(); + + assert_eq!(decoded, 3_u8); +} + +proptest! { + #[test] + fn encode_isize(x: isize) { + let bytes = encode(&x).unwrap(); + let decoded: isize = decode(&bytes).unwrap(); + assert_eq!(decoded, x); + } - let decoded: bool = decode(bytes.as_slice()).unwrap(); + #[test] + fn encode_usize(x: usize) { + let bytes = encode(&x).unwrap(); + let decoded: usize = decode(&bytes).unwrap(); + assert_eq!(decoded, x); + } - assert!(!decoded); + #[test] + fn encode_char(c: char) { + let bytes = encode(&c).unwrap(); + let decoded: char = decode(&bytes).unwrap(); + assert_eq!(decoded, c); } #[test] - fn encode_u8() { - let bytes = encode(&3_u8).unwrap(); + fn encode_string(str: String) { + let bytes = encode(&str).unwrap(); + let decoded: String = decode(&bytes).unwrap(); + assert_eq!(decoded, str); + } - assert_eq!(bytes, vec![0b00000011, 0b00000001]); + #[test] + fn encode_vec_u8(xs: Vec) { + let bytes = encode(&xs).unwrap(); + let decoded: Vec = decode(&bytes).unwrap(); + assert_eq!(decoded, xs); + } - let decoded: u8 = decode(bytes.as_slice()).unwrap(); + #[test] + fn encode_big_vec_u8(xs in arb_big_vec()) { + let bytes = encode(&xs).unwrap(); + let decoded: Vec = decode(&bytes).unwrap(); + assert_eq!(decoded, xs); + } - assert_eq!(decoded, 3_u8); + #[test] + fn encode_arr_u8(xs: Vec) { + let bytes = encode(&xs.as_slice()).unwrap(); + let decoded: Vec = decode(&bytes).unwrap(); + assert_eq!(decoded, xs); } - proptest! { - #[test] - fn encode_isize(x: isize) { - let bytes = encode(&x).unwrap(); - let decoded: isize = decode(&bytes).unwrap(); - assert_eq!(decoded, x); - } - - #[test] - fn encode_usize(x: usize) { - let bytes = encode(&x).unwrap(); - let decoded: usize = decode(&bytes).unwrap(); - assert_eq!(decoded, x); - } - - #[test] - fn encode_char(c: char) { - let bytes = encode(&c).unwrap(); - let decoded: char = decode(&bytes).unwrap(); - assert_eq!(decoded, c); - } - - #[test] - fn encode_string(str: String) { - let bytes = encode(&str).unwrap(); - let decoded: String = decode(&bytes).unwrap(); - assert_eq!(decoded, str); - } - - #[test] - fn encode_vec_u8(xs: Vec) { - let bytes = encode(&xs).unwrap(); - let decoded: Vec = decode(&bytes).unwrap(); - assert_eq!(decoded, xs); - } - - #[test] - fn encode_big_vec_u8(xs in arb_big_vec()) { - let bytes = encode(&xs).unwrap(); - let decoded: Vec = decode(&bytes).unwrap(); - assert_eq!(decoded, xs); - } - - #[test] - fn encode_arr_u8(xs: Vec) { - let bytes = encode(&xs.as_slice()).unwrap(); - let decoded: Vec = decode(&bytes).unwrap(); - assert_eq!(decoded, xs); - } - - #[test] - fn encode_big_arr_u8(xs in arb_big_vec()) { - let bytes = encode(&xs.as_slice()).unwrap(); - let decoded: Vec = decode(&bytes).unwrap(); - assert_eq!(decoded, xs); - } - - #[test] - fn encode_boxed(c: char) { - let boxed = Box::new(c); - let bytes = encode(&boxed).unwrap(); - let decoded: char = decode(&bytes).unwrap(); - assert_eq!(decoded, c); - } + #[test] + fn encode_big_arr_u8(xs in arb_big_vec()) { + let bytes = encode(&xs.as_slice()).unwrap(); + let decoded: Vec = decode(&bytes).unwrap(); + assert_eq!(decoded, xs); } #[test] - fn encode_filler() { - let bytes = encode(&Filler::FillerEnd).unwrap(); + fn encode_boxed(c: char) { + let boxed = Box::new(c); + let bytes = encode(&boxed).unwrap(); + let decoded: char = decode(&bytes).unwrap(); + assert_eq!(decoded, c); + } +} - assert_eq!(bytes, vec![0b0000001, 0b00000001]); +#[test] +fn encode_filler() { + let bytes = encode(&Filler::FillerEnd).unwrap(); - let bytes = encode(&Filler::FillerStart(Box::new(Filler::FillerEnd))).unwrap(); + assert_eq!(bytes, vec![0b0000001, 0b00000001]); - assert_eq!(bytes, vec![0b0000001, 0b00000001]); + let bytes = encode(&Filler::FillerStart(Box::new(Filler::FillerEnd))).unwrap(); - let bytes = encode(&Filler::FillerStart(Box::new(Filler::FillerStart( - Box::new(Filler::FillerEnd), - )))) - .unwrap(); + assert_eq!(bytes, vec![0b0000001, 0b00000001]); - assert_eq!(bytes, vec![0b0000001, 0b00000001]); - } + let bytes = encode(&Filler::FillerStart(Box::new(Filler::FillerStart( + Box::new(Filler::FillerEnd), + )))) + .unwrap(); + + assert_eq!(bytes, vec![0b0000001, 0b00000001]); } diff --git a/crates/flat-rs/tests/zigzag_test.rs b/crates/flat-rs/tests/zigzag_test.rs index f67acc770..5f6522dad 100644 --- a/crates/flat-rs/tests/zigzag_test.rs +++ b/crates/flat-rs/tests/zigzag_test.rs @@ -1,21 +1,18 @@ -#[cfg(test)] -mod test { - use flat_rs::zigzag::{to_isize, to_usize}; - use proptest::prelude::*; +use flat_rs::zigzag::{to_isize, to_usize}; +use proptest::prelude::*; - proptest! { - #[test] - fn zigzag(i: isize) { - let u = to_usize(i); - let converted_i = to_isize(u); - assert_eq!(converted_i, i); - } +proptest! { + #[test] + fn zigzag(i: isize) { + let u = to_usize(i); + let converted_i = to_isize(u); + assert_eq!(converted_i, i); + } - #[test] - fn zagzig(u: usize) { - let i = to_isize(u); - let converted_u = to_usize(i); - assert_eq!(converted_u, u); - } + #[test] + fn zagzig(u: usize) { + let i = to_isize(u); + let converted_u = to_usize(i); + assert_eq!(converted_u, u); } } diff --git a/crates/uplc/src/flat.rs b/crates/uplc/src/flat.rs index a84d19587..159ecdac9 100644 --- a/crates/uplc/src/flat.rs +++ b/crates/uplc/src/flat.rs @@ -808,7 +808,7 @@ pub fn decode_constant_tag(d: &mut Decoder) -> Result { } #[cfg(test)] -mod test { +mod tests { use super::{Constant, Program, Term}; use crate::{ ast::{DeBruijn, Name, Type}, diff --git a/crates/uplc/src/machine/cost_model.rs b/crates/uplc/src/machine/cost_model.rs index bfe648b2e..d50b89ecc 100644 --- a/crates/uplc/src/machine/cost_model.rs +++ b/crates/uplc/src/machine/cost_model.rs @@ -3213,7 +3213,7 @@ impl TryFrom for StepKind { } #[cfg(test)] -mod test { +mod tests { use super::*; use pretty_assertions::assert_eq; diff --git a/crates/uplc/src/optimize/shrinker.rs b/crates/uplc/src/optimize/shrinker.rs index bfb75a5a1..2b2a52ac0 100644 --- a/crates/uplc/src/optimize/shrinker.rs +++ b/crates/uplc/src/optimize/shrinker.rs @@ -503,7 +503,7 @@ fn replace_identity_usage(term: &Term, original: Rc) -> Term { } #[cfg(test)] -mod test { +mod tests { use pallas_primitives::babbage::{BigInt, PlutusData}; use pretty_assertions::assert_eq; diff --git a/crates/uplc/src/parser.rs b/crates/uplc/src/parser.rs index 370dd5f44..bfc9c4829 100644 --- a/crates/uplc/src/parser.rs +++ b/crates/uplc/src/parser.rs @@ -282,7 +282,7 @@ peg::parser! { } #[cfg(test)] -mod test { +mod tests { use num_bigint::BigInt; use crate::ast::{Constant, Name, Program, Term, Type, Unique};