@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
@@ -16,7 +16,9 @@ struct Codegen {
16
16
  fn stmt_terminates_switch(stmt: Option<&Statement>) -> bool {
17
17
  matches!(
18
18
  stmt,
19
- Some(Statement::Break { .. }) | Some(Statement::Return { .. }) | Some(Statement::Throw { .. })
19
+ Some(Statement::Break { .. })
20
+ | Some(Statement::Return { .. })
21
+ | Some(Statement::Throw { .. })
20
22
  )
21
23
  }
22
24
 
@@ -174,7 +176,12 @@ impl Codegen {
174
176
  self.emit_statement(body)?;
175
177
  self.indent -= 1;
176
178
  }
177
- Statement::ForOf { name, iterable, body, .. } => {
179
+ Statement::ForOf {
180
+ name,
181
+ iterable,
182
+ body,
183
+ ..
184
+ } => {
178
185
  let escaped = Self::escape_ident(name.as_ref());
179
186
  let it = self.emit_expr(iterable)?;
180
187
  self.writeln(&format!("for (const {} of {})", escaped, it));
@@ -204,7 +211,10 @@ impl Codegen {
204
211
  let async_prefix = if *async_ { "async " } else { "" };
205
212
  let escaped = Self::escape_ident(name.as_ref());
206
213
  let params_str = self.emit_params(params, rest_param.as_ref())?;
207
- self.writeln(&format!("{}function {} ({}) {{", async_prefix, escaped, params_str));
214
+ self.writeln(&format!(
215
+ "{}function {} ({}) {{",
216
+ async_prefix, escaped, params_str
217
+ ));
208
218
  self.indent += 1;
209
219
  if *async_ {
210
220
  self.in_async = true;
@@ -337,9 +347,7 @@ impl Codegen {
337
347
  let parts: Vec<String> = elements
338
348
  .iter()
339
349
  .map(|el| match el {
340
- Some(DestructElement::Ident(n)) => {
341
- Ok(Self::escape_ident(n.as_ref()))
342
- }
350
+ Some(DestructElement::Ident(n)) => Ok(Self::escape_ident(n.as_ref())),
343
351
  Some(DestructElement::Pattern(p)) => self.emit_destruct_pattern(p),
344
352
  Some(DestructElement::Rest(n)) => {
345
353
  Ok(format!("...{}", Self::escape_ident(n.as_ref())))
@@ -385,7 +393,9 @@ impl Codegen {
385
393
  Literal::Null => "null".to_string(),
386
394
  },
387
395
  Expr::Ident { name, .. } => Self::escape_ident(name.as_ref()),
388
- Expr::Binary { left, op, right, .. } => {
396
+ Expr::Binary {
397
+ left, op, right, ..
398
+ } => {
389
399
  let l = self.emit_expr(left)?;
390
400
  let r = self.emit_expr(right)?;
391
401
  let op_str = match op {
@@ -451,7 +461,9 @@ impl Codegen {
451
461
  let obj = self.emit_expr(object)?;
452
462
  let expr = match prop {
453
463
  MemberProp::Name(p) => {
454
- if p.parse::<u32>().is_ok() || !p.chars().all(|c| c.is_alphanumeric() || c == '_') {
464
+ if p.parse::<u32>().is_ok()
465
+ || !p.chars().all(|c| c.is_alphanumeric() || c == '_')
466
+ {
455
467
  format!("{}[{:?}]", obj, p.as_ref())
456
468
  } else {
457
469
  let sep = if *optional { "?." } else { "." };
@@ -548,7 +560,9 @@ impl Codegen {
548
560
  Expr::PrefixDec { name, .. } => {
549
561
  format!("--{}", Self::escape_ident(name.as_ref()))
550
562
  }
551
- Expr::CompoundAssign { name, op, value, .. } => {
563
+ Expr::CompoundAssign {
564
+ name, op, value, ..
565
+ } => {
552
566
  let n = Self::escape_ident(name.as_ref());
553
567
  let v = self.emit_expr(value)?;
554
568
  let op_str = match op {
@@ -560,7 +574,9 @@ impl Codegen {
560
574
  };
561
575
  format!("({} {} {})", n, op_str, v)
562
576
  }
563
- Expr::LogicalAssign { name, op, value, .. } => {
577
+ Expr::LogicalAssign {
578
+ name, op, value, ..
579
+ } => {
564
580
  let n = Self::escape_ident(name.as_ref());
565
581
  let v = self.emit_expr(value)?;
566
582
  let op_str = match op {
@@ -570,7 +586,12 @@ impl Codegen {
570
586
  };
571
587
  format!("({} {} {})", n, op_str, v)
572
588
  }
573
- Expr::MemberAssign { object, prop, value, .. } => {
589
+ Expr::MemberAssign {
590
+ object,
591
+ prop,
592
+ value,
593
+ ..
594
+ } => {
574
595
  let obj = self.emit_expr(object)?;
575
596
  let val = self.emit_expr(value)?;
576
597
  format!("({}.{} = {})", obj, prop.as_ref(), val)
@@ -652,7 +673,6 @@ impl Codegen {
652
673
  CallArg::Spread(e) => Ok(format!("...{}", self.emit_expr(e)?)),
653
674
  }
654
675
  }
655
-
656
676
  }
657
677
 
658
678
  /// Compile a single program (no imports) to JavaScript. JSX lowers to `h` / `Fragment` (Lattish).
@@ -679,7 +699,8 @@ pub fn compile_project_with_jsx(
679
699
  .map_err(|e| CompileError { message: e })?;
680
700
  tishlang_compile::detect_cycles(&modules).map_err(|e| CompileError { message: e })?;
681
701
  let program = {
682
- let prog = tishlang_compile::merge_modules(modules).map_err(|e| CompileError { message: e })?;
702
+ let prog =
703
+ tishlang_compile::merge_modules(modules).map_err(|e| CompileError { message: e })?;
683
704
  if optimize {
684
705
  tishlang_opt::optimize(&prog)
685
706
  } else {
@@ -11,7 +11,11 @@ mod tests {
11
11
  let src = r#"fn X() { return <div class="a">{"hi"}</div> }"#;
12
12
  let program = parse(src).unwrap();
13
13
  let js = compile_with_jsx(&program, false).unwrap();
14
- assert!(js.contains("h(\"div\", { class: \"a\" }, [\"hi\"])"), "{}", js);
14
+ assert!(
15
+ js.contains("h(\"div\", { class: \"a\" }, [\"hi\"])"),
16
+ "{}",
17
+ js
18
+ );
15
19
  assert!(!js.contains("function __h("));
16
20
  }
17
21
 
@@ -57,7 +61,11 @@ mod tests {
57
61
  let src = r#"fn X() { return <p>work!</p> }"#;
58
62
  let program = parse(src).unwrap();
59
63
  let js = compile_with_jsx(&program, false).unwrap();
60
- assert!(js.contains(r#""work!""#), "expected 'work!', got: {}", &js[..400.min(js.len())]);
64
+ assert!(
65
+ js.contains(r#""work!""#),
66
+ "expected 'work!', got: {}",
67
+ &js[..400.min(js.len())]
68
+ );
61
69
  }
62
70
 
63
71
  #[test]
@@ -65,7 +73,11 @@ mod tests {
65
73
  let src = r#"fn X() { return <p>hello 😔</p> }"#;
66
74
  let program = parse(src).unwrap();
67
75
  let js = compile_with_jsx(&program, false).unwrap();
68
- assert!(js.contains("😔"), "expected emoji, got: {}", &js[..400.min(js.len())]);
76
+ assert!(
77
+ js.contains("😔"),
78
+ "expected emoji, got: {}",
79
+ &js[..400.min(js.len())]
80
+ );
69
81
  }
70
82
 
71
83
  #[test]
@@ -93,10 +105,22 @@ mod tests {
93
105
  let src = r#"fn X() { return <div class="x">{"a"}</div> }"#;
94
106
  let program = parse(src).unwrap();
95
107
  let js = compile_with_jsx(&program, false).unwrap();
96
- assert!(js.contains("h(\"div\", { class: \"x\" }"), "{}", &js[..500.min(js.len())]);
108
+ assert!(
109
+ js.contains("h(\"div\", { class: \"x\" }"),
110
+ "{}",
111
+ &js[..500.min(js.len())]
112
+ );
97
113
  assert!(!js.contains("__vdom_h"), "{}", &js[..600.min(js.len())]);
98
- assert!(!js.contains("window.__LATTISH_JSX_VDOM"), "{}", &js[..600.min(js.len())]);
99
- assert!(!js.contains("__lattishVdomPatch"), "{}", &js[..600.min(js.len())]);
114
+ assert!(
115
+ !js.contains("window.__LATTISH_JSX_VDOM"),
116
+ "{}",
117
+ &js[..600.min(js.len())]
118
+ );
119
+ assert!(
120
+ !js.contains("__lattishVdomPatch"),
121
+ "{}",
122
+ &js[..600.min(js.len())]
123
+ );
100
124
  }
101
125
 
102
126
  /// Component calls like {Panel()} return DOM elements. Wrapping in String() produces [object HTMLDivElement].
@@ -14,29 +14,34 @@ use wasm_bindgen::prelude::*;
14
14
 
15
15
  #[wasm_bindgen]
16
16
  pub fn compile_to_bytecode(source: &str) -> Result<String, JsValue> {
17
- let program = tishlang_parser::parse(source.trim()).map_err(|e| JsValue::from_str(&e.to_string()))?;
17
+ let program =
18
+ tishlang_parser::parse(source.trim()).map_err(|e| JsValue::from_str(&e.to_string()))?;
18
19
  let program = tishlang_opt::optimize(&program);
19
- let chunk = tishlang_bytecode::compile(&program).map_err(|e| JsValue::from_str(&e.to_string()))?;
20
+ let chunk =
21
+ tishlang_bytecode::compile(&program).map_err(|e| JsValue::from_str(&e.to_string()))?;
20
22
  Ok(base64::engine::general_purpose::STANDARD.encode(tishlang_bytecode::serialize(&chunk)))
21
23
  }
22
24
 
23
25
  #[wasm_bindgen]
24
26
  pub fn compile_to_js(source: &str) -> Result<String, JsValue> {
25
- let program = tishlang_parser::parse(source.trim()).map_err(|e| JsValue::from_str(&e.to_string()))?;
26
- tishlang_compile_js::compile_with_jsx(&program, true)
27
- .map_err(|e| JsValue::from_str(&e.message))
27
+ let program =
28
+ tishlang_parser::parse(source.trim()).map_err(|e| JsValue::from_str(&e.to_string()))?;
29
+ tishlang_compile_js::compile_with_jsx(&program, true).map_err(|e| JsValue::from_str(&e.message))
28
30
  }
29
31
 
30
32
  #[wasm_bindgen]
31
- pub fn compile_to_bytecode_with_imports(entry_path: &str, files_json: &str) -> Result<String, JsValue> {
33
+ pub fn compile_to_bytecode_with_imports(
34
+ entry_path: &str,
35
+ files_json: &str,
36
+ ) -> Result<String, JsValue> {
32
37
  let files: HashMap<String, String> = serde_json::from_str(files_json)
33
38
  .map_err(|e| JsValue::from_str(&format!("Invalid files JSON: {}", e)))?;
34
- let modules = resolve_virtual(entry_path, &files)
35
- .map_err(|e| JsValue::from_str(&e))?;
39
+ let modules = resolve_virtual(entry_path, &files).map_err(|e| JsValue::from_str(&e))?;
36
40
  detect_cycles_virtual(&modules).map_err(|e| JsValue::from_str(&e))?;
37
41
  let program = merge_modules_virtual(modules).map_err(|e| JsValue::from_str(&e))?;
38
42
  let program = tishlang_opt::optimize(&program);
39
- let chunk = tishlang_bytecode::compile(&program).map_err(|e| JsValue::from_str(&e.to_string()))?;
43
+ let chunk =
44
+ tishlang_bytecode::compile(&program).map_err(|e| JsValue::from_str(&e.to_string()))?;
40
45
  Ok(base64::engine::general_purpose::STANDARD.encode(tishlang_bytecode::serialize(&chunk)))
41
46
  }
42
47
 
@@ -44,11 +49,9 @@ pub fn compile_to_bytecode_with_imports(entry_path: &str, files_json: &str) -> R
44
49
  pub fn compile_to_js_with_imports(entry_path: &str, files_json: &str) -> Result<String, JsValue> {
45
50
  let files: HashMap<String, String> = serde_json::from_str(files_json)
46
51
  .map_err(|e| JsValue::from_str(&format!("Invalid files JSON: {}", e)))?;
47
- let modules = resolve_virtual(entry_path, &files)
48
- .map_err(|e| JsValue::from_str(&e))?;
52
+ let modules = resolve_virtual(entry_path, &files).map_err(|e| JsValue::from_str(&e))?;
49
53
  detect_cycles_virtual(&modules).map_err(|e| JsValue::from_str(&e))?;
50
54
  let program = merge_modules_virtual(modules).map_err(|e| JsValue::from_str(&e))?;
51
55
  let program = tishlang_opt::optimize(&program);
52
- tishlang_compile_js::compile_with_jsx(&program, true)
53
- .map_err(|e| JsValue::from_str(&e.message))
56
+ tishlang_compile_js::compile_with_jsx(&program, true).map_err(|e| JsValue::from_str(&e.message))
54
57
  }
@@ -109,10 +109,16 @@ pub fn resolve_virtual(
109
109
  if files.contains_key(&with_ext) {
110
110
  with_ext
111
111
  } else {
112
- return Err(format!("Entry file '{}' not in virtual file map", entry_path));
112
+ return Err(format!(
113
+ "Entry file '{}' not in virtual file map",
114
+ entry_path
115
+ ));
113
116
  }
114
117
  } else {
115
- return Err(format!("Entry file '{}' not in virtual file map", entry_path));
118
+ return Err(format!(
119
+ "Entry file '{}' not in virtual file map",
120
+ entry_path
121
+ ));
116
122
  };
117
123
 
118
124
  let mut visited = HashSet::new();
@@ -148,9 +154,9 @@ fn load_module_recursive(
148
154
  }
149
155
  visited.insert(module_path.to_string());
150
156
 
151
- let source = files.get(module_path).ok_or_else(|| {
152
- format!("Module '{}' not in virtual file map", module_path)
153
- })?;
157
+ let source = files
158
+ .get(module_path)
159
+ .ok_or_else(|| format!("Module '{}' not in virtual file map", module_path))?;
154
160
  let program = tishlang_parser::parse(source.trim())
155
161
  .map_err(|e| format!("Parse error in {}: {}", module_path, e))?;
156
162
 
@@ -162,13 +168,7 @@ fn load_module_recursive(
162
168
  }
163
169
  let dep_key = resolve_import_to_key(from, from_dir, files)?;
164
170
  if !path_to_module.contains_key(&dep_key) {
165
- load_module_recursive(
166
- &dep_key,
167
- files,
168
- visited,
169
- path_to_module,
170
- load_order,
171
- )?;
171
+ load_module_recursive(&dep_key, files, visited, path_to_module, load_order)?;
172
172
  }
173
173
  }
174
174
  }
@@ -197,11 +197,11 @@ pub fn detect_cycles_virtual(modules: &[VirtualModule]) -> Result<(), String> {
197
197
  &mut stack,
198
198
  &mut HashSet::new(),
199
199
  )? {
200
- let path_names: Vec<_> = stack
201
- .iter()
202
- .map(|&i| modules[i].path.clone())
203
- .collect();
204
- return Err(format!("Circular import detected: {}", path_names.join(" -> ")));
200
+ let path_names: Vec<_> = stack.iter().map(|&i| modules[i].path.clone()).collect();
201
+ return Err(format!(
202
+ "Circular import detected: {}",
203
+ path_names.join(" -> ")
204
+ ));
205
205
  }
206
206
  }
207
207
  Ok(())
@@ -231,14 +231,8 @@ fn has_cycle_from(
231
231
  stack.push(dep_idx);
232
232
  let dep = &modules[dep_idx];
233
233
  let dep_dir = parent_dir(&dep.path);
234
- if has_cycle_from(
235
- dep_dir,
236
- &dep.program,
237
- path_to_idx,
238
- modules,
239
- stack,
240
- visiting,
241
- )? {
234
+ if has_cycle_from(dep_dir, &dep.program, path_to_idx, modules, stack, visiting)?
235
+ {
242
236
  return Ok(true);
243
237
  }
244
238
  stack.pop();
@@ -307,7 +301,11 @@ pub fn merge_modules_virtual(modules: Vec<VirtualModule>) -> Result<Program, Str
307
301
  let dir = parent_dir(&module.path);
308
302
  for stmt in &module.program.statements {
309
303
  match stmt {
310
- Statement::Import { specifiers, from, span } => {
304
+ Statement::Import {
305
+ specifiers,
306
+ from,
307
+ span,
308
+ } => {
311
309
  if is_native_import(from) {
312
310
  let canonical_spec =
313
311
  normalize_builtin_spec(from).unwrap_or_else(|| from.to_string());
@@ -387,18 +385,13 @@ pub fn merge_modules_virtual(modules: Vec<VirtualModule>) -> Result<Program, Str
387
385
  name: ns.clone(),
388
386
  mutable: false,
389
387
  type_ann: None,
390
- init: Some(Expr::Object {
391
- props,
392
- span: *span,
393
- }),
388
+ init: Some(Expr::Object { props, span: *span }),
394
389
  span: *span,
395
390
  });
396
391
  }
397
392
  ImportSpecifier::Default(bind) => {
398
- let source = dep_exports
399
- .get("default")
400
- .cloned()
401
- .ok_or_else(|| {
393
+ let source =
394
+ dep_exports.get("default").cloned().ok_or_else(|| {
402
395
  format!("Module '{}' has no default export", from)
403
396
  })?;
404
397
  statements.push(Statement::VarDecl {
@@ -415,21 +408,19 @@ pub fn merge_modules_virtual(modules: Vec<VirtualModule>) -> Result<Program, Str
415
408
  }
416
409
  }
417
410
  }
418
- Statement::Export { declaration, .. } => {
419
- match declaration.as_ref() {
420
- ExportDeclaration::Named(s) => statements.push(*s.clone()),
421
- ExportDeclaration::Default(e) => {
422
- let default_name = format!("__default_{}", idx);
423
- statements.push(Statement::VarDecl {
424
- name: Arc::from(default_name),
425
- mutable: false,
426
- type_ann: None,
427
- init: Some((*e).clone()),
428
- span: e.span(),
429
- });
430
- }
411
+ Statement::Export { declaration, .. } => match declaration.as_ref() {
412
+ ExportDeclaration::Named(s) => statements.push(*s.clone()),
413
+ ExportDeclaration::Default(e) => {
414
+ let default_name = format!("__default_{}", idx);
415
+ statements.push(Statement::VarDecl {
416
+ name: Arc::from(default_name),
417
+ mutable: false,
418
+ type_ann: None,
419
+ init: Some((*e).clone()),
420
+ span: e.span(),
421
+ });
431
422
  }
432
- }
423
+ },
433
424
  _ => statements.push(stmt.clone()),
434
425
  }
435
426
  }
@@ -228,7 +228,7 @@ fn parse_number(input: &str) -> Result<(Value, &str), String> {
228
228
 
229
229
  let num_str: String = chars[..end].iter().collect();
230
230
  let byte_len: usize = chars[..end].iter().map(|c| c.len_utf8()).sum();
231
-
231
+
232
232
  num_str
233
233
  .parse::<f64>()
234
234
  .map(|n| (Value::Number(n), &input[byte_len..]))
@@ -306,7 +306,9 @@ mod tests {
306
306
  assert!(matches!(json_parse("true").unwrap(), Value::Bool(true)));
307
307
  assert!(matches!(json_parse("false").unwrap(), Value::Bool(false)));
308
308
  assert!(matches!(json_parse("42").unwrap(), Value::Number(n) if n == 42.0));
309
- assert!(matches!(json_parse("\"hello\"").unwrap(), Value::String(s) if s.as_ref() == "hello"));
309
+ assert!(
310
+ matches!(json_parse("\"hello\"").unwrap(), Value::String(s) if s.as_ref() == "hello")
311
+ );
310
312
  }
311
313
 
312
314
  #[test]
@@ -315,7 +317,7 @@ mod tests {
315
317
  let value = json_parse(original).unwrap();
316
318
  let stringified = json_stringify(&value);
317
319
  let reparsed = json_parse(&stringified).unwrap();
318
-
320
+
319
321
  match (&value, &reparsed) {
320
322
  (Value::Object(a), Value::Object(b)) => {
321
323
  assert_eq!(a.borrow().len(), b.borrow().len());
@@ -10,6 +10,6 @@ mod uri;
10
10
  mod value;
11
11
 
12
12
  pub use console_style::{format_value_styled, format_values_for_console, use_console_colors};
13
- pub use value::*;
14
13
  pub use json::{json_parse, json_stringify};
15
14
  pub use uri::{percent_decode, percent_encode};
15
+ pub use value::*;
@@ -10,13 +10,13 @@ pub fn percent_decode(input: &str) -> Result<String, String> {
10
10
  "%2F", "%2f", // /
11
11
  "%3F", "%3f", // ?
12
12
  "%3A", "%3a", // :
13
- "%40", // @
14
- "%26", // &
13
+ "%40", // @
14
+ "%26", // &
15
15
  "%3D", "%3d", // =
16
16
  "%2B", "%2b", // +
17
- "%24", // $
17
+ "%24", // $
18
18
  "%2C", "%2c", // ,
19
- "%23", // #
19
+ "%23", // #
20
20
  ];
21
21
 
22
22
  let mut result = String::with_capacity(input.len());
@@ -46,11 +46,14 @@ pub fn percent_decode(input: &str) -> Result<String, String> {
46
46
  None => return Err("URIError: malformed URI sequence".to_string()),
47
47
  }
48
48
  }
49
-
49
+
50
50
  if hex.len() == 2 {
51
51
  let encoded = format!("%{}", hex);
52
52
  // Check if this is a reserved character that should NOT be decoded
53
- if RESERVED_ENCODED.iter().any(|r| r.eq_ignore_ascii_case(&encoded)) {
53
+ if RESERVED_ENCODED
54
+ .iter()
55
+ .any(|r| r.eq_ignore_ascii_case(&encoded))
56
+ {
54
57
  result.push_str(&encoded);
55
58
  } else if let Ok(byte) = u8::from_str_radix(&hex, 16) {
56
59
  result.push(byte as char);
@@ -54,29 +54,70 @@ impl RegExpFlags {
54
54
  let mut result = Self::default();
55
55
  for c in flags.chars() {
56
56
  match c {
57
- 'g' => { if result.global { return Err(format!("duplicate flag '{}'", c)); } result.global = true; }
58
- 'i' => { if result.ignore_case { return Err(format!("duplicate flag '{}'", c)); } result.ignore_case = true; }
59
- 'm' => { if result.multiline { return Err(format!("duplicate flag '{}'", c)); } result.multiline = true; }
60
- 's' => { if result.dot_all { return Err(format!("duplicate flag '{}'", c)); } result.dot_all = true; }
61
- 'u' => { if result.unicode { return Err(format!("duplicate flag '{}'", c)); } result.unicode = true; }
62
- 'y' => { if result.sticky { return Err(format!("duplicate flag '{}'", c)); } result.sticky = true; }
57
+ 'g' => {
58
+ if result.global {
59
+ return Err(format!("duplicate flag '{}'", c));
60
+ }
61
+ result.global = true;
62
+ }
63
+ 'i' => {
64
+ if result.ignore_case {
65
+ return Err(format!("duplicate flag '{}'", c));
66
+ }
67
+ result.ignore_case = true;
68
+ }
69
+ 'm' => {
70
+ if result.multiline {
71
+ return Err(format!("duplicate flag '{}'", c));
72
+ }
73
+ result.multiline = true;
74
+ }
75
+ 's' => {
76
+ if result.dot_all {
77
+ return Err(format!("duplicate flag '{}'", c));
78
+ }
79
+ result.dot_all = true;
80
+ }
81
+ 'u' => {
82
+ if result.unicode {
83
+ return Err(format!("duplicate flag '{}'", c));
84
+ }
85
+ result.unicode = true;
86
+ }
87
+ 'y' => {
88
+ if result.sticky {
89
+ return Err(format!("duplicate flag '{}'", c));
90
+ }
91
+ result.sticky = true;
92
+ }
63
93
  _ => return Err(format!("unknown flag '{}'", c)),
64
94
  }
65
95
  }
66
96
  Ok(result)
67
97
  }
68
-
69
98
  }
70
99
 
71
100
  #[cfg(feature = "regex")]
72
101
  impl std::fmt::Display for RegExpFlags {
73
102
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74
- if self.global { f.write_str("g")?; }
75
- if self.ignore_case { f.write_str("i")?; }
76
- if self.multiline { f.write_str("m")?; }
77
- if self.dot_all { f.write_str("s")?; }
78
- if self.unicode { f.write_str("u")?; }
79
- if self.sticky { f.write_str("y")?; }
103
+ if self.global {
104
+ f.write_str("g")?;
105
+ }
106
+ if self.ignore_case {
107
+ f.write_str("i")?;
108
+ }
109
+ if self.multiline {
110
+ f.write_str("m")?;
111
+ }
112
+ if self.dot_all {
113
+ f.write_str("s")?;
114
+ }
115
+ if self.unicode {
116
+ f.write_str("u")?;
117
+ }
118
+ if self.sticky {
119
+ f.write_str("y")?;
120
+ }
80
121
  Ok(())
81
122
  }
82
123
  }
@@ -96,23 +137,36 @@ impl TishRegExp {
96
137
  pub fn new(pattern: &str, flags_str: &str) -> Result<Self, String> {
97
138
  let flags = RegExpFlags::from_string(flags_str)?;
98
139
  let mut regex_pattern = pattern.to_string();
99
-
140
+
100
141
  if flags.ignore_case || flags.multiline || flags.dot_all {
101
142
  let mut flag_prefix = String::from("(?");
102
- if flags.ignore_case { flag_prefix.push('i'); }
103
- if flags.multiline { flag_prefix.push('m'); }
104
- if flags.dot_all { flag_prefix.push('s'); }
143
+ if flags.ignore_case {
144
+ flag_prefix.push('i');
145
+ }
146
+ if flags.multiline {
147
+ flag_prefix.push('m');
148
+ }
149
+ if flags.dot_all {
150
+ flag_prefix.push('s');
151
+ }
105
152
  flag_prefix.push(')');
106
153
  regex_pattern = format!("{}{}", flag_prefix, regex_pattern);
107
154
  }
108
-
109
- let regex = Regex::new(&regex_pattern)
110
- .map_err(|e| format!("Invalid regular expression: {}", e))?;
111
-
112
- Ok(Self { source: pattern.to_string(), flags, regex: Arc::new(regex), last_index: 0 })
155
+
156
+ let regex =
157
+ Regex::new(&regex_pattern).map_err(|e| format!("Invalid regular expression: {}", e))?;
158
+
159
+ Ok(Self {
160
+ source: pattern.to_string(),
161
+ flags,
162
+ regex: Arc::new(regex),
163
+ last_index: 0,
164
+ })
113
165
  }
114
166
 
115
- pub fn flags_string(&self) -> String { self.flags.to_string() }
167
+ pub fn flags_string(&self) -> String {
168
+ self.flags.to_string()
169
+ }
116
170
 
117
171
  pub fn test(&mut self, input: &str) -> bool {
118
172
  if self.flags.global || self.flags.sticky {
@@ -121,10 +175,10 @@ impl TishRegExp {
121
175
  self.last_index = 0;
122
176
  return false;
123
177
  }
124
-
178
+
125
179
  let byte_start: usize = input.chars().take(start).map(|c| c.len_utf8()).sum();
126
180
  let search_str = &input[byte_start..];
127
-
181
+
128
182
  match self.regex.find(search_str) {
129
183
  Ok(Some(m)) => {
130
184
  if self.flags.sticky && m.start() != 0 {
@@ -176,7 +230,12 @@ impl std::fmt::Debug for Value {
176
230
  Value::Object(obj) => write!(f, "Object({:?})", obj.borrow()),
177
231
  Value::Function(_) => write!(f, "Function"),
178
232
  #[cfg(feature = "regex")]
179
- Value::RegExp(re) => write!(f, "RegExp(/{}/{})", re.borrow().source, re.borrow().flags_string()),
233
+ Value::RegExp(re) => write!(
234
+ f,
235
+ "RegExp(/{}/{})",
236
+ re.borrow().source,
237
+ re.borrow().flags_string()
238
+ ),
180
239
  Value::Promise(_) => write!(f, "Promise"),
181
240
  Value::Opaque(o) => write!(f, "{}(opaque)", o.type_name()),
182
241
  }
@@ -202,7 +261,8 @@ impl Value {
202
261
  Value::Bool(b) => b.to_string(),
203
262
  Value::Null => "null".to_string(),
204
263
  Value::Array(arr) => {
205
- let inner: Vec<String> = arr.borrow().iter().map(|v| v.to_display_string()).collect();
264
+ let inner: Vec<String> =
265
+ arr.borrow().iter().map(|v| v.to_display_string()).collect();
206
266
  format!("[{}]", inner.join(", "))
207
267
  }
208
268
  Value::Object(obj) => {
@@ -378,7 +438,11 @@ impl Value {
378
438
  "trim".into(),
379
439
  ]
380
440
  }
381
- Value::Number(_) => vec!["toFixed".into(), "toExponential".into(), "toPrecision".into()],
441
+ Value::Number(_) => vec![
442
+ "toFixed".into(),
443
+ "toExponential".into(),
444
+ "toPrecision".into(),
445
+ ],
382
446
  _ => vec![],
383
447
  }
384
448
  }