diff --git a/CHANGELOG.md b/CHANGELOG.md index e0016ca0..9a65881a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ ### 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 diff --git a/Cargo.lock b/Cargo.lock index ae6c36f5..e37460ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,6 +85,7 @@ dependencies = [ "indoc", "itertools", "miette", + "num-bigint", "ordinal", "owo-colors", "pretty_assertions", diff --git a/crates/aiken-lang/Cargo.toml b/crates/aiken-lang/Cargo.toml index 9733cc5e..039b4d79 100644 --- a/crates/aiken-lang/Cargo.toml +++ b/crates/aiken-lang/Cargo.toml @@ -25,6 +25,7 @@ strum = "0.24.1" thiserror = "1.0.39" vec1 = "1.10.1" uplc = { path = '../uplc', version = "1.0.7-alpha" } +num-bigint = "0.4.3" [target.'cfg(not(target_family="wasm"))'.dependencies] chumsky = "0.9.2" diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 5f01af29..1f1374fc 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -1,7 +1,7 @@ use crate::{ builtins::{self, bool}, expr::{TypedExpr, UntypedExpr}, - parser::token::Token, + parser::token::{Base, Token}, tipo::{PatternConstructor, Type, TypeInfo}, }; use miette::Diagnostic; @@ -441,6 +441,7 @@ pub enum Constant { Int { location: Span, value: String, + base: Base, }, String { @@ -795,6 +796,7 @@ pub enum Pattern { Int { location: Span, value: String, + base: Base, }, /// The creation of a variable. @@ -880,7 +882,7 @@ impl Pattern { #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum ByteArrayFormatPreference { HexadecimalString, - ArrayOfBytes, + ArrayOfBytes(Base), Utf8String, } diff --git a/crates/aiken-lang/src/expr.rs b/crates/aiken-lang/src/expr.rs index a82a67f9..d669f171 100644 --- a/crates/aiken-lang/src/expr.rs +++ b/crates/aiken-lang/src/expr.rs @@ -9,6 +9,7 @@ use crate::{ TypedRecordUpdateArg, UnOp, UntypedClause, UntypedRecordUpdateArg, }, builtins::void, + parser::token::Base, tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor}, }; @@ -403,6 +404,7 @@ pub enum UntypedExpr { Int { location: Span, value: String, + base: Base, }, String { diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index 5c91ca83..5fb377c3 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -1,8 +1,3 @@ -use itertools::Itertools; -use ordinal::Ordinal; -use std::sync::Arc; -use vec1::Vec1; - use crate::{ ast::{ Annotation, Arg, ArgName, AssignmentKind, BinOp, ByteArrayFormatPreference, CallArg, @@ -14,12 +9,20 @@ use crate::{ }, docvec, expr::{UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR}, - parser::extra::{Comment, ModuleExtra}, + parser::{ + extra::{Comment, ModuleExtra}, + token::Base, + }, pretty::{ break_, concat, flex_break, join, line, lines, nil, prebreak, Document, Documentable, }, 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 DOCS_MAX_COLUMNS: isize = 80; @@ -348,7 +351,7 @@ impl<'comments> Formatter<'comments> { 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), } } @@ -662,7 +665,7 @@ impl<'comments> Formatter<'comments> { .append("\"") .append(Document::String(hex::encode(bytes))) .append("\""), - ByteArrayFormatPreference::ArrayOfBytes => "#" + ByteArrayFormatPreference::ArrayOfBytes(Base::Decimal { .. }) => "#" .to_doc() .append( flex_break("[", "[") @@ -672,6 +675,25 @@ impl<'comments> Formatter<'comments> { .append("]"), ) .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() .append("\"") .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::() + .chars() + .rev() + .collect::(); + + 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> { let comments = self.pop_comments(expr.start_byte_index()); @@ -700,7 +755,7 @@ impl<'comments> Formatter<'comments> { 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), @@ -1507,7 +1562,7 @@ impl<'comments> Formatter<'comments> { pub fn pattern<'a>(&mut self, pattern: &'a UntypedPattern) -> Document<'a> { let comments = self.pop_comments(pattern.location().start); let doc = match pattern { - Pattern::Int { value, .. } => value.to_doc(), + Pattern::Int { value, base, .. } => self.int(value, base), Pattern::Var { name, .. } => name.to_doc(), diff --git a/crates/aiken-lang/src/parser.rs b/crates/aiken-lang/src/parser.rs index 750e540d..b24a738c 100644 --- a/crates/aiken-lang/src/parser.rs +++ b/crates/aiken-lang/src/parser.rs @@ -13,7 +13,7 @@ use crate::{ use chumsky::{chain::Chain, prelude::*}; use error::ParseError; use extra::ModuleExtra; -use token::Token; +use token::{Base, Token}; use vec1::{vec1, Vec1}; pub fn module( @@ -397,9 +397,12 @@ fn constant_value_parser() -> impl Parser value}.map_with_span(|value, span| ast::Constant::Int { - location: span, - value, + select! {Token::Int {value, base} => (value, base)}.map_with_span(|(value, base), span| { + ast::Constant::Int { + location: span, + value, + base, + } }); let constant_bytearray_parser = @@ -422,8 +425,8 @@ 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| { + select! {Token::Int {value, base, ..} => (value, base)} + .validate(|(value, base), span, emit| { let byte: u8 = match value.parse() { Ok(b) => b, Err(_) => { @@ -432,18 +435,38 @@ pub fn bytearray_parser( None, Some(error::Pattern::Byte), )); - 0 } }; - - byte + (byte, base) }) .separated_by(just(Token::Comma)) .allow_trailing() .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::>(), base) + }) + .map(|(bytes, base)| (ByteArrayFormatPreference::ArrayOfBytes(base), bytes)); let bytearray_hexstring_parser = just(Token::Hash) @@ -596,12 +619,13 @@ pub fn expr_parser( } }); - let int_parser = select! { Token::Int {value} => value}.map_with_span(|value, span| { - expr::UntypedExpr::Int { + let int_parser = select! { Token::Int {value, base} => (value, base)}.map_with_span( + |(value, base), span| expr::UntypedExpr::Int { location: span, value, - } - }); + base, + }, + ); let record_update_parser = select! {Token::Name { name } => name} .map_with_span(|module, span: Span| (module, span)) @@ -1685,12 +1709,13 @@ pub fn pattern_parser() -> impl Parser value}.map_with_span(|value, span| { - ast::UntypedPattern::Int { + select! {Token::Int {value, base} => (value, base)}.map_with_span( + |(value, base), span| ast::UntypedPattern::Int { location: span, value, - } - }), + base, + }, + ), r.clone() .separated_by(just(Token::Comma)) .allow_trailing() diff --git a/crates/aiken-lang/src/parser/error.rs b/crates/aiken-lang/src/parser/error.rs index 312fc95c..aeacaba3 100644 --- a/crates/aiken-lang/src/parser/error.rs +++ b/crates/aiken-lang/src/parser/error.rs @@ -55,6 +55,26 @@ impl ParseError { 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 { @@ -114,6 +134,12 @@ pub enum ErrorKind { hint: Option, }, + #[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.")] #[diagnostic(help("{}", formatdoc! { r#"You can declare literal bytearrays from base16-encoded (a.k.a. hexadecimal) string literals. @@ -131,6 +157,11 @@ pub enum ErrorKind { }))] 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.")] #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#checking-equality-and-ordering-in-patterns"))] #[diagnostic(help("{}", formatdoc! { diff --git a/crates/aiken-lang/src/parser/lexer.rs b/crates/aiken-lang/src/parser/lexer.rs index 28202721..110d0335 100644 --- a/crates/aiken-lang/src/parser/lexer.rs +++ b/crates/aiken-lang/src/parser/lexer.rs @@ -1,13 +1,58 @@ -use chumsky::prelude::*; - +use super::{ + error::ParseError, + token::{Base, Token}, +}; use crate::ast::Span; - +use chumsky::prelude::*; +use num_bigint::BigInt; use ordinal::Ordinal; -use super::{error::ParseError, token::Token}; - pub fn lexer() -> impl Parser, 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::() + .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::(), + ) + .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) .then_with(|index: String| { diff --git a/crates/aiken-lang/src/parser/token.rs b/crates/aiken-lang/src/parser/token.rs index b06a4783..a64fc82e 100644 --- a/crates/aiken-lang/src/parser/token.rs +++ b/crates/aiken-lang/src/parser/token.rs @@ -1,5 +1,11 @@ use std::fmt; +#[derive(Clone, Debug, PartialEq, Hash, Eq, Copy)] +pub enum Base { + Decimal { numeric_underscore: bool }, + Hexadecimal, +} + #[derive(Clone, Debug, PartialEq, Hash, Eq)] pub enum Token { Error(char), @@ -7,7 +13,7 @@ pub enum Token { Ordinal { index: u32 }, UpName { name: String }, DiscardName { name: String }, - Int { value: String }, + Int { value: String, base: Base }, ByteString { value: String }, String { value: String }, // Groupings @@ -97,7 +103,7 @@ impl fmt::Display for Token { } Token::UpName { name } => name, Token::DiscardName { name } => name, - Token::Int { value } => value, + Token::Int { value, .. } => value, Token::String { value } => value, Token::ByteString { value } => value, Token::NewLineLeftParen => "↳(", diff --git a/crates/aiken-lang/src/tests/format.rs b/crates/aiken-lang/src/tests/format.rs index 9fc4aa83..8a0ca4d4 100644 --- a/crates/aiken-lang/src/tests/format.rs +++ b/crates/aiken-lang/src/tests/format.rs @@ -833,3 +833,17 @@ fn pipes_and_expressions() { 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); +} diff --git a/crates/aiken-lang/src/tests/parser.rs b/crates/aiken-lang/src/tests/parser.rs index b0d292df..19479451 100644 --- a/crates/aiken-lang/src/tests/parser.rs +++ b/crates/aiken-lang/src/tests/parser.rs @@ -1,6 +1,7 @@ use crate::{ ast::{self, Constant, DataType, Function, ModuleConstant, Span, TypeAlias, Use}, - expr, parser, + expr, + parser::{self, token::Base}, }; use chumsky::prelude::*; use indoc::indoc; @@ -742,6 +743,9 @@ fn plus_binop() { right: Box::new(expr::UntypedExpr::Int { location: Span::new((), 33..34), value: "1".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), }, doc: None, @@ -803,6 +807,9 @@ fn pipeline() { right: Box::new(expr::UntypedExpr::Int { location: Span::new((), 35..36), value: "2".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), }, expr::UntypedExpr::Var { @@ -861,10 +868,16 @@ fn if_expression() { left: Box::new(expr::UntypedExpr::Int { location: Span::new((), 27..28), value: "1".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), right: Box::new(expr::UntypedExpr::Int { location: Span::new((), 31..32), value: "1".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), }, location: Span::new((), 16..36), @@ -880,11 +893,17 @@ fn if_expression() { right: Box::new(expr::UntypedExpr::Int { location: Span::new((), 49..50), value: "4".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), }, body: expr::UntypedExpr::Int { location: Span::new((), 57..58), value: "5".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, location: Span::new((), 45..62), }, @@ -904,6 +923,9 @@ fn if_expression() { body: expr::UntypedExpr::Int { location: Span::new((), 84..85), value: "6".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, location: Span::new((), 71..89), }, @@ -911,6 +933,9 @@ fn if_expression() { final_else: Box::new(expr::UntypedExpr::Int { location: Span::new((), 101..102), value: "3".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), }, doc: None, @@ -979,6 +1004,9 @@ fn let_bindings() { right: Box::new(expr::UntypedExpr::Int { location: Span::new((), 39..40), value: "2".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), }, expr::UntypedExpr::Var { @@ -1006,10 +1034,16 @@ fn let_bindings() { expr::UntypedExpr::Int { location: Span::new((), 88..89), value: "1".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, expr::UntypedExpr::Int { location: Span::new((), 91..92), value: "2".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, expr::UntypedExpr::Var { location: Span::new((), 94..95), @@ -1102,6 +1136,9 @@ fn block() { value: Box::new(expr::UntypedExpr::Int { location: Span::new((), 45..46), value: "4".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), pattern: ast::Pattern::Var { location: Span::new((), 41..42), @@ -1120,6 +1157,9 @@ fn block() { right: Box::new(expr::UntypedExpr::Int { location: Span::new((), 56..57), value: "5".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), }, ], @@ -1196,11 +1236,17 @@ fn when() { patterns: vec1![ast::Pattern::Int { location: Span::new((), 39..40), value: "2".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }], guard: None, then: expr::UntypedExpr::Int { location: Span::new((), 44..45), value: "3".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, }, ast::UntypedClause { @@ -1208,15 +1254,24 @@ fn when() { patterns: vec1![ ast::Pattern::Int { location: Span::new((), 50..51), - value: "1".to_string() + value: "1".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, ast::Pattern::Int { location: Span::new((), 54..55), value: "4".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, ast::Pattern::Int { location: Span::new((), 58..59), value: "5".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, ], guard: None, @@ -1228,6 +1283,9 @@ fn when() { value: Box::new(expr::UntypedExpr::Int { location: Span::new((), 85..86), value: "5".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), pattern: ast::Pattern::Var { location: Span::new((), 75..82), @@ -1248,11 +1306,17 @@ fn when() { patterns: vec1![ast::Pattern::Int { location: Span::new((), 111..112), value: "3".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }], guard: None, then: expr::UntypedExpr::Int { location: Span::new((), 116..117), value: "9".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, }, ast::UntypedClause { @@ -1265,6 +1329,9 @@ fn when() { then: expr::UntypedExpr::Int { location: Span::new((), 127..128), value: "4".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, }, ], @@ -1329,6 +1396,9 @@ fn anonymous_function() { right: Box::new(expr::UntypedExpr::Int { location: Span::new((), 64..65), value: "1".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), }), return_annotation: Some(ast::Annotation::Constructor { @@ -1351,6 +1421,9 @@ fn anonymous_function() { expr::UntypedExpr::Int { location: Span::new((), 71..72), value: "2".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, expr::UntypedExpr::Var { location: Span::new((), 76..83), @@ -1452,6 +1525,9 @@ fn call() { value: expr::UntypedExpr::Int { location: Span::new((), 31..32), value: "3".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, }], fun: Box::new(expr::UntypedExpr::Var { @@ -1555,14 +1631,23 @@ fn call() { expr::UntypedExpr::Int { location: Span::new((), 98..99), value: "1".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, expr::UntypedExpr::Int { location: Span::new((), 101..102), value: "2".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, expr::UntypedExpr::Int { location: Span::new((), 104..105), value: "3".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, ], tail: None, @@ -1720,6 +1805,9 @@ fn record_create_labeled() { value: expr::UntypedExpr::Int { location: Span::new((), 50..51), 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 { location: Span::new((), 62..63), value: "2".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, }, ], @@ -1823,6 +1914,9 @@ fn record_create_unlabeled() { value: expr::UntypedExpr::Int { location: Span::new((), 34..35), value: "1".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, }, ast::CallArg { @@ -1880,18 +1974,30 @@ fn parse_tuple() { expr::UntypedExpr::Int { location: Span::new((), 26..27), value: "1".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, expr::UntypedExpr::Int { location: Span::new((), 29..30), value: "2".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, expr::UntypedExpr::Int { location: Span::new((), 32..33), value: "3".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, expr::UntypedExpr::Int { location: Span::new((), 35..36), value: "4".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, ], }), @@ -1985,6 +2091,9 @@ fn parse_tuple2() { value: expr::UntypedExpr::Int { location: Span::new((), 25..27), value: "14".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, }], fun: Box::new(expr::UntypedExpr::Var { @@ -2010,6 +2119,9 @@ fn parse_tuple2() { expr::UntypedExpr::Int { location: Span::new((), 35..37), value: "42".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, ], }, @@ -2043,6 +2155,9 @@ fn large_integer_constants() { value: Box::new(ast::Constant::Int { location: Span::new((), 23..47), value: "999999999999999999999999".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), tipo: (), })], @@ -2066,7 +2181,9 @@ fn plain_bytearray_literals() { value: Box::new(Constant::ByteArray { location: Span::new((), 25..39), bytes: vec![0, 170, 255], - preferred_format: ast::ByteArrayFormatPreference::ArrayOfBytes, + preferred_format: ast::ByteArrayFormatPreference::ArrayOfBytes(Base::Decimal { + numeric_underscore: false, + }), }), tipo: (), })], @@ -2194,6 +2311,9 @@ fn function_invoke() { value: expr::UntypedExpr::Int { location: Span::new((), 25..27), value: "42".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, }], }), @@ -2256,6 +2376,9 @@ fn function_ambiguous_sequence() { expr::UntypedExpr::Int { location: Span::new((), 30..32), value: "40".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, ], }, @@ -2289,6 +2412,9 @@ fn function_ambiguous_sequence() { expr::UntypedExpr::Int { location: Span::new((), 67..69), value: "40".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, ], }, @@ -2311,10 +2437,16 @@ fn function_ambiguous_sequence() { left: Box::new(expr::UntypedExpr::Int { location: Span::new((), 98..100), value: "40".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), right: Box::new(expr::UntypedExpr::Int { location: Span::new((), 101..102), value: "2".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), }), pattern: ast::Pattern::Var { @@ -2347,6 +2479,9 @@ fn function_ambiguous_sequence() { value: expr::UntypedExpr::Int { location: Span::new((), 134..136), value: "42".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, }], fun: Box::new(expr::UntypedExpr::Var { @@ -2375,11 +2510,17 @@ fn function_ambiguous_sequence() { right: Box::new(expr::UntypedExpr::Int { location: Span::new((), 145..147), value: "14".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), }), right: Box::new(expr::UntypedExpr::Int { location: Span::new((), 151..153), value: "42".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), }, ], @@ -2514,15 +2655,24 @@ fn subtraction_vs_negate() { left: Box::new(expr::UntypedExpr::Int { location: Span::new((), 14..15), value: "1".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), right: Box::new(expr::UntypedExpr::Int { location: Span::new((), 16..17), value: "1".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), }), right: Box::new(expr::UntypedExpr::Int { location: Span::new((), 22..23), value: "0".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), }, expr::UntypedExpr::Assignment { @@ -2533,6 +2683,9 @@ fn subtraction_vs_negate() { value: Box::new(expr::UntypedExpr::Int { location: Span::new((), 35..36), value: "2".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), }), pattern: ast::Pattern::Var { @@ -2556,6 +2709,9 @@ fn subtraction_vs_negate() { right: Box::new(expr::UntypedExpr::Int { location: Span::new((), 45..46), value: "4".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), }, expr::UntypedExpr::BinOp { @@ -2571,6 +2727,9 @@ fn subtraction_vs_negate() { value: Box::new(expr::UntypedExpr::Int { location: Span::new((), 54..55), value: "1".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), }, }], @@ -2583,6 +2742,9 @@ fn subtraction_vs_negate() { right: Box::new(expr::UntypedExpr::Int { location: Span::new((), 59..61), 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 { location: Span::new((), 34..36), value: "42".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, })), then: expr::UntypedExpr::Var { location: Span::new((), 40..44), @@ -2758,6 +2923,9 @@ fn clause_guards() { ast::Constant::Int { location: Span::new((), 162..164), value: "42".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, )), }), @@ -2772,6 +2940,9 @@ fn clause_guards() { ast::Constant::Int { location: Span::new((), 172..174), value: "14".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, )), }), @@ -2808,6 +2979,9 @@ fn clause_guards() { ast::Constant::Int { location: Span::new((), 206..208), value: "14".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }, )), }), @@ -3011,6 +3185,9 @@ fn trace_expressions() { left: Box::new(expr::UntypedExpr::Int { location: Span::new((), 108..110), value: "14".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), right: Box::new(expr::UntypedExpr::BinOp { location: Span::new((), 113..122), @@ -3018,10 +3195,16 @@ fn trace_expressions() { left: Box::new(expr::UntypedExpr::Int { location: Span::new((), 113..115), value: "42".to_string(), + base: Base::Decimal { + numeric_underscore: false, + }, }), right: Box::new(expr::UntypedExpr::Int { location: Span::new((), 118..122), 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, + })], + ) +} diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 9a198e82..aa613e71 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -1304,8 +1304,14 @@ impl<'a, 'b> ExprTyper<'a, 'b> { ) -> Result { let inferred = match value { Constant::Int { - location, value, .. - } => Ok(Constant::Int { location, value }), + location, + value, + base, + } => Ok(Constant::Int { + location, + value, + base, + }), Constant::String { location, value, .. diff --git a/crates/aiken-lang/src/tipo/pattern.rs b/crates/aiken-lang/src/tipo/pattern.rs index 314be952..e4241fad 100644 --- a/crates/aiken-lang/src/tipo/pattern.rs +++ b/crates/aiken-lang/src/tipo/pattern.rs @@ -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)?; - Ok(Pattern::Int { location, value }) + Ok(Pattern::Int { + location, + value, + base, + }) } Pattern::List {