@tishlang/tish 1.6.0 → 1.7.0

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 (79) hide show
  1. package/Cargo.toml +1 -0
  2. package/bin/tish +0 -0
  3. package/crates/js_to_tish/src/error.rs +2 -8
  4. package/crates/js_to_tish/src/transform/expr.rs +101 -130
  5. package/crates/js_to_tish/src/transform/stmt.rs +25 -22
  6. package/crates/tish/Cargo.toml +1 -1
  7. package/crates/tish/src/cli_help.rs +76 -29
  8. package/crates/tish/src/main.rs +85 -54
  9. package/crates/tish/tests/cargo_example_compile.rs +3 -1
  10. package/crates/tish/tests/integration_test.rs +197 -47
  11. package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
  12. package/crates/tish/tests/shortcircuit.rs +19 -4
  13. package/crates/tish_ast/src/ast.rs +12 -14
  14. package/crates/tish_build_utils/src/lib.rs +31 -6
  15. package/crates/tish_builtins/src/array.rs +52 -21
  16. package/crates/tish_builtins/src/construct.rs +2 -8
  17. package/crates/tish_builtins/src/globals.rs +30 -15
  18. package/crates/tish_builtins/src/lib.rs +5 -5
  19. package/crates/tish_builtins/src/math.rs +5 -3
  20. package/crates/tish_builtins/src/string.rs +71 -19
  21. package/crates/tish_bytecode/src/chunk.rs +0 -1
  22. package/crates/tish_bytecode/src/compiler.rs +164 -60
  23. package/crates/tish_bytecode/src/opcode.rs +13 -4
  24. package/crates/tish_bytecode/src/peephole.rs +2 -2
  25. package/crates/tish_compile/src/codegen.rs +921 -299
  26. package/crates/tish_compile/src/infer.rs +69 -19
  27. package/crates/tish_compile/src/lib.rs +15 -5
  28. package/crates/tish_compile/src/resolve.rs +112 -69
  29. package/crates/tish_compile/src/types.rs +10 -14
  30. package/crates/tish_compile_js/src/codegen.rs +34 -13
  31. package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
  32. package/crates/tish_compiler_wasm/src/lib.rs +16 -13
  33. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +39 -48
  34. package/crates/tish_core/src/json.rs +5 -3
  35. package/crates/tish_core/src/lib.rs +1 -1
  36. package/crates/tish_core/src/uri.rs +9 -6
  37. package/crates/tish_core/src/value.rs +92 -28
  38. package/crates/tish_cranelift/src/link.rs +6 -9
  39. package/crates/tish_cranelift/src/lower.rs +14 -8
  40. package/crates/tish_eval/src/eval.rs +389 -142
  41. package/crates/tish_eval/src/lib.rs +10 -6
  42. package/crates/tish_eval/src/natives.rs +95 -38
  43. package/crates/tish_eval/src/promise.rs +14 -8
  44. package/crates/tish_eval/src/timers.rs +28 -19
  45. package/crates/tish_eval/src/value.rs +10 -3
  46. package/crates/tish_fmt/src/lib.rs +29 -13
  47. package/crates/tish_lexer/src/lib.rs +217 -63
  48. package/crates/tish_lexer/src/token.rs +6 -6
  49. package/crates/tish_llvm/src/lib.rs +15 -8
  50. package/crates/tish_lsp/src/main.rs +41 -43
  51. package/crates/tish_native/src/build.rs +1 -6
  52. package/crates/tish_native/src/lib.rs +48 -19
  53. package/crates/tish_opt/src/lib.rs +67 -50
  54. package/crates/tish_parser/src/lib.rs +36 -11
  55. package/crates/tish_parser/src/parser.rs +172 -87
  56. package/crates/tish_runtime/src/http.rs +15 -6
  57. package/crates/tish_runtime/src/http_fetch.rs +24 -14
  58. package/crates/tish_runtime/src/lib.rs +224 -168
  59. package/crates/tish_runtime/src/promise.rs +1 -5
  60. package/crates/tish_runtime/src/ws.rs +45 -20
  61. package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
  62. package/crates/tish_ui/src/jsx.rs +41 -22
  63. package/crates/tish_ui/src/lib.rs +2 -2
  64. package/crates/tish_vm/src/vm.rs +309 -112
  65. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +8 -3
  66. package/crates/tish_wasm/src/lib.rs +38 -28
  67. package/crates/tishlang_cargo_bindgen/Cargo.toml +25 -0
  68. package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
  69. package/crates/tishlang_cargo_bindgen/src/discover.rs +52 -0
  70. package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
  71. package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
  72. package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
  73. package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
  74. package/package.json +1 -1
  75. package/platform/darwin-arm64/tish +0 -0
  76. package/platform/darwin-x64/tish +0 -0
  77. package/platform/linux-arm64/tish +0 -0
  78. package/platform/linux-x64/tish +0 -0
  79. package/platform/win32-x64/tish.exe +0 -0
@@ -1,9 +1,9 @@
1
1
  //! Array builtin methods.
2
2
 
3
+ use crate::helpers::normalize_index;
3
4
  use std::cell::RefCell;
4
5
  use std::rc::Rc;
5
6
  use tishlang_core::Value;
6
- use crate::helpers::normalize_index;
7
7
 
8
8
  /// Create a new array Value from a Vec of Values.
9
9
  pub fn from_vec(v: Vec<Value>) -> Value {
@@ -153,7 +153,11 @@ pub fn slice(arr: &Value, start: &Value, end: &Value) -> Value {
153
153
  let len = arr_borrow.len() as i64;
154
154
  let start_idx = normalize_index(start, len, 0);
155
155
  let end_idx = normalize_index(end, len, len as usize);
156
- let sliced = if start_idx < end_idx { arr_borrow[start_idx..end_idx].to_vec() } else { vec![] };
156
+ let sliced = if start_idx < end_idx {
157
+ arr_borrow[start_idx..end_idx].to_vec()
158
+ } else {
159
+ vec![]
160
+ };
157
161
  Value::Array(Rc::new(RefCell::new(sliced)))
158
162
  } else {
159
163
  Value::Null
@@ -188,7 +192,7 @@ pub fn flat(arr: &Value, depth: &Value) -> Value {
188
192
  result.push(v.clone());
189
193
  }
190
194
  }
191
-
195
+
192
196
  if let Value::Array(arr) = arr {
193
197
  let d = match depth {
194
198
  Value::Number(n) => *n as i32,
@@ -208,9 +212,11 @@ pub fn flat(arr: &Value, depth: &Value) -> Value {
208
212
  pub fn map(arr: &Value, callback: &Value) -> Value {
209
213
  if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
210
214
  let arr_borrow = arr.borrow();
211
- let result: Vec<Value> = arr_borrow.iter().enumerate().map(|(i, v)| {
212
- cb(&[v.clone(), Value::Number(i as f64)])
213
- }).collect();
215
+ let result: Vec<Value> = arr_borrow
216
+ .iter()
217
+ .enumerate()
218
+ .map(|(i, v)| cb(&[v.clone(), Value::Number(i as f64)]))
219
+ .collect();
214
220
  Value::Array(Rc::new(RefCell::new(result)))
215
221
  } else {
216
222
  Value::Null
@@ -220,10 +226,18 @@ pub fn map(arr: &Value, callback: &Value) -> Value {
220
226
  pub fn filter(arr: &Value, callback: &Value) -> Value {
221
227
  if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
222
228
  let arr_borrow = arr.borrow();
223
- let result: Vec<Value> = arr_borrow.iter().enumerate().filter_map(|(i, v)| {
224
- let keep = cb(&[v.clone(), Value::Number(i as f64)]);
225
- if keep.is_truthy() { Some(v.clone()) } else { None }
226
- }).collect();
229
+ let result: Vec<Value> = arr_borrow
230
+ .iter()
231
+ .enumerate()
232
+ .filter_map(|(i, v)| {
233
+ let keep = cb(&[v.clone(), Value::Number(i as f64)]);
234
+ if keep.is_truthy() {
235
+ Some(v.clone())
236
+ } else {
237
+ None
238
+ }
239
+ })
240
+ .collect();
227
241
  Value::Array(Rc::new(RefCell::new(result)))
228
242
  } else {
229
243
  Value::Null
@@ -234,9 +248,7 @@ pub fn reduce(arr: &Value, callback: &Value, initial: &Value) -> Value {
234
248
  if let (Value::Array(arr), Value::Function(cb)) = (arr, callback) {
235
249
  let arr_borrow = arr.borrow();
236
250
  let len = arr_borrow.len();
237
- let (start_idx, mut acc) = if matches!(initial, Value::Null)
238
- && !arr_borrow.is_empty()
239
- {
251
+ let (start_idx, mut acc) = if matches!(initial, Value::Null) && !arr_borrow.is_empty() {
240
252
  // No initial value: use first element as acc, start from index 1
241
253
  (1, arr_borrow[0].clone())
242
254
  } else {
@@ -335,7 +347,9 @@ pub fn flat_map(arr: &Value, callback: &Value) -> Value {
335
347
  }
336
348
 
337
349
  fn sort_by_impl<F>(arr: &Value, cmp: F) -> Value
338
- where F: FnMut(&Value, &Value) -> std::cmp::Ordering {
350
+ where
351
+ F: FnMut(&Value, &Value) -> std::cmp::Ordering,
352
+ {
339
353
  if let Value::Array(arr) = arr {
340
354
  arr.borrow_mut().sort_by(cmp);
341
355
  Value::Array(Rc::clone(arr))
@@ -345,7 +359,9 @@ where F: FnMut(&Value, &Value) -> std::cmp::Ordering {
345
359
  }
346
360
 
347
361
  pub fn sort_default(arr: &Value) -> Value {
348
- sort_by_impl(arr, |a, b| a.to_display_string().cmp(&b.to_display_string()))
362
+ sort_by_impl(arr, |a, b| {
363
+ a.to_display_string().cmp(&b.to_display_string())
364
+ })
349
365
  }
350
366
 
351
367
  pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
@@ -355,7 +371,7 @@ pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
355
371
  let mut indices: Vec<usize> = (0..len).collect();
356
372
  let mut elements: Vec<Value> = std::mem::take(&mut *arr_mut);
357
373
  let mut args_buf: [Value; 2] = [Value::Null, Value::Null];
358
-
374
+
359
375
  indices.sort_by(|&a, &b| {
360
376
  args_buf[0] = elements[a].clone();
361
377
  args_buf[1] = elements[b].clone();
@@ -365,8 +381,11 @@ pub fn sort_with_comparator(arr: &Value, comparator: &Value) -> Value {
365
381
  _ => std::cmp::Ordering::Equal,
366
382
  }
367
383
  });
368
-
369
- *arr_mut = indices.into_iter().map(|i| std::mem::replace(&mut elements[i], Value::Null)).collect();
384
+
385
+ *arr_mut = indices
386
+ .into_iter()
387
+ .map(|i| std::mem::replace(&mut elements[i], Value::Null))
388
+ .collect();
370
389
  drop(arr_mut);
371
390
  Value::Array(Rc::clone(arr))
372
391
  } else {
@@ -380,7 +399,11 @@ fn num_cmp(a: &Value, b: &Value, asc: bool) -> std::cmp::Ordering {
380
399
  _ => (f64::NAN, f64::NAN),
381
400
  };
382
401
  let cmp = na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal);
383
- if asc { cmp } else { cmp.reverse() }
402
+ if asc {
403
+ cmp
404
+ } else {
405
+ cmp.reverse()
406
+ }
384
407
  }
385
408
 
386
409
  pub fn sort_numeric_asc(arr: &Value) -> Value {
@@ -398,13 +421,21 @@ pub fn sort_by_property_numeric(arr: &Value, prop: &str, asc: bool) -> Value {
398
421
  let na = get_prop_number(a, &prop_arc);
399
422
  let nb = get_prop_number(b, &prop_arc);
400
423
  let cmp = na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal);
401
- if asc { cmp } else { cmp.reverse() }
424
+ if asc {
425
+ cmp
426
+ } else {
427
+ cmp.reverse()
428
+ }
402
429
  })
403
430
  }
404
431
 
405
432
  fn get_prop_number(v: &Value, prop: &std::sync::Arc<str>) -> f64 {
406
433
  match v {
407
- Value::Object(o) => o.borrow().get(prop.as_ref()).map(|v| v.as_number().unwrap_or(f64::NAN)).unwrap_or(f64::NAN),
434
+ Value::Object(o) => o
435
+ .borrow()
436
+ .get(prop.as_ref())
437
+ .map(|v| v.as_number().unwrap_or(f64::NAN))
438
+ .unwrap_or(f64::NAN),
408
439
  _ => f64::NAN,
409
440
  }
410
441
  }
@@ -82,10 +82,7 @@ fn buffer_source_stub() -> Value {
82
82
  Arc::from("start"),
83
83
  Value::Function(Rc::new(|_| Value::Null)),
84
84
  );
85
- m.insert(
86
- Arc::from("stop"),
87
- Value::Function(Rc::new(|_| Value::Null)),
88
- );
85
+ m.insert(Arc::from("stop"), Value::Function(Rc::new(|_| Value::Null)));
89
86
  Value::Object(Rc::new(RefCell::new(m)))
90
87
  }
91
88
 
@@ -98,10 +95,7 @@ fn oscillator_stub() -> Value {
98
95
  Arc::from("start"),
99
96
  Value::Function(Rc::new(|_| Value::Null)),
100
97
  );
101
- m.insert(
102
- Arc::from("stop"),
103
- Value::Function(Rc::new(|_| Value::Null)),
104
- );
98
+ m.insert(Arc::from("stop"), Value::Function(Rc::new(|_| Value::Null)));
105
99
  Value::Object(Rc::new(RefCell::new(m)))
106
100
  }
107
101
 
@@ -16,29 +16,35 @@ pub fn boolean(args: &[Value]) -> Value {
16
16
 
17
17
  /// decodeURI(str)
18
18
  pub fn decode_uri(args: &[Value]) -> Value {
19
- let s = args.first().map(Value::to_display_string).unwrap_or_default();
19
+ let s = args
20
+ .first()
21
+ .map(Value::to_display_string)
22
+ .unwrap_or_default();
20
23
  Value::String(percent_decode(&s).unwrap_or(s).into())
21
24
  }
22
25
 
23
26
  /// encodeURI(str)
24
27
  pub fn encode_uri(args: &[Value]) -> Value {
25
- let s = args.first().map(Value::to_display_string).unwrap_or_default();
28
+ let s = args
29
+ .first()
30
+ .map(Value::to_display_string)
31
+ .unwrap_or_default();
26
32
  Value::String(percent_encode(&s).into())
27
33
  }
28
34
 
29
35
  /// isFinite(value)
30
36
  pub fn is_finite(args: &[Value]) -> Value {
31
- Value::Bool(args.first().is_some_and(|v| matches!(v, Value::Number(n) if n.is_finite())))
37
+ Value::Bool(
38
+ args.first()
39
+ .is_some_and(|v| matches!(v, Value::Number(n) if n.is_finite())),
40
+ )
32
41
  }
33
42
 
34
43
  /// isNaN(value)
35
44
  pub fn is_nan(args: &[Value]) -> Value {
36
- Value::Bool(
37
- args.first().is_none_or(|v| {
38
- matches!(v, Value::Number(n) if n.is_nan())
39
- || !matches!(v, Value::Number(_))
40
- }),
41
- )
45
+ Value::Bool(args.first().is_none_or(|v| {
46
+ matches!(v, Value::Number(n) if n.is_nan()) || !matches!(v, Value::Number(_))
47
+ }))
42
48
  }
43
49
 
44
50
  /// Array.isArray(value)
@@ -144,12 +150,18 @@ pub fn object_assign(args: &[Value]) -> Value {
144
150
 
145
151
  /// parseInt(string, radix?)
146
152
  pub fn parse_int(args: &[Value]) -> Value {
147
- let s = args.first().map(Value::to_display_string).unwrap_or_default();
153
+ let s = args
154
+ .first()
155
+ .map(Value::to_display_string)
156
+ .unwrap_or_default();
148
157
  let s = s.trim();
149
- let radix = args.get(1).and_then(|v| match v {
150
- Value::Number(n) => Some(*n as i32),
151
- _ => None,
152
- }).unwrap_or(10);
158
+ let radix = args
159
+ .get(1)
160
+ .and_then(|v| match v {
161
+ Value::Number(n) => Some(*n as i32),
162
+ _ => None,
163
+ })
164
+ .unwrap_or(10);
153
165
 
154
166
  if (2..=36).contains(&radix) {
155
167
  let prefix: String = s
@@ -165,7 +177,10 @@ pub fn parse_int(args: &[Value]) -> Value {
165
177
 
166
178
  /// parseFloat(string)
167
179
  pub fn parse_float(args: &[Value]) -> Value {
168
- let s = args.first().map(Value::to_display_string).unwrap_or_default();
180
+ let s = args
181
+ .first()
182
+ .map(Value::to_display_string)
183
+ .unwrap_or_default();
169
184
  Value::Number(s.trim().parse().unwrap_or(f64::NAN))
170
185
  }
171
186
 
@@ -5,11 +5,11 @@
5
5
  //! and native signatures.
6
6
 
7
7
  pub mod array;
8
- pub mod string;
9
- pub mod object;
10
- pub mod math;
11
- pub mod helpers;
12
- pub mod globals;
13
8
  pub mod construct;
9
+ pub mod globals;
10
+ pub mod helpers;
11
+ pub mod math;
12
+ pub mod object;
13
+ pub mod string;
14
14
 
15
15
  pub use tishlang_core::Value;
@@ -1,7 +1,7 @@
1
1
  //! Math builtin functions.
2
2
 
3
- use tishlang_core::Value;
4
3
  use crate::helpers::extract_num;
4
+ use tishlang_core::Value;
5
5
 
6
6
  macro_rules! math_unary {
7
7
  ($name:ident, $op:ident) => {
@@ -31,14 +31,16 @@ math_unary!(trunc, trunc);
31
31
  math_unary!(cbrt, cbrt);
32
32
 
33
33
  pub fn min(args: &[Value]) -> Value {
34
- let n = args.iter()
34
+ let n = args
35
+ .iter()
35
36
  .filter_map(|v| extract_num(Some(v)))
36
37
  .fold(f64::INFINITY, f64::min);
37
38
  Value::Number(if n == f64::INFINITY { f64::NAN } else { n })
38
39
  }
39
40
 
40
41
  pub fn max(args: &[Value]) -> Value {
41
- let n = args.iter()
42
+ let n = args
43
+ .iter()
42
44
  .filter_map(|v| extract_num(Some(v)))
43
45
  .fold(f64::NEG_INFINITY, f64::max);
44
46
  Value::Number(if n == f64::NEG_INFINITY { f64::NAN } else { n })
@@ -3,11 +3,11 @@
3
3
  //! All indices use character (Unicode scalar) positions for consistency with
4
4
  //! JavaScript, matching .length and .charAt(). Byte offsets are never exposed.
5
5
 
6
+ use crate::helpers::normalize_index;
6
7
  use std::cell::RefCell;
7
8
  use std::rc::Rc;
8
9
  use std::sync::Arc;
9
10
  use tishlang_core::Value;
10
- use crate::helpers::normalize_index;
11
11
 
12
12
  /// Byte offset -> character index.
13
13
  fn byte_to_char_index(s: &str, byte_offset: usize) -> usize {
@@ -152,7 +152,10 @@ pub fn slice(s: &Value, start: &Value, end: &Value) -> Value {
152
152
  if let Value::String(s) = s {
153
153
  let chars: Vec<char> = s.chars().collect();
154
154
  let len = chars.len() as i64;
155
- let (si, ei) = (normalize_index(start, len, 0), normalize_index(end, len, len as usize));
155
+ let (si, ei) = (
156
+ normalize_index(start, len, 0),
157
+ normalize_index(end, len, len as usize),
158
+ );
156
159
  let result: String = if si < ei {
157
160
  chars[si..ei].iter().collect()
158
161
  } else {
@@ -193,7 +196,8 @@ pub fn split(s: &Value, sep: &Value) -> Value {
193
196
  Value::String(ss) => ss.as_ref(),
194
197
  _ => return Value::Array(Rc::new(RefCell::new(vec![Value::String(Arc::clone(s))]))),
195
198
  };
196
- let parts: Vec<Value> = s.split(separator)
199
+ let parts: Vec<Value> = s
200
+ .split(separator)
197
201
  .map(|p| Value::String(p.into()))
198
202
  .collect();
199
203
  Value::Array(Rc::new(RefCell::new(parts)))
@@ -244,9 +248,19 @@ pub fn ends_with(s: &Value, search: &Value) -> Value {
244
248
 
245
249
  fn replace_impl(s: &Value, search: &Value, replacement: &Value, all: bool) -> Value {
246
250
  if let Value::String(s) = s {
247
- let search_str = match search { Value::String(ss) => ss.as_ref(), _ => return Value::String(Arc::clone(s)) };
248
- let repl_str = match replacement { Value::String(ss) => ss.as_ref(), _ => "" };
249
- let result = if all { s.replace(search_str, repl_str) } else { s.replacen(search_str, repl_str, 1) };
251
+ let search_str = match search {
252
+ Value::String(ss) => ss.as_ref(),
253
+ _ => return Value::String(Arc::clone(s)),
254
+ };
255
+ let repl_str = match replacement {
256
+ Value::String(ss) => ss.as_ref(),
257
+ _ => "",
258
+ };
259
+ let result = if all {
260
+ s.replace(search_str, repl_str)
261
+ } else {
262
+ s.replacen(search_str, repl_str, 1)
263
+ };
250
264
  Value::String(result.into())
251
265
  } else {
252
266
  Value::Null
@@ -267,8 +281,13 @@ fn char_at_idx(s: &str, idx: usize) -> Option<char> {
267
281
 
268
282
  pub fn char_at(s: &Value, idx: &Value) -> Value {
269
283
  if let Value::String(s) = s {
270
- let idx = match idx { Value::Number(n) => *n as usize, _ => 0 };
271
- char_at_idx(s, idx).map(|c| Value::String(c.to_string().into())).unwrap_or(Value::String("".into()))
284
+ let idx = match idx {
285
+ Value::Number(n) => *n as usize,
286
+ _ => 0,
287
+ };
288
+ char_at_idx(s, idx)
289
+ .map(|c| Value::String(c.to_string().into()))
290
+ .unwrap_or(Value::String("".into()))
272
291
  } else {
273
292
  Value::Null
274
293
  }
@@ -276,8 +295,13 @@ pub fn char_at(s: &Value, idx: &Value) -> Value {
276
295
 
277
296
  pub fn char_code_at(s: &Value, idx: &Value) -> Value {
278
297
  if let Value::String(s) = s {
279
- let idx = match idx { Value::Number(n) => *n as usize, _ => 0 };
280
- char_at_idx(s, idx).map(|c| Value::Number(c as u32 as f64)).unwrap_or(Value::Number(f64::NAN))
298
+ let idx = match idx {
299
+ Value::Number(n) => *n as usize,
300
+ _ => 0,
301
+ };
302
+ char_at_idx(s, idx)
303
+ .map(|c| Value::Number(c as u32 as f64))
304
+ .unwrap_or(Value::Number(f64::NAN))
281
305
  } else {
282
306
  Value::Null
283
307
  }
@@ -311,7 +335,11 @@ fn pad_impl(s: &Value, target_len: &Value, pad: &Value, at_start: bool) -> Value
311
335
  }
312
336
  let needed = target_len - char_count;
313
337
  let padding: String = pad_str.chars().cycle().take(needed).collect();
314
- let result = if at_start { format!("{}{}", padding, s) } else { format!("{}{}", s, padding) };
338
+ let result = if at_start {
339
+ format!("{}{}", padding, s)
340
+ } else {
341
+ format!("{}{}", s, padding)
342
+ };
315
343
  Value::String(result.into())
316
344
  } else {
317
345
  Value::Null
@@ -382,16 +410,31 @@ mod tests {
382
410
  fn includes_basic() {
383
411
  assert_same!(includes(&s("hello"), &s("ll"), None), Value::Bool(true));
384
412
  assert_same!(includes(&s("hello"), &s("x"), None), Value::Bool(false));
385
- assert_same!(includes(&s("hello"), &s("l"), Some(&n(3.0))), Value::Bool(true));
386
- assert_same!(includes(&s("hello"), &s("l"), Some(&n(4.0))), Value::Bool(false));
413
+ assert_same!(
414
+ includes(&s("hello"), &s("l"), Some(&n(3.0))),
415
+ Value::Bool(true)
416
+ );
417
+ assert_same!(
418
+ includes(&s("hello"), &s("l"), Some(&n(4.0))),
419
+ Value::Bool(false)
420
+ );
387
421
  }
388
422
 
389
423
  #[test]
390
424
  fn includes_negative_from() {
391
- assert_same!(includes(&s("hello"), &s("o"), Some(&n(-1.0))), Value::Bool(true));
392
- assert_same!(includes(&s("hello"), &s("h"), Some(&n(-5.0))), Value::Bool(true));
425
+ assert_same!(
426
+ includes(&s("hello"), &s("o"), Some(&n(-1.0))),
427
+ Value::Bool(true)
428
+ );
429
+ assert_same!(
430
+ includes(&s("hello"), &s("h"), Some(&n(-5.0))),
431
+ Value::Bool(true)
432
+ );
393
433
  // fromIndex -1 → start at len-1 = 1 ("i" only), "h" not found
394
- assert_same!(includes(&s("hi"), &s("h"), Some(&n(-1.0))), Value::Bool(false));
434
+ assert_same!(
435
+ includes(&s("hi"), &s("h"), Some(&n(-1.0))),
436
+ Value::Bool(false)
437
+ );
395
438
  }
396
439
 
397
440
  #[test]
@@ -466,7 +509,10 @@ mod tests {
466
509
 
467
510
  #[test]
468
511
  fn last_index_of_basic() {
469
- assert_same!(last_index_of(&s("abcabc"), &s("a"), &n(f64::INFINITY)), n(3.0));
512
+ assert_same!(
513
+ last_index_of(&s("abcabc"), &s("a"), &n(f64::INFINITY)),
514
+ n(3.0)
515
+ );
470
516
  assert_same!(last_index_of(&s("abcabc"), &s("a"), &n(2.0)), n(0.0));
471
517
  assert_same!(last_index_of(&s("hello"), &s("l"), &n(3.0)), n(3.0));
472
518
  assert_same!(last_index_of(&s("hello"), &s("l"), &n(1.0)), n(-1.0));
@@ -490,8 +536,14 @@ mod tests {
490
536
 
491
537
  #[test]
492
538
  fn last_index_of_unicode() {
493
- assert_same!(last_index_of(&s("😀a😀"), &s("a"), &n(f64::INFINITY)), n(1.0));
494
- assert_same!(last_index_of(&s("😀a😀"), &s("😀"), &n(f64::INFINITY)), n(2.0));
539
+ assert_same!(
540
+ last_index_of(&s("😀a😀"), &s("a"), &n(f64::INFINITY)),
541
+ n(1.0)
542
+ );
543
+ assert_same!(
544
+ last_index_of(&s("😀a😀"), &s("😀"), &n(f64::INFINITY)),
545
+ n(2.0)
546
+ );
495
547
  }
496
548
 
497
549
  #[test]
@@ -30,7 +30,6 @@ impl Constant {
30
30
  }
31
31
  }
32
32
 
33
-
34
33
  /// A bytecode chunk: instructions and associated data.
35
34
  #[derive(Debug, Clone)]
36
35
  pub struct Chunk {