diff --git a/crates/lang/src/ast.rs b/crates/lang/src/ast.rs index 018c918c..93a70889 100644 --- a/crates/lang/src/ast.rs +++ b/crates/lang/src/ast.rs @@ -185,6 +185,11 @@ pub enum Constant { value: String, }, + Tuple { + location: Span, + elements: Vec, + }, + List { location: Span, elements: Vec, @@ -221,6 +226,9 @@ impl TypedConstant { Constant::Int { .. } => builtins::int(), Constant::String { .. } => builtins::string(), Constant::ByteArray { .. } => builtins::byte_array(), + Constant::Tuple { elements, .. } => { + builtins::tuple(elements.iter().map(|e| e.tipo()).collect()) + } Constant::List { tipo, .. } | Constant::Record { tipo, .. } | Constant::Var { tipo, .. } => tipo.clone(), @@ -232,6 +240,7 @@ impl Constant { pub fn location(&self) -> Span { match self { Constant::Int { location, .. } + | Constant::Tuple { location, .. } | Constant::List { location, .. } | Constant::String { location, .. } | Constant::Record { location, .. } diff --git a/crates/lang/src/format.rs b/crates/lang/src/format.rs index 7a0c0598..60d97f60 100644 --- a/crates/lang/src/format.rs +++ b/crates/lang/src/format.rs @@ -351,6 +351,13 @@ impl<'comments> Formatter<'comments> { module: Some(module), .. } => docvec![module, ".", name], + + Constant::Tuple { elements, .. } => "#" + .to_doc() + .append(wrap_args( + elements.iter().map(|e| (self.const_expr(e), false)), + )) + .group(), } } diff --git a/crates/lang/src/parser.rs b/crates/lang/src/parser.rs index 39117d6c..371c7266 100644 --- a/crates/lang/src/parser.rs +++ b/crates/lang/src/parser.rs @@ -74,6 +74,7 @@ fn module_parser() -> impl Parser, Error = ParseEr data_parser(), type_alias_parser(), fn_parser(), + constant_parser(), )) .repeated() .then_ignore(end()) @@ -265,6 +266,202 @@ pub fn fn_parser() -> impl 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: (), + }) + }) +} + +fn constant_value_parser() -> impl Parser { + recursive(|r| { + let constant_string_parser = + select! {Token::String {value} => value}.map_with_span(|value, span| { + ast::UntypedConstant::String { + location: span, + value, + } + }); + + let constant_int_parser = + select! {Token::Int {value} => value}.map_with_span(|value, span| { + ast::UntypedConstant::Int { + location: span, + value, + } + }); + + let constant_tuple_parser = just(Token::Hash) + .ignore_then(r.clone()) + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftParen), just(Token::RightParen)) + .map_with_span(|elements, span| ast::UntypedConstant::Tuple { + location: span, + elements, + }); + + let constant_bytearray_parser = just(Token::Hash) + .ignore_then( + select! {Token::Int {value} => value}.validate(|value, 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 + }), + ) + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftSquare), just(Token::RightSquare)) + .map_with_span(|bytes, span| ast::UntypedConstant::ByteArray { + location: span, + bytes, + }); + + let constant_list_parser = r + .clone() + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftSquare), just(Token::RightSquare)) + .map_with_span(|elements, span| ast::UntypedConstant::List { + location: span, + elements, + tipo: (), + }); + + let constant_record_parser = choice(( + choice(( + select! {Token::Name { name } => name} + .then_ignore(just(Token::Dot)) + .or_not() + .then(select! {Token::UpName { name } => name}) + .then( + select! {Token::Name {name} => name} + .then_ignore(just(Token::Colon)) + .or_not() + .then(r.clone()) + .validate(|(label_opt, value), span, emit| { + let label = if label_opt.is_some() { + label_opt + } else if let ast::UntypedConstant::Var { name, .. } = &value { + Some(name.clone()) + } else { + emit(ParseError::expected_input_found( + value.location(), + None, + Some(error::Pattern::RecordPunning), + )); + + None + }; + + ast::CallArg { + location: span, + value, + label, + } + }) + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), + ) + .map_with_span( + |((module, name), args), span| ast::UntypedConstant::Record { + location: span, + module, + name, + args, + tag: (), + tipo: (), + field_map: None, + }, + ), + select! {Token::Name { name } => name} + .then_ignore(just(Token::Dot)) + .or_not() + .then(select! {Token::UpName { name } => name}) + .then( + r.clone() + .map_with_span(|value, span| ast::CallArg { + location: span, + value, + label: None, + }) + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftParen), just(Token::RightParen)), + ) + .map_with_span( + |((module, name), args), span| ast::UntypedConstant::Record { + location: span, + module, + name, + args, + tag: (), + tipo: (), + field_map: None, + }, + ), + )), + select! {Token::Name { name } => name} + .then_ignore(just(Token::Dot)) + .then(select! {Token::Name{name} => name}) + .map_with_span(|(module, name), span: Span| ast::UntypedConstant::Var { + location: span.union(span), + module: Some(module), + name, + constructor: None, + tipo: (), + }), + )); + + let constant_var_parser = + select! {Token::Name {name} => name}.map_with_span(|name, span| { + ast::UntypedConstant::Var { + location: span, + module: None, + name, + constructor: None, + tipo: (), + } + }); + + choice(( + constant_string_parser, + constant_int_parser, + constant_tuple_parser, + constant_bytearray_parser, + constant_list_parser, + constant_record_parser, + constant_var_parser, + )) + }) +} + pub fn fn_param_parser() -> impl Parser { choice(( select! {Token::Name {name} => name} @@ -445,6 +642,10 @@ pub fn expr_parser( tail, }); + let block_parser = seq_r + .clone() + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)); + let anon_fn_parser = just(Token::Fn) .ignore_then( anon_fn_param_parser() @@ -453,11 +654,7 @@ pub fn expr_parser( .delimited_by(just(Token::LeftParen), just(Token::RightParen)), ) .then(just(Token::RArrow).ignore_then(type_parser()).or_not()) - .then( - seq_r - .clone() - .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), - ) + .then(seq_r.delimited_by(just(Token::LeftBrace), just(Token::RightBrace))) .map_with_span( |((arguments, return_annotation), body), span| expr::UntypedExpr::Fn { arguments, @@ -468,8 +665,6 @@ pub fn expr_parser( }, ); - let block_parser = seq_r.delimited_by(just(Token::LeftBrace), just(Token::RightBrace)); - // TODO: do guards later // let when_clause_guard_parser = just(Token::If); @@ -999,10 +1194,16 @@ pub fn pattern_parser() -> impl Parser for Pattern { diff --git a/crates/lang/src/tipo/error.rs b/crates/lang/src/tipo/error.rs index e4066464..61e9c1d8 100644 --- a/crates/lang/src/tipo/error.rs +++ b/crates/lang/src/tipo/error.rs @@ -8,7 +8,7 @@ use super::Type; #[derive(Debug, thiserror::Error, Diagnostic)] pub enum Error { - #[error("Duplicate argument\n\n{label}")] + #[error("Duplicate argument\n\n{label}\n")] #[diagnostic(help("Try renaming it"))] DuplicateArgument { #[label] @@ -16,7 +16,7 @@ pub enum Error { label: String, }, - #[error("Duplicate const\n\n{name}")] + #[error("Duplicate const\n\n{name}\n")] #[diagnostic(help("Try renaming it"))] DuplicateConstName { #[label] @@ -26,7 +26,7 @@ pub enum Error { name: String, }, - #[error("Duplicate import\n\n{name}")] + #[error("Duplicate import\n\n{name}\n")] #[diagnostic(help("Try renaming it"))] DuplicateImport { #[label] @@ -36,7 +36,7 @@ pub enum Error { name: String, }, - #[error("Duplicate field\n\n{label}")] + #[error("Duplicate field\n\n{label}\n")] #[diagnostic(help("Try renaming it"))] DuplicateField { #[label] @@ -44,7 +44,7 @@ pub enum Error { label: String, }, - #[error("Duplicate name\n\n{name}")] + #[error("Duplicate name\n\n{name}\n")] #[diagnostic(help("Try renaming it"))] DuplicateName { #[label] @@ -54,7 +54,7 @@ pub enum Error { name: String, }, - #[error("Duplicate type name\n\n{name}")] + #[error("Duplicate type name\n\n{name}\n")] #[diagnostic(help("Try renaming it"))] DuplicateTypeName { #[label] @@ -64,7 +64,7 @@ pub enum Error { name: String, }, - #[error("Incorrect arity\n\nExpected\n\n{expected}\n\nGiven\n\n{given}")] + #[error("Incorrect arity\n\nExpected\n\n{expected}\n\nGiven\n\n{given}\n")] IncorrectArity { #[label] location: Span, @@ -73,7 +73,7 @@ pub enum Error { labels: Vec, }, - #[error("Incorrect number of clause patterns\n\nExpected\n\n{expected}\n\nGiven\n\n{given}")] + #[error("Incorrect number of clause patterns\n\nExpected\n\n{expected}\n\nGiven\n\n{given}\n")] IncorrectNumClausePatterns { #[label] location: Span, @@ -81,7 +81,7 @@ pub enum Error { given: usize, }, - #[error("Incorrect type arity for `{name}`\n\nExpected\n\n{expected}\n\nGiven\n\n{given}")] + #[error("Incorrect type arity for `{name}`\n\nExpected\n\n{expected}\n\nGiven\n\n{given}\n")] IncorrectTypeArity { #[label] location: Span, @@ -90,7 +90,7 @@ pub enum Error { given: usize, }, - #[error("Non-exhaustive pattern match")] + #[error("Non-exhaustive pattern match\n")] NotExhaustivePatternMatch { #[label] location: Span, @@ -104,65 +104,65 @@ pub enum Error { tipo: Arc, }, - #[error("Module\n\n{name}\n\ncontains keyword\n\n{keyword}")] + #[error("Module\n\n{name}\n\ncontains keyword\n\n{keyword}\n")] KeywordInModuleName { name: String, keyword: String }, - #[error("Clause guard {name} is not local")] + #[error("Clause guard {name} is not local\n")] NonLocalClauseGuardVariable { #[label] location: Span, name: String, }, - #[error("Positional argument after labeled")] + #[error("Positional argument after labeled\n")] PositionalArgumentAfterLabeled { #[label] location: Span, }, - #[error("Private type leaked")] + #[error("Private type leaked\n")] PrivateTypeLeak { #[label] location: Span, leaked: Type, }, - #[error("Record access unknown type")] + #[error("Record access unknown type\n")] RecordAccessUnknownType { #[label] location: Span, }, - #[error("Record update invalid constructor")] + #[error("Record update invalid constructor\n")] RecordUpdateInvalidConstructor { #[label] location: Span, }, - #[error("{name} is a reserved module name")] + #[error("{name} is a reserved module name\n")] ReservedModuleName { name: String }, - #[error("Unexpected labeled argument\n\n{label}")] + #[error("Unexpected labeled argument\n\n{label}\n")] UnexpectedLabeledArg { #[label] location: Span, label: String, }, - #[error("Unexpected type hole")] + #[error("Unexpected type hole\n")] UnexpectedTypeHole { #[label] location: Span, }, - #[error("Unknown labels")] + #[error("Unknown labels\n")] UnknownLabels { unknown: Vec<(String, Span)>, valid: Vec, supplied: Vec, }, - #[error("Unknown module\n\n{name}")] + #[error("Unknown module\n\n{name}\n")] UnknownModule { #[label] location: Span, @@ -170,7 +170,7 @@ pub enum Error { imported_modules: Vec, }, - #[error("Unknown module field\n\n{name}\n\nin module\n\n{module_name}")] + #[error("Unknown module field\n\n{name}\n\nin module\n\n{module_name}\n")] UnknownModuleField { location: Span, name: String, @@ -179,7 +179,7 @@ pub enum Error { type_constructors: Vec, }, - #[error("Unknown module value\n\n{name}")] + #[error("Unknown module value\n\n{name}\n")] UnknownModuleValue { #[label] location: Span, @@ -188,7 +188,7 @@ pub enum Error { value_constructors: Vec, }, - #[error("Unknown type\n\n{name}\n\nin module\n\n{module_name}")] + #[error("Unknown type\n\n{name}\n\nin module\n\n{module_name}\n")] UnknownModuleType { #[label] location: Span, @@ -197,7 +197,7 @@ pub enum Error { type_constructors: Vec, }, - #[error("Unknown record field\n\n{label}")] + #[error("Unknown record field\n\n{label}\n")] UnknownRecordField { #[label] location: Span, @@ -207,7 +207,7 @@ pub enum Error { situation: Option, }, - #[error("Unknown type\n\n{name}")] + #[error("Unknown type\n\n{name}\n")] UnknownType { #[label] location: Span, @@ -215,7 +215,7 @@ pub enum Error { types: Vec, }, - #[error("Unknown variable\n\n{name}")] + #[error("Unknown variable\n\n{name}\n")] UnknownVariable { #[label] location: Span, @@ -223,14 +223,14 @@ pub enum Error { variables: Vec, }, - #[error("Unnecessary spread operator")] + #[error("Unnecessary spread operator\n")] UnnecessarySpreadOperator { #[label] location: Span, arity: usize, }, - #[error("Cannot update a type with multiple constructors")] + #[error("Cannot update a type with multiple constructors\n")] UpdateMultiConstructorType { #[label] location: Span, @@ -271,7 +271,7 @@ pub enum Error { name: String, }, - #[error("")] + #[error("Recursive type detected\n")] RecursiveType { #[label] location: Span, @@ -363,7 +363,7 @@ impl Error { #[derive(Debug, PartialEq, Clone, thiserror::Error, Diagnostic)] pub enum Warning { - #[error("todo")] + #[error("Todo\n")] Todo { kind: TodoKind, #[label] @@ -371,31 +371,31 @@ pub enum Warning { tipo: Arc, }, - #[error("implicitly discarded result")] + #[error("Implicitly discarded result\n")] ImplicitlyDiscardedResult { #[label] location: Span, }, - #[error("unused literal")] + #[error("Unused literal\n")] UnusedLiteral { #[label] location: Span, }, - #[error("record update with no fields")] + #[error("Record update with no fields\n")] NoFieldsRecordUpdate { #[label] location: Span, }, - #[error("record update using all fields")] + #[error("Record update using all fields\n")] AllFieldsRecordUpdate { #[label] location: Span, }, - #[error("unused type {name}")] + #[error("Unused type {name}\n")] UnusedType { #[label] location: Span, @@ -403,7 +403,7 @@ pub enum Warning { name: String, }, - #[error("unused constructor {name}")] + #[error("Unused constructor {name}\n")] UnusedConstructor { #[label] location: Span, @@ -411,35 +411,35 @@ pub enum Warning { name: String, }, - #[error("unused imported value {name}")] + #[error("Unused imported value {name}\n")] UnusedImportedValue { #[label] location: Span, name: String, }, - #[error("unused imported module {name}")] + #[error("Unused imported module {name}\n")] UnusedImportedModule { #[label] location: Span, name: String, }, - #[error("unused private module constant {name}")] + #[error("Unused private module constant {name}\n")] UnusedPrivateModuleConstant { #[label] location: Span, name: String, }, - #[error("unused private function {name}")] + #[error("Unused private function {name}\n")] UnusedPrivateFunction { #[label] location: Span, name: String, }, - #[error("unused variable {name}")] + #[error("Unused variable {name}\n")] UnusedVariable { #[label] location: Span, diff --git a/crates/lang/src/tipo/expr.rs b/crates/lang/src/tipo/expr.rs index ed651d19..8a8bb137 100644 --- a/crates/lang/src/tipo/expr.rs +++ b/crates/lang/src/tipo/expr.rs @@ -1177,6 +1177,22 @@ impl<'a, 'b> ExprTyper<'a, 'b> { Ok((typed_pattern, typed_alternatives)) } + fn infer_const_tuple( + &mut self, + untyped_elements: Vec, + location: Span, + ) -> Result { + let mut elements = Vec::with_capacity(untyped_elements.len()); + + for element in untyped_elements { + let element = self.infer_const(&None, element)?; + + elements.push(element); + } + + Ok(Constant::Tuple { elements, location }) + } + // TODO: extract the type annotation checking into a infer_module_const // function that uses this function internally pub fn infer_const( @@ -1193,6 +1209,10 @@ impl<'a, 'b> ExprTyper<'a, 'b> { location, value, .. } => Ok(Constant::String { location, value }), + Constant::Tuple { + elements, location, .. + } => self.infer_const_tuple(elements, location), + Constant::List { elements, location, .. } => self.infer_const_list(elements, location), diff --git a/crates/project/src/error.rs b/crates/project/src/error.rs index 7b7622ce..46548e41 100644 --- a/crates/project/src/error.rs +++ b/crates/project/src/error.rs @@ -265,7 +265,7 @@ impl Diagnostic for Error { #[derive(thiserror::Error)] pub enum Warning { - #[error("type checking")] + #[error("Checking")] Type { path: PathBuf, src: String, diff --git a/examples/sample/validators/swap.ak b/examples/sample/validators/swap.ak index 3c59740e..d0931bce 100644 --- a/examples/sample/validators/swap.ak +++ b/examples/sample/validators/swap.ak @@ -3,6 +3,8 @@ use sample/mint use sample/spend use aiken/builtin +const something = 5 + pub type Redeemer { signer: ByteArray, amount: Int,