Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,20 @@

([Giacomo Cavalieri](https://github.com/giacomocavalieri))

- The language server now offers a code action to rewrite integers in a
different base. For example:

```gleam
pub fn lucky_number() {
0b1011
//^^^^^^ Hovering this
}
```

The language server is going to show code actions to rewrite it as `11`,
`0o13`, or `0xB`.
([Giacomo Cavalieri](https://github.com/giacomocavalieri))

- The "remove unreachable patterns" code action can now be triggered on
unreachable alternative patterns of a case expression.
([Giacomo Cavalieri](https://github.com/giacomocavalieri))
Expand Down Expand Up @@ -555,6 +569,10 @@
when finding the Gleam files of a package.
([Giacomo Cavalieri](https://github.com/giacomocavalieri))

- Fixed a bug where the compiler would not be able to correctly parse negative
numbers written in binary, octal, or hexadecimal base.
([Giacomo Cavalieri](https://github.com/giacomocavalieri))

- The formatter now properly formats binary operations in bit array size
segments.
([Andrey Kozhev](https://github.com/ankddev))
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 7 additions & 5 deletions compiler-core/src/ast/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,10 @@ pub trait Visit<'ast> {
&mut self,
location: &'ast SrcSpan,
type_: &'ast Arc<Type>,
value: &'ast EcoString,
string_value: &'ast EcoString,
int_value: &'ast BigInt,
) {
visit_typed_expr_int(self, location, type_, value);
visit_typed_expr_int(self, location, type_, string_value, int_value);
}

fn visit_typed_expr_float(
Expand Down Expand Up @@ -1261,8 +1262,8 @@ where
location,
type_,
value,
int_value: _,
} => v.visit_typed_expr_int(location, type_, value),
int_value,
} => v.visit_typed_expr_int(location, type_, value, int_value),
TypedExpr::Float {
location,
type_,
Expand Down Expand Up @@ -1428,7 +1429,8 @@ pub fn visit_typed_expr_int<'a, V>(
_v: &mut V,
_location: &'a SrcSpan,
_type_: &'a Arc<Type>,
_value: &'a EcoString,
_string_value: &'a EcoString,
_int_value: &'a BigInt,
) where
V: Visit<'a> + ?Sized,
{
Expand Down
91 changes: 61 additions & 30 deletions compiler-core/src/parse/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::parse::LiteralFloatValue;
use crate::parse::error::{LexicalError, LexicalErrorType};
use crate::parse::token::Token;
use std::char;
use std::ops::Neg;

use super::error::InvalidUnicodeEscapeError;

Expand Down Expand Up @@ -879,27 +880,41 @@ where

fn lex_number(&mut self) -> LexResult {
let start_pos = self.get_pos();

// We call this function after making sure that what comes next starts
// with what seems to be a valid number. If we see that it starts with
// `-` we consume the token and record that the number is negative.
let is_negative = if self.chr0 == Some('-') {
let _ = self.next_char();
true
} else {
false
};

let num = if self.chr0 == Some('0') {
if self.chr1 == Some('x') || self.chr1 == Some('X') {
// Hex!
let _ = self.next_char();
let _ = self.next_char();
self.lex_number_radix(start_pos, 16, "0x")?
} else if self.chr1 == Some('o') || self.chr1 == Some('O') {
// Octal!
let _ = self.next_char();
let _ = self.next_char();
self.lex_number_radix(start_pos, 8, "0o")?
} else if self.chr1 == Some('b') || self.chr1 == Some('B') {
// Binary!
let _ = self.next_char();
let _ = self.next_char();
self.lex_number_radix(start_pos, 2, "0b")?
} else {
self.lex_decimal_number()?
match self.chr1 {
Some('x' | 'X') => {
// Hex!
let _ = self.next_char();
let _ = self.next_char();
self.lex_number_radix(start_pos, 16, is_negative, "0x")?
}
Some('o' | 'O') => {
// Octal!
let _ = self.next_char();
let _ = self.next_char();
self.lex_number_radix(start_pos, 8, is_negative, "0o")?
}
Some('b' | 'B') => {
// Binary!
let _ = self.next_char();
let _ = self.next_char();
self.lex_number_radix(start_pos, 2, is_negative, "0b")?
}
_ => self.lex_decimal_number(start_pos, is_negative)?,
}
} else {
self.lex_decimal_number()?
self.lex_decimal_number(start_pos, is_negative)?
};

if Some('_') == self.chr0 {
Expand All @@ -917,7 +932,13 @@ where
}

// Lex a hex/octal/decimal/binary number without a decimal point.
fn lex_number_radix(&mut self, start_pos: u32, radix: u32, prefix: &str) -> LexResult {
fn lex_number_radix(
&mut self,
start_pos: u32,
radix: u32,
is_negative: bool,
prefix: &str,
) -> LexResult {
let num = self.radix_run(radix);
if num.is_empty() {
let location = self.get_pos() - 1;
Expand All @@ -941,6 +962,13 @@ where
let value = format!("{prefix}{num}");
let int_value = super::parse_int_value(&value).expect("int value to parse as bigint");
let end_pos = self.get_pos();

let (value, int_value) = if is_negative {
(format!("-{value}"), int_value.neg())
} else {
(value, int_value)
};

Ok((
start_pos,
Token::Int {
Expand All @@ -954,21 +982,24 @@ where

// Lex a normal number, that is, no octal, hex or binary number.
// This function cannot be reached without the head of the stream being either 0-9 or '-', 0-9
fn lex_decimal_number(&mut self) -> LexResult {
self.lex_decimal_or_int_number(true)
fn lex_decimal_number(&mut self, start_pos: u32, is_negative: bool) -> LexResult {
self.lex_decimal_or_int_number(start_pos, is_negative, true)
}

fn lex_int_number(&mut self) -> LexResult {
self.lex_decimal_or_int_number(false)
fn lex_int_number(&mut self, start_pos: u32, is_negative: bool) -> LexResult {
self.lex_decimal_or_int_number(start_pos, is_negative, false)
}

fn lex_decimal_or_int_number(&mut self, can_lex_decimal: bool) -> LexResult {
let start_pos = self.get_pos();
fn lex_decimal_or_int_number(
&mut self,
start_pos: u32,
is_negative: bool,
can_lex_decimal: bool,
) -> LexResult {
let mut value = String::new();
// consume negative sign
if self.chr0 == Some('-') {
value.push(self.next_char().expect("lex_normal_number negative"));
}
if is_negative {
value.push('-')
};
// consume first run of digits
value.push_str(&self.radix_run(10));

Expand Down Expand Up @@ -1025,7 +1056,7 @@ where
// It can be nested like: `tuple.1.2.3.4`
loop {
if matches!(self.chr0, Some('0'..='9')) {
let number = self.lex_int_number()?;
let number = self.lex_int_number(self.get_pos(), false)?;
self.emit(number);
} else {
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
source: compiler-core/src/parse/tests.rs
expression: "pub fn main() { -0b101 }"
---
Parsed {
module: Module {
name: "",
documentation: [],
type_info: (),
definitions: [
TargetedDefinition {
definition: Function(
Function {
location: SrcSpan {
start: 0,
end: 13,
},
body_start: Some(
14,
),
end_position: 24,
name: Some(
(
SrcSpan {
start: 7,
end: 11,
},
"main",
),
),
arguments: [],
body: [
Expression(
Int {
location: SrcSpan {
start: 16,
end: 22,
},
value: "-0b101",
int_value: -5,
},
),
],
publicity: Public,
deprecation: NotDeprecated,
return_annotation: None,
return_type: (),
documentation: None,
external_erlang: None,
external_javascript: None,
implementations: Implementations {
gleam: true,
can_run_on_erlang: true,
can_run_on_javascript: true,
uses_erlang_externals: false,
uses_javascript_externals: false,
},
purity: Pure,
},
),
target: None,
},
],
names: Names {
local_types: {},
imported_modules: {},
type_variables: {},
local_value_constructors: {},
reexport_aliases: {},
},
unused_definition_positions: {},
},
extra: ModuleExtra {
module_comments: [],
doc_comments: [],
comments: [],
empty_lines: [],
new_lines: [],
trailing_commas: [],
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
source: compiler-core/src/parse/tests.rs
expression: "pub fn main() { -0x6f }"
---
Parsed {
module: Module {
name: "",
documentation: [],
type_info: (),
definitions: [
TargetedDefinition {
definition: Function(
Function {
location: SrcSpan {
start: 0,
end: 13,
},
body_start: Some(
14,
),
end_position: 23,
name: Some(
(
SrcSpan {
start: 7,
end: 11,
},
"main",
),
),
arguments: [],
body: [
Expression(
Int {
location: SrcSpan {
start: 16,
end: 21,
},
value: "-0x6f",
int_value: -111,
},
),
],
publicity: Public,
deprecation: NotDeprecated,
return_annotation: None,
return_type: (),
documentation: None,
external_erlang: None,
external_javascript: None,
implementations: Implementations {
gleam: true,
can_run_on_erlang: true,
can_run_on_javascript: true,
uses_erlang_externals: false,
uses_javascript_externals: false,
},
purity: Pure,
},
),
target: None,
},
],
names: Names {
local_types: {},
imported_modules: {},
type_variables: {},
local_value_constructors: {},
reexport_aliases: {},
},
unused_definition_positions: {},
},
extra: ModuleExtra {
module_comments: [],
doc_comments: [],
comments: [],
empty_lines: [],
new_lines: [],
trailing_commas: [],
},
}
Loading
Loading