@tishlang/tish-format 1.0.13 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/Cargo.toml +2 -0
  2. package/bin/tish-format +0 -0
  3. package/crates/js_to_tish/src/transform/expr.rs +1 -0
  4. package/crates/tish/Cargo.toml +10 -2
  5. package/crates/tish/build.rs +21 -0
  6. package/crates/tish/src/cli_help.rs +15 -4
  7. package/crates/tish/src/main.rs +93 -21
  8. package/crates/tish/src/repl_completion.rs +0 -1
  9. package/crates/tish/tests/error_source_location.rs +36 -0
  10. package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
  11. package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
  12. package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
  13. package/crates/tish/tests/integration_test.rs +402 -91
  14. package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
  15. package/crates/tish/tests/tty_capability.rs +43 -0
  16. package/crates/tish_ast/src/ast.rs +37 -8
  17. package/crates/tish_builtins/Cargo.toml +2 -0
  18. package/crates/tish_builtins/src/array.rs +375 -13
  19. package/crates/tish_builtins/src/collections.rs +481 -0
  20. package/crates/tish_builtins/src/construct.rs +59 -19
  21. package/crates/tish_builtins/src/date.rs +538 -0
  22. package/crates/tish_builtins/src/globals.rs +86 -6
  23. package/crates/tish_builtins/src/iterator.rs +129 -0
  24. package/crates/tish_builtins/src/lib.rs +5 -0
  25. package/crates/tish_builtins/src/number.rs +96 -0
  26. package/crates/tish_builtins/src/object.rs +2 -2
  27. package/crates/tish_builtins/src/string.rs +19 -20
  28. package/crates/tish_builtins/src/symbol.rs +1 -1
  29. package/crates/tish_builtins/src/typedarrays.rs +298 -0
  30. package/crates/tish_bytecode/src/chunk.rs +69 -1
  31. package/crates/tish_bytecode/src/compiler.rs +933 -89
  32. package/crates/tish_bytecode/src/encoding.rs +2 -0
  33. package/crates/tish_bytecode/src/lib.rs +2 -1
  34. package/crates/tish_bytecode/src/opcode.rs +47 -4
  35. package/crates/tish_bytecode/src/serialize.rs +31 -1
  36. package/crates/tish_compile/Cargo.toml +1 -0
  37. package/crates/tish_compile/src/check.rs +774 -0
  38. package/crates/tish_compile/src/codegen.rs +2334 -349
  39. package/crates/tish_compile/src/infer.rs +1395 -6
  40. package/crates/tish_compile/src/lib.rs +50 -8
  41. package/crates/tish_compile/src/resolve.rs +584 -21
  42. package/crates/tish_compile/src/types.rs +106 -2
  43. package/crates/tish_compile_js/src/codegen.rs +67 -0
  44. package/crates/tish_compile_js/src/tests_jsx.rs +64 -0
  45. package/crates/tish_core/Cargo.toml +7 -1
  46. package/crates/tish_core/src/console_style.rs +11 -1
  47. package/crates/tish_core/src/json.rs +81 -38
  48. package/crates/tish_core/src/lib.rs +3 -0
  49. package/crates/tish_core/src/shape.rs +85 -0
  50. package/crates/tish_core/src/value.rs +679 -25
  51. package/crates/tish_core/src/vmref.rs +13 -8
  52. package/crates/tish_cranelift/src/link.rs +17 -4
  53. package/crates/tish_cranelift_runtime/Cargo.toml +1 -0
  54. package/crates/tish_eval/Cargo.toml +6 -0
  55. package/crates/tish_eval/src/eval.rs +665 -117
  56. package/crates/tish_eval/src/http.rs +4 -1
  57. package/crates/tish_eval/src/natives.rs +165 -13
  58. package/crates/tish_eval/src/value.rs +31 -13
  59. package/crates/tish_eval/src/value_convert.rs +10 -4
  60. package/crates/tish_ffi/Cargo.toml +26 -0
  61. package/crates/tish_ffi/src/lib.rs +518 -0
  62. package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
  63. package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
  64. package/crates/tish_ffi/tests/loader.rs +65 -0
  65. package/crates/tish_fmt/Cargo.toml +1 -1
  66. package/crates/tish_fmt/src/lib.rs +61 -5
  67. package/crates/tish_lexer/src/lib.rs +397 -9
  68. package/crates/tish_lexer/src/token.rs +7 -0
  69. package/crates/tish_lint/src/lib.rs +2 -10
  70. package/crates/tish_lsp/src/import_goto.rs +2 -0
  71. package/crates/tish_lsp/src/main.rs +439 -26
  72. package/crates/tish_native/src/build.rs +55 -1
  73. package/crates/tish_opt/src/lib.rs +126 -23
  74. package/crates/tish_parser/src/lib.rs +55 -1
  75. package/crates/tish_parser/src/parser.rs +456 -34
  76. package/crates/tish_pg/src/lib.rs +3 -3
  77. package/crates/tish_resolve/src/lib.rs +99 -59
  78. package/crates/tish_runtime/Cargo.toml +4 -0
  79. package/crates/tish_runtime/src/http.rs +66 -17
  80. package/crates/tish_runtime/src/http_fetch.rs +29 -8
  81. package/crates/tish_runtime/src/http_hyper.rs +25 -2
  82. package/crates/tish_runtime/src/lib.rs +299 -44
  83. package/crates/tish_runtime/src/promise.rs +328 -18
  84. package/crates/tish_runtime/src/timers.rs +13 -7
  85. package/crates/tish_runtime/src/tty.rs +226 -0
  86. package/crates/tish_runtime/src/ws.rs +35 -18
  87. package/crates/tish_runtime/tests/fetch_readable_stream.rs +2 -2
  88. package/crates/tish_ui/src/jsx.rs +10 -0
  89. package/crates/tish_ui/src/runtime/hooks.rs +19 -15
  90. package/crates/tish_ui/src/runtime/mod.rs +15 -12
  91. package/crates/tish_vm/Cargo.toml +14 -1
  92. package/crates/tish_vm/src/jit.rs +1050 -0
  93. package/crates/tish_vm/src/lib.rs +2 -0
  94. package/crates/tish_vm/src/vm.rs +1546 -202
  95. package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
  96. package/crates/tish_wasm/src/lib.rs +6 -2
  97. package/crates/tish_wasm_runtime/src/gpu.rs +17 -1
  98. package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
  99. package/crates/tishlang_cargo_bindgen/src/lib.rs +2 -2
  100. package/crates/tishlang_cargo_bindgen/src/metadata.rs +1 -1
  101. package/justfile +8 -0
  102. package/package.json +2 -2
  103. package/platform/darwin-arm64/tish-fmt +0 -0
  104. package/platform/darwin-x64/tish-fmt +0 -0
  105. package/platform/linux-arm64/tish-fmt +0 -0
  106. package/platform/linux-x64/tish-fmt +0 -0
  107. package/platform/win32-x64/tish-fmt.exe +0 -0
  108. package/README.md +0 -138
@@ -0,0 +1,298 @@
1
+ //! Typed arrays (`Float32Array`, `Float64Array`, `Int8Array`, `Uint8Array`, `Uint8ClampedArray`,
2
+ //! `Int16Array`, `Uint16Array`, `Int32Array`, `Uint32Array`) for the non-JS targets.
3
+ //!
4
+ //! ## Representation
5
+ //! Unlike `Date`/`Set`/`Map`, typed arrays are array-LIKE — they need indexing (`ta[i]`), `.length`,
6
+ //! `for…of`, and the array methods. So a typed array is just a **`Value::Array`** (which gives all of
7
+ //! that for free across every backend), with each element **coerced to the view's element type at
8
+ //! construction**: `Float32Array` rounds to f32 precision, the integer views truncate-and-wrap
9
+ //! (`ToInt8`/`ToUint32`/…), and `Uint8ClampedArray` clamps to `0..=255` (round-half-to-even).
10
+ //!
11
+ //! ## v1 scope / gaps (documented in tishlang-web)
12
+ //! - **Element coercion happens on construction / `.from` / `.of`, not on element assignment.**
13
+ //! `ta[i] = 300` stores `300` (a plain array write); rebuild via `Uint8Array.of(...)` if you need
14
+ //! the wrap. (A packed-native representation enforces write-coercion via the Rust element type — a
15
+ //! fast-follow.)
16
+ //! - No `ArrayBuffer` / `DataView` / `.buffer` / `.byteLength` / `.subarray` / `.set` yet.
17
+ //! - A typed array is a regular array at runtime, so `Array.isArray(ta)` is `true` and there is no
18
+ //! `instanceof` distinction.
19
+
20
+ use std::sync::Arc;
21
+ use tishlang_core::{ObjectMap, Value, VmRef};
22
+
23
+ const CONSTRUCT: &str = "__construct";
24
+
25
+ /// Element type of a typed-array view.
26
+ #[derive(Clone, Copy)]
27
+ enum Kind {
28
+ F64,
29
+ F32,
30
+ I8,
31
+ U8,
32
+ U8Clamped,
33
+ I16,
34
+ U16,
35
+ I32,
36
+ U32,
37
+ }
38
+
39
+ /// `ToUintN(x)` — ECMAScript truncate-then-wrap into `[0, 2^bits)`.
40
+ fn to_uint(x: f64, bits: u32) -> f64 {
41
+ if !x.is_finite() {
42
+ return 0.0;
43
+ }
44
+ let m = 2f64.powi(bits as i32);
45
+ // `trunc` rounds toward zero (ToInteger); `rem_euclid` yields a non-negative remainder.
46
+ x.trunc().rem_euclid(m)
47
+ }
48
+
49
+ /// `ToIntN(x)` — `ToUintN` reinterpreted as a signed `bits`-wide integer.
50
+ fn to_int(x: f64, bits: u32) -> f64 {
51
+ let u = to_uint(x, bits);
52
+ let half = 2f64.powi(bits as i32 - 1);
53
+ if u >= half {
54
+ u - 2.0 * half
55
+ } else {
56
+ u
57
+ }
58
+ }
59
+
60
+ /// Coerce one number to the view's element type.
61
+ fn coerce(kind: Kind, x: f64) -> f64 {
62
+ match kind {
63
+ Kind::F64 => x,
64
+ Kind::F32 => x as f32 as f64,
65
+ Kind::U8Clamped => {
66
+ if x.is_nan() || x <= 0.0 {
67
+ 0.0
68
+ } else if x >= 255.0 {
69
+ 255.0
70
+ } else {
71
+ x.round_ties_even()
72
+ }
73
+ }
74
+ Kind::I8 => to_int(x, 8),
75
+ Kind::U8 => to_uint(x, 8),
76
+ Kind::I16 => to_int(x, 16),
77
+ Kind::U16 => to_uint(x, 16),
78
+ Kind::I32 => to_int(x, 32),
79
+ Kind::U32 => to_uint(x, 32),
80
+ }
81
+ }
82
+
83
+ fn bytes_per_element(kind: Kind) -> f64 {
84
+ match kind {
85
+ Kind::F64 => 8.0,
86
+ Kind::I32 | Kind::U32 | Kind::F32 => 4.0,
87
+ Kind::I16 | Kind::U16 => 2.0,
88
+ Kind::I8 | Kind::U8 | Kind::U8Clamped => 1.0,
89
+ }
90
+ }
91
+
92
+ /// Array-like `value` → its elements (arrays and packed number-arrays; otherwise empty).
93
+ fn elements(value: &Value) -> Vec<Value> {
94
+ match value {
95
+ Value::Array(a) => a.borrow().clone(),
96
+ Value::NumberArray(a) => a.borrow().iter().map(|n| Value::Number(*n)).collect(),
97
+ _ => Vec::new(),
98
+ }
99
+ }
100
+
101
+ /// Build the backing `Value::Array`, coercing every element to `kind`. Non-numeric elements become
102
+ /// `NaN` first (→ `0` for the integer views).
103
+ fn from_values(kind: Kind, vals: &[Value]) -> Value {
104
+ let out: Vec<Value> = vals
105
+ .iter()
106
+ .map(|v| Value::Number(coerce(kind, v.as_number().unwrap_or(f64::NAN))))
107
+ .collect();
108
+ Value::Array(VmRef::new(out))
109
+ }
110
+
111
+ /// `new X(...)`: a length `n` (zero-filled), or an array-like to copy+coerce, or empty.
112
+ fn construct(kind: Kind, args: &[Value]) -> Value {
113
+ match args.first() {
114
+ None => Value::Array(VmRef::new(Vec::new())),
115
+ // `0` coerces to `0` for every element type, so a length-`n` view is `n` zeros.
116
+ Some(Value::Number(n)) => {
117
+ let len = n.max(0.0) as usize;
118
+ Value::Array(VmRef::new(vec![Value::Number(0.0); len]))
119
+ }
120
+ Some(v) => from_values(kind, &elements(v)),
121
+ }
122
+ }
123
+
124
+ /// Native-codegen-only packed constructor for `new Float64Array(...)`.
125
+ ///
126
+ /// `Float64Array` is the one view whose element type *is* `f64`, so it needs no coercion and maps
127
+ /// exactly onto the packed [`Value::NumberArray`] (`Vec<f64>`) representation — eliminating the
128
+ /// per-element `Value` boxing the generic boxed `Value::Array` backing pays. When
129
+ /// [`Value::packed_arrays_enabled`] is **off** (the default), this returns the *identical* boxed
130
+ /// value the generic constructor would, so default builds stay byte-for-byte unchanged; the packed
131
+ /// form is only ever produced under `TISH_PACKED_ARRAYS=1`.
132
+ ///
133
+ /// This lives behind the native codegen (interp/VM keep the boxed `Value::Array` — their value
134
+ /// bridges have no `NumberArray` variant), so on the native path a `NumberArray` is *always* a
135
+ /// `Float64Array`. That makes storing writes as `f64` the correct view semantics (and closes the
136
+ /// construction-only-coercion gap for this one view). Any op without a packed fast path materialises
137
+ /// it back to a boxed array, so every array method keeps working.
138
+ pub fn float64_array_packed(args: &[Value]) -> Value {
139
+ if !Value::packed_arrays_enabled() {
140
+ // Byte-identical fallback to the generic boxed `Value::Array` backing.
141
+ return construct(Kind::F64, args);
142
+ }
143
+ let nums: Vec<f64> = match args.first() {
144
+ None => Vec::new(),
145
+ // Length form: `new Float64Array(n)` → `n` zeros (0.0 is the F64 coercion of 0).
146
+ Some(Value::Number(n)) => vec![0.0; n.max(0.0) as usize],
147
+ // Array-like copy: mirror `from_values(F64, …)` — non-numeric elements become `NaN`.
148
+ Some(v) => elements(v)
149
+ .iter()
150
+ .map(|e| e.as_number().unwrap_or(f64::NAN))
151
+ .collect(),
152
+ };
153
+ Value::number_array(nums)
154
+ }
155
+
156
+ /// The constructor object for one view kind: `__construct` + `from` / `of` / `BYTES_PER_ELEMENT`.
157
+ fn make_constructor(kind: Kind) -> Value {
158
+ let mut m = ObjectMap::default();
159
+ m.insert(
160
+ Arc::from(CONSTRUCT),
161
+ Value::native(move |args: &[Value]| construct(kind, args)),
162
+ );
163
+ m.insert(
164
+ Arc::from("from"),
165
+ Value::native(move |args: &[Value]| {
166
+ from_values(kind, &args.first().map(elements).unwrap_or_default())
167
+ }),
168
+ );
169
+ m.insert(
170
+ Arc::from("of"),
171
+ Value::native(move |args: &[Value]| from_values(kind, args)),
172
+ );
173
+ m.insert(
174
+ Arc::from("BYTES_PER_ELEMENT"),
175
+ Value::Number(bytes_per_element(kind)),
176
+ );
177
+ Value::object(m)
178
+ }
179
+
180
+ macro_rules! ctor_fn {
181
+ ($name:ident, $kind:expr) => {
182
+ pub fn $name() -> Value {
183
+ make_constructor($kind)
184
+ }
185
+ };
186
+ }
187
+
188
+ ctor_fn!(float64_array_constructor_value, Kind::F64);
189
+ ctor_fn!(float32_array_constructor_value, Kind::F32);
190
+ ctor_fn!(int8_array_constructor_value, Kind::I8);
191
+ ctor_fn!(uint8_array_constructor_value, Kind::U8);
192
+ ctor_fn!(uint8_clamped_array_constructor_value, Kind::U8Clamped);
193
+ ctor_fn!(int16_array_constructor_value, Kind::I16);
194
+ ctor_fn!(uint16_array_constructor_value, Kind::U16);
195
+ ctor_fn!(int32_array_constructor_value, Kind::I32);
196
+ ctor_fn!(uint32_array_constructor_value, Kind::U32);
197
+
198
+ #[cfg(test)]
199
+ mod tests {
200
+ use super::*;
201
+
202
+ fn nums(v: &Value) -> Vec<f64> {
203
+ match v {
204
+ Value::Array(a) => a
205
+ .borrow()
206
+ .iter()
207
+ .map(|e| match e {
208
+ Value::Number(n) => *n,
209
+ _ => f64::NAN,
210
+ })
211
+ .collect(),
212
+ _ => vec![],
213
+ }
214
+ }
215
+
216
+ #[test]
217
+ fn float32_rounds_to_f32_precision() {
218
+ // 1.1 is not representable in f32; the stored value is the f32-rounded double.
219
+ let v = from_values(Kind::F32, &[Value::Number(1.1)]);
220
+ assert_eq!(nums(&v)[0], 1.1f32 as f64);
221
+ assert_ne!(nums(&v)[0], 1.1);
222
+ }
223
+
224
+ #[test]
225
+ fn uint8_wraps() {
226
+ let v = from_values(Kind::U8, &[Value::Number(300.0), Value::Number(-1.0), Value::Number(256.0)]);
227
+ assert_eq!(nums(&v), vec![44.0, 255.0, 0.0]);
228
+ }
229
+
230
+ #[test]
231
+ fn int8_wraps_signed() {
232
+ let v = from_values(Kind::I8, &[Value::Number(127.0), Value::Number(128.0), Value::Number(-129.0)]);
233
+ assert_eq!(nums(&v), vec![127.0, -128.0, 127.0]);
234
+ }
235
+
236
+ #[test]
237
+ fn uint8_clamped_clamps_and_rounds_half_even() {
238
+ let v = from_values(
239
+ Kind::U8Clamped,
240
+ &[Value::Number(-5.0), Value::Number(300.0), Value::Number(2.5), Value::Number(3.5)],
241
+ );
242
+ // 2.5 → 2 (round to even), 3.5 → 4 (round to even).
243
+ assert_eq!(nums(&v), vec![0.0, 255.0, 2.0, 4.0]);
244
+ }
245
+
246
+ #[test]
247
+ fn int_views_map_nan_to_zero() {
248
+ let v = from_values(Kind::I32, &[Value::Null, Value::String("x".into())]);
249
+ assert_eq!(nums(&v), vec![0.0, 0.0]);
250
+ }
251
+
252
+ #[test]
253
+ fn construct_length_is_zero_filled() {
254
+ let v = construct(Kind::U16, &[Value::Number(3.0)]);
255
+ assert_eq!(nums(&v), vec![0.0, 0.0, 0.0]);
256
+ }
257
+
258
+ #[test]
259
+ fn uint32_wraps_large() {
260
+ let v = from_values(Kind::U32, &[Value::Number(4294967296.0), Value::Number(4294967297.0)]);
261
+ assert_eq!(nums(&v), vec![0.0, 1.0]);
262
+ }
263
+
264
+ // `float64_array_packed` toggles on a process-global env var. No other test in this crate reads
265
+ // `packed_arrays_enabled`, so the set/remove here can't perturb a concurrent test; we restore the
266
+ // default (off) on exit regardless.
267
+ #[test]
268
+ fn float64_packed_respects_flag() {
269
+ // Flag off (default): byte-identical boxed `Value::Array` fallback, no packed value produced.
270
+ std::env::remove_var("TISH_PACKED_ARRAYS");
271
+ let boxed = float64_array_packed(&[Value::Number(3.0)]);
272
+ assert!(matches!(boxed, Value::Array(_)), "packed-off must return boxed Array");
273
+ assert_eq!(nums(&boxed), vec![0.0, 0.0, 0.0]);
274
+
275
+ // Flag on: packed `Value::NumberArray`. F64 needs no coercion (exact), non-numeric → NaN
276
+ // (matching the boxed `from_values(F64, …)`), and the length form zero-fills.
277
+ std::env::set_var("TISH_PACKED_ARRAYS", "1");
278
+ let packed = float64_array_packed(&[Value::Array(VmRef::new(vec![
279
+ Value::Number(1.1),
280
+ Value::Number(2.2),
281
+ Value::Null,
282
+ ]))]);
283
+ match &packed {
284
+ Value::NumberArray(a) => {
285
+ let v = a.borrow();
286
+ assert_eq!(v[0], 1.1);
287
+ assert_eq!(v[1], 2.2);
288
+ assert!(v[2].is_nan());
289
+ }
290
+ _ => panic!("packed-on must return NumberArray"),
291
+ }
292
+ assert!(matches!(
293
+ float64_array_packed(&[Value::Number(2.0)]),
294
+ Value::NumberArray(_)
295
+ ));
296
+ std::env::remove_var("TISH_PACKED_ARRAYS");
297
+ }
298
+ }
@@ -1,8 +1,23 @@
1
1
  //! Bytecode chunk: instructions and constants.
2
2
 
3
+ use std::sync::atomic::AtomicU64;
3
4
  use std::sync::Arc;
4
5
  use tishlang_core::Value;
5
6
 
7
+ /// Per-property-name inline cache for object access (the JavaScriptCore inline-cache idea), indexed by
8
+ /// the name index that `GetMember`/`SetMember` already carry. Each cell packs
9
+ /// `(shape_id:u32 << 32) | slot_index:u32`; `0` = uncached. A racy `Relaxed` load/store is sound: a
10
+ /// stale read just falls to the slow path, which re-checks the object's shape and refills. This is a
11
+ /// runtime cache, NOT program data — a cloned `Chunk` (e.g. each closure instance) starts empty.
12
+ #[derive(Debug, Default)]
13
+ pub struct InlineCaches(pub Vec<AtomicU64>);
14
+
15
+ impl Clone for InlineCaches {
16
+ fn clone(&self) -> Self {
17
+ InlineCaches(self.0.iter().map(|_| AtomicU64::new(0)).collect())
18
+ }
19
+ }
20
+
6
21
  /// A constant in the constants table.
7
22
  #[derive(Debug, Clone)]
8
23
  pub enum Constant {
@@ -19,7 +34,7 @@ impl Constant {
19
34
  pub fn to_value(&self) -> Value {
20
35
  match self {
21
36
  Constant::Number(n) => Value::Number(*n),
22
- Constant::String(s) => Value::String(Arc::clone(s)),
37
+ Constant::String(s) => Value::String(tishlang_core::ArcStr::from(s.as_ref())),
23
38
  Constant::Bool(b) => Value::Bool(*b),
24
39
  Constant::Null => Value::Null,
25
40
  Constant::Closure(_) => {
@@ -45,6 +60,25 @@ pub struct Chunk {
45
60
  pub rest_param_index: u16,
46
61
  /// Number of leading names that are parameters (for proper closure arg binding).
47
62
  pub param_count: u16,
63
+ /// Number of local variable slots this chunk's call frame needs (params + body locals).
64
+ /// Frame `locals` Vec is sized to this. Only meaningful when `slot_based`.
65
+ pub num_slots: u16,
66
+ /// When true, this chunk resolves its locals via integer frame slots
67
+ /// (`LoadLocal`/`StoreLocal`) instead of name-keyed scope maps. Set for
68
+ /// self-contained functions (no free-variable / global references), whose
69
+ /// call frame is a bare `Vec<Value>` of length `num_slots` — no per-call
70
+ /// hashmap, no name lookups. Name-based chunks (top level, closures that
71
+ /// capture outer scope) leave this `false` and use the legacy path.
72
+ pub slot_based: bool,
73
+ /// Inline caches for object property access, one cell per entry in `names` (so indexed by the
74
+ /// same name index `GetMember`/`SetMember` carry). Runtime-only; not part of the serialized program.
75
+ pub inline_caches: InlineCaches,
76
+ /// Source line table: `(code_offset, line)` pairs, sorted by offset, one entry per line change
77
+ /// (issue #74). Consulted only when formatting a runtime error, so it adds zero execution
78
+ /// overhead. Debug-only / runtime-only — not serialized (persisted bytecode loses line info).
79
+ pub lines: Vec<(u32, u32)>,
80
+ /// Source file path for error messages (`file:line`); propagated to nested chunks. Runtime-only.
81
+ pub source: Option<Arc<str>>,
48
82
  }
49
83
 
50
84
  impl Chunk {
@@ -56,6 +90,39 @@ impl Chunk {
56
90
  nested: Vec::new(),
57
91
  rest_param_index: super::NO_REST_PARAM,
58
92
  param_count: 0,
93
+ num_slots: 0,
94
+ slot_based: false,
95
+ inline_caches: InlineCaches::default(),
96
+ lines: Vec::new(),
97
+ source: None,
98
+ }
99
+ }
100
+
101
+ /// Record that the instruction starting at `offset` originates from source `line` (1-based).
102
+ /// Only stored when the line changes, keeping the table compact. Issue #74.
103
+ pub fn mark_line(&mut self, offset: usize, line: u32) {
104
+ if line == 0 {
105
+ return;
106
+ }
107
+ match self.lines.last() {
108
+ Some(&(_, last_line)) if last_line == line => {}
109
+ _ => self.lines.push((offset as u32, line)),
110
+ }
111
+ }
112
+
113
+ /// Source line for a bytecode `offset` (the line of the nearest preceding `mark_line`), or
114
+ /// `None` if no line info is available (e.g. deserialized bytecode). Issue #74.
115
+ pub fn line_at(&self, offset: usize) -> Option<u32> {
116
+ if self.lines.is_empty() {
117
+ return None;
118
+ }
119
+ let off = offset as u32;
120
+ // Largest recorded offset <= `off`.
121
+ let idx = self.lines.partition_point(|&(o, _)| o <= off);
122
+ if idx == 0 {
123
+ Some(self.lines[0].1)
124
+ } else {
125
+ Some(self.lines[idx - 1].1)
59
126
  }
60
127
  }
61
128
 
@@ -79,6 +146,7 @@ impl Chunk {
79
146
  }
80
147
  let idx = self.names.len();
81
148
  self.names.push(name);
149
+ self.inline_caches.0.push(AtomicU64::new(0)); // keep the IC table sized to `names`
82
150
  idx as u16
83
151
  }
84
152