diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 6c807bc9..63afd819 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -326,11 +326,21 @@ pub struct DefinitionLocation<'module> { #[derive(Debug, Clone, PartialEq)] pub enum Constant { - Int { location: Span, value: String }, + Int { + location: Span, + value: String, + }, - String { location: Span, value: String }, + String { + location: Span, + value: String, + }, - ByteArray { location: Span, bytes: Vec }, + ByteArray { + location: Span, + bytes: Vec, + preferred_format: ByteArrayFormatPreference, + }, } impl Constant { @@ -748,6 +758,13 @@ impl Pattern { } } +#[derive(Debug, Clone, PartialEq, Eq, Copy)] +pub enum ByteArrayFormatPreference { + HexadecimalString, + ArrayOfBytes, + Utf8String, +} + #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum AssignmentKind { Let, diff --git a/crates/aiken-lang/src/expr.rs b/crates/aiken-lang/src/expr.rs index 2ce99d3b..8f6ac7c9 100644 --- a/crates/aiken-lang/src/expr.rs +++ b/crates/aiken-lang/src/expr.rs @@ -4,9 +4,9 @@ use vec1::Vec1; use crate::{ ast::{ - Annotation, Arg, AssignmentKind, BinOp, CallArg, Clause, DefinitionLocation, IfBranch, - Pattern, RecordUpdateSpread, Span, TraceKind, TypedRecordUpdateArg, UnOp, - UntypedRecordUpdateArg, + Annotation, Arg, AssignmentKind, BinOp, ByteArrayFormatPreference, CallArg, Clause, + DefinitionLocation, IfBranch, Pattern, RecordUpdateSpread, Span, TraceKind, + TypedRecordUpdateArg, UnOp, UntypedRecordUpdateArg, }, builtins::void, tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor}, @@ -361,6 +361,7 @@ pub enum UntypedExpr { ByteArray { location: Span, bytes: Vec, + preferred_format: ByteArrayFormatPreference, }, PipeLine { diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index ffeeca5d..9d6b07f8 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -5,12 +5,12 @@ use vec1::Vec1; use crate::{ ast::{ - Annotation, Arg, ArgName, AssignmentKind, BinOp, CallArg, ClauseGuard, Constant, DataType, - Definition, Function, IfBranch, ModuleConstant, Pattern, RecordConstructor, - RecordConstructorArg, RecordUpdateSpread, Span, TraceKind, TypeAlias, TypedArg, UnOp, - UnqualifiedImport, UntypedArg, UntypedClause, UntypedClauseGuard, UntypedDefinition, - UntypedFunction, UntypedModule, UntypedPattern, UntypedRecordUpdateArg, Use, Validator, - CAPTURE_VARIABLE, + Annotation, Arg, ArgName, AssignmentKind, BinOp, ByteArrayFormatPreference, CallArg, + ClauseGuard, Constant, DataType, Definition, Function, IfBranch, ModuleConstant, Pattern, + RecordConstructor, RecordConstructorArg, RecordUpdateSpread, Span, TraceKind, TypeAlias, + TypedArg, UnOp, UnqualifiedImport, UntypedArg, UntypedClause, UntypedClauseGuard, + UntypedDefinition, UntypedFunction, UntypedModule, UntypedPattern, UntypedRecordUpdateArg, + Use, Validator, CAPTURE_VARIABLE, }, docvec, expr::{UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR}, @@ -326,7 +326,11 @@ impl<'comments> Formatter<'comments> { fn const_expr<'a>(&mut self, value: &'a Constant) -> Document<'a> { match value { - Constant::ByteArray { bytes, .. } => self.bytearray(bytes), + Constant::ByteArray { + bytes, + preferred_format, + .. + } => self.bytearray(bytes, preferred_format), Constant::Int { value, .. } => value.to_doc(), Constant::String { value, .. } => self.string(value), } @@ -635,18 +639,40 @@ impl<'comments> Formatter<'comments> { } } - pub fn bytearray<'a>(&mut self, bytes: &'a [u8]) -> Document<'a> { - "#".to_doc() - .append("\"") - .append(Document::String(hex::encode(bytes))) - .append("\"") + pub fn bytearray<'a>( + &mut self, + bytes: &'a [u8], + preferred_format: &ByteArrayFormatPreference, + ) -> Document<'a> { + match preferred_format { + ByteArrayFormatPreference::HexadecimalString => "#" + .to_doc() + .append("\"") + .append(Document::String(hex::encode(bytes))) + .append("\""), + ByteArrayFormatPreference::ArrayOfBytes => "#" + .to_doc() + .append( + flex_break("[", "[") + .append(join(bytes.iter().map(|b| b.to_doc()), break_(",", ", "))) + .nest(INDENT) + .append(break_(",", "")) + .append("]"), + ) + .group(), + ByteArrayFormatPreference::Utf8String => todo!(), + } } pub fn expr<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { let comments = self.pop_comments(expr.start_byte_index()); let document = match expr { - UntypedExpr::ByteArray { bytes, .. } => self.bytearray(bytes), + UntypedExpr::ByteArray { + bytes, + preferred_format, + .. + } => self.bytearray(bytes, preferred_format), UntypedExpr::If { branches, diff --git a/crates/aiken-lang/src/parser.rs b/crates/aiken-lang/src/parser.rs index fabdec17..3b3cf039 100644 --- a/crates/aiken-lang/src/parser.rs +++ b/crates/aiken-lang/src/parser.rs @@ -7,7 +7,10 @@ pub mod lexer; pub mod token; use crate::{ - ast::{self, BinOp, Span, TraceKind, UnOp, UntypedDefinition, CAPTURE_VARIABLE}, + ast::{ + self, BinOp, ByteArrayFormatPreference, Span, TraceKind, UnOp, UntypedDefinition, + CAPTURE_VARIABLE, + }, expr, }; @@ -402,9 +405,12 @@ fn constant_value_parser() -> impl Parser impl Parser impl Parser, Error = ParseError> { - let bytearray_list_parser = just(Token::Hash).ignore_then( - select! {Token::Int {value} => value} - .validate(|value, span, emit| { - let byte: u8 = match value.parse() { - Ok(b) => b, +pub fn bytearray_parser( +) -> impl Parser), Error = ParseError> { + let bytearray_list_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(|token| (ByteArrayFormatPreference::ArrayOfBytes, token)); + + let bytearray_hexstring_parser = just(Token::Hash) + .ignore_then( + select! {Token::String {value} => value}.validate( + |value, span, emit| match hex::decode(value) { + Ok(bytes) => bytes, Err(_) => { - emit(ParseError::expected_input_found( - span, - None, - Some(error::Pattern::Byte), - )); - - 0 + emit(ParseError::malformed_base16_string_literal(span)); + vec![] } - }; - - byte - }) - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::LeftSquare), just(Token::RightSquare)), - ); - - let bytearray_hexstring_parser = - just(Token::Hash).ignore_then(select! {Token::String {value} => value}.validate( - |value, span, emit| match hex::decode(value) { - Ok(bytes) => bytes, - Err(_) => { - emit(ParseError::malformed_base16_string_literal(span)); - vec![] - } - }, - )); + }, + ), + ) + .map(|token| (ByteArrayFormatPreference::HexadecimalString, token)); choice((bytearray_list_parser, bytearray_hexstring_parser)) } @@ -816,11 +828,13 @@ pub fn expr_parser( elems, }); - let bytearray = - bytearray_parser().map_with_span(|bytes, span| expr::UntypedExpr::ByteArray { + let bytearray = bytearray_parser().map_with_span(|(preferred_format, bytes), span| { + expr::UntypedExpr::ByteArray { location: span, bytes, - }); + preferred_format, + } + }); let list_parser = just(Token::LeftSquare) .ignore_then(r.clone().separated_by(just(Token::Comma))) diff --git a/crates/aiken-lang/src/tests/format.rs b/crates/aiken-lang/src/tests/format.rs index 12d21276..797a81fa 100644 --- a/crates/aiken-lang/src/tests/format.rs +++ b/crates/aiken-lang/src/tests/format.rs @@ -311,22 +311,6 @@ fn test_block_logical_expr() { assert_fmt(src, expected); } -#[test] -fn test_format_bytearray_literals() { - let src = indoc! {r#" - const foo = #"ff00" - const bar = #[0, 255] - "#}; - - let expected = indoc! { r#" - const foo = #"ff00" - - const bar = #"00ff" - "#}; - - assert_fmt(src, expected); -} - #[test] fn test_nested_function_calls() { let src = indoc! {r#" @@ -503,3 +487,20 @@ fn test_newline_module_comments() { assert_fmt(src, out); } + +#[test] +fn test_bytearray_literals() { + let src = indoc! {r#" + const foo_const_array = #[102, 111, 111] + + const foo_const_hex = #"666f6f" + + fn foo() { + let foo_const_array = #[102, 111, 111] + + let foo_const_hex = #"666f6f" + } + "#}; + + assert_fmt(src, src); +} diff --git a/crates/aiken-lang/src/tests/parser.rs b/crates/aiken-lang/src/tests/parser.rs index 0a945fb9..97210e26 100644 --- a/crates/aiken-lang/src/tests/parser.rs +++ b/crates/aiken-lang/src/tests/parser.rs @@ -1785,6 +1785,7 @@ fn plain_bytearray_literals() { value: Box::new(Constant::ByteArray { location: Span::new((), 25..39), bytes: vec![0, 170, 255], + preferred_format: ast::ByteArrayFormatPreference::ArrayOfBytes, }), tipo: (), })], @@ -1813,6 +1814,7 @@ fn base16_bytearray_literals() { value: Box::new(Constant::ByteArray { location: Span::new((), 25..34), bytes: vec![0, 170, 255], + preferred_format: ast::ByteArrayFormatPreference::HexadecimalString, }), tipo: (), }), @@ -1828,6 +1830,7 @@ fn base16_bytearray_literals() { right: Box::new(expr::UntypedExpr::ByteArray { location: Span::new((), 71..80), bytes: vec![0, 170, 255], + preferred_format: ast::ByteArrayFormatPreference::HexadecimalString, }), }, doc: None, diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 8bb97739..0c388f5c 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -350,9 +350,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> { .. } => self.infer_tuple_index(*tuple, index, location), - UntypedExpr::ByteArray { location, bytes } => { - Ok(self.infer_byte_array(bytes, location)) - } + UntypedExpr::ByteArray { + location, bytes, .. + } => Ok(self.infer_byte_array(bytes, location)), UntypedExpr::RecordUpdate { location, @@ -1353,7 +1353,15 @@ impl<'a, 'b> ExprTyper<'a, 'b> { location, value, .. } => Ok(Constant::String { location, value }), - Constant::ByteArray { location, bytes } => Ok(Constant::ByteArray { location, bytes }), + Constant::ByteArray { + location, + bytes, + preferred_format, + } => Ok(Constant::ByteArray { + location, + bytes, + preferred_format, + }), }?; // Check type annotation is accurate.