test(parser): rename definitions to definition and more tests

This commit is contained in:
rvcas
2023-07-03 14:58:04 -04:00
parent baf807ca2d
commit 6b05d6a91e
28 changed files with 337 additions and 362 deletions

View File

@@ -0,0 +1,65 @@
use chumsky::prelude::*;
use crate::{
ast,
parser::{annotation, error::ParseError, token::Token, utils},
};
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
utils::public()
.or_not()
.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: public.is_some(),
name,
annotation,
value: Box::new(value),
tipo: (),
})
})
}
pub fn value() -> impl Parser<Token, ast::Constant, Error = ParseError> {
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 =
utils::bytearray().map_with_span(|(preferred_format, bytes), span| {
ast::Constant::ByteArray {
location: span,
bytes,
preferred_format,
}
});
choice((
constant_string_parser,
constant_int_parser,
constant_bytearray_parser,
))
}

View File

@@ -0,0 +1,94 @@
use chumsky::prelude::*;
use crate::{
ast,
parser::{annotation, error::ParseError, token::Token, utils},
};
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
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::public()
.then(just(Token::Opaque).ignored().or_not())
.or_not()
.then(utils::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![],
})
})
}
fn labeled_constructor_type_args(
) -> impl Parser<Token, Vec<ast::RecordConstructorArg<()>>, 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))
}

View File

@@ -0,0 +1,89 @@
use chumsky::prelude::*;
use crate::{
ast,
expr::UntypedExpr,
parser::{annotation, error::ParseError, expr, token::Token, utils},
};
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
utils::public()
.or_not()
.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(
|((((opt_pub, 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: opt_pub.is_some(),
return_annotation,
return_type: (),
can_error: true,
})
},
)
}
pub fn param(is_validator_param: bool) -> impl Parser<Token, ast::UntypedArg, Error = ParseError> {
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,
})
}

View File

@@ -0,0 +1,83 @@
use chumsky::prelude::*;
use crate::{
ast,
parser::{error::ParseError, token::Token},
};
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
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 chumsky::Parser;
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");
}
}

View File

@@ -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<Token, ast::UntypedDefinition, Error = ParseError> {
choice((
import(),
data_type(),
type_alias(),
validator(),
function(),
test(),
constant(),
))
}

View File

@@ -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: [],
},
)

View File

@@ -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: [],
},
)

View File

@@ -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,
},
],
},
)

View File

@@ -0,0 +1,37 @@
use chumsky::prelude::*;
use crate::{
ast,
expr::UntypedExpr,
parser::{error::ParseError, expr, token::Token},
};
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
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(),
})
})
}

View File

@@ -0,0 +1,25 @@
use chumsky::prelude::*;
use crate::{
ast,
parser::{annotation, error::ParseError, token::Token, utils},
};
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
utils::public()
.or_not()
.then(utils::type_name_with_args())
.then_ignore(just(Token::Equal))
.then(annotation())
.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: (),
})
})
}

View File

@@ -0,0 +1,60 @@
use chumsky::prelude::*;
use crate::{
ast,
parser::{error::ParseError, token::Token},
};
use super::function;
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
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,
})
})
}