@tishlang/tish 1.6.0 → 1.8.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 (113) hide show
  1. package/Cargo.toml +2 -0
  2. package/README.md +2 -0
  3. package/bin/tish +0 -0
  4. package/crates/js_to_tish/src/error.rs +2 -8
  5. package/crates/js_to_tish/src/transform/expr.rs +128 -137
  6. package/crates/js_to_tish/src/transform/stmt.rs +62 -32
  7. package/crates/tish/Cargo.toml +15 -5
  8. package/crates/tish/src/cargo_native_registry.rs +29 -0
  9. package/crates/tish/src/cli_help.rs +92 -39
  10. package/crates/tish/src/main.rs +172 -86
  11. package/crates/tish/src/repl_completion.rs +3 -3
  12. package/crates/tish/tests/cargo_example_compile.rs +4 -2
  13. package/crates/tish/tests/integration_test.rs +216 -54
  14. package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
  15. package/crates/tish/tests/shortcircuit.rs +20 -5
  16. package/crates/tish_ast/src/ast.rs +92 -23
  17. package/crates/tish_build_utils/Cargo.toml +4 -0
  18. package/crates/tish_build_utils/src/lib.rs +136 -8
  19. package/crates/tish_builtins/Cargo.toml +5 -1
  20. package/crates/tish_builtins/src/array.rs +65 -33
  21. package/crates/tish_builtins/src/construct.rs +34 -39
  22. package/crates/tish_builtins/src/globals.rs +42 -26
  23. package/crates/tish_builtins/src/helpers.rs +2 -1
  24. package/crates/tish_builtins/src/lib.rs +5 -5
  25. package/crates/tish_builtins/src/math.rs +5 -3
  26. package/crates/tish_builtins/src/object.rs +3 -2
  27. package/crates/tish_builtins/src/string.rs +144 -22
  28. package/crates/tish_bytecode/src/chunk.rs +0 -1
  29. package/crates/tish_bytecode/src/compiler.rs +173 -71
  30. package/crates/tish_bytecode/src/opcode.rs +24 -6
  31. package/crates/tish_bytecode/src/peephole.rs +2 -2
  32. package/crates/tish_compile/Cargo.toml +1 -0
  33. package/crates/tish_compile/src/codegen.rs +1621 -453
  34. package/crates/tish_compile/src/infer.rs +75 -19
  35. package/crates/tish_compile/src/lib.rs +19 -8
  36. package/crates/tish_compile/src/resolve.rs +278 -137
  37. package/crates/tish_compile/src/types.rs +184 -24
  38. package/crates/tish_compile_js/Cargo.toml +1 -0
  39. package/crates/tish_compile_js/src/codegen.rs +181 -37
  40. package/crates/tish_compile_js/src/lib.rs +3 -1
  41. package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
  42. package/crates/tish_compiler_wasm/src/lib.rs +16 -13
  43. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +69 -59
  44. package/crates/tish_core/Cargo.toml +8 -0
  45. package/crates/tish_core/src/json.rs +107 -56
  46. package/crates/tish_core/src/lib.rs +4 -2
  47. package/crates/tish_core/src/macros.rs +5 -5
  48. package/crates/tish_core/src/uri.rs +9 -6
  49. package/crates/tish_core/src/value.rs +145 -43
  50. package/crates/tish_core/src/vmref.rs +178 -0
  51. package/crates/tish_cranelift/src/link.rs +6 -9
  52. package/crates/tish_cranelift/src/lower.rs +14 -8
  53. package/crates/tish_eval/Cargo.toml +17 -2
  54. package/crates/tish_eval/src/eval.rs +474 -165
  55. package/crates/tish_eval/src/http.rs +61 -0
  56. package/crates/tish_eval/src/lib.rs +12 -8
  57. package/crates/tish_eval/src/natives.rs +136 -38
  58. package/crates/tish_eval/src/promise.rs +14 -8
  59. package/crates/tish_eval/src/timers.rs +28 -19
  60. package/crates/tish_eval/src/value.rs +17 -6
  61. package/crates/tish_eval/src/value_convert.rs +13 -5
  62. package/crates/tish_fmt/src/lib.rs +149 -43
  63. package/crates/tish_lexer/src/lib.rs +232 -63
  64. package/crates/tish_lexer/src/token.rs +10 -6
  65. package/crates/tish_llvm/src/lib.rs +17 -8
  66. package/crates/tish_lsp/Cargo.toml +4 -1
  67. package/crates/tish_lsp/README.md +1 -1
  68. package/crates/tish_lsp/src/builtin_goto.rs +261 -0
  69. package/crates/tish_lsp/src/import_goto.rs +549 -0
  70. package/crates/tish_lsp/src/main.rs +504 -106
  71. package/crates/tish_native/src/build.rs +4 -8
  72. package/crates/tish_native/src/lib.rs +54 -21
  73. package/crates/tish_opt/src/lib.rs +84 -52
  74. package/crates/tish_parser/src/lib.rs +45 -13
  75. package/crates/tish_parser/src/parser.rs +505 -130
  76. package/crates/tish_resolve/Cargo.toml +13 -0
  77. package/crates/tish_resolve/src/lib.rs +3436 -0
  78. package/crates/tish_resolve/src/pos.rs +133 -0
  79. package/crates/tish_runtime/Cargo.toml +68 -3
  80. package/crates/tish_runtime/src/http.rs +1136 -145
  81. package/crates/tish_runtime/src/http_fetch.rs +38 -27
  82. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  83. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  84. package/crates/tish_runtime/src/lib.rs +375 -189
  85. package/crates/tish_runtime/src/promise.rs +199 -40
  86. package/crates/tish_runtime/src/promise_io.rs +2 -1
  87. package/crates/tish_runtime/src/timers.rs +37 -1
  88. package/crates/tish_runtime/src/ws.rs +65 -42
  89. package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
  90. package/crates/tish_ui/src/jsx.rs +317 -27
  91. package/crates/tish_ui/src/lib.rs +5 -2
  92. package/crates/tish_ui/src/runtime/hooks.rs +406 -45
  93. package/crates/tish_ui/src/runtime/mod.rs +36 -9
  94. package/crates/tish_vm/Cargo.toml +15 -5
  95. package/crates/tish_vm/src/vm.rs +725 -281
  96. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +11 -4
  97. package/crates/tish_wasm/src/lib.rs +55 -42
  98. package/crates/tish_wasm_runtime/Cargo.toml +2 -1
  99. package/crates/tish_wasm_runtime/src/lib.rs +1 -1
  100. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  101. package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
  102. package/crates/tishlang_cargo_bindgen/src/discover.rs +120 -0
  103. package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
  104. package/crates/tishlang_cargo_bindgen/src/lib.rs +350 -0
  105. package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
  106. package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
  107. package/justfile +8 -0
  108. package/package.json +1 -1
  109. package/platform/darwin-arm64/tish +0 -0
  110. package/platform/darwin-x64/tish +0 -0
  111. package/platform/linux-arm64/tish +0 -0
  112. package/platform/linux-x64/tish +0 -0
  113. package/platform/win32-x64/tish.exe +0 -0
@@ -1,6 +1,7 @@
1
1
  //! HTTP server for the Tish interpreter. Client `fetch` uses `tishlang_runtime` from eval.
2
2
 
3
3
  use crate::value::{PropMap, Value};
4
+ use std::fs::File;
4
5
  use std::sync::Arc;
5
6
 
6
7
  use tokio::runtime::Runtime;
@@ -101,6 +102,66 @@ pub fn value_to_response(value: &Value) -> (u16, Vec<(String, String)>, String)
101
102
  (status, headers, body)
102
103
  }
103
104
 
105
+ /// If the response value has a `file` key, stream that path (binary-safe). Matches `tishlang_runtime` HTTP behavior.
106
+ pub(crate) fn extract_file_from_response(value: &Value) -> Option<(u16, Vec<(String, String)>, String)> {
107
+ let Value::Object(obj) = value else {
108
+ return None;
109
+ };
110
+ let obj_ref = obj.borrow();
111
+ let file_val = obj_ref.get(&Arc::from("file"))?;
112
+ let Value::String(file_path) = file_val else {
113
+ return None;
114
+ };
115
+ let file_path = file_path.to_string();
116
+ let status = obj_ref
117
+ .get(&Arc::from("status"))
118
+ .and_then(|v| match v {
119
+ Value::Number(n) => Some(*n as u16),
120
+ _ => None,
121
+ })
122
+ .unwrap_or(200);
123
+ let headers = obj_ref
124
+ .get(&Arc::from("headers"))
125
+ .and_then(|v| match v {
126
+ Value::Object(h) => Some(
127
+ h.borrow()
128
+ .iter()
129
+ .map(|(k, v)| (k.to_string(), v.to_string()))
130
+ .collect(),
131
+ ),
132
+ _ => None,
133
+ })
134
+ .unwrap_or_default();
135
+ Some((status, headers, file_path))
136
+ }
137
+
138
+ pub(crate) fn send_file_response(
139
+ request: tiny_http::Request,
140
+ status: u16,
141
+ headers: Vec<(String, String)>,
142
+ file_path: String,
143
+ ) {
144
+ let file = match File::open(&file_path) {
145
+ Ok(f) => f,
146
+ Err(e) => {
147
+ eprintln!("Failed to open file {}: {}", file_path, e);
148
+ let fallback =
149
+ tiny_http::Response::from_string(format!("File not found: {}", file_path))
150
+ .with_status_code(tiny_http::StatusCode(500));
151
+ let _ = request.respond(fallback);
152
+ return;
153
+ }
154
+ };
155
+ let status_code = tiny_http::StatusCode(status);
156
+ let mut response = tiny_http::Response::from_file(file).with_status_code(status_code);
157
+ for (key, value) in headers {
158
+ if let Ok(header) = tiny_http::Header::from_bytes(key.as_bytes(), value.as_bytes()) {
159
+ response = response.with_header(header);
160
+ }
161
+ }
162
+ let _ = request.respond(response);
163
+ }
164
+
104
165
  /// Send a response using tiny_http.
105
166
  pub fn send_response(
106
167
  request: tiny_http::Request,
@@ -3,15 +3,15 @@
3
3
  mod eval;
4
4
  #[cfg(feature = "http")]
5
5
  mod http;
6
- pub mod value_convert;
6
+ mod natives;
7
7
  #[cfg(feature = "http")]
8
8
  mod promise;
9
- #[cfg(feature = "http")]
10
- mod timers;
11
- mod natives;
12
9
  #[cfg(feature = "regex")]
13
10
  pub mod regex;
11
+ #[cfg(feature = "timers")]
12
+ mod timers;
14
13
  mod value;
14
+ pub mod value_convert;
15
15
 
16
16
  pub use eval::Evaluator;
17
17
  pub use value::PropMap;
@@ -36,7 +36,7 @@ pub fn run(source: &str) -> Result<Value, String> {
36
36
  let program = tishlang_parser::parse(source)?;
37
37
  let mut eval = Evaluator::new();
38
38
  let result = eval.eval_program(&program)?;
39
- #[cfg(feature = "http")]
39
+ #[cfg(feature = "timers")]
40
40
  eval.run_timer_phase()?;
41
41
  Ok(result)
42
42
  }
@@ -51,16 +51,20 @@ pub fn format_value_for_console(value: &Value, colors: bool) -> String {
51
51
  }
52
52
 
53
53
  /// Run a Tish file with import/export support. Resolves relative imports from the file's directory.
54
- pub fn run_file(path: &std::path::Path, project_root: Option<&std::path::Path>) -> Result<Value, String> {
54
+ pub fn run_file(
55
+ path: &std::path::Path,
56
+ project_root: Option<&std::path::Path>,
57
+ ) -> Result<Value, String> {
55
58
  let path = path
56
59
  .canonicalize()
57
60
  .map_err(|e| format!("Cannot canonicalize {}: {}", path.display(), e))?;
58
- let source = std::fs::read_to_string(&path).map_err(|e| format!("Cannot read {}: {}", path.display(), e))?;
61
+ let source = std::fs::read_to_string(&path)
62
+ .map_err(|e| format!("Cannot read {}: {}", path.display(), e))?;
59
63
  let program = tishlang_parser::parse(&source)?;
60
64
  let mut eval = Evaluator::new();
61
65
  eval.set_current_dir(project_root.or(path.parent()));
62
66
  let result = eval.eval_program(&program)?;
63
- #[cfg(feature = "http")]
67
+ #[cfg(feature = "timers")]
64
68
  eval.run_timer_phase()?;
65
69
  Ok(result)
66
70
  }
@@ -57,16 +57,21 @@ fn get_log_level() -> u8 {
57
57
  pub fn parse_int(args: &[Value]) -> Result<Value, String> {
58
58
  let s = args.first().map(|v| v.to_string()).unwrap_or_default();
59
59
  let s = s.trim();
60
- let radix = args.get(1).and_then(|v| match v {
61
- Value::Number(n) => Some(*n as i32),
62
- _ => None,
63
- }).unwrap_or(10);
60
+ let radix = args
61
+ .get(1)
62
+ .and_then(|v| match v {
63
+ Value::Number(n) => Some(*n as i32),
64
+ _ => None,
65
+ })
66
+ .unwrap_or(10);
64
67
  let n = if (2..=36).contains(&radix) {
65
68
  let prefix: String = s
66
69
  .chars()
67
70
  .take_while(|c| *c == '-' || *c == '+' || c.is_digit(radix as u32))
68
71
  .collect();
69
- i64::from_str_radix(&prefix, radix as u32).ok().map(|n| n as f64)
72
+ i64::from_str_radix(&prefix, radix as u32)
73
+ .ok()
74
+ .map(|n| n as f64)
70
75
  } else {
71
76
  None
72
77
  };
@@ -80,12 +85,16 @@ pub fn parse_float(args: &[Value]) -> Result<Value, String> {
80
85
  }
81
86
 
82
87
  pub fn is_finite(args: &[Value]) -> Result<Value, String> {
83
- let b = args.first().is_some_and(|v| matches!(v, Value::Number(n) if n.is_finite()));
88
+ let b = args
89
+ .first()
90
+ .is_some_and(|v| matches!(v, Value::Number(n) if n.is_finite()));
84
91
  Ok(Value::Bool(b))
85
92
  }
86
93
 
87
94
  pub fn is_nan(args: &[Value]) -> Result<Value, String> {
88
- let b = args.first().is_none_or(|v| matches!(v, Value::Number(n) if n.is_nan()) || !matches!(v, Value::Number(_)));
95
+ let b = args.first().is_none_or(|v| {
96
+ matches!(v, Value::Number(n) if n.is_nan()) || !matches!(v, Value::Number(_))
97
+ });
89
98
  Ok(Value::Bool(b))
90
99
  }
91
100
 
@@ -96,7 +105,9 @@ pub fn boolean_native(args: &[Value]) -> Result<Value, String> {
96
105
 
97
106
  pub fn decode_uri(args: &[Value]) -> Result<Value, String> {
98
107
  let s = args.first().map(|v| v.to_string()).unwrap_or_default();
99
- Ok(Value::String(tishlang_core::percent_decode(&s).unwrap_or(s).into()))
108
+ Ok(Value::String(
109
+ tishlang_core::percent_decode(&s).unwrap_or(s).into(),
110
+ ))
100
111
  }
101
112
 
102
113
  pub fn encode_uri(args: &[Value]) -> Result<Value, String> {
@@ -104,42 +115,103 @@ pub fn encode_uri(args: &[Value]) -> Result<Value, String> {
104
115
  Ok(Value::String(tishlang_core::percent_encode(&s).into()))
105
116
  }
106
117
 
118
+ pub fn html_escape(args: &[Value]) -> Result<Value, String> {
119
+ let input = match args.first() {
120
+ Some(Value::String(s)) => s.to_string(),
121
+ Some(v) => v.to_string(),
122
+ None => return Ok(Value::Null),
123
+ };
124
+ let bytes = input.as_bytes();
125
+ let mut extra = 0usize;
126
+ for b in bytes {
127
+ match b {
128
+ b'&' => extra += 4,
129
+ b'<' | b'>' => extra += 3,
130
+ b'"' => extra += 5,
131
+ b'\'' => extra += 4,
132
+ _ => {}
133
+ }
134
+ }
135
+ if extra == 0 {
136
+ return Ok(Value::String(input.into()));
137
+ }
138
+ let mut out = String::with_capacity(input.len() + extra);
139
+ let mut last = 0usize;
140
+ for (i, b) in bytes.iter().enumerate() {
141
+ let repl: Option<&'static str> = match b {
142
+ b'&' => Some("&amp;"),
143
+ b'<' => Some("&lt;"),
144
+ b'>' => Some("&gt;"),
145
+ b'"' => Some("&quot;"),
146
+ b'\'' => Some("&#39;"),
147
+ _ => None,
148
+ };
149
+ if let Some(r) = repl {
150
+ out.push_str(&input[last..i]);
151
+ out.push_str(r);
152
+ last = i + 1;
153
+ }
154
+ }
155
+ out.push_str(&input[last..]);
156
+ Ok(Value::String(out.into()))
157
+ }
158
+
107
159
  pub fn math_abs(args: &[Value]) -> Result<Value, String> {
108
- Ok(Value::Number(get_num(args.first().unwrap_or(&Value::Null)).abs()))
160
+ Ok(Value::Number(
161
+ get_num(args.first().unwrap_or(&Value::Null)).abs(),
162
+ ))
109
163
  }
110
164
 
111
165
  pub fn math_sqrt(args: &[Value]) -> Result<Value, String> {
112
- Ok(Value::Number(get_num(args.first().unwrap_or(&Value::Null)).sqrt()))
166
+ Ok(Value::Number(
167
+ get_num(args.first().unwrap_or(&Value::Null)).sqrt(),
168
+ ))
113
169
  }
114
170
 
115
171
  pub fn math_min(args: &[Value]) -> Result<Value, String> {
116
- let nums: Vec<f64> = args.iter().filter_map(|v| match v {
117
- Value::Number(n) => Some(*n),
118
- _ => None,
119
- }).collect();
172
+ let nums: Vec<f64> = args
173
+ .iter()
174
+ .filter_map(|v| match v {
175
+ Value::Number(n) => Some(*n),
176
+ _ => None,
177
+ })
178
+ .collect();
120
179
  let n = nums.into_iter().fold(f64::INFINITY, f64::min);
121
180
  Ok(Value::Number(if n == f64::INFINITY { f64::NAN } else { n }))
122
181
  }
123
182
 
124
183
  pub fn math_max(args: &[Value]) -> Result<Value, String> {
125
- let nums: Vec<f64> = args.iter().filter_map(|v| match v {
126
- Value::Number(n) => Some(*n),
127
- _ => None,
128
- }).collect();
184
+ let nums: Vec<f64> = args
185
+ .iter()
186
+ .filter_map(|v| match v {
187
+ Value::Number(n) => Some(*n),
188
+ _ => None,
189
+ })
190
+ .collect();
129
191
  let n = nums.into_iter().fold(f64::NEG_INFINITY, f64::max);
130
- Ok(Value::Number(if n == f64::NEG_INFINITY { f64::NAN } else { n }))
192
+ Ok(Value::Number(if n == f64::NEG_INFINITY {
193
+ f64::NAN
194
+ } else {
195
+ n
196
+ }))
131
197
  }
132
198
 
133
199
  pub fn math_floor(args: &[Value]) -> Result<Value, String> {
134
- Ok(Value::Number(get_num(args.first().unwrap_or(&Value::Null)).floor()))
200
+ Ok(Value::Number(
201
+ get_num(args.first().unwrap_or(&Value::Null)).floor(),
202
+ ))
135
203
  }
136
204
 
137
205
  pub fn math_ceil(args: &[Value]) -> Result<Value, String> {
138
- Ok(Value::Number(get_num(args.first().unwrap_or(&Value::Null)).ceil()))
206
+ Ok(Value::Number(
207
+ get_num(args.first().unwrap_or(&Value::Null)).ceil(),
208
+ ))
139
209
  }
140
210
 
141
211
  pub fn math_round(args: &[Value]) -> Result<Value, String> {
142
- Ok(Value::Number(get_num(args.first().unwrap_or(&Value::Null)).round()))
212
+ Ok(Value::Number(
213
+ get_num(args.first().unwrap_or(&Value::Null)).round(),
214
+ ))
143
215
  }
144
216
 
145
217
  pub fn math_random(_args: &[Value]) -> Result<Value, String> {
@@ -156,33 +228,53 @@ pub fn math_pow(args: &[Value]) -> Result<Value, String> {
156
228
  }
157
229
 
158
230
  pub fn math_sin(args: &[Value]) -> Result<Value, String> {
159
- Ok(Value::Number(get_num(args.first().unwrap_or(&Value::Null)).sin()))
231
+ Ok(Value::Number(
232
+ get_num(args.first().unwrap_or(&Value::Null)).sin(),
233
+ ))
160
234
  }
161
235
 
162
236
  pub fn math_cos(args: &[Value]) -> Result<Value, String> {
163
- Ok(Value::Number(get_num(args.first().unwrap_or(&Value::Null)).cos()))
237
+ Ok(Value::Number(
238
+ get_num(args.first().unwrap_or(&Value::Null)).cos(),
239
+ ))
164
240
  }
165
241
 
166
242
  pub fn math_tan(args: &[Value]) -> Result<Value, String> {
167
- Ok(Value::Number(get_num(args.first().unwrap_or(&Value::Null)).tan()))
243
+ Ok(Value::Number(
244
+ get_num(args.first().unwrap_or(&Value::Null)).tan(),
245
+ ))
168
246
  }
169
247
 
170
248
  pub fn math_log(args: &[Value]) -> Result<Value, String> {
171
- Ok(Value::Number(get_num(args.first().unwrap_or(&Value::Null)).ln()))
249
+ Ok(Value::Number(
250
+ get_num(args.first().unwrap_or(&Value::Null)).ln(),
251
+ ))
172
252
  }
173
253
 
174
254
  pub fn math_exp(args: &[Value]) -> Result<Value, String> {
175
- Ok(Value::Number(get_num(args.first().unwrap_or(&Value::Null)).exp()))
255
+ Ok(Value::Number(
256
+ get_num(args.first().unwrap_or(&Value::Null)).exp(),
257
+ ))
176
258
  }
177
259
 
178
260
  pub fn math_sign(args: &[Value]) -> Result<Value, String> {
179
261
  let n = get_num(args.first().unwrap_or(&Value::Null));
180
- let sign = if n.is_nan() { f64::NAN } else if n > 0.0 { 1.0 } else if n < 0.0 { -1.0 } else { 0.0 };
262
+ let sign = if n.is_nan() {
263
+ f64::NAN
264
+ } else if n > 0.0 {
265
+ 1.0
266
+ } else if n < 0.0 {
267
+ -1.0
268
+ } else {
269
+ 0.0
270
+ };
181
271
  Ok(Value::Number(sign))
182
272
  }
183
273
 
184
274
  pub fn math_trunc(args: &[Value]) -> Result<Value, String> {
185
- Ok(Value::Number(get_num(args.first().unwrap_or(&Value::Null)).trunc()))
275
+ Ok(Value::Number(
276
+ get_num(args.first().unwrap_or(&Value::Null)).trunc(),
277
+ ))
186
278
  }
187
279
 
188
280
  pub fn date_now(_args: &[Value]) -> Result<Value, String> {
@@ -199,19 +291,25 @@ pub fn array_is_array(args: &[Value]) -> Result<Value, String> {
199
291
  }
200
292
 
201
293
  pub fn string_from_char_code(args: &[Value]) -> Result<Value, String> {
202
- let s: String = args.iter().filter_map(|v| match v {
203
- Value::Number(n) => Some(char::from_u32(*n as u32).unwrap_or('\u{FFFD}')),
204
- _ => None,
205
- }).collect();
294
+ let s: String = args
295
+ .iter()
296
+ .filter_map(|v| match v {
297
+ Value::Number(n) => Some(char::from_u32(*n as u32).unwrap_or('\u{FFFD}')),
298
+ _ => None,
299
+ })
300
+ .collect();
206
301
  Ok(Value::String(s.into()))
207
302
  }
208
303
 
209
304
  #[cfg(feature = "process")]
210
305
  pub fn process_exit(args: &[Value]) -> Result<Value, String> {
211
- let code = args.first().and_then(|v| match v {
212
- Value::Number(n) => Some(*n as i32),
213
- _ => None,
214
- }).unwrap_or(0);
306
+ let code = args
307
+ .first()
308
+ .and_then(|v| match v {
309
+ Value::Number(n) => Some(*n as i32),
310
+ _ => None,
311
+ })
312
+ .unwrap_or(0);
215
313
  std::process::exit(code);
216
314
  }
217
315
 
@@ -274,7 +372,7 @@ pub fn is_dir(args: &[Value]) -> Result<Value, String> {
274
372
  pub fn read_dir(args: &[Value]) -> Result<Value, String> {
275
373
  use std::cell::RefCell;
276
374
  use std::rc::Rc;
277
-
375
+
278
376
  let path = args.first().map(|v| v.to_string()).unwrap_or_default();
279
377
  match std::fs::read_dir(&path) {
280
378
  Ok(entries) => {
@@ -116,7 +116,11 @@ pub fn settle_promise(
116
116
  }
117
117
  };
118
118
  if let Some(tx) = tx {
119
- let result = if is_resolve { Ok(value.clone()) } else { Err(value.clone()) };
119
+ let result = if is_resolve {
120
+ Ok(value.clone())
121
+ } else {
122
+ Err(value.clone())
123
+ };
120
124
  let _ = tx.send(result);
121
125
  }
122
126
  Ok((value, is_resolve, reactions))
@@ -134,7 +138,9 @@ pub fn add_reaction(state: &PromiseStateRef, reaction: Reaction) {
134
138
  impl Clone for Reaction {
135
139
  fn clone(&self) -> Self {
136
140
  match self {
137
- Reaction::Then(a, b, r1, r2) => Reaction::Then(a.clone(), b.clone(), r1.clone(), r2.clone()),
141
+ Reaction::Then(a, b, r1, r2) => {
142
+ Reaction::Then(a.clone(), b.clone(), r1.clone(), r2.clone())
143
+ }
138
144
  Reaction::Finally(f, r1, r2) => Reaction::Finally(f.clone(), r1.clone(), r2.clone()),
139
145
  }
140
146
  }
@@ -156,18 +162,18 @@ pub fn block_until_settled(promise_ref: &PromiseRef) -> PromiseAwaitResult {
156
162
  match result {
157
163
  Ok(Ok(v)) => PromiseAwaitResult::Fulfilled(v),
158
164
  Ok(Err(v)) => PromiseAwaitResult::Rejected(v),
159
- Err(_) => PromiseAwaitResult::Error(
160
- "Promise channel dropped before settlement".to_string(),
161
- ),
165
+ Err(_) => {
166
+ PromiseAwaitResult::Error("Promise channel dropped before settlement".to_string())
167
+ }
162
168
  }
163
169
  } else {
164
170
  let state = promise_ref.state.borrow();
165
171
  match &*state {
166
172
  PromiseState::Fulfilled(v) => PromiseAwaitResult::Fulfilled(v.clone()),
167
173
  PromiseState::Rejected(v) => PromiseAwaitResult::Rejected(v.clone()),
168
- PromiseState::Pending { .. } => PromiseAwaitResult::Error(
169
- "Promise receiver already consumed".to_string(),
170
- ),
174
+ PromiseState::Pending { .. } => {
175
+ PromiseAwaitResult::Error("Promise receiver already consumed".to_string())
176
+ }
171
177
  }
172
178
  }
173
179
  }
@@ -4,8 +4,8 @@
4
4
 
5
5
  use std::cell::RefCell;
6
6
  use std::collections::HashMap;
7
- use std::time::{Duration, Instant};
8
7
  use std::sync::atomic::{AtomicU64, Ordering};
8
+ use std::time::{Duration, Instant};
9
9
 
10
10
  use crate::value::Value;
11
11
 
@@ -32,12 +32,15 @@ pub fn setTimeout(callback: Value, args: Vec<Value>, delay_ms: u64) -> u64 {
32
32
  let id = next_id();
33
33
  let due = Instant::now() + Duration::from_millis(delay_ms);
34
34
  REGISTRY.with(|r| {
35
- r.borrow_mut().insert(id, TimerEntry {
36
- due,
37
- callback,
38
- args,
39
- interval_ms: 0,
40
- });
35
+ r.borrow_mut().insert(
36
+ id,
37
+ TimerEntry {
38
+ due,
39
+ callback,
40
+ args,
41
+ interval_ms: 0,
42
+ },
43
+ );
41
44
  });
42
45
  id
43
46
  }
@@ -48,12 +51,15 @@ pub fn setInterval(callback: Value, args: Vec<Value>, delay_ms: u64) -> u64 {
48
51
  let id = next_id();
49
52
  let due = Instant::now() + Duration::from_millis(delay_ms);
50
53
  REGISTRY.with(|r| {
51
- r.borrow_mut().insert(id, TimerEntry {
52
- due,
53
- callback,
54
- args,
55
- interval_ms: delay_ms,
56
- });
54
+ r.borrow_mut().insert(
55
+ id,
56
+ TimerEntry {
57
+ due,
58
+ callback,
59
+ args,
60
+ interval_ms: delay_ms,
61
+ },
62
+ );
57
63
  });
58
64
  id
59
65
  }
@@ -88,12 +94,15 @@ pub fn take_due_timers() -> Vec<(u64, Value, Vec<Value>, u64)> {
88
94
  pub fn re_register_interval(id: u64, callback: Value, args: Vec<Value>, interval_ms: u64) {
89
95
  let due = Instant::now() + Duration::from_millis(interval_ms);
90
96
  REGISTRY.with(|r| {
91
- r.borrow_mut().insert(id, TimerEntry {
92
- due,
93
- callback,
94
- args,
95
- interval_ms,
96
- });
97
+ r.borrow_mut().insert(
98
+ id,
99
+ TimerEntry {
100
+ due,
101
+ callback,
102
+ args,
103
+ interval_ms,
104
+ },
105
+ );
97
106
  });
98
107
  }
99
108
 
@@ -15,9 +15,9 @@ use tishlang_core::NativeFn as CoreNativeFn;
15
15
 
16
16
  /// Property map for interpreter `Value::Object` (uses `eval::Value`, not `tishlang_core::Value`).
17
17
  pub type PropMap = AHashMap<Arc<str>, Value>;
18
+ use tishlang_core::TishOpaque;
18
19
  #[cfg(feature = "http")]
19
20
  use tishlang_core::TishPromise;
20
- use tishlang_core::TishOpaque;
21
21
 
22
22
  #[cfg(feature = "http")]
23
23
  pub use crate::promise::PromiseResolver;
@@ -61,7 +61,7 @@ pub enum Value {
61
61
  #[cfg(feature = "http")]
62
62
  BoundPromiseMethod(crate::promise::PromiseRef, std::sync::Arc<str>),
63
63
  /// Timer builtins: setTimeout, setInterval. Need evaluator for callback.
64
- #[cfg(feature = "http")]
64
+ #[cfg(feature = "timers")]
65
65
  TimerBuiltin(std::sync::Arc<str>),
66
66
  /// Native `tishlang_core` Promise (fetch / reader.read / response.text).
67
67
  #[cfg(feature = "http")]
@@ -88,7 +88,12 @@ impl std::fmt::Debug for Value {
88
88
  #[cfg(feature = "http")]
89
89
  Value::Serve => write!(f, "Serve"),
90
90
  #[cfg(feature = "regex")]
91
- Value::RegExp(re) => write!(f, "RegExp(/{}/{})", re.borrow().source, re.borrow().flags_string()),
91
+ Value::RegExp(re) => write!(
92
+ f,
93
+ "RegExp(/{}/{})",
94
+ re.borrow().source,
95
+ re.borrow().flags_string()
96
+ ),
92
97
  #[cfg(feature = "http")]
93
98
  Value::Promise(_) => write!(f, "Promise"),
94
99
  #[cfg(feature = "http")]
@@ -96,7 +101,9 @@ impl std::fmt::Debug for Value {
96
101
  #[cfg(feature = "http")]
97
102
  Value::PromiseConstructor => write!(f, "[Function: Promise]"),
98
103
  #[cfg(feature = "http")]
99
- Value::BoundPromiseMethod(_, _) | Value::TimerBuiltin(_) => write!(f, "[Function]"),
104
+ Value::BoundPromiseMethod(_, _) => write!(f, "[Function]"),
105
+ #[cfg(feature = "timers")]
106
+ Value::TimerBuiltin(_) => write!(f, "[Function]"),
100
107
  #[cfg(feature = "http")]
101
108
  Value::CorePromise(_) => write!(f, "Promise"),
102
109
  Value::CoreFn(_) => write!(f, "CoreFn"),
@@ -151,7 +158,9 @@ impl std::fmt::Display for Value {
151
158
  #[cfg(feature = "http")]
152
159
  Value::PromiseConstructor => write!(f, "function Promise() {{ [native code] }}"),
153
160
  #[cfg(feature = "http")]
154
- Value::BoundPromiseMethod(_, _) | Value::TimerBuiltin(_) => write!(f, "[Function]"),
161
+ Value::BoundPromiseMethod(_, _) => write!(f, "[Function]"),
162
+ #[cfg(feature = "timers")]
163
+ Value::TimerBuiltin(_) => write!(f, "[Function]"),
155
164
  #[cfg(feature = "http")]
156
165
  Value::CorePromise(_) => write!(f, "[Promise]"),
157
166
  Value::CoreFn(_) => write!(f, "[Function]"),
@@ -187,7 +196,9 @@ impl Value {
187
196
  (Value::Array(a), Value::Array(b)) => Rc::ptr_eq(a, b),
188
197
  (Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b),
189
198
  (Value::Opaque(a), Value::Opaque(b)) => Arc::ptr_eq(a, b),
190
- (Value::OpaqueMethod(a, ak), Value::OpaqueMethod(b, bk)) => Arc::ptr_eq(a, b) && ak == bk,
199
+ (Value::OpaqueMethod(a, ak), Value::OpaqueMethod(b, bk)) => {
200
+ Arc::ptr_eq(a, b) && ak == bk
201
+ }
191
202
  _ => false,
192
203
  }
193
204
  }
@@ -4,7 +4,9 @@ use std::cell::RefCell;
4
4
  use std::rc::Rc;
5
5
  use std::sync::Arc;
6
6
 
7
- use tishlang_core::{ObjectMap, Value as CoreValue};
7
+ use tishlang_core::{ObjectMap, Value as CoreValue, VmRef};
8
+ #[cfg(feature = "regex")]
9
+ use tishlang_core::TishRegExp;
8
10
 
9
11
  use crate::value::{PropMap, Value};
10
12
 
@@ -20,14 +22,14 @@ pub fn eval_to_core(v: &Value) -> Result<CoreValue, String> {
20
22
  for item in arr.borrow().iter() {
21
23
  out.push(eval_to_core(item)?);
22
24
  }
23
- Ok(CoreValue::Array(Rc::new(RefCell::new(out))))
25
+ Ok(CoreValue::Array(VmRef::new(out)))
24
26
  }
25
27
  Value::Object(map) => {
26
28
  let mut out = ObjectMap::default();
27
29
  for (k, v) in map.borrow().iter() {
28
30
  out.insert(Arc::clone(k), eval_to_core(v)?);
29
31
  }
30
- Ok(CoreValue::Object(Rc::new(RefCell::new(out))))
32
+ Ok(CoreValue::Object(VmRef::new(out)))
31
33
  }
32
34
  Value::Opaque(o) => Ok(CoreValue::Opaque(Arc::clone(o))),
33
35
  _ => Err(format!(
@@ -63,13 +65,19 @@ pub fn core_to_eval(v: CoreValue) -> Value {
63
65
  CoreValue::Promise(p) => Value::CorePromise(Arc::clone(&p)),
64
66
  #[cfg(not(feature = "http"))]
65
67
  CoreValue::Promise(_) => Value::Null,
66
- CoreValue::Function(f) => Value::CoreFn(Rc::clone(&f)),
68
+ // `CoreNativeFn` is feature-gated (Rc vs Arc), so use Clone::clone
69
+ // which works for either.
70
+ CoreValue::Function(f) => Value::CoreFn(f.clone()),
67
71
  // tishlang_core gets regex from http or regex features; handle RegExp when it exists
68
72
  #[cfg(any(feature = "http", feature = "regex"))]
69
73
  CoreValue::RegExp(re) => {
70
74
  #[cfg(feature = "regex")]
71
75
  {
72
- Value::RegExp(re)
76
+ // Core uses `VmRef<TishRegExp>` (potentially `Arc<Mutex>`),
77
+ // interpreter uses `Rc<RefCell<TishRegExp>>`. Clone the
78
+ // inner state across so the two storage shapes can coexist.
79
+ let inner: TishRegExp = re.borrow().clone();
80
+ Value::RegExp(Rc::new(RefCell::new(inner)))
73
81
  }
74
82
  #[cfg(not(feature = "regex"))]
75
83
  {