@tishlang/tish 1.9.2 → 1.10.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 (78) 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/main.rs +11 -8
  7. package/crates/tish/tests/integration_test.rs +145 -7
  8. package/crates/tish_ast/src/ast.rs +3 -9
  9. package/crates/tish_build_utils/src/lib.rs +43 -15
  10. package/crates/tish_builtins/src/array.rs +2 -3
  11. package/crates/tish_builtins/src/construct.rs +15 -28
  12. package/crates/tish_builtins/src/globals.rs +18 -16
  13. package/crates/tish_builtins/src/helpers.rs +1 -4
  14. package/crates/tish_builtins/src/lib.rs +1 -0
  15. package/crates/tish_builtins/src/object.rs +10 -10
  16. package/crates/tish_builtins/src/string.rs +1 -3
  17. package/crates/tish_builtins/src/symbol.rs +83 -0
  18. package/crates/tish_compile/src/codegen.rs +123 -138
  19. package/crates/tish_compile/src/lib.rs +25 -3
  20. package/crates/tish_compile/src/resolve.rs +6 -3
  21. package/crates/tish_compile/src/types.rs +6 -6
  22. package/crates/tish_compile_js/src/codegen.rs +8 -5
  23. package/crates/tish_core/src/console_style.rs +9 -0
  24. package/crates/tish_core/src/json.rs +17 -7
  25. package/crates/tish_core/src/macros.rs +2 -2
  26. package/crates/tish_core/src/value.rs +192 -4
  27. package/crates/tish_cranelift_runtime/Cargo.toml +4 -0
  28. package/crates/tish_eval/src/eval.rs +135 -73
  29. package/crates/tish_eval/src/http.rs +18 -12
  30. package/crates/tish_eval/src/lib.rs +29 -0
  31. package/crates/tish_eval/src/regex.rs +1 -1
  32. package/crates/tish_eval/src/value.rs +89 -4
  33. package/crates/tish_eval/src/value_convert.rs +30 -8
  34. package/crates/tish_fmt/src/lib.rs +4 -1
  35. package/crates/tish_lexer/src/lib.rs +7 -2
  36. package/crates/tish_llvm/src/lib.rs +2 -2
  37. package/crates/tish_lsp/src/builtin_goto.rs +111 -10
  38. package/crates/tish_lsp/src/import_goto.rs +35 -22
  39. package/crates/tish_lsp/src/main.rs +118 -85
  40. package/crates/tish_native/src/build.rs +187 -10
  41. package/crates/tish_native/src/lib.rs +92 -8
  42. package/crates/tish_parser/src/lib.rs +5 -2
  43. package/crates/tish_parser/src/parser.rs +45 -75
  44. package/crates/tish_pg/src/error.rs +1 -1
  45. package/crates/tish_pg/src/lib.rs +61 -73
  46. package/crates/tish_resolve/src/lib.rs +283 -158
  47. package/crates/tish_resolve/src/pos.rs +10 -2
  48. package/crates/tish_runtime/Cargo.toml +3 -0
  49. package/crates/tish_runtime/src/http.rs +39 -39
  50. package/crates/tish_runtime/src/http_fetch.rs +12 -12
  51. package/crates/tish_runtime/src/lib.rs +26 -43
  52. package/crates/tish_runtime/src/native_promise.rs +0 -11
  53. package/crates/tish_runtime/src/promise.rs +14 -1
  54. package/crates/tish_runtime/src/promise_io.rs +1 -4
  55. package/crates/tish_runtime/src/ws.rs +40 -27
  56. package/crates/tish_runtime/tests/fetch_readable_stream.rs +10 -8
  57. package/crates/tish_ui/src/jsx.rs +6 -4
  58. package/crates/tish_ui/src/lib.rs +2 -2
  59. package/crates/tish_ui/src/runtime/hooks.rs +5 -15
  60. package/crates/tish_ui/src/runtime/mod.rs +16 -17
  61. package/crates/tish_vm/Cargo.toml +2 -0
  62. package/crates/tish_vm/src/vm.rs +218 -153
  63. package/crates/tish_wasm/src/lib.rs +33 -7
  64. package/crates/tish_wasm_runtime/Cargo.toml +4 -1
  65. package/crates/tish_wasm_runtime/src/lib.rs +2 -1
  66. package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
  67. package/crates/tishlang_cargo_bindgen/src/discover.rs +10 -5
  68. package/crates/tishlang_cargo_bindgen/src/infer.rs +18 -8
  69. package/crates/tishlang_cargo_bindgen/src/lib.rs +25 -26
  70. package/crates/tishlang_cargo_bindgen/src/main.rs +41 -38
  71. package/crates/tishlang_cargo_bindgen/src/metadata.rs +4 -1
  72. package/justfile +3 -3
  73. package/package.json +1 -1
  74. package/platform/darwin-arm64/tish +0 -0
  75. package/platform/darwin-x64/tish +0 -0
  76. package/platform/linux-arm64/tish +0 -0
  77. package/platform/linux-x64/tish +0 -0
  78. package/platform/win32-x64/tish.exe +0 -0
@@ -72,7 +72,11 @@ impl Codegen {
72
72
  }
73
73
 
74
74
  fn output_line(&self) -> u32 {
75
- self.output.as_bytes().iter().filter(|&&b| b == b'\n').count() as u32
75
+ self.output
76
+ .as_bytes()
77
+ .iter()
78
+ .filter(|&&b| b == b'\n')
79
+ .count() as u32
76
80
  }
77
81
 
78
82
  fn escape_ident(s: &str) -> String {
@@ -779,7 +783,8 @@ fn compile_project_js_inner(
779
783
  let modules = tishlang_compile::resolve_project(entry_path, project_root)
780
784
  .map_err(|e| CompileError { message: e })?;
781
785
  tishlang_compile::detect_cycles(&modules).map_err(|e| CompileError { message: e })?;
782
- let merged = tishlang_compile::merge_modules(modules).map_err(|e| CompileError { message: e })?;
786
+ let merged =
787
+ tishlang_compile::merge_modules(modules).map_err(|e| CompileError { message: e })?;
783
788
  let program = if optimize {
784
789
  tishlang_opt::optimize(&merged.program)
785
790
  } else {
@@ -862,7 +867,5 @@ pub fn compile_project_with_jsx(
862
867
  } else {
863
868
  format!("{stem}.js")
864
869
  };
865
- Ok(
866
- compile_project_js_inner(entry_path, project_root, optimize, false, &out_name)?.js,
867
- )
870
+ Ok(compile_project_js_inner(entry_path, project_root, optimize, false, &out_name)?.js)
868
871
  }
@@ -84,6 +84,7 @@ fn format_value_styled_inner(value: &Value, colors: bool, quote_strings: bool) -
84
84
  Value::Object(obj) => {
85
85
  let inner: Vec<String> = obj
86
86
  .borrow()
87
+ .strings
87
88
  .iter()
88
89
  .map(|(k, v)| {
89
90
  format!(
@@ -96,6 +97,14 @@ fn format_value_styled_inner(value: &Value, colors: bool, quote_strings: bool) -
96
97
  let sep = format!("{PUNCT}, {RESET}");
97
98
  format!("{PUNCT}{{{RESET} {} {PUNCT}}}{RESET}", inner.join(&sep))
98
99
  }
100
+ Value::Symbol(s) => {
101
+ let body = s
102
+ .description
103
+ .as_ref()
104
+ .map(|d| d.as_ref())
105
+ .unwrap_or("");
106
+ format!("{SPECIAL}Symbol({body}){RESET}")
107
+ }
99
108
  Value::Function(_) => format!("{SPECIAL}[Function]{RESET}"),
100
109
  Value::Promise(_) => format!("{SPECIAL}[object Promise]{RESET}"),
101
110
  Value::Opaque(o) => format!("{SPECIAL}[object {}]{RESET}", o.type_name()),
@@ -70,8 +70,8 @@ pub fn json_stringify_into(buf: &mut String, value: &Value) {
70
70
  let borrowed = obj.borrow();
71
71
  // Sort keys for deterministic output. Pre-allocate to avoid
72
72
  // a fresh `Vec` realloc inside `keys().collect()`.
73
- let mut keys: Vec<&Arc<str>> = Vec::with_capacity(borrowed.len());
74
- keys.extend(borrowed.keys());
73
+ let mut keys: Vec<&Arc<str>> = Vec::with_capacity(borrowed.strings.len());
74
+ keys.extend(borrowed.strings.keys());
75
75
  keys.sort_unstable_by(|a, b| a.as_ref().cmp(b.as_ref()));
76
76
  buf.push('{');
77
77
  for (i, key) in keys.into_iter().enumerate() {
@@ -81,11 +81,13 @@ pub fn json_stringify_into(buf: &mut String, value: &Value) {
81
81
  buf.push('"');
82
82
  escape_json_string_into(buf, key);
83
83
  buf.push_str("\":");
84
- json_stringify_into(buf, borrowed.get(key).unwrap());
84
+ json_stringify_into(buf, borrowed.strings.get(key).unwrap());
85
85
  }
86
86
  buf.push('}');
87
87
  }
88
- Value::Function(_) | Value::Promise(_) | Value::Opaque(_) => buf.push_str("null"),
88
+ Value::Function(_) | Value::Promise(_) | Value::Opaque(_) | Value::Symbol(_) => {
89
+ buf.push_str("null");
90
+ }
89
91
  #[cfg(feature = "regex")]
90
92
  Value::RegExp(_) => buf.push_str("null"),
91
93
  }
@@ -312,7 +314,10 @@ fn parse_object(input: &str) -> Result<(Value, &str), String> {
312
314
 
313
315
  input = input.trim_start();
314
316
  if let Some(rest) = input.strip_prefix('}') {
315
- return Ok((Value::Object(VmRef::new(map)), rest));
317
+ return Ok((
318
+ Value::Object(VmRef::new(crate::ObjectData::from_strings(map))),
319
+ rest,
320
+ ));
316
321
  }
317
322
 
318
323
  loop {
@@ -339,7 +344,12 @@ fn parse_object(input: &str) -> Result<(Value, &str), String> {
339
344
 
340
345
  match input.chars().next() {
341
346
  Some(',') => input = &input[1..],
342
- Some('}') => return Ok((Value::Object(VmRef::new(map)), &input[1..])),
347
+ Some('}') => {
348
+ return Ok((
349
+ Value::Object(VmRef::new(crate::ObjectData::from_strings(map))),
350
+ &input[1..],
351
+ ));
352
+ }
343
353
  _ => return Err("Expected ',' or '}' in object".to_string()),
344
354
  }
345
355
  }
@@ -369,7 +379,7 @@ mod tests {
369
379
 
370
380
  match (&value, &reparsed) {
371
381
  (Value::Object(a), Value::Object(b)) => {
372
- assert_eq!(a.borrow().len(), b.borrow().len());
382
+ assert_eq!(a.borrow().len_entries(), b.borrow().len_entries());
373
383
  }
374
384
  _ => panic!("Expected objects"),
375
385
  }
@@ -24,13 +24,13 @@
24
24
  macro_rules! tish_module {
25
25
  ($($name:expr => $fn:expr),* $(,)?) => {{
26
26
  use std::sync::Arc;
27
- use $crate::{ObjectMap, Value, VmRef};
27
+ use $crate::{ObjectMap, Value};
28
28
  let mut map = ObjectMap::default();
29
29
  $(
30
30
  // `Value::native` picks the right Rc / Arc wrapper depending on
31
31
  // whether the `send-values` feature is enabled upstream.
32
32
  map.insert(Arc::from($name), Value::native($fn));
33
33
  )*
34
- Value::Object(VmRef::new(map))
34
+ Value::object(map)
35
35
  }};
36
36
  }
@@ -1,5 +1,6 @@
1
1
  //! Unified Value type for Tish runtime values.
2
2
 
3
+ use std::sync::atomic::{AtomicU64, Ordering};
3
4
  use std::sync::Arc;
4
5
 
5
6
  use ahash::AHashMap;
@@ -10,6 +11,47 @@ use crate::vmref::VmRef;
10
11
  /// Uses a faster hasher than `std::collections::HashMap` for string-heavy workloads.
11
12
  pub type ObjectMap = AHashMap<Arc<str>, Value>;
12
13
 
14
+ static NEXT_SYMBOL_ID: AtomicU64 = AtomicU64::new(1);
15
+
16
+ fn next_symbol_id() -> u64 {
17
+ NEXT_SYMBOL_ID.fetch_add(1, Ordering::Relaxed)
18
+ }
19
+
20
+ /// Allocate a unique symbol id (for `Symbol()` and first-time `Symbol.for` entries).
21
+ #[inline]
22
+ pub fn alloc_symbol_id() -> u64 {
23
+ next_symbol_id()
24
+ }
25
+
26
+ /// Primitive Symbol (ECMAScript-style): identity is `Arc` pointer equality.
27
+ #[derive(Debug)]
28
+ pub struct TishSymbol {
29
+ pub id: u64,
30
+ pub description: Option<Arc<str>>,
31
+ /// Set when created via `Symbol.for(key)` (global registry).
32
+ pub registry_key: Option<Arc<str>>,
33
+ }
34
+
35
+ impl TishSymbol {
36
+ /// Unique symbol (`Symbol("desc")`).
37
+ pub fn new_unique(description: Option<Arc<str>>) -> Arc<Self> {
38
+ Arc::new(Self {
39
+ id: next_symbol_id(),
40
+ description,
41
+ registry_key: None,
42
+ })
43
+ }
44
+
45
+ /// Registry symbol (`Symbol.for`): stable `id` for this registry key.
46
+ pub fn new_registry(id: u64, registry_key: Arc<str>, description: Option<Arc<str>>) -> Arc<Self> {
47
+ Arc::new(Self {
48
+ id,
49
+ description,
50
+ registry_key: Some(registry_key),
51
+ })
52
+ }
53
+ }
54
+
13
55
  #[cfg(feature = "regex")]
14
56
  use fancy_regex::Regex;
15
57
 
@@ -222,7 +264,9 @@ pub enum Value {
222
264
  Bool(bool),
223
265
  Null,
224
266
  Array(VmRef<Vec<Value>>),
225
- Object(VmRef<ObjectMap>),
267
+ Object(VmRef<ObjectData>),
268
+ /// ECMAScript-style primitive symbol (identity by `Arc`).
269
+ Symbol(Arc<TishSymbol>),
226
270
  Function(NativeFn),
227
271
  #[cfg(feature = "regex")]
228
272
  RegExp(VmRef<TishRegExp>),
@@ -232,6 +276,134 @@ pub enum Value {
232
276
  Opaque(Arc<dyn TishOpaque>),
233
277
  }
234
278
 
279
+ /// Ordinary object: string-keyed properties plus optional symbol-keyed side map.
280
+ #[derive(Clone, Debug, Default)]
281
+ pub struct ObjectData {
282
+ pub strings: ObjectMap,
283
+ pub symbols: Option<AHashMap<u64, Value>>,
284
+ }
285
+
286
+ impl ObjectData {
287
+ #[inline]
288
+ pub fn from_strings(strings: ObjectMap) -> Self {
289
+ Self {
290
+ strings,
291
+ symbols: None,
292
+ }
293
+ }
294
+
295
+ #[inline]
296
+ pub fn len_entries(&self) -> usize {
297
+ self.strings.len() + self.symbols.as_ref().map(|s| s.len()).unwrap_or(0)
298
+ }
299
+ }
300
+
301
+ /// Read a property from an object value.
302
+ pub fn object_get(obj: &Value, key: &Value) -> Option<Value> {
303
+ let Value::Object(od) = obj else {
304
+ return None;
305
+ };
306
+ let b = od.borrow();
307
+ match key {
308
+ Value::Symbol(s) => b.symbols.as_ref()?.get(&s.id).cloned(),
309
+ Value::Number(n) => {
310
+ let k: Arc<str> = n.to_string().into();
311
+ b.strings.get(&k).cloned()
312
+ }
313
+ Value::String(k) => b.strings.get(k.as_ref()).cloned(),
314
+ _ => None,
315
+ }
316
+ }
317
+
318
+ /// Set a property on an object.
319
+ pub fn object_set(obj: &Value, key: &Value, val: Value) -> Result<(), String> {
320
+ let Value::Object(od) = obj else {
321
+ return Err(format!("Cannot set property on {}", obj.type_name()));
322
+ };
323
+ let mut b = od.borrow_mut();
324
+ match key {
325
+ Value::Symbol(s) => {
326
+ if b.symbols.is_none() {
327
+ b.symbols = Some(AHashMap::default());
328
+ }
329
+ b.symbols.as_mut().unwrap().insert(s.id, val);
330
+ Ok(())
331
+ }
332
+ Value::Number(n) => {
333
+ b.strings.insert(n.to_string().into(), val);
334
+ Ok(())
335
+ }
336
+ Value::String(k) => {
337
+ b.strings.insert(Arc::clone(k), val);
338
+ Ok(())
339
+ }
340
+ _ => Err(format!(
341
+ "Object key must be string, number, or symbol, got {}",
342
+ key.type_name()
343
+ )),
344
+ }
345
+ }
346
+
347
+ /// `key in obj` for objects.
348
+ pub fn object_has(obj: &Value, key: &Value) -> bool {
349
+ let Value::Object(od) = obj else {
350
+ return false;
351
+ };
352
+ let b = od.borrow();
353
+ match key {
354
+ Value::Symbol(s) => b.symbols.as_ref().is_some_and(|m| m.contains_key(&s.id)),
355
+ Value::Number(n) => {
356
+ let k: Arc<str> = n.to_string().into();
357
+ b.strings.contains_key(&k)
358
+ }
359
+ Value::String(k) => b.strings.contains_key(k.as_ref()),
360
+ _ => false,
361
+ }
362
+ }
363
+
364
+ /// Invoke a callable [`Value`]: [`Value::Function`], or an object exposing `__call` (e.g. `Symbol`).
365
+ pub fn value_call(callee: &Value, args: &[Value]) -> Value {
366
+ match callee {
367
+ Value::Function(f) => f(args),
368
+ Value::Object(o) => {
369
+ let inner = o.borrow().strings.get("__call").cloned();
370
+ if let Some(inner) = inner {
371
+ return value_call(&inner, args);
372
+ }
373
+ panic!(
374
+ "Not a function: tried to call {:?} as a function (e.g. method on Null when read failed)",
375
+ callee
376
+ );
377
+ }
378
+ _ => panic!(
379
+ "Not a function: tried to call {:?} as a function (e.g. method on Null when read failed)",
380
+ callee
381
+ ),
382
+ }
383
+ }
384
+
385
+ /// Merge two object payloads (spread / VM MergeObject).
386
+ pub fn merge_object_data(left: &VmRef<ObjectData>, right: &VmRef<ObjectData>) -> ObjectData {
387
+ let l = left.borrow();
388
+ let r = right.borrow();
389
+ let mut strings = ObjectMap::with_capacity(l.strings.len() + r.strings.len());
390
+ strings.extend(l.strings.iter().map(|(k, v)| (Arc::clone(k), v.clone())));
391
+ strings.extend(r.strings.iter().map(|(k, v)| (Arc::clone(k), v.clone())));
392
+ let mut symbols: Option<AHashMap<u64, Value>> = None;
393
+ if let Some(ls) = &l.symbols {
394
+ symbols = Some(ls.clone());
395
+ }
396
+ if let Some(rs) = &r.symbols {
397
+ match &mut symbols {
398
+ Some(m) => {
399
+ m.extend(rs.iter().map(|(k, v)| (*k, v.clone())));
400
+ }
401
+ None => symbols = Some(rs.clone()),
402
+ }
403
+ }
404
+ ObjectData { strings, symbols }
405
+ }
406
+
235
407
  impl std::fmt::Debug for Value {
236
408
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237
409
  match self {
@@ -241,6 +413,7 @@ impl std::fmt::Debug for Value {
241
413
  Value::Null => write!(f, "Null"),
242
414
  Value::Array(arr) => write!(f, "Array({:?})", arr.borrow()),
243
415
  Value::Object(obj) => write!(f, "Object({:?})", obj.borrow()),
416
+ Value::Symbol(s) => write!(f, "Symbol({})", s.id),
244
417
  Value::Function(_) => write!(f, "Function"),
245
418
  #[cfg(feature = "regex")]
246
419
  Value::RegExp(re) => write!(
@@ -281,11 +454,19 @@ impl Value {
281
454
  Value::Object(obj) => {
282
455
  let inner: Vec<String> = obj
283
456
  .borrow()
457
+ .strings
284
458
  .iter()
285
459
  .map(|(k, v)| format!("{}: {}", k.as_ref(), v.to_display_string()))
286
460
  .collect();
287
461
  format!("{{{}}}", inner.join(", "))
288
462
  }
463
+ Value::Symbol(s) => {
464
+ if let Some(d) = &s.description {
465
+ format!("Symbol({})", d)
466
+ } else {
467
+ "Symbol()".to_string()
468
+ }
469
+ }
289
470
  Value::Function(_) => "[Function]".to_string(),
290
471
  Value::Promise(_) => "[object Promise]".to_string(),
291
472
  Value::Opaque(o) => format!("[object {}]", o.type_name()),
@@ -331,6 +512,7 @@ impl Value {
331
512
  (Value::RegExp(a), Value::RegExp(b)) => VmRef::ptr_eq(a, b),
332
513
  (Value::Promise(a), Value::Promise(b)) => Arc::ptr_eq(a, b),
333
514
  (Value::Opaque(a), Value::Opaque(b)) => Arc::ptr_eq(a, b),
515
+ (Value::Symbol(a), Value::Symbol(b)) => Arc::ptr_eq(a, b),
334
516
  _ => false,
335
517
  }
336
518
  }
@@ -364,7 +546,7 @@ impl Value {
364
546
 
365
547
  /// Create a new object Value from a property map.
366
548
  pub fn object(map: ObjectMap) -> Self {
367
- Value::Object(VmRef::new(map))
549
+ Value::Object(VmRef::new(ObjectData::from_strings(map)))
368
550
  }
369
551
 
370
552
  /// Create an empty array Value.
@@ -374,7 +556,7 @@ impl Value {
374
556
 
375
557
  /// Create an empty object Value.
376
558
  pub fn empty_object() -> Self {
377
- Value::Object(VmRef::new(ObjectMap::default()))
559
+ Value::Object(VmRef::new(ObjectData::default()))
378
560
  }
379
561
 
380
562
  /// Extract the number value, if this is a Number.
@@ -399,6 +581,7 @@ impl Value {
399
581
  Value::RegExp(_) => "object",
400
582
  Value::Promise(_) => "object",
401
583
  Value::Opaque(o) => o.type_name(),
584
+ Value::Symbol(_) => "symbol",
402
585
  }
403
586
  }
404
587
 
@@ -406,7 +589,12 @@ impl Value {
406
589
  pub fn completion_keys(&self) -> Vec<String> {
407
590
  match self {
408
591
  Value::Object(m) => {
409
- let mut keys: Vec<String> = m.borrow().keys().map(|k| k.to_string()).collect();
592
+ let mut keys: Vec<String> = m
593
+ .borrow()
594
+ .strings
595
+ .keys()
596
+ .map(|k| k.to_string())
597
+ .collect();
410
598
  keys.sort();
411
599
  keys
412
600
  }
@@ -11,6 +11,10 @@ default = []
11
11
  fs = ["tishlang_vm/fs"]
12
12
  process = ["tishlang_vm/process"]
13
13
  http = ["tishlang_vm/http"]
14
+ promise = ["tishlang_vm/promise"]
15
+ timers = ["tishlang_vm/timers"]
16
+ ws = ["tishlang_vm/ws"]
17
+ regex = ["tishlang_vm/regex"]
14
18
 
15
19
  [lib]
16
20
  crate-type = ["staticlib", "rlib"]