Repository: gleam-lang/gleam (rust)

Issue:
Gleam can't compile numbers with underscores in JavaScript
<!--
Please include the following:
- Versions for Gleam, OS, the runtime and any other software used
- Logs from running Gleam with the `GLEAM_LOG` environment variable set to `trace`
-->
### Steps and code to reproduce the problem
Set the target to `javascript` in the gleam.toml
add lustre v5:  lustre = { git = "https://github.com/lustre-labs/lustre.git", ref = "main" }


```sh
gleam add gluid
gleam run -m lustre/dev start
```

### Version
- gleam --version: 1.10.0 
- Arch linux

### Logs
```txt
✅ Project compiled successfully
✅ Esbuild already installed!
❌ Bundling with esbuild

I ran into an unexpected issue while trying to bundle your project with esbuild.
Here's the error message I got:

    ✘ [ERROR] Syntax error "_"

    build/dev/javascript/gluid/gluid.mjs:34:43:
      34 │   let _pipe$4 = $int.bitwise_or(_pipe$3, 0x_00_40_00);
         ╵                                            ^

1 error
```


Pull request:
Fix bug with underscores after leading zero
Fixes #4481

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e6afd3d67..f4b2feafd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -161,3 +161,7 @@
 - Fixed a bug where `case` expressions in custom panic messages would compile
   to invalid syntax on the JavaScript target.
   ([Surya Rose](https://github.com/GearsDatapacks))
+
+- Fixed a bug where an underscore after a zero in a number would compile to
+  invalid syntax on the JavaScript target.
+  ([Surya Rose](https://github.com/GearsDatapacks))
diff --git a/compiler-core/src/javascript/expression.rs b/compiler-core/src/javascript/expression.rs
index 3cf6b771f..8e4e4390d 100644
--- a/compiler-core/src/javascript/expression.rs
+++ b/compiler-core/src/javascript/expression.rs
@@ -1466,14 +1466,16 @@ pub fn int(value: &str) -> Document<'_> {
         value
     };
 
-    // If the number starts with `0x_`, `0b_` or `0o_`, that is valid Gleam syntax
-    // but not valid JavaScript syntax, so we remove the `_` here.
-    let value = value.trim_start_matches('_');
-
     let value = value.trim_start_matches('0');
     if value.is_empty() {
         out.push('0');
     }
+
+    // If the number starts with a `0` then an underscore, the `0` will be stripped,
+    // leaving the number to look something like `_1_2_3`, which is not valid syntax.
+    // Therefore, we strip the `_` to avoid this case.
+    let value = value.trim_start_matches('_');
+
     out.push_str(value);
 
     out.to_doc()


Test patch:
diff --git a/compiler-core/src/javascript/tests/numbers.rs b/compiler-core/src/javascript/tests/numbers.rs
index 5176cfaf2..5a88ba090 100644
--- a/compiler-core/src/javascript/tests/numbers.rs
+++ b/compiler-core/src/javascript/tests/numbers.rs
@@ -349,3 +349,84 @@ pub fn main() {
 "
     );
 }
+
+// https://github.com/gleam-lang/gleam/issues/4481
+#[test]
+fn underscore_after_zero_after_hex_prefix() {
+    assert_js!(
+        "
+pub fn main() {
+  0x0_1_2_3
+}
+"
+    );
+}
+
+// https://github.com/gleam-lang/gleam/issues/4481
+#[test]
+fn underscore_after_zero_after_octal_prefix() {
+    assert_js!(
+        "
+pub fn main() {
+  0o0_1_2_3
+}
+"
+    );
+}
+
+// https://github.com/gleam-lang/gleam/issues/4481
+#[test]
+fn underscore_after_zero_after_binary_prefix() {
+    assert_js!(
+        "
+pub fn main() {
+  0b0_1_0_1
+}
+"
+    );
+}
+
+// https://github.com/gleam-lang/gleam/issues/4481
+#[test]
+fn underscore_after_zero() {
+    assert_js!(
+        "
+pub fn main() {
+  0_1_2_3
+}
+"
+    );
+}
+
+#[test]
+fn zero_after_underscore_after_hex_prefix() {
+    assert_js!(
+        "
+pub fn main() {
+  0x_0_1_2_3
+}
+"
+    );
+}
+
+#[test]
+fn zero_after_underscore_after_octal_prefix() {
+    assert_js!(
+        "
+pub fn main() {
+  0o_0_1_2_3
+}
+"
+    );
+}
+
+#[test]
+fn zero_after_underscore_after_binary_prefix() {
+    assert_js!(
+        "
+pub fn main() {
+  0b_0_1_0_1
+}
+"
+    );
+}
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_zero.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_zero.snap
new file mode 100644
index 000000000..21934a13f
--- /dev/null
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_zero.snap
@@ -0,0 +1,15 @@
+---
+source: compiler-core/src/javascript/tests/numbers.rs
+expression: "\npub fn main() {\n  0_1_2_3\n}\n"
+---
+----- SOURCE CODE
+
+pub fn main() {
+  0_1_2_3
+}
+
+
+----- COMPILED JAVASCRIPT
+export function main() {
+  return 1_2_3;
+}
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_zero_after_binary_prefix.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_zero_after_binary_prefix.snap
new file mode 100644
index 000000000..92c0d0c50
--- /dev/null
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_zero_after_binary_prefix.snap
@@ -0,0 +1,15 @@
+---
+source: compiler-core/src/javascript/tests/numbers.rs
+expression: "\npub fn main() {\n  0b0_1_0_1\n}\n"
+---
+----- SOURCE CODE
+
+pub fn main() {
+  0b0_1_0_1
+}
+
+
+----- COMPILED JAVASCRIPT
+export function main() {
+  return 0b1_0_1;
+}
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_zero_after_hex_prefix.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_zero_after_hex_prefix.snap
new file mode 100644
index 000000000..07943c0b2
--- /dev/null
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_zero_after_hex_prefix.snap
@@ -0,0 +1,15 @@
+---
+source: compiler-core/src/javascript/tests/numbers.rs
+expression: "\npub fn main() {\n  0x0_1_2_3\n}\n"
+---
+----- SOURCE CODE
+
+pub fn main() {
+  0x0_1_2_3
+}
+
+
+----- COMPILED JAVASCRIPT
+export function main() {
+  return 0x1_2_3;
+}
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_zero_after_octal_prefix.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_zero_after_octal_prefix.snap
new file mode 100644
index 000000000..3e6461e66
--- /dev/null
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_zero_after_octal_prefix.snap
@@ -0,0 +1,15 @@
+---
+source: compiler-core/src/javascript/tests/numbers.rs
+expression: "\npub fn main() {\n  0o0_1_2_3\n}\n"
+---
+----- SOURCE CODE
+
+pub fn main() {
+  0o0_1_2_3
+}
+
+
+----- COMPILED JAVASCRIPT
+export function main() {
+  return 0o1_2_3;
+}
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__zero_after_underscore_after_binary_prefix.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__zero_after_underscore_after_binary_prefix.snap
new file mode 100644
index 000000000..50c7eaec8
--- /dev/null
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__zero_after_underscore_after_binary_prefix.snap
@@ -0,0 +1,15 @@
+---
+source: compiler-core/src/javascript/tests/numbers.rs
+expression: "\npub fn main() {\n  0b_0_1_0_1\n}\n"
+---
+----- SOURCE CODE
+
+pub fn main() {
+  0b_0_1_0_1
+}
+
+
+----- COMPILED JAVASCRIPT
+export function main() {
+  return 0b0_1_0_1;
+}
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__zero_after_underscore_after_hex_prefix.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__zero_after_underscore_after_hex_prefix.snap
new file mode 100644
index 000000000..1bf69a0b4
--- /dev/null
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__zero_after_underscore_after_hex_prefix.snap
@@ -0,0 +1,15 @@
+---
+source: compiler-core/src/javascript/tests/numbers.rs
+expression: "\npub fn main() {\n  0x_0_1_2_3\n}\n"
+---
+----- SOURCE CODE
+
+pub fn main() {
+  0x_0_1_2_3
+}
+
+
+----- COMPILED JAVASCRIPT
+export function main() {
+  return 0x0_1_2_3;
+}
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__zero_after_underscore_after_octal_prefix.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__zero_after_underscore_after_octal_prefix.snap
new file mode 100644
index 000000000..6e809444b
--- /dev/null
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__zero_after_underscore_after_octal_prefix.snap
@@ -0,0 +1,15 @@
+---
+source: compiler-core/src/javascript/tests/numbers.rs
+expression: "\npub fn main() {\n  0o_0_1_2_3\n}\n"
+---
+----- SOURCE CODE
+
+pub fn main() {
+  0o_0_1_2_3
+}
+
+
+----- COMPILED JAVASCRIPT
+export function main() {
+  return 0o0_1_2_3;
+}