Merge pull request #593 from aiken-lang/hex-and-numeric-underscore

Hex and numeric underscore
This commit is contained in:
Matthias Benkort 2023-06-08 18:50:47 +02:00 committed by GitHub
commit 8ed3979517
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 573 additions and 46 deletions

View File

@ -4,7 +4,8 @@
### Added ### Added
N/A - **aiken-lang**: numbers can now be written as hexadecimal digits (e.g. `0x42`)
- **aiken-lang**: numbers can now be written using numeric underscores as separator (e.g. `1_000_000`)
### Fixed ### Fixed

1
Cargo.lock generated vendored
View File

@ -85,6 +85,7 @@ dependencies = [
"indoc", "indoc",
"itertools", "itertools",
"miette", "miette",
"num-bigint",
"ordinal", "ordinal",
"owo-colors", "owo-colors",
"pretty_assertions", "pretty_assertions",

View File

@ -25,6 +25,7 @@ strum = "0.24.1"
thiserror = "1.0.39" thiserror = "1.0.39"
vec1 = "1.10.1" vec1 = "1.10.1"
uplc = { path = '../uplc', version = "1.0.7-alpha" } uplc = { path = '../uplc', version = "1.0.7-alpha" }
num-bigint = "0.4.3"
[target.'cfg(not(target_family="wasm"))'.dependencies] [target.'cfg(not(target_family="wasm"))'.dependencies]
chumsky = "0.9.2" chumsky = "0.9.2"

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
builtins::{self, bool}, builtins::{self, bool},
expr::{TypedExpr, UntypedExpr}, expr::{TypedExpr, UntypedExpr},
parser::token::Token, parser::token::{Base, Token},
tipo::{PatternConstructor, Type, TypeInfo}, tipo::{PatternConstructor, Type, TypeInfo},
}; };
use miette::Diagnostic; use miette::Diagnostic;
@ -441,6 +441,7 @@ pub enum Constant {
Int { Int {
location: Span, location: Span,
value: String, value: String,
base: Base,
}, },
String { String {
@ -795,6 +796,7 @@ pub enum Pattern<Constructor, Type> {
Int { Int {
location: Span, location: Span,
value: String, value: String,
base: Base,
}, },
/// The creation of a variable. /// The creation of a variable.
@ -880,7 +882,7 @@ impl<A, B> Pattern<A, B> {
#[derive(Debug, Clone, PartialEq, Eq, Copy)] #[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub enum ByteArrayFormatPreference { pub enum ByteArrayFormatPreference {
HexadecimalString, HexadecimalString,
ArrayOfBytes, ArrayOfBytes(Base),
Utf8String, Utf8String,
} }

View File

@ -9,6 +9,7 @@ use crate::{
TypedRecordUpdateArg, UnOp, UntypedClause, UntypedRecordUpdateArg, TypedRecordUpdateArg, UnOp, UntypedClause, UntypedRecordUpdateArg,
}, },
builtins::void, builtins::void,
parser::token::Base,
tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor}, tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor},
}; };
@ -403,6 +404,7 @@ pub enum UntypedExpr {
Int { Int {
location: Span, location: Span,
value: String, value: String,
base: Base,
}, },
String { String {

View File

@ -1,8 +1,3 @@
use itertools::Itertools;
use ordinal::Ordinal;
use std::sync::Arc;
use vec1::Vec1;
use crate::{ use crate::{
ast::{ ast::{
Annotation, Arg, ArgName, AssignmentKind, BinOp, ByteArrayFormatPreference, CallArg, Annotation, Arg, ArgName, AssignmentKind, BinOp, ByteArrayFormatPreference, CallArg,
@ -14,12 +9,20 @@ use crate::{
}, },
docvec, docvec,
expr::{UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR}, expr::{UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR},
parser::extra::{Comment, ModuleExtra}, parser::{
extra::{Comment, ModuleExtra},
token::Base,
},
pretty::{ pretty::{
break_, concat, flex_break, join, line, lines, nil, prebreak, Document, Documentable, break_, concat, flex_break, join, line, lines, nil, prebreak, Document, Documentable,
}, },
tipo::{self, Type}, tipo::{self, Type},
}; };
use itertools::Itertools;
use num_bigint::BigInt;
use ordinal::Ordinal;
use std::sync::Arc;
use vec1::Vec1;
const INDENT: isize = 2; const INDENT: isize = 2;
const DOCS_MAX_COLUMNS: isize = 80; const DOCS_MAX_COLUMNS: isize = 80;
@ -348,7 +351,7 @@ impl<'comments> Formatter<'comments> {
preferred_format, preferred_format,
.. ..
} => self.bytearray(bytes, preferred_format), } => self.bytearray(bytes, preferred_format),
Constant::Int { value, .. } => value.to_doc(), Constant::Int { value, base, .. } => self.int(value, base),
Constant::String { value, .. } => self.string(value), Constant::String { value, .. } => self.string(value),
} }
} }
@ -662,7 +665,7 @@ impl<'comments> Formatter<'comments> {
.append("\"") .append("\"")
.append(Document::String(hex::encode(bytes))) .append(Document::String(hex::encode(bytes)))
.append("\""), .append("\""),
ByteArrayFormatPreference::ArrayOfBytes => "#" ByteArrayFormatPreference::ArrayOfBytes(Base::Decimal { .. }) => "#"
.to_doc() .to_doc()
.append( .append(
flex_break("[", "[") flex_break("[", "[")
@ -672,6 +675,25 @@ impl<'comments> Formatter<'comments> {
.append("]"), .append("]"),
) )
.group(), .group(),
ByteArrayFormatPreference::ArrayOfBytes(Base::Hexadecimal) => "#"
.to_doc()
.append(
flex_break("[", "[")
.append(join(
bytes.iter().map(|b| {
Document::String(if *b < 16 {
format!("0x0{b:x}")
} else {
format!("{b:#x}")
})
}),
break_(",", ", "),
))
.nest(INDENT)
.append(break_(",", ""))
.append("]"),
)
.group(),
ByteArrayFormatPreference::Utf8String => nil() ByteArrayFormatPreference::Utf8String => nil()
.append("\"") .append("\"")
.append(Document::String(String::from_utf8(bytes.to_vec()).unwrap())) .append(Document::String(String::from_utf8(bytes.to_vec()).unwrap()))
@ -679,6 +701,39 @@ impl<'comments> Formatter<'comments> {
} }
} }
pub fn int<'a>(&mut self, s: &'a str, base: &Base) -> Document<'a> {
match base {
Base::Decimal { numeric_underscore } if *numeric_underscore => {
let s = s
.chars()
.rev()
.enumerate()
.flat_map(|(i, c)| {
if i != 0 && i % 3 == 0 {
Some('_')
} else {
None
}
.into_iter()
.chain(std::iter::once(c))
})
.collect::<String>()
.chars()
.rev()
.collect::<String>();
Document::String(s)
}
Base::Decimal { .. } => s.to_doc(),
Base::Hexadecimal => Document::String(format!(
"0x{}",
BigInt::parse_bytes(s.as_bytes(), 10)
.expect("Invalid parsed hexadecimal digits ?!")
.to_str_radix(16),
)),
}
}
pub fn expr<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { pub fn expr<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> {
let comments = self.pop_comments(expr.start_byte_index()); let comments = self.pop_comments(expr.start_byte_index());
@ -700,7 +755,7 @@ impl<'comments> Formatter<'comments> {
one_liner, one_liner,
} => self.pipeline(expressions, *one_liner), } => self.pipeline(expressions, *one_liner),
UntypedExpr::Int { value, .. } => value.to_doc(), UntypedExpr::Int { value, base, .. } => self.int(value, base),
UntypedExpr::String { value, .. } => self.string(value), UntypedExpr::String { value, .. } => self.string(value),
@ -1507,7 +1562,7 @@ impl<'comments> Formatter<'comments> {
pub fn pattern<'a>(&mut self, pattern: &'a UntypedPattern) -> Document<'a> { pub fn pattern<'a>(&mut self, pattern: &'a UntypedPattern) -> Document<'a> {
let comments = self.pop_comments(pattern.location().start); let comments = self.pop_comments(pattern.location().start);
let doc = match pattern { let doc = match pattern {
Pattern::Int { value, .. } => value.to_doc(), Pattern::Int { value, base, .. } => self.int(value, base),
Pattern::Var { name, .. } => name.to_doc(), Pattern::Var { name, .. } => name.to_doc(),

View File

@ -13,7 +13,7 @@ use crate::{
use chumsky::{chain::Chain, prelude::*}; use chumsky::{chain::Chain, prelude::*};
use error::ParseError; use error::ParseError;
use extra::ModuleExtra; use extra::ModuleExtra;
use token::Token; use token::{Base, Token};
use vec1::{vec1, Vec1}; use vec1::{vec1, Vec1};
pub fn module( pub fn module(
@ -397,9 +397,12 @@ fn constant_value_parser() -> impl Parser<Token, ast::Constant, Error = ParseErr
}); });
let constant_int_parser = let constant_int_parser =
select! {Token::Int {value} => value}.map_with_span(|value, span| ast::Constant::Int { select! {Token::Int {value, base} => (value, base)}.map_with_span(|(value, base), span| {
location: span, ast::Constant::Int {
value, location: span,
value,
base,
}
}); });
let constant_bytearray_parser = let constant_bytearray_parser =
@ -422,8 +425,8 @@ pub fn bytearray_parser(
) -> impl Parser<Token, (ByteArrayFormatPreference, Vec<u8>), Error = ParseError> { ) -> impl Parser<Token, (ByteArrayFormatPreference, Vec<u8>), Error = ParseError> {
let bytearray_list_parser = just(Token::Hash) let bytearray_list_parser = just(Token::Hash)
.ignore_then( .ignore_then(
select! {Token::Int {value} => value} select! {Token::Int {value, base, ..} => (value, base)}
.validate(|value, span, emit| { .validate(|(value, base), span, emit| {
let byte: u8 = match value.parse() { let byte: u8 = match value.parse() {
Ok(b) => b, Ok(b) => b,
Err(_) => { Err(_) => {
@ -432,18 +435,38 @@ pub fn bytearray_parser(
None, None,
Some(error::Pattern::Byte), Some(error::Pattern::Byte),
)); ));
0 0
} }
}; };
(byte, base)
byte
}) })
.separated_by(just(Token::Comma)) .separated_by(just(Token::Comma))
.allow_trailing() .allow_trailing()
.delimited_by(just(Token::LeftSquare), just(Token::RightSquare)), .delimited_by(just(Token::LeftSquare), just(Token::RightSquare)),
) )
.map(|token| (ByteArrayFormatPreference::ArrayOfBytes, token)); .validate(|bytes, span, emit| {
let base = bytes.iter().fold(Ok(None), |acc, (_, base)| match acc {
Ok(None) => Ok(Some(base)),
Ok(Some(previous_base)) if previous_base == base => Ok(Some(base)),
_ => Err(()),
});
let base = match base {
Err(()) => {
emit(ParseError::hybrid_notation_in_bytearray(span));
Base::Decimal {
numeric_underscore: false,
}
}
Ok(None) => Base::Decimal {
numeric_underscore: false,
},
Ok(Some(base)) => *base,
};
(bytes.into_iter().map(|(b, _)| b).collect::<Vec<u8>>(), base)
})
.map(|(bytes, base)| (ByteArrayFormatPreference::ArrayOfBytes(base), bytes));
let bytearray_hexstring_parser = let bytearray_hexstring_parser =
just(Token::Hash) just(Token::Hash)
@ -596,12 +619,13 @@ pub fn expr_parser(
} }
}); });
let int_parser = select! { Token::Int {value} => value}.map_with_span(|value, span| { let int_parser = select! { Token::Int {value, base} => (value, base)}.map_with_span(
expr::UntypedExpr::Int { |(value, base), span| expr::UntypedExpr::Int {
location: span, location: span,
value, value,
} base,
}); },
);
let record_update_parser = select! {Token::Name { name } => name} let record_update_parser = select! {Token::Name { name } => name}
.map_with_span(|module, span: Span| (module, span)) .map_with_span(|module, span: Span| (module, span))
@ -1685,12 +1709,13 @@ pub fn pattern_parser() -> impl Parser<Token, ast::UntypedPattern, Error = Parse
location: span, location: span,
} }
}), }),
select! {Token::Int {value} => value}.map_with_span(|value, span| { select! {Token::Int {value, base} => (value, base)}.map_with_span(
ast::UntypedPattern::Int { |(value, base), span| ast::UntypedPattern::Int {
location: span, location: span,
value, value,
} base,
}), },
),
r.clone() r.clone()
.separated_by(just(Token::Comma)) .separated_by(just(Token::Comma))
.allow_trailing() .allow_trailing()

View File

@ -55,6 +55,26 @@ impl ParseError {
label: None, label: None,
} }
} }
pub fn malformed_base16_digits(span: Span) -> Self {
Self {
kind: ErrorKind::MalformedBase16Digits,
span,
while_parsing: None,
expected: HashSet::new(),
label: None,
}
}
pub fn hybrid_notation_in_bytearray(span: Span) -> Self {
Self {
kind: ErrorKind::HybridNotationInByteArray,
span,
while_parsing: None,
expected: HashSet::new(),
label: None,
}
}
} }
impl PartialEq for ParseError { impl PartialEq for ParseError {
@ -114,6 +134,12 @@ pub enum ErrorKind {
hint: Option<String>, hint: Option<String>,
}, },
#[error("I tripped over a malformed hexadecimal digits.")]
#[diagnostic(help("{}", formatdoc! {
r#"When numbers starts with '0x', they are treated as hexadecimal numbers. Thus, only digits from 0-9 or letter from a-f (or A-F) can be used following a '0x' number declaration. Plus, hexadecimal digits always go by pairs, so the total number of digits must be even (not counting leading zeros)."#
}))]
MalformedBase16Digits,
#[error("I tripped over a malformed base16-encoded string literal.")] #[error("I tripped over a malformed base16-encoded string literal.")]
#[diagnostic(help("{}", formatdoc! { #[diagnostic(help("{}", formatdoc! {
r#"You can declare literal bytearrays from base16-encoded (a.k.a. hexadecimal) string literals. r#"You can declare literal bytearrays from base16-encoded (a.k.a. hexadecimal) string literals.
@ -131,6 +157,11 @@ pub enum ErrorKind {
}))] }))]
MalformedBase16StringLiteral, MalformedBase16StringLiteral,
#[error("I came across a bytearray declared using two different notations")]
#[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#bytearray"))]
#[diagnostic(help("Either use decimal or hexadecimal notation, but don't mix them."))]
HybridNotationInByteArray,
#[error("I failed to understand a when clause guard.")] #[error("I failed to understand a when clause guard.")]
#[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#checking-equality-and-ordering-in-patterns"))] #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#checking-equality-and-ordering-in-patterns"))]
#[diagnostic(help("{}", formatdoc! { #[diagnostic(help("{}", formatdoc! {

View File

@ -1,13 +1,58 @@
use chumsky::prelude::*; use super::{
error::ParseError,
token::{Base, Token},
};
use crate::ast::Span; use crate::ast::Span;
use chumsky::prelude::*;
use num_bigint::BigInt;
use ordinal::Ordinal; use ordinal::Ordinal;
use super::{error::ParseError, token::Token};
pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = ParseError> { pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = ParseError> {
let int = text::int(10).map(|value| Token::Int { value }); let base10 = text::int(10).map(|value| Token::Int {
value,
base: Base::Decimal {
numeric_underscore: false,
},
});
let base10_underscore = one_of("0123456789")
.repeated()
.at_least(1)
.at_most(3)
.separated_by(just("_"))
.at_least(2)
.flatten()
.collect::<String>()
.map(|value| Token::Int {
value,
base: Base::Decimal {
numeric_underscore: true,
},
});
let base16 = just("0x")
.ignore_then(
one_of("0123456789abcdefABCDEF")
.repeated()
.at_least(2)
.collect::<String>(),
)
.validate(|value: String, span, emit| {
let value = match BigInt::parse_bytes(value.as_bytes(), 16) {
None => {
emit(ParseError::malformed_base16_digits(span));
String::new()
}
Some(n) => n.to_str_radix(10),
};
Token::Int {
value,
base: Base::Hexadecimal,
}
});
let int = choice((base16, base10_underscore, base10));
let ordinal = text::int(10) let ordinal = text::int(10)
.then_with(|index: String| { .then_with(|index: String| {

View File

@ -1,5 +1,11 @@
use std::fmt; use std::fmt;
#[derive(Clone, Debug, PartialEq, Hash, Eq, Copy)]
pub enum Base {
Decimal { numeric_underscore: bool },
Hexadecimal,
}
#[derive(Clone, Debug, PartialEq, Hash, Eq)] #[derive(Clone, Debug, PartialEq, Hash, Eq)]
pub enum Token { pub enum Token {
Error(char), Error(char),
@ -7,7 +13,7 @@ pub enum Token {
Ordinal { index: u32 }, Ordinal { index: u32 },
UpName { name: String }, UpName { name: String },
DiscardName { name: String }, DiscardName { name: String },
Int { value: String }, Int { value: String, base: Base },
ByteString { value: String }, ByteString { value: String },
String { value: String }, String { value: String },
// Groupings // Groupings
@ -97,7 +103,7 @@ impl fmt::Display for Token {
} }
Token::UpName { name } => name, Token::UpName { name } => name,
Token::DiscardName { name } => name, Token::DiscardName { name } => name,
Token::Int { value } => value, Token::Int { value, .. } => value,
Token::String { value } => value, Token::String { value } => value,
Token::ByteString { value } => value, Token::ByteString { value } => value,
Token::NewLineLeftParen => "↳(", Token::NewLineLeftParen => "↳(",

View File

@ -833,3 +833,17 @@ fn pipes_and_expressions() {
assert_fmt(src, expected); assert_fmt(src, expected);
} }
#[test]
fn hex_and_numeric_underscore() {
let src = indoc! {r#"
fn foo() {
let a = 1_000_000 + 1_423 + 10393841
let b = 0xa4 - 0xcd
let c = #[0xfd, 0x12, 0x00, 0x1b, 0x0a, 0x90]
let d = -100_000
}
"#};
assert_fmt(src, src);
}

View File

@ -1,6 +1,7 @@
use crate::{ use crate::{
ast::{self, Constant, DataType, Function, ModuleConstant, Span, TypeAlias, Use}, ast::{self, Constant, DataType, Function, ModuleConstant, Span, TypeAlias, Use},
expr, parser, expr,
parser::{self, token::Base},
}; };
use chumsky::prelude::*; use chumsky::prelude::*;
use indoc::indoc; use indoc::indoc;
@ -742,6 +743,9 @@ fn plus_binop() {
right: Box::new(expr::UntypedExpr::Int { right: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 33..34), location: Span::new((), 33..34),
value: "1".to_string(), value: "1".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}, },
doc: None, doc: None,
@ -803,6 +807,9 @@ fn pipeline() {
right: Box::new(expr::UntypedExpr::Int { right: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 35..36), location: Span::new((), 35..36),
value: "2".to_string(), value: "2".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}, },
expr::UntypedExpr::Var { expr::UntypedExpr::Var {
@ -861,10 +868,16 @@ fn if_expression() {
left: Box::new(expr::UntypedExpr::Int { left: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 27..28), location: Span::new((), 27..28),
value: "1".to_string(), value: "1".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
right: Box::new(expr::UntypedExpr::Int { right: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 31..32), location: Span::new((), 31..32),
value: "1".to_string(), value: "1".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}, },
location: Span::new((), 16..36), location: Span::new((), 16..36),
@ -880,11 +893,17 @@ fn if_expression() {
right: Box::new(expr::UntypedExpr::Int { right: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 49..50), location: Span::new((), 49..50),
value: "4".to_string(), value: "4".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}, },
body: expr::UntypedExpr::Int { body: expr::UntypedExpr::Int {
location: Span::new((), 57..58), location: Span::new((), 57..58),
value: "5".to_string(), value: "5".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
location: Span::new((), 45..62), location: Span::new((), 45..62),
}, },
@ -904,6 +923,9 @@ fn if_expression() {
body: expr::UntypedExpr::Int { body: expr::UntypedExpr::Int {
location: Span::new((), 84..85), location: Span::new((), 84..85),
value: "6".to_string(), value: "6".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
location: Span::new((), 71..89), location: Span::new((), 71..89),
}, },
@ -911,6 +933,9 @@ fn if_expression() {
final_else: Box::new(expr::UntypedExpr::Int { final_else: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 101..102), location: Span::new((), 101..102),
value: "3".to_string(), value: "3".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}, },
doc: None, doc: None,
@ -979,6 +1004,9 @@ fn let_bindings() {
right: Box::new(expr::UntypedExpr::Int { right: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 39..40), location: Span::new((), 39..40),
value: "2".to_string(), value: "2".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}, },
expr::UntypedExpr::Var { expr::UntypedExpr::Var {
@ -1006,10 +1034,16 @@ fn let_bindings() {
expr::UntypedExpr::Int { expr::UntypedExpr::Int {
location: Span::new((), 88..89), location: Span::new((), 88..89),
value: "1".to_string(), value: "1".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
expr::UntypedExpr::Int { expr::UntypedExpr::Int {
location: Span::new((), 91..92), location: Span::new((), 91..92),
value: "2".to_string(), value: "2".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
expr::UntypedExpr::Var { expr::UntypedExpr::Var {
location: Span::new((), 94..95), location: Span::new((), 94..95),
@ -1102,6 +1136,9 @@ fn block() {
value: Box::new(expr::UntypedExpr::Int { value: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 45..46), location: Span::new((), 45..46),
value: "4".to_string(), value: "4".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
pattern: ast::Pattern::Var { pattern: ast::Pattern::Var {
location: Span::new((), 41..42), location: Span::new((), 41..42),
@ -1120,6 +1157,9 @@ fn block() {
right: Box::new(expr::UntypedExpr::Int { right: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 56..57), location: Span::new((), 56..57),
value: "5".to_string(), value: "5".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}, },
], ],
@ -1196,11 +1236,17 @@ fn when() {
patterns: vec1![ast::Pattern::Int { patterns: vec1![ast::Pattern::Int {
location: Span::new((), 39..40), location: Span::new((), 39..40),
value: "2".to_string(), value: "2".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}], }],
guard: None, guard: None,
then: expr::UntypedExpr::Int { then: expr::UntypedExpr::Int {
location: Span::new((), 44..45), location: Span::new((), 44..45),
value: "3".to_string(), value: "3".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
}, },
ast::UntypedClause { ast::UntypedClause {
@ -1208,15 +1254,24 @@ fn when() {
patterns: vec1![ patterns: vec1![
ast::Pattern::Int { ast::Pattern::Int {
location: Span::new((), 50..51), location: Span::new((), 50..51),
value: "1".to_string() value: "1".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
ast::Pattern::Int { ast::Pattern::Int {
location: Span::new((), 54..55), location: Span::new((), 54..55),
value: "4".to_string(), value: "4".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
ast::Pattern::Int { ast::Pattern::Int {
location: Span::new((), 58..59), location: Span::new((), 58..59),
value: "5".to_string(), value: "5".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
], ],
guard: None, guard: None,
@ -1228,6 +1283,9 @@ fn when() {
value: Box::new(expr::UntypedExpr::Int { value: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 85..86), location: Span::new((), 85..86),
value: "5".to_string(), value: "5".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
pattern: ast::Pattern::Var { pattern: ast::Pattern::Var {
location: Span::new((), 75..82), location: Span::new((), 75..82),
@ -1248,11 +1306,17 @@ fn when() {
patterns: vec1![ast::Pattern::Int { patterns: vec1![ast::Pattern::Int {
location: Span::new((), 111..112), location: Span::new((), 111..112),
value: "3".to_string(), value: "3".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}], }],
guard: None, guard: None,
then: expr::UntypedExpr::Int { then: expr::UntypedExpr::Int {
location: Span::new((), 116..117), location: Span::new((), 116..117),
value: "9".to_string(), value: "9".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
}, },
ast::UntypedClause { ast::UntypedClause {
@ -1265,6 +1329,9 @@ fn when() {
then: expr::UntypedExpr::Int { then: expr::UntypedExpr::Int {
location: Span::new((), 127..128), location: Span::new((), 127..128),
value: "4".to_string(), value: "4".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
}, },
], ],
@ -1329,6 +1396,9 @@ fn anonymous_function() {
right: Box::new(expr::UntypedExpr::Int { right: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 64..65), location: Span::new((), 64..65),
value: "1".to_string(), value: "1".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}), }),
return_annotation: Some(ast::Annotation::Constructor { return_annotation: Some(ast::Annotation::Constructor {
@ -1351,6 +1421,9 @@ fn anonymous_function() {
expr::UntypedExpr::Int { expr::UntypedExpr::Int {
location: Span::new((), 71..72), location: Span::new((), 71..72),
value: "2".to_string(), value: "2".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
expr::UntypedExpr::Var { expr::UntypedExpr::Var {
location: Span::new((), 76..83), location: Span::new((), 76..83),
@ -1452,6 +1525,9 @@ fn call() {
value: expr::UntypedExpr::Int { value: expr::UntypedExpr::Int {
location: Span::new((), 31..32), location: Span::new((), 31..32),
value: "3".to_string(), value: "3".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
}], }],
fun: Box::new(expr::UntypedExpr::Var { fun: Box::new(expr::UntypedExpr::Var {
@ -1555,14 +1631,23 @@ fn call() {
expr::UntypedExpr::Int { expr::UntypedExpr::Int {
location: Span::new((), 98..99), location: Span::new((), 98..99),
value: "1".to_string(), value: "1".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
expr::UntypedExpr::Int { expr::UntypedExpr::Int {
location: Span::new((), 101..102), location: Span::new((), 101..102),
value: "2".to_string(), value: "2".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
expr::UntypedExpr::Int { expr::UntypedExpr::Int {
location: Span::new((), 104..105), location: Span::new((), 104..105),
value: "3".to_string(), value: "3".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
], ],
tail: None, tail: None,
@ -1720,6 +1805,9 @@ fn record_create_labeled() {
value: expr::UntypedExpr::Int { value: expr::UntypedExpr::Int {
location: Span::new((), 50..51), location: Span::new((), 50..51),
value: "2".to_string(), value: "2".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
}, },
], ],
@ -1778,6 +1866,9 @@ fn record_create_labeled_with_field_access() {
value: expr::UntypedExpr::Int { value: expr::UntypedExpr::Int {
location: Span::new((), 62..63), location: Span::new((), 62..63),
value: "2".to_string(), value: "2".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
}, },
], ],
@ -1823,6 +1914,9 @@ fn record_create_unlabeled() {
value: expr::UntypedExpr::Int { value: expr::UntypedExpr::Int {
location: Span::new((), 34..35), location: Span::new((), 34..35),
value: "1".to_string(), value: "1".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
}, },
ast::CallArg { ast::CallArg {
@ -1880,18 +1974,30 @@ fn parse_tuple() {
expr::UntypedExpr::Int { expr::UntypedExpr::Int {
location: Span::new((), 26..27), location: Span::new((), 26..27),
value: "1".to_string(), value: "1".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
expr::UntypedExpr::Int { expr::UntypedExpr::Int {
location: Span::new((), 29..30), location: Span::new((), 29..30),
value: "2".to_string(), value: "2".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
expr::UntypedExpr::Int { expr::UntypedExpr::Int {
location: Span::new((), 32..33), location: Span::new((), 32..33),
value: "3".to_string(), value: "3".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
expr::UntypedExpr::Int { expr::UntypedExpr::Int {
location: Span::new((), 35..36), location: Span::new((), 35..36),
value: "4".to_string(), value: "4".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
], ],
}), }),
@ -1985,6 +2091,9 @@ fn parse_tuple2() {
value: expr::UntypedExpr::Int { value: expr::UntypedExpr::Int {
location: Span::new((), 25..27), location: Span::new((), 25..27),
value: "14".to_string(), value: "14".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
}], }],
fun: Box::new(expr::UntypedExpr::Var { fun: Box::new(expr::UntypedExpr::Var {
@ -2010,6 +2119,9 @@ fn parse_tuple2() {
expr::UntypedExpr::Int { expr::UntypedExpr::Int {
location: Span::new((), 35..37), location: Span::new((), 35..37),
value: "42".to_string(), value: "42".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
], ],
}, },
@ -2043,6 +2155,9 @@ fn large_integer_constants() {
value: Box::new(ast::Constant::Int { value: Box::new(ast::Constant::Int {
location: Span::new((), 23..47), location: Span::new((), 23..47),
value: "999999999999999999999999".to_string(), value: "999999999999999999999999".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
tipo: (), tipo: (),
})], })],
@ -2066,7 +2181,9 @@ fn plain_bytearray_literals() {
value: Box::new(Constant::ByteArray { value: Box::new(Constant::ByteArray {
location: Span::new((), 25..39), location: Span::new((), 25..39),
bytes: vec![0, 170, 255], bytes: vec![0, 170, 255],
preferred_format: ast::ByteArrayFormatPreference::ArrayOfBytes, preferred_format: ast::ByteArrayFormatPreference::ArrayOfBytes(Base::Decimal {
numeric_underscore: false,
}),
}), }),
tipo: (), tipo: (),
})], })],
@ -2194,6 +2311,9 @@ fn function_invoke() {
value: expr::UntypedExpr::Int { value: expr::UntypedExpr::Int {
location: Span::new((), 25..27), location: Span::new((), 25..27),
value: "42".to_string(), value: "42".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
}], }],
}), }),
@ -2256,6 +2376,9 @@ fn function_ambiguous_sequence() {
expr::UntypedExpr::Int { expr::UntypedExpr::Int {
location: Span::new((), 30..32), location: Span::new((), 30..32),
value: "40".to_string(), value: "40".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
], ],
}, },
@ -2289,6 +2412,9 @@ fn function_ambiguous_sequence() {
expr::UntypedExpr::Int { expr::UntypedExpr::Int {
location: Span::new((), 67..69), location: Span::new((), 67..69),
value: "40".to_string(), value: "40".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
], ],
}, },
@ -2311,10 +2437,16 @@ fn function_ambiguous_sequence() {
left: Box::new(expr::UntypedExpr::Int { left: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 98..100), location: Span::new((), 98..100),
value: "40".to_string(), value: "40".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
right: Box::new(expr::UntypedExpr::Int { right: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 101..102), location: Span::new((), 101..102),
value: "2".to_string(), value: "2".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}), }),
pattern: ast::Pattern::Var { pattern: ast::Pattern::Var {
@ -2347,6 +2479,9 @@ fn function_ambiguous_sequence() {
value: expr::UntypedExpr::Int { value: expr::UntypedExpr::Int {
location: Span::new((), 134..136), location: Span::new((), 134..136),
value: "42".to_string(), value: "42".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
}], }],
fun: Box::new(expr::UntypedExpr::Var { fun: Box::new(expr::UntypedExpr::Var {
@ -2375,11 +2510,17 @@ fn function_ambiguous_sequence() {
right: Box::new(expr::UntypedExpr::Int { right: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 145..147), location: Span::new((), 145..147),
value: "14".to_string(), value: "14".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}), }),
right: Box::new(expr::UntypedExpr::Int { right: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 151..153), location: Span::new((), 151..153),
value: "42".to_string(), value: "42".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}, },
], ],
@ -2514,15 +2655,24 @@ fn subtraction_vs_negate() {
left: Box::new(expr::UntypedExpr::Int { left: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 14..15), location: Span::new((), 14..15),
value: "1".to_string(), value: "1".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
right: Box::new(expr::UntypedExpr::Int { right: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 16..17), location: Span::new((), 16..17),
value: "1".to_string(), value: "1".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}), }),
right: Box::new(expr::UntypedExpr::Int { right: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 22..23), location: Span::new((), 22..23),
value: "0".to_string(), value: "0".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}, },
expr::UntypedExpr::Assignment { expr::UntypedExpr::Assignment {
@ -2533,6 +2683,9 @@ fn subtraction_vs_negate() {
value: Box::new(expr::UntypedExpr::Int { value: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 35..36), location: Span::new((), 35..36),
value: "2".to_string(), value: "2".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}), }),
pattern: ast::Pattern::Var { pattern: ast::Pattern::Var {
@ -2556,6 +2709,9 @@ fn subtraction_vs_negate() {
right: Box::new(expr::UntypedExpr::Int { right: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 45..46), location: Span::new((), 45..46),
value: "4".to_string(), value: "4".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}, },
expr::UntypedExpr::BinOp { expr::UntypedExpr::BinOp {
@ -2571,6 +2727,9 @@ fn subtraction_vs_negate() {
value: Box::new(expr::UntypedExpr::Int { value: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 54..55), location: Span::new((), 54..55),
value: "1".to_string(), value: "1".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}, },
}], }],
@ -2583,6 +2742,9 @@ fn subtraction_vs_negate() {
right: Box::new(expr::UntypedExpr::Int { right: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 59..61), location: Span::new((), 59..61),
value: "42".to_string(), value: "42".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}, },
], ],
@ -2635,6 +2797,9 @@ fn clause_guards() {
guard: Some(ast::UntypedClauseGuard::Constant(ast::Constant::Int { guard: Some(ast::UntypedClauseGuard::Constant(ast::Constant::Int {
location: Span::new((), 34..36), location: Span::new((), 34..36),
value: "42".to_string(), value: "42".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
})), })),
then: expr::UntypedExpr::Var { then: expr::UntypedExpr::Var {
location: Span::new((), 40..44), location: Span::new((), 40..44),
@ -2758,6 +2923,9 @@ fn clause_guards() {
ast::Constant::Int { ast::Constant::Int {
location: Span::new((), 162..164), location: Span::new((), 162..164),
value: "42".to_string(), value: "42".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
)), )),
}), }),
@ -2772,6 +2940,9 @@ fn clause_guards() {
ast::Constant::Int { ast::Constant::Int {
location: Span::new((), 172..174), location: Span::new((), 172..174),
value: "14".to_string(), value: "14".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
)), )),
}), }),
@ -2808,6 +2979,9 @@ fn clause_guards() {
ast::Constant::Int { ast::Constant::Int {
location: Span::new((), 206..208), location: Span::new((), 206..208),
value: "14".to_string(), value: "14".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}, },
)), )),
}), }),
@ -3011,6 +3185,9 @@ fn trace_expressions() {
left: Box::new(expr::UntypedExpr::Int { left: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 108..110), location: Span::new((), 108..110),
value: "14".to_string(), value: "14".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
right: Box::new(expr::UntypedExpr::BinOp { right: Box::new(expr::UntypedExpr::BinOp {
location: Span::new((), 113..122), location: Span::new((), 113..122),
@ -3018,10 +3195,16 @@ fn trace_expressions() {
left: Box::new(expr::UntypedExpr::Int { left: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 113..115), location: Span::new((), 113..115),
value: "42".to_string(), value: "42".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
right: Box::new(expr::UntypedExpr::Int { right: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 118..122), location: Span::new((), 118..122),
value: "1337".to_string(), value: "1337".to_string(),
base: Base::Decimal {
numeric_underscore: false,
},
}), }),
}), }),
}), }),
@ -3337,3 +3520,150 @@ fn brackets_followed_by_parenthesis() {
} }
"#}); "#});
} }
#[test]
fn int_parsing_hex() {
let code = indoc! {r#"
fn foo() {
let i = 0xff
}
"#};
assert_definitions(
code,
vec![ast::Definition::Fn(Function {
arguments: vec![],
body: expr::UntypedExpr::Assignment {
location: Span::new((), 13..25),
value: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 21..25),
value: "255".to_string(),
base: Base::Hexadecimal,
}),
pattern: ast::Pattern::Var {
location: Span::new((), 17..18),
name: "i".to_string(),
},
kind: ast::AssignmentKind::Let,
annotation: None,
},
doc: None,
location: Span::new((), 0..8),
name: "foo".to_string(),
public: false,
return_annotation: None,
return_type: (),
end_position: 26,
can_error: true,
})],
)
}
#[test]
fn int_parsing_hex_bytes() {
let code = indoc! {r#"
fn foo() {
#[ 0x01, 0xa2, 0x03 ]
}
"#};
assert_definitions(
code,
vec![ast::Definition::Fn(Function {
arguments: vec![],
body: expr::UntypedExpr::ByteArray {
location: Span::new((), 13..34),
bytes: vec![1, 162, 3],
preferred_format: ast::ByteArrayFormatPreference::ArrayOfBytes(Base::Hexadecimal),
},
doc: None,
location: Span::new((), 0..8),
name: "foo".to_string(),
public: false,
return_annotation: None,
return_type: (),
end_position: 35,
can_error: true,
})],
)
}
#[test]
fn int_parsing_numeric_underscore() {
let code = indoc! {r#"
fn foo() {
let i = 1_234_567
let j = 1_000_000
let k = -10_000
}
"#};
assert_definitions(
code,
vec![ast::Definition::Fn(Function {
arguments: vec![],
body: expr::UntypedExpr::Sequence {
location: Span::new((), 17..76),
expressions: vec![
expr::UntypedExpr::Assignment {
location: Span::new((), 17..34),
value: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 25..34),
value: "1234567".to_string(),
base: Base::Decimal {
numeric_underscore: true,
},
}),
pattern: ast::Pattern::Var {
location: Span::new((), 21..22),
name: "i".to_string(),
},
kind: ast::AssignmentKind::Let,
annotation: None,
},
expr::UntypedExpr::Assignment {
location: Span::new((), 39..56),
value: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 47..56),
value: "1000000".to_string(),
base: Base::Decimal {
numeric_underscore: true,
},
}),
pattern: ast::Pattern::Var {
location: Span::new((), 43..44),
name: "j".to_string(),
},
kind: ast::AssignmentKind::Let,
annotation: None,
},
expr::UntypedExpr::Assignment {
location: Span::new((), 61..76),
value: Box::new(expr::UntypedExpr::UnOp {
op: ast::UnOp::Negate,
location: Span::new((), 69..76),
value: Box::new(expr::UntypedExpr::Int {
location: Span::new((), 70..76),
value: "10000".to_string(),
base: Base::Decimal {
numeric_underscore: true,
},
}),
}),
pattern: ast::Pattern::Var {
location: Span::new((), 65..66),
name: "k".to_string(),
},
kind: ast::AssignmentKind::Let,
annotation: None,
},
],
},
doc: None,
location: Span::new((), 2..10),
name: "foo".to_string(),
public: false,
return_annotation: None,
return_type: (),
end_position: 77,
can_error: true,
})],
)
}

View File

@ -1304,8 +1304,14 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
) -> Result<Constant, Error> { ) -> Result<Constant, Error> {
let inferred = match value { let inferred = match value {
Constant::Int { Constant::Int {
location, value, .. location,
} => Ok(Constant::Int { location, value }), value,
base,
} => Ok(Constant::Int {
location,
value,
base,
}),
Constant::String { Constant::String {
location, value, .. location, value, ..

View File

@ -182,10 +182,18 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
}) })
} }
Pattern::Int { location, value } => { Pattern::Int {
location,
value,
base,
} => {
self.environment.unify(tipo, int(), location, false)?; self.environment.unify(tipo, int(), location, false)?;
Ok(Pattern::Int { location, value }) Ok(Pattern::Int {
location,
value,
base,
})
} }
Pattern::List { Pattern::List {