From a50b51e086af51669d7599a309cc729aa74739b0 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Tue, 31 Jan 2023 17:19:40 +0100 Subject: [PATCH] Fix bytearray literals parsing and formatting. Weirdly enough, we got the parsing wrong for byte literals in expressions (but did okay in constants). But got the formatting wrong in constants (yet did okay for formatting expressions). I've factored out the code in both cases to avoid the duplication that led to this in the first place. Plus added test coverage to make sure this doesn't happen in the future. --- crates/aiken-lang/src/format.rs | 29 +++---- crates/aiken-lang/src/parser.rs | 111 ++++++++++---------------- crates/aiken-lang/src/tests/format.rs | 16 ++++ crates/aiken-lang/src/tests/parser.rs | 50 +++++++++--- 4 files changed, 106 insertions(+), 100 deletions(-) diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index 2fc87ab4..1792b087 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -318,16 +318,7 @@ impl<'comments> Formatter<'comments> { fn const_expr<'a, A, B>(&mut self, value: &'a Constant) -> Document<'a> { match value { - Constant::ByteArray { bytes, .. } => "#" - .to_doc() - .append( - flex_break("[", "[") - .append(join(bytes.iter().map(|b| b.to_doc()), break_(",", ", "))) - .nest(INDENT) - .append(break_(",", "")) - .append("]"), - ) - .group(), + Constant::ByteArray { bytes, .. } => self.bytearray(bytes), Constant::Int { value, .. } => value.to_doc(), Constant::String { value, .. } => self.string(value), @@ -647,20 +638,18 @@ 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 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, .. } => "#" - .to_doc() - .append( - flex_break("[", "[") - .append(join(bytes.iter().map(|b| b.to_doc()), break_(",", ", "))) - .nest(INDENT) - .append(break_(",", "")) - .append("]"), - ) - .group(), + UntypedExpr::ByteArray { bytes, .. } => self.bytearray(bytes), UntypedExpr::If { branches, final_else, diff --git a/crates/aiken-lang/src/parser.rs b/crates/aiken-lang/src/parser.rs index 201e61a7..c63a7a3f 100644 --- a/crates/aiken-lang/src/parser.rs +++ b/crates/aiken-lang/src/parser.rs @@ -360,55 +360,12 @@ fn constant_value_parser() -> impl Parser 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 { + let constant_bytearray_parser = + bytearray_parser().map_with_span(|bytes, span| ast::UntypedConstant::ByteArray { location: span, bytes, }); - let constant_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_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)) @@ -529,6 +486,44 @@ fn constant_value_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, + 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)), + ); + + 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![] + } + }, + )); + + choice((bytearray_list_parser, bytearray_hexstring_parser)) +} + pub fn fn_param_parser() -> impl Parser { choice(( select! {Token::Name {name} => name} @@ -913,30 +908,8 @@ pub fn expr_parser( elems, }); - let bytearray = 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| expr::UntypedExpr::ByteArray { + let bytearray = + bytearray_parser().map_with_span(|bytes, span| expr::UntypedExpr::ByteArray { location: span, bytes, }); diff --git a/crates/aiken-lang/src/tests/format.rs b/crates/aiken-lang/src/tests/format.rs index 4290cf0c..5746b747 100644 --- a/crates/aiken-lang/src/tests/format.rs +++ b/crates/aiken-lang/src/tests/format.rs @@ -256,3 +256,19 @@ fn test_block_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); +} diff --git a/crates/aiken-lang/src/tests/parser.rs b/crates/aiken-lang/src/tests/parser.rs index 6caa7e3f..845c9393 100644 --- a/crates/aiken-lang/src/tests/parser.rs +++ b/crates/aiken-lang/src/tests/parser.rs @@ -1652,22 +1652,50 @@ fn plain_bytearray_literals() { fn base16_bytearray_literals() { let code = indoc! {r#" pub const my_policy_id = #"00aaff" + + pub fn foo() { + my_policy_id == #"00aaff" + } "#}; assert_definitions( code, - vec![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], + vec![ + 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: (), }), - tipo: (), - })], + ast::UntypedDefinition::Fn(Function { + arguments: vec![], + body: expr::UntypedExpr::BinOp { + location: Span::new((), 55..80), + name: ast::BinOp::Eq, + left: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 55..67), + name: "my_policy_id".to_string(), + }), + right: Box::new(expr::UntypedExpr::ByteArray { + location: Span::new((), 71..80), + bytes: vec![0, 170, 255], + }), + }, + doc: None, + location: Span::new((), 36..48), + name: "foo".to_string(), + public: true, + return_annotation: None, + return_type: (), + end_position: 81, + }), + ], ) }