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.
This commit is contained in:
KtorZ 2023-01-31 17:19:40 +01:00
parent 0e65fecf69
commit a50b51e086
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
4 changed files with 106 additions and 100 deletions

View File

@ -318,16 +318,7 @@ impl<'comments> Formatter<'comments> {
fn const_expr<'a, A, B>(&mut self, value: &'a Constant<A, B>) -> 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,

View File

@ -360,55 +360,12 @@ fn constant_value_parser() -> impl Parser<Token, ast::UntypedConstant, Error = P
elements,
});
let constant_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_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<Token, ast::UntypedConstant, Error = P
})
}
pub fn bytearray_parser() -> impl Parser<Token, Vec<u8>, 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<Token, ast::UntypedArg, Error = ParseError> {
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,
});

View File

@ -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);
}

View File

@ -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,
}),
],
)
}