@tishlang/tish 1.9.2 → 1.12.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 (84) hide show
  1. package/bin/tish +0 -0
  2. package/crates/js_to_tish/src/transform/expr.rs +8 -6
  3. package/crates/js_to_tish/src/transform/stmt.rs +12 -13
  4. package/crates/tish/Cargo.toml +1 -1
  5. package/crates/tish/src/cargo_native_registry.rs +4 -1
  6. package/crates/tish/src/cli_help.rs +9 -1
  7. package/crates/tish/src/main.rs +66 -11
  8. package/crates/tish/tests/integration_test.rs +145 -7
  9. package/crates/tish_ast/src/ast.rs +3 -9
  10. package/crates/tish_build_utils/src/lib.rs +74 -23
  11. package/crates/tish_builtins/src/array.rs +2 -3
  12. package/crates/tish_builtins/src/construct.rs +15 -28
  13. package/crates/tish_builtins/src/globals.rs +18 -16
  14. package/crates/tish_builtins/src/helpers.rs +1 -4
  15. package/crates/tish_builtins/src/lib.rs +1 -0
  16. package/crates/tish_builtins/src/math.rs +7 -0
  17. package/crates/tish_builtins/src/object.rs +10 -10
  18. package/crates/tish_builtins/src/string.rs +27 -3
  19. package/crates/tish_builtins/src/symbol.rs +83 -0
  20. package/crates/tish_compile/src/codegen.rs +324 -158
  21. package/crates/tish_compile/src/lib.rs +39 -7
  22. package/crates/tish_compile/src/resolve.rs +191 -6
  23. package/crates/tish_compile/src/types.rs +6 -6
  24. package/crates/tish_compile_js/src/codegen.rs +8 -5
  25. package/crates/tish_core/src/console_style.rs +9 -0
  26. package/crates/tish_core/src/json.rs +17 -7
  27. package/crates/tish_core/src/macros.rs +2 -2
  28. package/crates/tish_core/src/value.rs +213 -4
  29. package/crates/tish_cranelift/src/link.rs +1 -1
  30. package/crates/tish_cranelift_runtime/Cargo.toml +4 -0
  31. package/crates/tish_eval/src/eval.rs +135 -73
  32. package/crates/tish_eval/src/http.rs +18 -12
  33. package/crates/tish_eval/src/lib.rs +29 -0
  34. package/crates/tish_eval/src/regex.rs +1 -1
  35. package/crates/tish_eval/src/value.rs +89 -4
  36. package/crates/tish_eval/src/value_convert.rs +30 -8
  37. package/crates/tish_fmt/src/lib.rs +4 -1
  38. package/crates/tish_lexer/src/lib.rs +7 -2
  39. package/crates/tish_llvm/src/lib.rs +2 -2
  40. package/crates/tish_lsp/src/builtin_goto.rs +111 -10
  41. package/crates/tish_lsp/src/import_goto.rs +35 -22
  42. package/crates/tish_lsp/src/main.rs +118 -85
  43. package/crates/tish_native/src/build.rs +270 -24
  44. package/crates/tish_native/src/config.rs +48 -0
  45. package/crates/tish_native/src/lib.rs +139 -12
  46. package/crates/tish_parser/src/lib.rs +5 -2
  47. package/crates/tish_parser/src/parser.rs +45 -75
  48. package/crates/tish_pg/src/error.rs +1 -1
  49. package/crates/tish_pg/src/lib.rs +61 -73
  50. package/crates/tish_resolve/src/lib.rs +283 -158
  51. package/crates/tish_resolve/src/pos.rs +10 -2
  52. package/crates/tish_runtime/Cargo.toml +3 -0
  53. package/crates/tish_runtime/src/http.rs +39 -39
  54. package/crates/tish_runtime/src/http_fetch.rs +12 -12
  55. package/crates/tish_runtime/src/lib.rs +35 -44
  56. package/crates/tish_runtime/src/native_promise.rs +0 -11
  57. package/crates/tish_runtime/src/promise.rs +14 -1
  58. package/crates/tish_runtime/src/promise_io.rs +1 -4
  59. package/crates/tish_runtime/src/timers.rs +12 -7
  60. package/crates/tish_runtime/src/ws.rs +40 -27
  61. package/crates/tish_runtime/tests/fetch_readable_stream.rs +10 -8
  62. package/crates/tish_ui/src/jsx.rs +6 -4
  63. package/crates/tish_ui/src/lib.rs +5 -4
  64. package/crates/tish_ui/src/runtime/hooks.rs +123 -37
  65. package/crates/tish_ui/src/runtime/mod.rs +21 -41
  66. package/crates/tish_vm/Cargo.toml +2 -0
  67. package/crates/tish_vm/src/vm.rs +258 -153
  68. package/crates/tish_wasm/src/lib.rs +60 -7
  69. package/crates/tish_wasm_runtime/Cargo.toml +10 -1
  70. package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
  71. package/crates/tish_wasm_runtime/src/lib.rs +7 -1
  72. package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
  73. package/crates/tishlang_cargo_bindgen/src/discover.rs +10 -5
  74. package/crates/tishlang_cargo_bindgen/src/infer.rs +18 -8
  75. package/crates/tishlang_cargo_bindgen/src/lib.rs +25 -26
  76. package/crates/tishlang_cargo_bindgen/src/main.rs +41 -38
  77. package/crates/tishlang_cargo_bindgen/src/metadata.rs +4 -1
  78. package/justfile +3 -3
  79. package/package.json +1 -1
  80. package/platform/darwin-arm64/tish +0 -0
  81. package/platform/darwin-x64/tish +0 -0
  82. package/platform/linux-arm64/tish +0 -0
  83. package/platform/linux-x64/tish +0 -0
  84. package/platform/win32-x64/tish.exe +0 -0
@@ -104,7 +104,12 @@ pub fn byte_offset_to_lsp(source: &str, byte: usize) -> Option<(u32, u32)> {
104
104
  }
105
105
 
106
106
  /// True if LSP position lies inside `span` (half-open in byte space).
107
- pub fn span_contains_lsp_position(source: &str, span: &Span, lsp_line: u32, lsp_character: u32) -> bool {
107
+ pub fn span_contains_lsp_position(
108
+ source: &str,
109
+ span: &Span,
110
+ lsp_line: u32,
111
+ lsp_character: u32,
112
+ ) -> bool {
108
113
  let Some(b) = lsp_position_to_byte_offset(source, lsp_line, lsp_character) else {
109
114
  return false;
110
115
  };
@@ -117,7 +122,10 @@ pub fn span_contains_lsp_position(source: &str, span: &Span, lsp_line: u32, lsp_
117
122
  /// LSP start (inclusive) and end (exclusive) positions for a lexer span.
118
123
  pub fn span_to_lsp_range_exclusive(source: &str, span: &Span) -> Option<((u32, u32), (u32, u32))> {
119
124
  let (lo, hi) = lex_span_byte_range(source, span)?;
120
- Some((byte_offset_to_lsp(source, lo)?, byte_offset_to_lsp(source, hi)?))
125
+ Some((
126
+ byte_offset_to_lsp(source, lo)?,
127
+ byte_offset_to_lsp(source, hi)?,
128
+ ))
121
129
  }
122
130
 
123
131
  #[cfg(test)]
@@ -31,6 +31,9 @@ send-values = [
31
31
  "tishlang_core/send-values",
32
32
  "tishlang_builtins/send-values",
33
33
  ]
34
+ # Promise / `await` on settled values only (no `fetch` / HTTP stack). Used for WASI and other
35
+ # targets where the full `http` feature does not compile.
36
+ promise = ["send-values"]
34
37
  # Phase-3 item 13: io_uring-backed accept + send loop (Linux only).
35
38
  # Disabled by default; enable with `--feature http,http-io-uring` on Linux
36
39
  # to pick up the experimental accept path.
@@ -10,8 +10,8 @@
10
10
  //! gets its own tiny_http connection pool but the accept queue is shared at
11
11
  //! the kernel level.
12
12
  //!
13
- //! The Tish handler closure captures `Value::Function(Rc<…>)` which is
14
- //! `!Send`, so handler execution stays on the VM (caller) thread:
13
+ //! Without `send-values`, the handler captures `Value::Function` backed by
14
+ //! `Rc` and is `!Send`, so handler execution stays on a single VM thread:
15
15
  //!
16
16
  //! ```text
17
17
  //! worker 0 ──┐
@@ -24,14 +24,15 @@
24
24
  //! threaded VM guarantee. Cached `Date:` header + shared `Arc<str>` response
25
25
  //! bodies round out the hot-path optimisations.
26
26
 
27
- use std::cell::RefCell;
28
- use tishlang_core::VmRef;
29
- use std::collections::VecDeque;
30
27
  use std::fs::File;
31
28
  use std::io::Write;
32
- use std::rc::Rc;
33
29
  use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
34
- use std::sync::{mpsc, Arc, Mutex, OnceLock};
30
+ use std::sync::{Arc, OnceLock};
31
+
32
+ #[cfg(not(feature = "send-values"))]
33
+ use std::collections::VecDeque;
34
+ #[cfg(not(feature = "send-values"))]
35
+ use std::sync::mpsc;
35
36
  use std::thread;
36
37
  use std::time::{Duration, SystemTime, UNIX_EPOCH};
37
38
 
@@ -64,17 +65,17 @@ where
64
65
  }
65
66
 
66
67
  pub fn await_fetch(args: Vec<Value>) -> Value {
67
- crate::native_promise::await_promise(crate::native_promise::fetch_promise(args))
68
+ crate::promise::await_promise(crate::native_promise::fetch_promise(args))
68
69
  }
69
70
 
70
71
  pub fn await_fetch_all(args: Vec<Value>) -> Value {
71
- crate::native_promise::await_promise(crate::native_promise::fetch_all_promise(args))
72
+ crate::promise::await_promise(crate::native_promise::fetch_all_promise(args))
72
73
  }
73
74
 
74
75
  pub(crate) fn extract_method(options: Option<&Value>) -> String {
75
76
  options
76
77
  .and_then(|v| match v {
77
- Value::Object(obj) => obj.borrow().get(&Arc::from("method")).cloned(),
78
+ Value::Object(obj) => obj.borrow().strings.get("method").cloned(),
78
79
  _ => None,
79
80
  })
80
81
  .map(|v| v.to_display_string().to_uppercase())
@@ -84,12 +85,13 @@ pub(crate) fn extract_method(options: Option<&Value>) -> String {
84
85
  pub(crate) fn extract_headers(options: Option<&Value>) -> Vec<(String, String)> {
85
86
  options
86
87
  .and_then(|v| match v {
87
- Value::Object(obj) => obj.borrow().get(&Arc::from("headers")).cloned(),
88
+ Value::Object(obj) => obj.borrow().strings.get("headers").cloned(),
88
89
  _ => None,
89
90
  })
90
91
  .map(|v| match v {
91
92
  Value::Object(obj) => obj
92
93
  .borrow()
94
+ .strings
93
95
  .iter()
94
96
  .map(|(k, v)| (k.to_string(), v.to_display_string()))
95
97
  .collect(),
@@ -102,7 +104,8 @@ pub(crate) fn extract_body(options: Option<&Value>) -> Option<String> {
102
104
  options.and_then(|v| match v {
103
105
  Value::Object(obj) => obj
104
106
  .borrow()
105
- .get(&Arc::from("body"))
107
+ .strings
108
+ .get("body")
106
109
  .map(|v| v.to_display_string()),
107
110
  _ => None,
108
111
  })
@@ -112,7 +115,7 @@ pub(crate) fn build_error_response(error: &str) -> Value {
112
115
  let mut obj: ObjectMap = ObjectMap::with_capacity(2);
113
116
  obj.insert(Arc::from("error"), Value::String(error.into()));
114
117
  obj.insert(Arc::from("ok"), Value::Bool(false));
115
- Value::Object(VmRef::new(obj))
118
+ Value::object(obj)
116
119
  }
117
120
 
118
121
  // -------- cached Date header -----------------------------------------------
@@ -194,12 +197,6 @@ pub fn cached_date_header_arc() -> Arc<String> {
194
197
  ensure_date_thread().load_full()
195
198
  }
196
199
 
197
- /// Back-compat String flavour (allocates). New hot-path code should use
198
- /// `cached_date_header_arc`.
199
- pub fn cached_date_header() -> String {
200
- cached_date_header_arc().as_str().to_string()
201
- }
202
-
203
200
  // -------- Send-safe request/response primitives ----------------------------
204
201
 
205
202
  #[derive(Debug, Clone)]
@@ -270,10 +267,10 @@ impl RequestPrimitive {
270
267
  }
271
268
  obj.insert(
272
269
  Arc::clone(&keys.headers),
273
- Value::Object(VmRef::new(h)),
270
+ Value::object(h),
274
271
  );
275
272
  obj.insert(Arc::clone(&keys.body), Value::String(self.body.into()));
276
- Value::Object(VmRef::new(obj))
273
+ Value::object(obj)
277
274
  })
278
275
  }
279
276
  }
@@ -331,16 +328,17 @@ impl ResponsePrimitive {
331
328
  let obj_ref = obj.borrow();
332
329
 
333
330
  let status = obj_ref
334
- .get(&Arc::from("status"))
331
+ .strings
332
+ .get("status")
335
333
  .and_then(|v| match v {
336
334
  Value::Number(n) => Some(*n as u16),
337
335
  _ => None,
338
336
  })
339
337
  .unwrap_or(default_status);
340
338
 
341
- let has_error = obj_ref.contains_key(&Arc::from("error"));
339
+ let has_error = obj_ref.strings.contains_key("error");
342
340
 
343
- let body: ResponseBody = if let Some(bb) = obj_ref.get(&Arc::from("bodyBytes")) {
341
+ let body: ResponseBody = if let Some(bb) = obj_ref.strings.get("bodyBytes") {
344
342
  match bb {
345
343
  Value::Array(a) => {
346
344
  let v: Vec<u8> = a
@@ -355,9 +353,9 @@ impl ResponsePrimitive {
355
353
  }
356
354
  _ => ResponseBody::Text(Arc::from(bb.to_display_string())),
357
355
  }
358
- } else if let Some(b) = obj_ref.get(&Arc::from("body")) {
356
+ } else if let Some(b) = obj_ref.strings.get("body") {
359
357
  match b {
360
- Value::String(s) => ResponseBody::Text(Arc::clone(s)),
358
+ Value::String(s) => ResponseBody::Text(Arc::clone(&s)),
361
359
  Value::Array(a) => {
362
360
  let borrow = a.borrow();
363
361
  if !borrow.is_empty()
@@ -381,7 +379,8 @@ impl ResponsePrimitive {
381
379
  } else if has_error {
382
380
  ResponseBody::Text(Arc::from(
383
381
  obj_ref
384
- .get(&Arc::from("error"))
382
+ .strings
383
+ .get("error")
385
384
  .map(|v| v.to_display_string())
386
385
  .unwrap_or_default(),
387
386
  ))
@@ -396,10 +395,12 @@ impl ResponsePrimitive {
396
395
  };
397
396
 
398
397
  let headers = obj_ref
399
- .get(&Arc::from("headers"))
398
+ .strings
399
+ .get("headers")
400
400
  .and_then(|v| match v {
401
401
  Value::Object(h) => Some(
402
402
  h.borrow()
403
+ .strings
403
404
  .iter()
404
405
  .map(|(k, v)| (k.to_string(), v.to_display_string()))
405
406
  .collect(),
@@ -451,22 +452,25 @@ fn extract_file_from_response(value: &Value) -> Option<(u16, Vec<(String, String
451
452
  return None;
452
453
  };
453
454
  let obj_ref = obj.borrow();
454
- let Value::String(file_path) = obj_ref.get(&Arc::from("file"))? else {
455
+ let Value::String(file_path) = obj_ref.strings.get("file")? else {
455
456
  return None;
456
457
  };
457
458
  let file_path = file_path.to_string();
458
459
  let status = obj_ref
459
- .get(&Arc::from("status"))
460
+ .strings
461
+ .get("status")
460
462
  .and_then(|v| match v {
461
463
  Value::Number(n) => Some(*n as u16),
462
464
  _ => None,
463
465
  })
464
466
  .unwrap_or(200);
465
467
  let headers = obj_ref
466
- .get(&Arc::from("headers"))
468
+ .strings
469
+ .get("headers")
467
470
  .and_then(|v| match v {
468
471
  Value::Object(h) => Some(
469
472
  h.borrow()
473
+ .strings
470
474
  .iter()
471
475
  .map(|(k, v)| (k.to_string(), v.to_display_string()))
472
476
  .collect(),
@@ -770,8 +774,6 @@ pub(crate) fn num_prefork_workers() -> usize {
770
774
 
771
775
  // -------- serve() ----------------------------------------------------------
772
776
 
773
- type Job = (RequestPrimitive, mpsc::SyncSender<ResponsePrimitive>);
774
-
775
777
  /// Start an HTTP server that handles requests using the provided handler function.
776
778
  ///
777
779
  /// When `send-values` is enabled (required for the `http` feature) the
@@ -995,6 +997,9 @@ fn serve_impl_with_factory(
995
997
  Value::Null
996
998
  }
997
999
 
1000
+ #[cfg(not(feature = "send-values"))]
1001
+ type Job = (RequestPrimitive, mpsc::SyncSender<ResponsePrimitive>);
1002
+
998
1003
  #[cfg(not(feature = "send-values"))]
999
1004
  fn serve_impl<F>(args: &[Value], handler: F) -> Value
1000
1005
  where
@@ -1130,6 +1135,7 @@ fn worker_loop_direct(
1130
1135
  }
1131
1136
  }
1132
1137
 
1138
+ #[cfg(not(feature = "send-values"))]
1133
1139
  fn worker_loop(
1134
1140
  listener: std::net::TcpListener,
1135
1141
  dispatch: mpsc::SyncSender<Job>,
@@ -1202,12 +1208,6 @@ fn worker_loop(
1202
1208
  }
1203
1209
  }
1204
1210
 
1205
- #[allow(dead_code)]
1206
- pub fn create_server(port: u16) -> Result<tiny_http::Server, String> {
1207
- let addr = format!("0.0.0.0:{}", port);
1208
- tiny_http::Server::http(&addr).map_err(|e| format!("Failed to start server: {}", e))
1209
- }
1210
-
1211
1211
  // -------- public shims for alternate HTTP backends ------------------------
1212
1212
  //
1213
1213
  // Exposed so `http_hyper.rs` (and any future backend) can reuse the same
@@ -1,11 +1,10 @@
1
1
  //! Web Fetch–aligned Response, ReadableStream, reader.read(), text()/json().
2
2
 
3
- use std::cell::RefCell;
4
- use tishlang_core::VmRef;
5
3
  use std::pin::Pin;
6
- use std::rc::Rc;
7
4
  use std::sync::{Arc, Mutex};
8
5
 
6
+ use tishlang_core::VmRef;
7
+
9
8
  use bytes::Bytes;
10
9
  use futures::Stream;
11
10
  use futures::StreamExt;
@@ -87,19 +86,19 @@ impl TishPromise for ReadChunkPromise {
87
86
  let mut o = ObjectMap::default();
88
87
  o.insert(Arc::from("done"), Value::Bool(true));
89
88
  o.insert(Arc::from("value"), Value::Null);
90
- Ok(Value::Object(VmRef::new(o)))
89
+ Ok(Value::object(o))
91
90
  }
92
91
  Ok(Ok(ReadChunk::Bytes(b))) => {
93
92
  let arr: Vec<Value> = b.iter().map(|u| Value::Number(*u as f64)).collect();
94
93
  let mut o = ObjectMap::default();
95
94
  o.insert(Arc::from("done"), Value::Bool(false));
96
95
  o.insert(Arc::from("value"), Value::Array(VmRef::new(arr)));
97
- Ok(Value::Object(VmRef::new(o)))
96
+ Ok(Value::object(o))
98
97
  }
99
98
  Ok(Err(e)) => Err({
100
99
  let mut obj = ObjectMap::default();
101
100
  obj.insert(Arc::from("error"), Value::String(e.into()));
102
- Value::Object(VmRef::new(obj))
101
+ Value::object(obj)
103
102
  }),
104
103
  Err(_) => Err(Value::String("Promise dropped".into())),
105
104
  }
@@ -124,13 +123,13 @@ impl TishPromise for JsonTextPromise {
124
123
  Err(e) => Err({
125
124
  let mut obj = ObjectMap::default();
126
125
  obj.insert(Arc::from("error"), Value::String(e.into()));
127
- Value::Object(VmRef::new(obj))
126
+ Value::object(obj)
128
127
  }),
129
128
  },
130
129
  Ok(Err(e)) => Err({
131
130
  let mut obj = ObjectMap::default();
132
131
  obj.insert(Arc::from("error"), Value::String(e.into()));
133
- Value::Object(VmRef::new(obj))
132
+ Value::object(obj)
134
133
  }),
135
134
  Err(_) => Err(Value::String("Promise dropped".into())),
136
135
  }
@@ -241,7 +240,7 @@ impl TishOpaque for HttpReadableStream {
241
240
  Err(e) => {
242
241
  let mut m = ObjectMap::default();
243
242
  m.insert(Arc::from("error"), Value::String(e.into()));
244
- Value::Object(VmRef::new(m))
243
+ Value::object(m)
245
244
  }
246
245
  }))
247
246
  }
@@ -306,7 +305,7 @@ fn headers_to_value(headers: &reqwest::header::HeaderMap) -> Value {
306
305
  headers_obj.insert(Arc::from(key.as_str()), Value::String(v.into()));
307
306
  }
308
307
  }
309
- Value::Object(VmRef::new(headers_obj))
308
+ Value::object(headers_obj)
310
309
  }
311
310
 
312
311
  pub fn response_value_from_reqwest(response: reqwest::Response) -> Value {
@@ -351,7 +350,7 @@ pub fn response_value_from_reqwest(response: reqwest::Response) -> Value {
351
350
  obj.insert(Arc::from("body"), body_stream_val);
352
351
  obj.insert(Arc::from("text"), Value::Function(text_fn));
353
352
  obj.insert(Arc::from("json"), Value::Function(json_fn));
354
- Value::Object(VmRef::new(obj))
353
+ Value::object(obj)
355
354
  }
356
355
 
357
356
  async fn send_request_parts(
@@ -424,7 +423,8 @@ pub fn fetch_all_promise_from_args(args: Vec<Value>) -> Value {
424
423
  Value::Object(obj) => {
425
424
  let obj_ref = obj.borrow();
426
425
  match obj_ref
427
- .get(&Arc::from("url"))
426
+ .strings
427
+ .get("url")
428
428
  .map(|v| v.to_display_string())
429
429
  {
430
430
  Some(u) => (u, Some(req.clone())),
@@ -3,18 +3,17 @@
3
3
  //! Re-exports core types from tishlang_core and provides console, Math,
4
4
  //! and other builtin functions for compiled Tish programs.
5
5
 
6
- #[cfg(feature = "regex")]
7
- use std::cell::RefCell;
8
6
  use std::fmt;
9
- #[cfg(feature = "regex")]
10
- use std::rc::Rc;
11
7
  use std::sync::OnceLock;
12
8
  use tishlang_builtins::helpers::extract_num;
13
9
  #[cfg(feature = "fs")]
14
10
  use tishlang_builtins::helpers::make_error_value;
15
11
 
12
+ pub use tishlang_builtins::symbol::symbol_object;
16
13
  pub use tishlang_core::ObjectMap;
17
14
  pub use tishlang_core::Value;
15
+ /// Used by native codegen for `f()` / `obj()` dispatch (`Value::Function` or `__call` on objects).
16
+ pub use tishlang_core::value_call;
18
17
  // Re-export the shared-mutable wrapper so the Rust code emitted by
19
18
  // `tishlang_compile::codegen` can write `VmRef::new(...)` without needing
20
19
  // a direct dependency on `tishlang_core` from the generated crate.
@@ -48,7 +47,8 @@ pub use tishlang_builtins::string::{
48
47
  pad_start as string_pad_start_impl, repeat as string_repeat_impl,
49
48
  replace as string_replace_impl, replace_all as string_replace_all_impl,
50
49
  slice as string_slice_impl, split as string_split_impl,
51
- starts_with as string_starts_with_impl, substring as string_substring_impl,
50
+ starts_with as string_starts_with_impl, substr as string_substr_impl,
51
+ substring as string_substring_impl,
52
52
  to_lower_case as string_to_lower_case, to_upper_case as string_to_upper_case,
53
53
  trim as string_trim,
54
54
  };
@@ -106,6 +106,9 @@ pub fn string_slice(s: &Value, start: &Value, end: &Value) -> Value {
106
106
  pub fn string_substring(s: &Value, start: &Value, end: &Value) -> Value {
107
107
  string_substring_impl(s, start, end)
108
108
  }
109
+ pub fn string_substr(s: &Value, start: &Value, length: &Value) -> Value {
110
+ string_substr_impl(s, start, length)
111
+ }
109
112
  pub fn string_split(s: &Value, sep: &Value) -> Value {
110
113
  string_split_impl(s, sep)
111
114
  }
@@ -421,6 +424,7 @@ pub use tishlang_builtins::math::{
421
424
  exp as tish_math_exp_impl, floor as tish_math_floor_impl, max as tish_math_max_impl,
422
425
  min as tish_math_min_impl, pow as tish_math_pow_impl, random as tish_math_random_impl,
423
426
  round as tish_math_round_impl, sign as tish_math_sign_impl, sin as tish_math_sin_impl,
427
+ imul as tish_math_imul_impl,
424
428
  sqrt as tish_math_sqrt_impl, tan as tish_math_tan_impl, trunc as tish_math_trunc_impl,
425
429
  };
426
430
 
@@ -461,6 +465,9 @@ pub fn math_exp(args: &[Value]) -> Value {
461
465
  pub fn math_trunc(args: &[Value]) -> Value {
462
466
  tish_math_trunc_impl(args)
463
467
  }
468
+ pub fn math_imul(args: &[Value]) -> Value {
469
+ tish_math_imul_impl(args)
470
+ }
464
471
  pub fn math_pow(args: &[Value]) -> Value {
465
472
  tish_math_pow_impl(args)
466
473
  }
@@ -590,8 +597,6 @@ pub fn is_dir(args: &[Value]) -> Value {
590
597
 
591
598
  #[cfg(feature = "fs")]
592
599
  pub fn read_dir(args: &[Value]) -> Value {
593
- use std::cell::RefCell;
594
- use std::rc::Rc;
595
600
  let path = args
596
601
  .first()
597
602
  .map(|v| v.to_display_string())
@@ -632,7 +637,7 @@ pub fn get_prop(obj: &Value, key: impl AsRef<str>) -> Value {
632
637
  // directly. Previously we allocated a fresh `Arc<str>` on
633
638
  // every call (one heap alloc per `r.field` read in tight
634
639
  // handler loops); this version is alloc-free on the hit path.
635
- map.borrow().get(key).cloned().unwrap_or(Value::Null)
640
+ map.borrow().strings.get(key).cloned().unwrap_or(Value::Null)
636
641
  }
637
642
  Value::Array(arr) => {
638
643
  if key == "length" {
@@ -685,14 +690,7 @@ pub fn get_index(obj: &Value, index: &Value) -> Value {
685
690
  };
686
691
  arr.borrow().get(idx).cloned().unwrap_or(Value::Null)
687
692
  }
688
- Value::Object(map) => {
689
- let key: Arc<str> = match index {
690
- Value::Number(n) => n.to_string().into(),
691
- Value::String(s) => Arc::clone(s),
692
- _ => return Value::Null,
693
- };
694
- map.borrow().get(&key).cloned().unwrap_or(Value::Null)
695
- }
693
+ Value::Object(_) => tishlang_core::object_get(obj, index).unwrap_or(Value::Null),
696
694
  _ => Value::Null,
697
695
  }
698
696
  }
@@ -705,10 +703,10 @@ pub fn set_prop(obj: &Value, key: &str, val: Value) -> Value {
705
703
  // exists we re-use the existing `Arc<str>` and skip the
706
704
  // alloc. Only newly-inserted keys pay for `Arc::from(key)`.
707
705
  let mut m = map.borrow_mut();
708
- if let Some(slot) = m.get_mut(key) {
706
+ if let Some(slot) = m.strings.get_mut(key) {
709
707
  *slot = val.clone();
710
708
  } else {
711
- m.insert(Arc::from(key), val.clone());
709
+ m.strings.insert(Arc::from(key), val.clone());
712
710
  }
713
711
  val
714
712
  }
@@ -731,13 +729,8 @@ pub fn set_index(obj: &Value, idx: &Value, val: Value) -> Value {
731
729
  arr_mut[index] = val.clone();
732
730
  val
733
731
  }
734
- Value::Object(map) => {
735
- let key: Arc<str> = match idx {
736
- Value::Number(n) => n.to_string().into(),
737
- Value::String(s) => Arc::clone(s),
738
- _ => panic!("Object key must be string or number"),
739
- };
740
- map.borrow_mut().insert(key, val.clone());
732
+ Value::Object(_) => {
733
+ tishlang_core::object_set(obj, idx, val.clone()).expect("object set");
741
734
  val
742
735
  }
743
736
  _ => panic!("Cannot index assign on non-array/object"),
@@ -745,26 +738,24 @@ pub fn set_index(obj: &Value, idx: &Value, val: Value) -> Value {
745
738
  }
746
739
 
747
740
  pub fn in_operator(key: &Value, obj: &Value) -> Value {
748
- let key_str: Arc<str> = match key {
749
- Value::String(s) => Arc::clone(s),
750
- Value::Number(n) => n.to_string().into(),
751
- _ => return Value::Bool(false),
752
- };
753
-
754
- let result = match obj {
755
- Value::Object(map) => map.borrow().contains_key(&key_str),
741
+ match obj {
742
+ Value::Object(_) => Value::Bool(tishlang_core::object_has(obj, key)),
756
743
  Value::Array(arr) => {
757
- key_str.as_ref() == "length"
744
+ let key_str: Arc<str> = match key {
745
+ Value::String(s) => Arc::clone(s),
746
+ Value::Number(n) => n.to_string().into(),
747
+ _ => return Value::Bool(false),
748
+ };
749
+ let result = key_str.as_ref() == "length"
758
750
  || key_str
759
751
  .parse::<usize>()
760
752
  .ok()
761
753
  .map(|i| i < arr.borrow().len())
762
- .unwrap_or(false)
754
+ .unwrap_or(false);
755
+ Value::Bool(result)
763
756
  }
764
- _ => false,
765
- };
766
-
767
- Value::Bool(result)
757
+ _ => Value::Bool(false),
758
+ }
768
759
  }
769
760
 
770
761
  // Object functions - delegate to tishlang_builtins::globals
@@ -809,7 +800,7 @@ mod http_fetch;
809
800
 
810
801
  mod timers;
811
802
 
812
- #[cfg(feature = "http")]
803
+ #[cfg(any(feature = "http", feature = "promise"))]
813
804
  mod promise;
814
805
 
815
806
  #[cfg(feature = "http")]
@@ -893,11 +884,11 @@ pub use timers::{
893
884
  set_interval as timer_set_interval, set_timeout as timer_set_timeout,
894
885
  };
895
886
 
896
- #[cfg(feature = "http")]
897
- pub use promise::{promise_instance_catch, promise_instance_then, promise_object};
887
+ #[cfg(any(feature = "http", feature = "promise"))]
888
+ pub use promise::{await_promise, promise_instance_catch, promise_instance_then, promise_object};
898
889
 
899
890
  #[cfg(feature = "http")]
900
- pub use native_promise::{await_promise, fetch_all_promise, fetch_async_promise, fetch_promise};
891
+ pub use native_promise::{fetch_all_promise, fetch_async_promise, fetch_promise};
901
892
 
902
893
  // RegExp Support
903
894
  #[cfg(feature = "regex")]
@@ -999,7 +990,7 @@ fn regexp_exec_impl(re: &mut tishlang_core::TishRegExp, input: &str) -> Value {
999
990
  };
1000
991
  }
1001
992
 
1002
- Value::Object(VmRef::new(obj))
993
+ Value::object(obj)
1003
994
  }
1004
995
  Ok(None) | Err(_) => {
1005
996
  if re.flags.global || re.flags.sticky {
@@ -13,14 +13,3 @@ pub fn fetch_all_promise(args: Vec<Value>) -> Value {
13
13
  pub fn fetch_async_promise(args: Vec<Value>) -> Value {
14
14
  fetch_promise(args)
15
15
  }
16
-
17
- pub fn await_promise(v: Value) -> Value {
18
- if let Value::Promise(p) = v {
19
- match p.block_until_settled() {
20
- Ok(val) => val,
21
- Err(rejection) => rejection,
22
- }
23
- } else {
24
- v
25
- }
26
- }
@@ -213,7 +213,7 @@ pub fn promise_object() -> Value {
213
213
  Arc::from("race"),
214
214
  Value::native(|args: &[Value]| promise_race(args)),
215
215
  );
216
- Value::Object(VmRef::new(map))
216
+ Value::object(map)
217
217
  }
218
218
 
219
219
  /// `.then(onFulfilled, onRejected)` for a `Value::Promise` instance (VM `GetMember`).
@@ -233,3 +233,16 @@ pub fn promise_instance_catch(p: &Arc<dyn TishPromise>, args: &[Value]) -> Value
233
233
  on_rejected: args.first().cloned(),
234
234
  }))
235
235
  }
236
+
237
+ /// Unwrap a settled [`Value::Promise`], or pass non-promise values through (VM `AwaitPromise` /
238
+ /// `tish:http.await`). Fetch promises still require the `http` feature.
239
+ pub fn await_promise(v: Value) -> Value {
240
+ if let Value::Promise(p) = v {
241
+ match p.block_until_settled() {
242
+ Ok(val) => val,
243
+ Err(rejection) => rejection,
244
+ }
245
+ } else {
246
+ v
247
+ }
248
+ }
@@ -1,8 +1,5 @@
1
1
  //! Promises carrying only Send payloads (string results for text(), etc.).
2
2
 
3
- use std::cell::RefCell;
4
- use tishlang_core::VmRef;
5
- use std::rc::Rc;
6
3
  use std::sync::{Arc, Mutex};
7
4
  use tishlang_core::{ObjectMap, TishPromise, Value};
8
5
  use tokio::sync::oneshot;
@@ -11,7 +8,7 @@ fn error_value(msg: String) -> Value {
11
8
  let mut obj: ObjectMap = ObjectMap::with_capacity(2);
12
9
  obj.insert(Arc::from("error"), Value::String(msg.into()));
13
10
  obj.insert(Arc::from("ok"), Value::Bool(false));
14
- Value::Object(VmRef::new(obj))
11
+ Value::object(obj)
15
12
  }
16
13
 
17
14
  pub struct StringResultPromise {
@@ -47,15 +47,20 @@ pub fn drain_timers() {
47
47
  run_due_timers();
48
48
  }
49
49
 
50
- /// Run all due timer callbacks.
50
+ /// Run all due timer callbacks (including timers scheduled by other timers).
51
51
  fn run_due_timers() {
52
- let due = take_due_timers();
53
- for (id, callback, args, interval_ms) in due {
54
- if let Value::Function(f) = &callback {
55
- let _ = f(&args);
52
+ for _ in 0..64 {
53
+ let due = take_due_timers();
54
+ if due.is_empty() {
55
+ break;
56
56
  }
57
- if interval_ms > 0 {
58
- re_register_interval(id, callback, args, interval_ms);
57
+ for (id, callback, args, interval_ms) in due {
58
+ if let Value::Function(f) = &callback {
59
+ let _ = f(&args);
60
+ }
61
+ if interval_ms > 0 {
62
+ re_register_interval(id, callback, args, interval_ms);
63
+ }
59
64
  }
60
65
  }
61
66
  }