diff --git a/Cargo.lock b/Cargo.lock index d04953ea..ef92cb2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,6 +75,7 @@ name = "aiken-lang" version = "0.0.26" dependencies = [ "chumsky", + "hex", "indexmap", "indoc", "itertools", diff --git a/crates/aiken-lang/Cargo.toml b/crates/aiken-lang/Cargo.toml index d87f2b2e..fa814c23 100644 --- a/crates/aiken-lang/Cargo.toml +++ b/crates/aiken-lang/Cargo.toml @@ -12,6 +12,7 @@ authors = ["Lucas Rosa ", "Kasey White "] [dependencies] chumsky = "0.8.0" +hex = "0.4.3" indexmap = "1.9.1" indoc = "1.0.7" itertools = "0.10.5" diff --git a/crates/aiken-lang/src/parser.rs b/crates/aiken-lang/src/parser.rs index 81857b79..7e53e04b 100644 --- a/crates/aiken-lang/src/parser.rs +++ b/crates/aiken-lang/src/parser.rs @@ -344,7 +344,7 @@ fn constant_value_parser() -> impl Parser value} .validate(|value, span, emit| { @@ -372,6 +372,27 @@ fn constant_value_parser() -> impl Parser value}.validate( + |value, span, emit| match hex::decode(&value) { + Ok(bytes) => bytes, + Err(_) => { + emit(ParseError::malformed_base16_string_literal(span)); + vec![] + } + }, + )) + .map_with_span(|bytes, span| ast::UntypedConstant::ByteArray { + location: span, + bytes, + }); + + let constant_bytearray_parser = choice(( + constant_bytearray_list_parser, + constant_bytearray_hexstring_parser, + )); + let constant_list_parser = r .clone() .separated_by(just(Token::Comma)) diff --git a/crates/aiken-lang/src/parser/error.rs b/crates/aiken-lang/src/parser/error.rs index 16fcaa3b..81c8427e 100644 --- a/crates/aiken-lang/src/parser/error.rs +++ b/crates/aiken-lang/src/parser/error.rs @@ -3,6 +3,7 @@ use std::collections::HashSet; use miette::Diagnostic; use crate::{ast::Span, parser::token::Token}; +use indoc::formatdoc; #[derive(Debug, Diagnostic, thiserror::Error)] #[error("{kind}\n")] @@ -35,6 +36,16 @@ impl ParseError { label: None, } } + + pub fn malformed_base16_string_literal(span: Span) -> Self { + Self { + kind: ErrorKind::MalformedBase16StringLiteral, + span, + while_parsing: None, + expected: HashSet::new(), + label: None, + } + } } impl PartialEq for ParseError { @@ -91,6 +102,18 @@ pub enum ErrorKind { #[help] hint: Option, }, + #[error("I tripped over a malformed base16-encoded string literal")] + #[diagnostic(help("{}", formatdoc! { + r#"You can declare literal bytearrays from base16-encoded (a.k.a. hexadecimal) + string literals. + + For example: + + pub const my_policy_id = + #"f4c9f9c4252d86702c2f4c2e49e6648c7cffe3c8f2b6b7d779788f50" + "# + }))] + MalformedBase16StringLiteral, } #[derive(Debug, PartialEq, Eq, Hash, Diagnostic, thiserror::Error)] diff --git a/crates/aiken-lang/src/tests/parser.rs b/crates/aiken-lang/src/tests/parser.rs index 012b387d..a28f10bf 100644 --- a/crates/aiken-lang/src/tests/parser.rs +++ b/crates/aiken-lang/src/tests/parser.rs @@ -3,7 +3,7 @@ use indoc::indoc; use pretty_assertions::assert_eq; use crate::{ - ast::{self, DataType, Function, Span, TypeAlias, Use}, + ast::{self, Constant, DataType, Function, ModuleConstant, Span, TypeAlias, Use}, expr, parser, }; @@ -1542,3 +1542,49 @@ fn parse_tuple() { }), ) } + +#[test] +fn plain_bytearray_literals() { + let code = indoc! {r#" + pub const my_policy_id = #[0, 170, 255] + "#}; + + assert_definition( + code, + 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], + }), + tipo: (), + }), + ) +} + +#[test] +fn base16_bytearray_literals() { + let code = indoc! {r#" + pub const my_policy_id = #"00aaff" + "#}; + + assert_definition( + code, + 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], + }), + tipo: (), + }), + ) +}