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 00000000..320723f2 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/block.rs @@ -0,0 +1,20 @@ +use chumsky::prelude::*; + +use crate::{ + expr::UntypedExpr, + parser::{error::ParseError, token::Token}, +}; + +pub fn parser( + seq_r: Recursive<'_, Token, UntypedExpr, ParseError>, +) -> impl 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), + ), + )) +} 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 00000000..66b356f7 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/bytearray.rs @@ -0,0 +1,14 @@ +use chumsky::prelude::*; + +use crate::{ + expr::UntypedExpr, + parser::{error::ParseError, token::Token, utils}, +}; + +pub fn parser() -> impl Parser { + utils::bytearray().map_with_span(|(preferred_format, bytes), span| UntypedExpr::ByteArray { + location: span, + bytes, + preferred_format, + }) +} 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 00000000..0d186818 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/if_else.rs @@ -0,0 +1,48 @@ +use chumsky::prelude::*; + +use crate::{ + ast, + expr::UntypedExpr, + parser::{error::ParseError, token::Token}, +}; + +use super::block; + +pub fn parser<'a>( + seq_r: Recursive<'a, Token, UntypedExpr, ParseError>, + r: Recursive<'a, Token, UntypedExpr, ParseError>, +) -> impl Parser + 'a { + just(Token::If) + .ignore_then(r.clone().then(block(seq_r.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(seq_r.clone())).map_with_span( + |(condition, body), span| ast::IfBranch { + condition, + body, + location: span, + }, + )) + .repeated(), + ) + .then_ignore(just(Token::Else)) + .then(block(seq_r)) + .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), + } + }) +} 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 00000000..aa73a32d --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/int.rs @@ -0,0 +1,16 @@ +use chumsky::prelude::*; + +use crate::{ + expr::UntypedExpr, + parser::{error::ParseError, token::Token}, +}; + +pub fn parser() -> impl Parser { + select! { Token::Int {value, base} => (value, base)}.map_with_span(|(value, base), span| { + UntypedExpr::Int { + location: span, + value, + base, + } + }) +} 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 00000000..87f37bbe --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/list.rs @@ -0,0 +1,29 @@ +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, + }) +} diff --git a/crates/aiken-lang/src/parser/expr/mod.rs b/crates/aiken-lang/src/parser/expr/mod.rs index ca360101..41d1fb50 100644 --- a/crates/aiken-lang/src/parser/expr/mod.rs +++ b/crates/aiken-lang/src/parser/expr/mod.rs @@ -1,15 +1,33 @@ use chumsky::prelude::*; use vec1::{vec1, Vec1}; +mod block; +mod bytearray; +mod if_else; +mod int; +mod list; +mod record; +mod record_update; mod sequence; pub mod string; +mod tuple; +mod var; +pub use block::parser as block; +pub use bytearray::parser as bytearray; +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; use crate::{ ast::{self, Span}, expr::UntypedExpr, - parser::error, }; use super::{error::ParseError, token::Token}; @@ -18,241 +36,6 @@ pub fn parser( seq_r: Recursive<'_, Token, UntypedExpr, ParseError>, ) -> impl Parser + '_ { recursive(|r| { - let string_parser = - select! {Token::String {value} => value}.map_with_span(|value, span| { - UntypedExpr::String { - location: span, - value, - } - }); - - let int_parser = select! { Token::Int {value, base} => (value, base)}.map_with_span( - |(value, base), span| 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: 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 { - 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 = 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(), - } - }); - - 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), - )); - - 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: 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: 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, - } - }); - let field_access_constructor = select! {Token::Name { name } => name} .map_with_span(|module, span| (module, span)) .then_ignore(just(Token::Dot)) @@ -266,66 +49,6 @@ pub fn parser( }), }); - let var_parser = select! { - Token::Name { name } => name, - Token::UpName { name } => name, - } - .map_with_span(|name, span| 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| UntypedExpr::Tuple { - location: span, - elems, - }); - - let bytearray = utils::bytearray().map_with_span(|(preferred_format, bytes), span| { - 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| 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() @@ -523,57 +246,23 @@ pub fn parser( }, ); - 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); - - 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, + string(), + int(), + record_update(r), + record(r), field_access_constructor, - var_parser, - tuple, - bytearray, - list_parser, + var(), + tuple(r), + bytearray(), + list(r), anon_fn_parser, anon_binop_parser, - block_parser, + block(seq_r), when_parser, let_parser, expect_parser, - if_parser, + if_else(seq_r, r), )); // Parsing a function call into the appropriate structure 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 00000000..d27184f7 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/record.rs @@ -0,0 +1,152 @@ +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, + } + }) +} 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 00000000..11dc9ea6 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/record_update.rs @@ -0,0 +1,86 @@ +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(), + } + }) +} diff --git a/crates/aiken-lang/src/parser/expr/string.rs b/crates/aiken-lang/src/parser/expr/string.rs index 1f004fd4..ca5c94ba 100644 --- a/crates/aiken-lang/src/parser/expr/string.rs +++ b/crates/aiken-lang/src/parser/expr/string.rs @@ -1,4 +1,17 @@ -use crate::{ast, expr::UntypedExpr}; +use chumsky::prelude::*; + +use crate::{ + ast, + expr::UntypedExpr, + parser::{error::ParseError, token::Token}, +}; + +pub fn parser() -> impl Parser { + select! {Token::String {value} => value}.map_with_span(|value, span| UntypedExpr::String { + location: span, + value, + }) +} /// Interpret bytearray string literals written as utf-8 strings, as strings. /// 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 00000000..6f1e1bbf --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/tuple.rs @@ -0,0 +1,23 @@ +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, + }) +} 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 00000000..b80c1da8 --- /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, + }) +}