717 lines
27 KiB
Rust
717 lines
27 KiB
Rust
use chumsky::prelude::*;
|
|
|
|
use crate::{ast, error::ParseError, expr, token::Token};
|
|
|
|
pub fn module_parser(
|
|
kind: ast::ModuleKind,
|
|
) -> impl Parser<Token, ast::UntypedModule, Error = ParseError> {
|
|
choice((
|
|
import_parser(),
|
|
data_parser(),
|
|
type_alias_parser(),
|
|
fn_parser(),
|
|
))
|
|
.repeated()
|
|
.then_ignore(end())
|
|
.map(move |definitions| ast::UntypedModule {
|
|
kind,
|
|
definitions,
|
|
docs: vec![],
|
|
name: vec![],
|
|
type_info: (),
|
|
})
|
|
}
|
|
|
|
pub fn import_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))
|
|
.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 {
|
|
module,
|
|
as_name,
|
|
unqualified: unqualified.unwrap_or_default(),
|
|
package: (),
|
|
location: span,
|
|
},
|
|
)
|
|
}
|
|
|
|
pub fn data_parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
|
|
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))
|
|
.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,
|
|
documentation: 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,
|
|
documentation: 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 {
|
|
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<Token, ast::UntypedDefinition, Error = ParseError> {
|
|
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 {
|
|
alias,
|
|
annotation,
|
|
doc: None,
|
|
location: span,
|
|
parameters: parameters.unwrap_or_default(),
|
|
public: opt_pub.is_some(),
|
|
tipo: (),
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn fn_parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
|
|
pub_parser()
|
|
.or_not()
|
|
.then_ignore(just(Token::Fn))
|
|
.then(select! {Token::Name {name} => name})
|
|
.then(
|
|
fn_param_parser()
|
|
.separated_by(just(Token::Comma))
|
|
.delimited_by(just(Token::LeftParen), just(Token::RightParen)),
|
|
)
|
|
.then(just(Token::RArrow).ignore_then(type_parser()).or_not())
|
|
.then_ignore(just(Token::LeftBrace))
|
|
.then(expr_seq_parser())
|
|
.then_ignore(just(Token::RightBrace))
|
|
.map_with_span(
|
|
|((((opt_pub, name), arguments), return_annotation), body), span| {
|
|
ast::UntypedDefinition::Fn {
|
|
arguments,
|
|
body,
|
|
doc: None,
|
|
location: span,
|
|
name,
|
|
public: opt_pub.is_some(),
|
|
return_annotation,
|
|
return_type: (),
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
pub fn fn_param_parser() -> 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::LabeledDiscard {
|
|
label,
|
|
name,
|
|
location: span,
|
|
}),
|
|
select! {Token::DiscardName {name} => name}.map_with_span(|name, span| {
|
|
ast::ArgName::Discard {
|
|
name,
|
|
location: span,
|
|
}
|
|
}),
|
|
select! {Token::Name {name} => name}
|
|
.then(select! {Token::Name {name} => name})
|
|
.map_with_span(|(label, name), span| ast::ArgName::NamedLabeled {
|
|
label,
|
|
name,
|
|
location: span,
|
|
}),
|
|
select! {Token::Name {name} => name}.map_with_span(|name, span| ast::ArgName::Named {
|
|
name,
|
|
location: span,
|
|
}),
|
|
))
|
|
.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 expr_seq_parser() -> impl Parser<Token, expr::UntypedExpr, Error = ParseError> {
|
|
recursive(|r| {
|
|
choice((
|
|
just(Token::Try)
|
|
.ignore_then(pattern_parser())
|
|
.then(just(Token::Colon).ignore_then(type_parser()).or_not())
|
|
.then_ignore(just(Token::Equal))
|
|
.then(expr_parser())
|
|
.then(r.clone())
|
|
.map_with_span(|(((pattern, annotation), value), then_), span| {
|
|
expr::UntypedExpr::Try {
|
|
location: span,
|
|
value: Box::new(value),
|
|
pattern,
|
|
then: Box::new(then_),
|
|
annotation,
|
|
}
|
|
}),
|
|
expr_parser()
|
|
.then(r.repeated())
|
|
.map_with_span(|(expr, exprs), span| {
|
|
exprs
|
|
.into_iter()
|
|
.fold(expr, |acc, elem| acc.append_in_sequence(elem))
|
|
}),
|
|
))
|
|
})
|
|
}
|
|
|
|
pub fn expr_parser() -> impl Parser<Token, expr::UntypedExpr, Error = ParseError> {}
|
|
|
|
pub fn expr_unit_parser() -> impl Parser<Token, expr::UntypedExpr, Error = ParseError> {}
|
|
|
|
pub fn type_parser() -> impl Parser<Token, ast::Annotation, Error = ParseError> {
|
|
recursive(|r| {
|
|
choice((
|
|
select! {Token::DiscardName { name } => name}.map_with_span(|name, span| {
|
|
ast::Annotation::Hole {
|
|
location: span,
|
|
name,
|
|
}
|
|
}),
|
|
just(Token::Fn)
|
|
.ignore_then(
|
|
r.clone()
|
|
.separated_by(just(Token::Comma))
|
|
.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),
|
|
}),
|
|
select! {Token::UpName { name } => name}
|
|
.then(
|
|
r.clone()
|
|
.separated_by(just(Token::Comma))
|
|
.delimited_by(just(Token::LeftParen), just(Token::RightParen))
|
|
.or_not(),
|
|
)
|
|
.map_with_span(|(name, arguments), span| ast::Annotation::Constructor {
|
|
location: span,
|
|
module: None,
|
|
name,
|
|
arguments: arguments.unwrap_or_default(),
|
|
}),
|
|
select! {Token::Name { name } => name}
|
|
.then(
|
|
just(Token::Dot)
|
|
.ignore_then(select! {Token::UpName {name} => name})
|
|
.then(
|
|
r.separated_by(just(Token::Comma))
|
|
.delimited_by(just(Token::LeftParen), just(Token::RightParen))
|
|
.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 {
|
|
ast::Annotation::Var {
|
|
location: span,
|
|
name: mod_name,
|
|
}
|
|
}
|
|
}),
|
|
))
|
|
})
|
|
}
|
|
|
|
pub fn labeled_constructor_type_args(
|
|
) -> impl Parser<Token, Vec<ast::RecordConstructorArg<()>>, 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))
|
|
.delimited_by(just(Token::LeftBrace), just(Token::RightBrace))
|
|
}
|
|
|
|
pub fn type_name_with_args() -> impl Parser<Token, (String, Option<Vec<String>>), Error = ParseError>
|
|
{
|
|
just(Token::Type).ignore_then(
|
|
select! {Token::UpName { name } => name}.then(
|
|
select! {Token::Name { name } => name}
|
|
.separated_by(just(Token::Comma))
|
|
.delimited_by(just(Token::LeftParen), just(Token::RightParen))
|
|
.or_not(),
|
|
),
|
|
)
|
|
}
|
|
|
|
pub fn pub_parser() -> impl Parser<Token, (), Error = ParseError> {
|
|
just(Token::Pub).ignored()
|
|
}
|
|
|
|
pub fn pattern_parser() -> impl Parser<Token, ast::UntypedPattern, Error = ParseError> {
|
|
recursive(|r| {
|
|
let 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,
|
|
}),
|
|
r.map_with_span(|pattern, span| ast::CallArg {
|
|
location: span,
|
|
value: pattern,
|
|
label: None,
|
|
}),
|
|
));
|
|
|
|
let constructor_pattern_args_parser = constructor_pattern_arg_parser
|
|
.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))
|
|
.or_not()
|
|
.map(|opt_args| {
|
|
opt_args
|
|
.map(|(a, b)| (a, b.is_some()))
|
|
.unwrap_or_else(|| (vec![], 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))) = opt_pattern {
|
|
ast::UntypedPattern::Constructor {
|
|
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)), span| {
|
|
ast::UntypedPattern::Constructor {
|
|
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::String {value} => value}.map_with_span(|value, span| {
|
|
ast::UntypedPattern::String {
|
|
location: span,
|
|
value,
|
|
}
|
|
}),
|
|
select! {Token::Int {value} => value}.map_with_span(|value, span| {
|
|
ast::UntypedPattern::Int {
|
|
location: span,
|
|
value,
|
|
}
|
|
}),
|
|
))
|
|
.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
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use chumsky::prelude::*;
|
|
|
|
use crate::{
|
|
ast::{self, Span, SrcId},
|
|
lexer,
|
|
parser::module_parser,
|
|
};
|
|
|
|
#[test]
|
|
fn simple() {
|
|
let code = r#"
|
|
use std/list
|
|
use std/address.{Address as A, thing as w}
|
|
use std/tx as t
|
|
|
|
type Option(a) {
|
|
Some(a, Int)
|
|
None
|
|
Wow { name: Int, age: Int }
|
|
}
|
|
|
|
pub opaque type User {
|
|
name: _w
|
|
}
|
|
|
|
type Thing = Option(Int)
|
|
|
|
pub type Me = Option(String)
|
|
|
|
pub fn add_one(a) {
|
|
a + 1
|
|
}
|
|
|
|
pub fn add_one(a: Int) -> Int {
|
|
[1, 2, 3]
|
|
|> list.map(fn(x) { x + a })
|
|
}
|
|
"#;
|
|
let len = code.chars().count();
|
|
|
|
let span = |i| Span::new(SrcId::empty(), i..i + 1);
|
|
|
|
let tokens = lexer::lexer()
|
|
.parse(chumsky::Stream::from_iter(
|
|
span(len),
|
|
code.chars().enumerate().map(|(i, c)| (c, span(i))),
|
|
))
|
|
.unwrap();
|
|
|
|
let res = module_parser(ast::ModuleKind::Script)
|
|
.parse(chumsky::Stream::from_iter(span(len), tokens.into_iter()))
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
res,
|
|
ast::UntypedModule {
|
|
docs: vec![],
|
|
kind: ast::ModuleKind::Script,
|
|
name: vec![],
|
|
type_info: (),
|
|
definitions: vec![
|
|
ast::UntypedDefinition::Use {
|
|
location: Span::new(SrcId::empty(), 13..25),
|
|
module: vec!["std".to_string(), "list".to_string()],
|
|
as_name: None,
|
|
unqualified: vec![],
|
|
package: (),
|
|
},
|
|
ast::UntypedDefinition::Use {
|
|
location: Span::new(SrcId::empty(), 38..80),
|
|
module: vec!["std".to_string(), "address".to_string()],
|
|
as_name: None,
|
|
unqualified: vec![
|
|
ast::UnqualifiedImport {
|
|
as_name: Some("A".to_string()),
|
|
location: Span::new(SrcId::empty(), 55..67),
|
|
layer: Default::default(),
|
|
name: "Address".to_string()
|
|
},
|
|
ast::UnqualifiedImport {
|
|
as_name: Some("w".to_string()),
|
|
location: Span::new(SrcId::empty(), 69..79),
|
|
layer: Default::default(),
|
|
name: "thing".to_string()
|
|
}
|
|
],
|
|
package: (),
|
|
},
|
|
ast::UntypedDefinition::Use {
|
|
location: Span::new(SrcId::empty(), 93..108),
|
|
module: vec!["std".to_string(), "tx".to_string()],
|
|
as_name: Some("t".to_string()),
|
|
unqualified: vec![],
|
|
package: (),
|
|
},
|
|
ast::UntypedDefinition::DataType {
|
|
location: Span::new(SrcId::empty(), 122..246),
|
|
constructors: vec![
|
|
ast::RecordConstructor {
|
|
location: Span::new(SrcId::empty(), 155..167),
|
|
name: "Some".to_string(),
|
|
arguments: vec![
|
|
ast::RecordConstructorArg {
|
|
label: None,
|
|
annotation: ast::Annotation::Var {
|
|
location: Span::new(SrcId::empty(), 160..161),
|
|
name: "a".to_string(),
|
|
},
|
|
location: Span::new(SrcId::empty(), 160..161),
|
|
tipo: (),
|
|
doc: None,
|
|
},
|
|
ast::RecordConstructorArg {
|
|
label: None,
|
|
annotation: ast::Annotation::Constructor {
|
|
location: Span::new(SrcId::empty(), 163..166),
|
|
module: None,
|
|
name: "Int".to_string(),
|
|
arguments: vec![],
|
|
},
|
|
location: Span::new(SrcId::empty(), 163..166),
|
|
tipo: (),
|
|
doc: None,
|
|
},
|
|
],
|
|
documentation: None,
|
|
sugar: false,
|
|
},
|
|
ast::RecordConstructor {
|
|
location: Span::new(SrcId::empty(), 184..188),
|
|
name: "None".to_string(),
|
|
arguments: vec![],
|
|
documentation: None,
|
|
sugar: false,
|
|
},
|
|
ast::RecordConstructor {
|
|
location: Span::new(SrcId::empty(), 205..232),
|
|
name: "Wow".to_string(),
|
|
arguments: vec![
|
|
ast::RecordConstructorArg {
|
|
label: Some("name".to_string(),),
|
|
annotation: ast::Annotation::Constructor {
|
|
location: Span::new(SrcId::empty(), 217..220),
|
|
module: None,
|
|
name: "Int".to_string(),
|
|
arguments: vec![],
|
|
},
|
|
location: Span::new(SrcId::empty(), 211..220),
|
|
tipo: (),
|
|
doc: None,
|
|
},
|
|
ast::RecordConstructorArg {
|
|
label: Some("age".to_string(),),
|
|
annotation: ast::Annotation::Constructor {
|
|
location: Span::new(SrcId::empty(), 227..230),
|
|
module: None,
|
|
name: "Int".to_string(),
|
|
arguments: vec![],
|
|
},
|
|
location: Span::new(SrcId::empty(), 222..230),
|
|
tipo: (),
|
|
doc: None,
|
|
},
|
|
],
|
|
documentation: None,
|
|
sugar: false,
|
|
},
|
|
],
|
|
doc: None,
|
|
name: "Option".to_string(),
|
|
opaque: false,
|
|
parameters: vec!["a".to_string(),],
|
|
public: false,
|
|
typed_parameters: vec![],
|
|
},
|
|
ast::UntypedDefinition::DataType {
|
|
location: Span::new(SrcId::empty(), 260..321),
|
|
constructors: vec![ast::RecordConstructor {
|
|
location: Span::new(SrcId::empty(), 281..321),
|
|
name: "User".to_string(),
|
|
arguments: vec![ast::RecordConstructorArg {
|
|
label: Some("name".to_string(),),
|
|
annotation: ast::Annotation::Hole {
|
|
location: Span::new(SrcId::empty(), 305..307),
|
|
name: "_w".to_string(),
|
|
},
|
|
location: Span::new(SrcId::empty(), 299..307),
|
|
tipo: (),
|
|
doc: None,
|
|
},],
|
|
documentation: None,
|
|
sugar: true,
|
|
},],
|
|
doc: None,
|
|
name: "User".to_string(),
|
|
opaque: true,
|
|
parameters: vec![],
|
|
public: true,
|
|
typed_parameters: vec![],
|
|
},
|
|
ast::UntypedDefinition::TypeAlias {
|
|
alias: "Thing".to_string(),
|
|
annotation: ast::Annotation::Constructor {
|
|
location: Span::new(SrcId::empty(), 348..359),
|
|
module: None,
|
|
name: "Option".to_string(),
|
|
arguments: vec![ast::Annotation::Constructor {
|
|
location: Span::new(SrcId::empty(), 355..358),
|
|
module: None,
|
|
name: "Int".to_string(),
|
|
arguments: vec![],
|
|
},],
|
|
},
|
|
doc: None,
|
|
location: Span::new(SrcId::empty(), 335..359),
|
|
parameters: vec![],
|
|
public: false,
|
|
tipo: (),
|
|
},
|
|
ast::UntypedDefinition::TypeAlias {
|
|
alias: "Me".to_string(),
|
|
annotation: ast::Annotation::Constructor {
|
|
location: Span::new(SrcId::empty(), 387..401),
|
|
module: None,
|
|
name: "Option".to_string(),
|
|
arguments: vec![ast::Annotation::Constructor {
|
|
location: Span::new(SrcId::empty(), 394..400),
|
|
module: None,
|
|
name: "String".to_string(),
|
|
arguments: vec![],
|
|
},],
|
|
},
|
|
doc: None,
|
|
location: Span::new(SrcId::empty(), 373..401),
|
|
parameters: vec![],
|
|
public: true,
|
|
tipo: (),
|
|
},
|
|
]
|
|
},
|
|
"{:#?}",
|
|
res,
|
|
);
|
|
}
|
|
}
|