@tishlang/tish 1.0.29 → 1.0.34

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 (68) hide show
  1. package/Cargo.toml +1 -0
  2. package/crates/js_to_tish/src/transform/expr.rs +15 -6
  3. package/crates/tish/Cargo.toml +1 -1
  4. package/crates/tish/src/main.rs +1 -1
  5. package/crates/tish/tests/integration_test.rs +4 -3
  6. package/crates/tish_ast/src/ast.rs +65 -2
  7. package/crates/tish_build_utils/src/lib.rs +10 -2
  8. package/crates/tish_builtins/src/construct.rs +177 -0
  9. package/crates/tish_builtins/src/globals.rs +3 -5
  10. package/crates/tish_builtins/src/helpers.rs +2 -3
  11. package/crates/tish_builtins/src/lib.rs +1 -0
  12. package/crates/tish_builtins/src/object.rs +3 -4
  13. package/crates/tish_bytecode/src/compiler.rs +85 -11
  14. package/crates/tish_bytecode/src/opcode.rs +7 -3
  15. package/crates/tish_compile/Cargo.toml +1 -0
  16. package/crates/tish_compile/src/codegen.rs +604 -106
  17. package/crates/tish_compile/src/infer.rs +236 -0
  18. package/crates/tish_compile/src/lib.rs +52 -5
  19. package/crates/tish_compile/src/types.rs +42 -5
  20. package/crates/tish_compile_js/Cargo.toml +1 -0
  21. package/crates/tish_compile_js/src/codegen.rs +38 -94
  22. package/crates/tish_compile_js/src/lib.rs +0 -1
  23. package/crates/tish_compile_js/src/tests_jsx.rs +68 -0
  24. package/crates/tish_core/Cargo.toml +4 -0
  25. package/crates/tish_core/src/console_style.rs +7 -1
  26. package/crates/tish_core/src/json.rs +1 -2
  27. package/crates/tish_core/src/macros.rs +2 -3
  28. package/crates/tish_core/src/value.rs +13 -5
  29. package/crates/tish_cranelift/src/lib.rs +6 -4
  30. package/crates/tish_cranelift/src/lower.rs +5 -3
  31. package/crates/tish_cranelift_runtime/src/lib.rs +4 -2
  32. package/crates/tish_eval/Cargo.toml +2 -0
  33. package/crates/tish_eval/src/eval.rs +172 -79
  34. package/crates/tish_eval/src/http.rs +3 -4
  35. package/crates/tish_eval/src/lib.rs +7 -0
  36. package/crates/tish_eval/src/regex.rs +3 -2
  37. package/crates/tish_eval/src/value.rs +11 -13
  38. package/crates/tish_eval/src/value_convert.rs +4 -8
  39. package/crates/tish_fmt/src/lib.rs +49 -10
  40. package/crates/tish_lexer/src/token.rs +2 -0
  41. package/crates/tish_lint/src/lib.rs +9 -0
  42. package/crates/tish_llvm/src/lib.rs +4 -4
  43. package/crates/tish_lsp/README.md +1 -1
  44. package/crates/tish_native/src/build.rs +16 -2
  45. package/crates/tish_native/src/lib.rs +10 -11
  46. package/crates/tish_opt/src/lib.rs +15 -0
  47. package/crates/tish_parser/src/lib.rs +101 -1
  48. package/crates/tish_parser/src/parser.rs +168 -51
  49. package/crates/tish_runtime/src/http.rs +4 -5
  50. package/crates/tish_runtime/src/http_fetch.rs +17 -10
  51. package/crates/tish_runtime/src/lib.rs +9 -2
  52. package/crates/tish_runtime/src/promise.rs +2 -3
  53. package/crates/tish_runtime/src/promise_io.rs +2 -3
  54. package/crates/tish_runtime/src/ws.rs +7 -7
  55. package/crates/tish_ui/Cargo.toml +17 -0
  56. package/crates/tish_ui/src/jsx.rs +390 -0
  57. package/crates/tish_ui/src/lib.rs +16 -0
  58. package/crates/tish_ui/src/runtime/hooks.rs +122 -0
  59. package/crates/tish_ui/src/runtime/mod.rs +173 -0
  60. package/crates/tish_vm/src/vm.rs +121 -27
  61. package/justfile +3 -3
  62. package/package.json +1 -1
  63. package/platform/darwin-arm64/tish +0 -0
  64. package/platform/darwin-x64/tish +0 -0
  65. package/platform/linux-arm64/tish +0 -0
  66. package/platform/linux-x64/tish +0 -0
  67. package/platform/win32-x64/tish.exe +0 -0
  68. package/crates/tish_compile_js/src/js_intrinsics.rs +0 -82
@@ -8,14 +8,14 @@ use std::path::{Path, PathBuf};
8
8
  use std::rc::Rc;
9
9
  use std::sync::Arc;
10
10
 
11
- use tishlang_ast::{BinOp, CompoundOp, ExportDeclaration, Expr, ImportSpecifier, Literal, LogicalAssignOp, MemberProp, Span, Statement, UnaryOp};
11
+ use tishlang_ast::{BinOp, CompoundOp, ExportDeclaration, Expr, FunParam, ImportSpecifier, Literal, LogicalAssignOp, MemberProp, Span, Statement, UnaryOp};
12
12
 
13
- use crate::value::Value;
13
+ use crate::value::{PropMap, Value};
14
14
  #[cfg(any(feature = "fs", feature = "process"))]
15
15
  use crate::natives;
16
16
 
17
17
  struct Scope {
18
- vars: HashMap<Arc<str>, Value>,
18
+ vars: PropMap,
19
19
  consts: std::collections::HashSet<Arc<str>>,
20
20
  parent: Option<Rc<std::cell::RefCell<Scope>>>,
21
21
  }
@@ -23,7 +23,7 @@ struct Scope {
23
23
  impl Scope {
24
24
  fn new() -> Rc<std::cell::RefCell<Self>> {
25
25
  Rc::new(std::cell::RefCell::new(Self {
26
- vars: HashMap::new(),
26
+ vars: PropMap::default(),
27
27
  consts: std::collections::HashSet::new(),
28
28
  parent: None,
29
29
  }))
@@ -31,7 +31,7 @@ impl Scope {
31
31
 
32
32
  fn child(parent: Rc<std::cell::RefCell<Scope>>) -> Rc<std::cell::RefCell<Self>> {
33
33
  Rc::new(std::cell::RefCell::new(Self {
34
- vars: HashMap::new(),
34
+ vars: PropMap::default(),
35
35
  consts: std::collections::HashSet::new(),
36
36
  parent: Some(parent),
37
37
  }))
@@ -75,6 +75,8 @@ pub struct Evaluator {
75
75
  module_cache: Rc<RefCell<HashMap<PathBuf, Value>>>,
76
76
  /// Directory of the file currently being evaluated (for resolving relative imports)
77
77
  current_dir: RefCell<Option<PathBuf>>,
78
+ /// Extra `tish:*` builtins from `TishNativeModule::virtual_builtin_modules` (shared across nested evaluators).
79
+ virtual_builtins: Rc<RefCell<HashMap<Arc<str>, Value>>>,
78
80
  }
79
81
 
80
82
  impl Evaluator {
@@ -85,7 +87,7 @@ impl Evaluator {
85
87
  let scope = Scope::new();
86
88
  {
87
89
  let mut s = scope.borrow_mut();
88
- let mut console = HashMap::with_capacity(5);
90
+ let mut console = PropMap::with_capacity(5);
89
91
  console.insert("debug".into(), Value::Native(natives::console_debug));
90
92
  console.insert("info".into(), Value::Native(natives::console_info));
91
93
  console.insert("log".into(), Value::Native(natives::console_log));
@@ -101,7 +103,7 @@ impl Evaluator {
101
103
  s.set("isNaN".into(), Value::Native(natives::is_nan), true);
102
104
  s.set("Infinity".into(), Value::Number(f64::INFINITY), true);
103
105
  s.set("NaN".into(), Value::Number(f64::NAN), true);
104
- let mut math = HashMap::with_capacity(18);
106
+ let mut math = PropMap::with_capacity(18);
105
107
  math.insert("abs".into(), Value::Native(natives::math_abs));
106
108
  math.insert("sqrt".into(), Value::Native(natives::math_sqrt));
107
109
  math.insert("min".into(), Value::Native(natives::math_min));
@@ -122,12 +124,12 @@ impl Evaluator {
122
124
  math.insert("E".into(), Value::Number(std::f64::consts::E));
123
125
  s.set("Math".into(), Value::Object(Rc::new(RefCell::new(math))), true);
124
126
 
125
- let mut json = HashMap::with_capacity(2);
127
+ let mut json = PropMap::with_capacity(2);
126
128
  json.insert("parse".into(), Value::Native(Self::json_parse_native));
127
129
  json.insert("stringify".into(), Value::Native(Self::json_stringify_native));
128
130
  s.set("JSON".into(), Value::Object(Rc::new(RefCell::new(json))), true);
129
131
 
130
- let mut object = HashMap::with_capacity(5);
132
+ let mut object = PropMap::with_capacity(5);
131
133
  object.insert("keys".into(), Value::Native(Self::object_keys));
132
134
  object.insert("values".into(), Value::Native(Self::object_values));
133
135
  object.insert("entries".into(), Value::Native(Self::object_entries));
@@ -135,18 +137,33 @@ impl Evaluator {
135
137
  object.insert("fromEntries".into(), Value::Native(Self::object_from_entries));
136
138
  s.set("Object".into(), Value::Object(Rc::new(RefCell::new(object))), true);
137
139
 
138
- let mut array_obj = HashMap::with_capacity(1);
140
+ let mut array_obj = PropMap::with_capacity(1);
139
141
  array_obj.insert("isArray".into(), Value::Native(natives::array_is_array));
140
142
  s.set("Array".into(), Value::Object(Rc::new(RefCell::new(array_obj))), true);
141
143
 
142
- let mut string_obj = HashMap::with_capacity(1);
144
+ let mut string_obj = PropMap::with_capacity(1);
143
145
  string_obj.insert("fromCharCode".into(), Value::Native(natives::string_from_char_code));
144
146
  s.set("String".into(), Value::Object(Rc::new(RefCell::new(string_obj))), true);
145
147
 
146
- let mut date = HashMap::with_capacity(1);
148
+ let mut date = PropMap::with_capacity(1);
147
149
  date.insert("now".into(), Value::Native(natives::date_now));
148
150
  s.set("Date".into(), Value::Object(Rc::new(RefCell::new(date))), true);
149
151
 
152
+ s.set(
153
+ "Uint8Array".into(),
154
+ crate::value_convert::core_to_eval(
155
+ tishlang_builtins::construct::uint8_array_constructor_value(),
156
+ ),
157
+ true,
158
+ );
159
+ s.set(
160
+ "AudioContext".into(),
161
+ crate::value_convert::core_to_eval(
162
+ tishlang_builtins::construct::audio_context_constructor_value(),
163
+ ),
164
+ true,
165
+ );
166
+
150
167
  #[cfg(feature = "regex")]
151
168
  {
152
169
  s.set("RegExp".into(), Value::Native(Self::regexp_constructor_native), true);
@@ -158,6 +175,7 @@ impl Evaluator {
158
175
  scope,
159
176
  module_cache: Rc::new(RefCell::new(HashMap::new())),
160
177
  current_dir: RefCell::new(None),
178
+ virtual_builtins: Rc::new(RefCell::new(HashMap::new())),
161
179
  }
162
180
  }
163
181
 
@@ -172,6 +190,14 @@ impl Evaluator {
172
190
  }
173
191
  }
174
192
  }
193
+ {
194
+ let mut vb = eval.virtual_builtins.borrow_mut();
195
+ for module in modules {
196
+ for (spec, value) in module.virtual_builtin_modules() {
197
+ vb.insert(Arc::from(spec), value);
198
+ }
199
+ }
200
+ }
175
201
  eval
176
202
  }
177
203
 
@@ -323,14 +349,11 @@ impl Evaluator {
323
349
  body,
324
350
  ..
325
351
  } => {
326
- // Extract parameter names and defaults using Arc for cheap cloning
327
- let param_names: Arc<[Arc<str>]> = params.iter().map(|p| Arc::clone(&p.name)).collect();
328
- let defaults: Arc<[Option<Expr>]> = params.iter().map(|p| p.default.clone()).collect();
352
+ let formals: Arc<[FunParam]> = Arc::from(params.clone());
329
353
  let rest_param_name = rest_param.as_ref().map(|p| Arc::clone(&p.name));
330
354
  let body = Arc::new(body.as_ref().clone());
331
355
  let func = Value::Function {
332
- params: param_names,
333
- defaults,
356
+ formals,
334
357
  rest_param: rest_param_name,
335
358
  body,
336
359
  };
@@ -490,7 +513,7 @@ impl Evaluator {
490
513
  /// Load and evaluate a module, returning its exports object. Uses cache.
491
514
  fn load_module(&mut self, from: &str) -> Result<Value, EvalError> {
492
515
  if from.starts_with("tish:") {
493
- return Self::load_builtin_module(from);
516
+ return self.load_builtin_module(from);
494
517
  }
495
518
  let dir = self.current_dir.borrow().clone().ok_or_else(|| {
496
519
  EvalError::Error("Cannot resolve imports: no current file directory (use run_file)".to_string())
@@ -536,7 +559,7 @@ impl Evaluator {
536
559
  let _ = self.eval_statement(stmt);
537
560
  }
538
561
  }
539
- let mut exports: HashMap<Arc<str>, Value> = HashMap::new();
562
+ let mut exports: PropMap = PropMap::default();
540
563
  for name in export_names {
541
564
  if let Some(v) = module_scope.borrow().get(&name) {
542
565
  exports.insert(Arc::from(name.as_str()), v);
@@ -570,13 +593,16 @@ impl Evaluator {
570
593
  Ok(path)
571
594
  }
572
595
 
573
- /// Load built-in module (tish:fs, tish:http, tish:process). Features auto-enabled when imported.
574
- fn load_builtin_module(spec: &str) -> Result<Value, EvalError> {
596
+ /// Load built-in module (tish:fs, tish:http, tish:process, …) or a virtual module from native crates.
597
+ fn load_builtin_module(&self, spec: &str) -> Result<Value, EvalError> {
598
+ if let Some(v) = self.virtual_builtins.borrow().get(spec) {
599
+ return Ok(v.clone());
600
+ }
575
601
  match spec {
576
602
  "tish:fs" => {
577
603
  #[cfg(feature = "fs")]
578
604
  {
579
- let mut exports: HashMap<Arc<str>, Value> = HashMap::new();
605
+ let mut exports: PropMap = PropMap::default();
580
606
  exports.insert("readFile".into(), Value::Native(natives::read_file));
581
607
  exports.insert("writeFile".into(), Value::Native(natives::write_file));
582
608
  exports.insert("fileExists".into(), Value::Native(natives::file_exists));
@@ -595,7 +621,7 @@ impl Evaluator {
595
621
  "tish:http" => {
596
622
  #[cfg(feature = "http")]
597
623
  {
598
- let mut exports: HashMap<Arc<str>, Value> = HashMap::new();
624
+ let mut exports: PropMap = PropMap::default();
599
625
  exports.insert("fetch".into(), Value::Native(Self::fetch_native));
600
626
  exports.insert("fetchAll".into(), Value::Native(Self::fetch_all_native));
601
627
  exports.insert("serve".into(), Value::Serve);
@@ -616,7 +642,7 @@ impl Evaluator {
616
642
  "tish:ws" => {
617
643
  #[cfg(feature = "ws")]
618
644
  {
619
- let mut exports: HashMap<Arc<str>, Value> = HashMap::new();
645
+ let mut exports: PropMap = PropMap::default();
620
646
  exports.insert("WebSocket".into(), Value::Native(Self::ws_web_socket_native));
621
647
  exports.insert("Server".into(), Value::Native(Self::ws_server_native));
622
648
  exports.insert("wsSend".into(), Value::Native(Self::ws_send_native));
@@ -633,7 +659,7 @@ impl Evaluator {
633
659
  "tish:process" => {
634
660
  #[cfg(feature = "process")]
635
661
  {
636
- let mut exports: HashMap<Arc<str>, Value> = HashMap::new();
662
+ let mut exports: PropMap = PropMap::default();
637
663
  exports.insert("exit".into(), Value::Native(natives::process_exit));
638
664
  exports.insert("cwd".into(), Value::Native(natives::process_cwd));
639
665
  exports.insert("exec".into(), Value::Native(natives::process_exec));
@@ -641,11 +667,11 @@ impl Evaluator {
641
667
  .map(|s| Value::String(s.into()))
642
668
  .collect();
643
669
  exports.insert("argv".into(), Value::Array(Rc::new(RefCell::new(argv.clone()))));
644
- let env_obj: HashMap<Arc<str>, Value> = std::env::vars()
670
+ let env_obj: PropMap = std::env::vars()
645
671
  .map(|(key, value)| (Arc::from(key.as_str()), Value::String(value.into())))
646
672
  .collect();
647
673
  exports.insert("env".into(), Value::Object(Rc::new(RefCell::new(env_obj.clone()))));
648
- let mut process_obj = HashMap::new();
674
+ let mut process_obj = PropMap::default();
649
675
  process_obj.insert("exit".into(), Value::Native(natives::process_exit));
650
676
  process_obj.insert("cwd".into(), Value::Native(natives::process_cwd));
651
677
  process_obj.insert("exec".into(), Value::Native(natives::process_exec));
@@ -663,15 +689,15 @@ impl Evaluator {
663
689
  }
664
690
  _ => {
665
691
  return Err(EvalError::Error(format!(
666
- "Unknown built-in module: {}. Supported: tish:fs, tish:http, tish:process, tish:ws",
692
+ "Unknown built-in module: {}. Supported: tish:fs, tish:http, tish:process, tish:ws (plus any registered by native modules)",
667
693
  spec
668
694
  )));
669
695
  }
670
696
  }
671
697
  }
672
698
 
673
- fn load_builtin_export(spec: &str, export_name: &str) -> Result<Value, EvalError> {
674
- let module = Self::load_builtin_module(spec)?;
699
+ fn load_builtin_export(&self, spec: &str, export_name: &str) -> Result<Value, EvalError> {
700
+ let module = self.load_builtin_module(spec)?;
675
701
  let exports = match &module {
676
702
  Value::Object(m) => m.borrow().clone(),
677
703
  _ => return Err(EvalError::Error("Built-in module must be object".into())),
@@ -1534,7 +1560,7 @@ impl Evaluator {
1534
1560
  Ok(Value::Array(Rc::new(RefCell::new(vals))))
1535
1561
  }
1536
1562
  Expr::Object { props, .. } => {
1537
- let mut map = HashMap::new();
1563
+ let mut map = PropMap::default();
1538
1564
  for prop in props {
1539
1565
  match prop {
1540
1566
  tishlang_ast::ObjectProp::KeyValue(k, v) => {
@@ -1561,11 +1587,16 @@ impl Evaluator {
1561
1587
  }
1562
1588
  }
1563
1589
  Expr::Await { operand, .. } => self.eval_await(operand),
1590
+ Expr::New { callee, args, .. } => {
1591
+ let c = self.eval_expr(callee)?;
1592
+ let arg_vals = self.eval_call_args(args)?;
1593
+ self.construct_value(&c, &arg_vals)
1594
+ }
1564
1595
  Expr::JsxElement { .. } | Expr::JsxFragment { .. } => Err(EvalError::Error(
1565
1596
  "JSX is not supported in the interpreter. Use 'tish compile --target js' to compile to JavaScript.".to_string(),
1566
1597
  )),
1567
1598
  Expr::NativeModuleLoad { spec, export_name, .. } => {
1568
- Self::load_builtin_export(spec.as_ref(), export_name.as_ref())
1599
+ self.load_builtin_export(spec.as_ref(), export_name.as_ref())
1569
1600
  }
1570
1601
  Expr::TypeOf { operand, .. } => {
1571
1602
  let v = self.eval_expr(operand)?;
@@ -1577,7 +1608,6 @@ impl Evaluator {
1577
1608
  Value::Array(_) => "object".into(),
1578
1609
  Value::Object(_) => "object".into(),
1579
1610
  Value::Function { .. } | Value::Native(_) => "function".into(),
1580
- #[cfg(any(feature = "http", feature = "ws"))]
1581
1611
  Value::CoreFn(_) => "function".into(),
1582
1612
  #[cfg(feature = "http")]
1583
1613
  Value::CorePromise(_) => "object".into(),
@@ -1755,9 +1785,7 @@ impl Evaluator {
1755
1785
  }
1756
1786
  Expr::ArrowFunction { params, body, .. } => {
1757
1787
  use tishlang_ast::ArrowBody;
1758
- // Convert arrow function to regular function using Arc for cheap cloning
1759
- let param_names: Arc<[Arc<str>]> = params.iter().map(|p| Arc::clone(&p.name)).collect();
1760
- let defaults: Arc<[Option<tishlang_ast::Expr>]> = params.iter().map(|p| p.default.clone()).collect();
1788
+ let formals: Arc<[FunParam]> = Arc::from(params.clone());
1761
1789
  let body_stmt = match body {
1762
1790
  ArrowBody::Expr(expr) => {
1763
1791
  // Expression body: wrap in implicit return
@@ -1769,8 +1797,7 @@ impl Evaluator {
1769
1797
  ArrowBody::Block(stmt) => stmt.as_ref().clone(),
1770
1798
  };
1771
1799
  Ok(Value::Function {
1772
- params: param_names,
1773
- defaults,
1800
+ formals,
1774
1801
  rest_param: None,
1775
1802
  body: Arc::new(body_stmt),
1776
1803
  })
@@ -1862,18 +1889,21 @@ impl Evaluator {
1862
1889
  /// descending = false: checks for `(a, b) => a - b`
1863
1890
  /// descending = true: checks for `(a, b) => b - a`
1864
1891
  fn is_numeric_sort_comparator(f: &Value, descending: bool) -> bool {
1865
- if let Value::Function { params, body, defaults, rest_param } = f {
1866
- // Must have exactly 2 params, no defaults, no rest
1867
- if params.len() != 2 || rest_param.is_some() {
1868
- return false;
1869
- }
1870
- if defaults.iter().any(|d| d.is_some()) {
1892
+ if let Value::Function { formals, body, rest_param, .. } = f {
1893
+ // Must have exactly 2 simple params, no defaults, no rest
1894
+ if formals.len() != 2 || rest_param.is_some() {
1871
1895
  return false;
1872
1896
  }
1897
+ let (param_a, param_b) = match (&formals[0], &formals[1]) {
1898
+ (FunParam::Simple(a), FunParam::Simple(b))
1899
+ if a.default.is_none() && b.default.is_none() =>
1900
+ {
1901
+ (&a.name, &b.name)
1902
+ }
1903
+ _ => return false,
1904
+ };
1873
1905
 
1874
1906
  // Body must be a return of a - b (or b - a for descending)
1875
- let param_a = &params[0];
1876
- let param_b = &params[1];
1877
1907
 
1878
1908
  // Check for both Statement::Return and Statement::ExprStmt (arrow implicit return)
1879
1909
  let expr = match body.as_ref() {
@@ -1947,20 +1977,32 @@ impl Evaluator {
1947
1977
  /// Optimized callback invocation for array methods.
1948
1978
  /// Creates a reusable scope that can be updated for each iteration.
1949
1979
  fn create_callback_scope(&self, f: &Value) -> Option<(Rc<RefCell<Scope>>, Arc<[Arc<str>]>, Arc<Statement>)> {
1950
- if let Value::Function { params, body, defaults, rest_param } = f {
1951
- // Only optimize simple cases: no defaults used, no rest params
1952
- if rest_param.is_some() || defaults.iter().any(|d| d.is_some()) {
1980
+ if let Value::Function { formals, body, rest_param, .. } = f {
1981
+ if rest_param.is_some() {
1953
1982
  return None;
1954
1983
  }
1984
+ for fp in formals.iter() {
1985
+ match fp {
1986
+ FunParam::Simple(tp) if tp.default.is_none() => {}
1987
+ _ => return None,
1988
+ }
1989
+ }
1955
1990
  let scope = Scope::child(Rc::clone(&self.scope));
1956
- // Pre-initialize parameters to Null
1957
1991
  {
1958
1992
  let mut s = scope.borrow_mut();
1959
- for p in params.iter() {
1960
- s.set(Arc::clone(p), Value::Null, true);
1993
+ for fp in formals.iter() {
1994
+ for n in fp.bound_names() {
1995
+ s.set(n, Value::Null, true);
1996
+ }
1961
1997
  }
1962
1998
  }
1963
- return Some((scope, Arc::clone(params), Arc::clone(body)));
1999
+ let flat_names: Arc<[Arc<str>]> = Arc::from(
2000
+ formals
2001
+ .iter()
2002
+ .flat_map(|fp| fp.bound_names())
2003
+ .collect::<Vec<_>>(),
2004
+ );
2005
+ return Some((scope, flat_names, Arc::clone(body)));
1964
2006
  }
1965
2007
  None
1966
2008
  }
@@ -1987,6 +2029,7 @@ impl Evaluator {
1987
2029
  scope: Rc::clone(scope),
1988
2030
  module_cache: Rc::clone(&self.module_cache),
1989
2031
  current_dir: RefCell::new(self.current_dir.borrow().clone()),
2032
+ virtual_builtins: Rc::clone(&self.virtual_builtins),
1990
2033
  };
1991
2034
  match eval.eval_statement(body) {
1992
2035
  Ok(v) => Ok(v),
@@ -2002,12 +2045,14 @@ impl Evaluator {
2002
2045
  f: &Value,
2003
2046
  args: &[Value],
2004
2047
  ) -> Option<Result<Value, EvalError>> {
2005
- if let Value::Function { params, body, defaults, rest_param } = f {
2006
- // Only optimize single-parameter functions without defaults or rest
2007
- if params.len() != 1 || rest_param.is_some() || defaults.iter().any(|d| d.is_some()) {
2048
+ if let Value::Function { formals, body, rest_param, .. } = f {
2049
+ if formals.len() != 1 || rest_param.is_some() {
2008
2050
  return None;
2009
2051
  }
2010
- let param_name = &params[0];
2052
+ let param_name = match &formals[0] {
2053
+ FunParam::Simple(tp) if tp.default.is_none() => &tp.name,
2054
+ _ => return None,
2055
+ };
2011
2056
  let arg = args.first().cloned().unwrap_or(Value::Null);
2012
2057
 
2013
2058
  // Get the expression from the body
@@ -2078,6 +2123,27 @@ impl Evaluator {
2078
2123
  }
2079
2124
  }
2080
2125
 
2126
+ /// Host `new`: `__construct` on objects; otherwise same callables as `call_func`, else null.
2127
+ fn construct_value(&self, callee: &Value, args: &[Value]) -> Result<Value, EvalError> {
2128
+ if let Value::Object(o) = callee {
2129
+ if let Some(ctor) = o.borrow().get(&Arc::from("__construct")) {
2130
+ return self.call_func(ctor, args);
2131
+ }
2132
+ }
2133
+ match callee {
2134
+ Value::Native(_) | Value::Function { .. } | Value::CoreFn(_) => {
2135
+ self.call_func(callee, args)
2136
+ }
2137
+ #[cfg(feature = "http")]
2138
+ Value::PromiseConstructor
2139
+ | Value::Serve
2140
+ | Value::BoundPromiseMethod(_, _)
2141
+ | Value::TimerBuiltin(_) => self.call_func(callee, args),
2142
+ Value::OpaqueMethod(_, _) => self.call_func(callee, args),
2143
+ _ => Ok(Value::Null),
2144
+ }
2145
+ }
2146
+
2081
2147
  fn call_func(&self, f: &Value, args: &[Value]) -> Result<Value, EvalError> {
2082
2148
  match f {
2083
2149
  Value::Native(native_fn) => {
@@ -2142,7 +2208,6 @@ impl Evaluator {
2142
2208
  }
2143
2209
  #[cfg(feature = "http")]
2144
2210
  Value::Serve => self.run_http_server(args),
2145
- #[cfg(any(feature = "http", feature = "ws"))]
2146
2211
  Value::CoreFn(f) => {
2147
2212
  let ca: Result<Vec<tishlang_core::Value>, String> =
2148
2213
  args.iter().map(crate::value_convert::eval_to_core).collect();
@@ -2167,15 +2232,19 @@ impl Evaluator {
2167
2232
  let result = method(&core_args);
2168
2233
  Ok(crate::value_convert::core_to_eval(result))
2169
2234
  }
2170
- Value::Function { params, defaults, rest_param, body } => {
2235
+ Value::Function { formals, rest_param, body } => {
2171
2236
  let scope = Scope::child(Rc::clone(&self.scope));
2172
2237
  {
2173
2238
  let mut s = scope.borrow_mut();
2174
- for (i, p) in params.iter().enumerate() {
2239
+ for (i, formal) in formals.iter().enumerate() {
2175
2240
  let val = match args.get(i) {
2176
2241
  Some(v) => v.clone(),
2177
2242
  None => {
2178
- if let Some(Some(default_expr)) = defaults.get(i) {
2243
+ let def = match formal {
2244
+ FunParam::Simple(tp) => tp.default.as_ref(),
2245
+ FunParam::Destructure { default, .. } => default.as_ref(),
2246
+ };
2247
+ if let Some(default_expr) = def {
2179
2248
  drop(s);
2180
2249
  let default_val = self.eval_expr(default_expr)?;
2181
2250
  s = scope.borrow_mut();
@@ -2185,10 +2254,19 @@ impl Evaluator {
2185
2254
  }
2186
2255
  }
2187
2256
  };
2188
- s.set(Arc::clone(p), val, true);
2257
+ match formal {
2258
+ FunParam::Simple(tp) => {
2259
+ s.set(Arc::clone(&tp.name), val, true);
2260
+ }
2261
+ FunParam::Destructure { pattern, .. } => {
2262
+ drop(s);
2263
+ Self::bind_destruct_pattern_scoped(&scope, pattern, &val, true)?;
2264
+ s = scope.borrow_mut();
2265
+ }
2266
+ }
2189
2267
  }
2190
2268
  if let Some(ref rest_name) = rest_param {
2191
- let rest_vals: Vec<Value> = args.iter().skip(params.len()).cloned().collect();
2269
+ let rest_vals: Vec<Value> = args.iter().skip(formals.len()).cloned().collect();
2192
2270
  s.set(Arc::clone(rest_name), Value::Array(Rc::new(RefCell::new(rest_vals))), true);
2193
2271
  }
2194
2272
  }
@@ -2196,6 +2274,7 @@ impl Evaluator {
2196
2274
  scope,
2197
2275
  module_cache: Rc::clone(&self.module_cache),
2198
2276
  current_dir: RefCell::new(self.current_dir.borrow().clone()),
2277
+ virtual_builtins: Rc::clone(&self.virtual_builtins),
2199
2278
  };
2200
2279
  match eval.eval_statement(body) {
2201
2280
  Ok(v) => Ok(v),
@@ -2449,13 +2528,13 @@ impl Evaluator {
2449
2528
  let response_value = match self.call_func(&handler, &[req_value]) {
2450
2529
  Ok(v) => v,
2451
2530
  Err(EvalError::Throw(v)) => {
2452
- let mut err_obj: HashMap<Arc<str>, Value> = HashMap::with_capacity(2);
2531
+ let mut err_obj: PropMap = PropMap::with_capacity(2);
2453
2532
  err_obj.insert(Arc::from("status"), Value::Number(500.0));
2454
2533
  err_obj.insert(Arc::from("body"), Value::String(v.to_string().into()));
2455
2534
  Value::Object(Rc::new(RefCell::new(err_obj)))
2456
2535
  }
2457
2536
  Err(e) => {
2458
- let mut err_obj: HashMap<Arc<str>, Value> = HashMap::with_capacity(2);
2537
+ let mut err_obj: PropMap = PropMap::with_capacity(2);
2459
2538
  err_obj.insert(Arc::from("status"), Value::Number(500.0));
2460
2539
  err_obj.insert(Arc::from("body"), Value::String(e.to_string().into()));
2461
2540
  Value::Object(Rc::new(RefCell::new(err_obj)))
@@ -2491,28 +2570,37 @@ impl Evaluator {
2491
2570
  Ok(result)
2492
2571
  }
2493
2572
 
2494
- fn bind_destruct_pattern(&mut self, pattern: &tishlang_ast::DestructPattern, value: &Value, mutable: bool) -> Result<(), EvalError> {
2573
+ fn bind_destruct_pattern_scoped(
2574
+ scope: &Rc<RefCell<Scope>>,
2575
+ pattern: &tishlang_ast::DestructPattern,
2576
+ value: &Value,
2577
+ mutable: bool,
2578
+ ) -> Result<(), EvalError> {
2495
2579
  match pattern {
2496
2580
  tishlang_ast::DestructPattern::Array(elements) => {
2497
2581
  let arr = match value {
2498
2582
  Value::Array(a) => a.borrow().clone(),
2499
2583
  _ => return Err(EvalError::Error("Cannot destructure non-array value".to_string())),
2500
2584
  };
2501
-
2585
+
2502
2586
  for (i, elem) in elements.iter().enumerate() {
2503
2587
  if let Some(el) = elem {
2504
2588
  match el {
2505
2589
  tishlang_ast::DestructElement::Ident(name) => {
2506
2590
  let val = arr.get(i).cloned().unwrap_or(Value::Null);
2507
- self.scope.borrow_mut().set(Arc::clone(name), val, mutable);
2591
+ scope.borrow_mut().set(Arc::clone(name), val, mutable);
2508
2592
  }
2509
2593
  tishlang_ast::DestructElement::Pattern(nested) => {
2510
2594
  let val = arr.get(i).cloned().unwrap_or(Value::Null);
2511
- self.bind_destruct_pattern(nested, &val, mutable)?;
2595
+ Self::bind_destruct_pattern_scoped(scope, nested, &val, mutable)?;
2512
2596
  }
2513
2597
  tishlang_ast::DestructElement::Rest(name) => {
2514
2598
  let rest: Vec<Value> = arr.iter().skip(i).cloned().collect();
2515
- self.scope.borrow_mut().set(Arc::clone(name), Value::Array(Rc::new(RefCell::new(rest))), mutable);
2599
+ scope.borrow_mut().set(
2600
+ Arc::clone(name),
2601
+ Value::Array(Rc::new(RefCell::new(rest))),
2602
+ mutable,
2603
+ );
2516
2604
  break;
2517
2605
  }
2518
2606
  }
@@ -2524,18 +2612,20 @@ impl Evaluator {
2524
2612
  Value::Object(o) => o.borrow().clone(),
2525
2613
  _ => return Err(EvalError::Error("Cannot destructure non-object value".to_string())),
2526
2614
  };
2527
-
2615
+
2528
2616
  for prop in props {
2529
2617
  let val = obj.get(&prop.key).cloned().unwrap_or(Value::Null);
2530
2618
  match &prop.value {
2531
2619
  tishlang_ast::DestructElement::Ident(name) => {
2532
- self.scope.borrow_mut().set(Arc::clone(name), val, mutable);
2620
+ scope.borrow_mut().set(Arc::clone(name), val, mutable);
2533
2621
  }
2534
2622
  tishlang_ast::DestructElement::Pattern(nested) => {
2535
- self.bind_destruct_pattern(nested, &val, mutable)?;
2623
+ Self::bind_destruct_pattern_scoped(scope, nested, &val, mutable)?;
2536
2624
  }
2537
2625
  tishlang_ast::DestructElement::Rest(_) => {
2538
- return Err(EvalError::Error("Rest not supported in object destructuring".to_string()));
2626
+ return Err(EvalError::Error(
2627
+ "Rest not supported in object destructuring".to_string(),
2628
+ ));
2539
2629
  }
2540
2630
  }
2541
2631
  }
@@ -2544,6 +2634,10 @@ impl Evaluator {
2544
2634
  Ok(())
2545
2635
  }
2546
2636
 
2637
+ fn bind_destruct_pattern(&mut self, pattern: &tishlang_ast::DestructPattern, value: &Value, mutable: bool) -> Result<(), EvalError> {
2638
+ Self::bind_destruct_pattern_scoped(&self.scope, pattern, value, mutable)
2639
+ }
2640
+
2547
2641
  fn get_prop(&self, obj: &Value, key: &str) -> Result<Value, String> {
2548
2642
  match obj {
2549
2643
  Value::Object(map) => Ok(map.borrow().get(key).cloned().unwrap_or(Value::Null)),
@@ -2745,9 +2839,9 @@ impl Evaluator {
2745
2839
  fn json_parse_object(s: &str) -> Result<Value, ()> {
2746
2840
  let s = s[1..].trim_start();
2747
2841
  if s.starts_with('}') {
2748
- return Ok(Value::Object(Rc::new(RefCell::new(HashMap::new()))));
2842
+ return Ok(Value::Object(Rc::new(RefCell::new(PropMap::default()))));
2749
2843
  }
2750
- let mut map = HashMap::new();
2844
+ let mut map = PropMap::default();
2751
2845
  let mut rest = s;
2752
2846
  loop {
2753
2847
  if !rest.starts_with('"') {
@@ -2873,7 +2967,6 @@ impl Evaluator {
2873
2967
  Value::Function { .. } | Value::Native(_) => "null".to_string(),
2874
2968
  #[cfg(feature = "http")]
2875
2969
  Value::CorePromise(_) => "null".to_string(),
2876
- #[cfg(any(feature = "http", feature = "ws"))]
2877
2970
  Value::CoreFn(_) => "null".to_string(),
2878
2971
  #[cfg(feature = "http")]
2879
2972
  Value::Serve
@@ -2949,7 +3042,7 @@ impl Evaluator {
2949
3042
 
2950
3043
  fn object_from_entries(args: &[Value]) -> Result<Value, String> {
2951
3044
  if let Some(Value::Array(arr)) = args.first() {
2952
- let mut map = HashMap::new();
3045
+ let mut map = PropMap::default();
2953
3046
  for entry in arr.borrow().iter() {
2954
3047
  if let Value::Array(pair) = entry {
2955
3048
  let pair = pair.borrow();
@@ -2961,7 +3054,7 @@ impl Evaluator {
2961
3054
  }
2962
3055
  Ok(Value::Object(Rc::new(RefCell::new(map))))
2963
3056
  } else {
2964
- Ok(Value::Object(Rc::new(RefCell::new(HashMap::new()))))
3057
+ Ok(Value::Object(Rc::new(RefCell::new(PropMap::default()))))
2965
3058
  }
2966
3059
  }
2967
3060
 
@@ -1,7 +1,6 @@
1
1
  //! HTTP server for the Tish interpreter. Client `fetch` uses `tishlang_runtime` from eval.
2
2
 
3
- use crate::value::Value;
4
- use std::collections::HashMap;
3
+ use crate::value::{PropMap, Value};
5
4
  use std::sync::Arc;
6
5
 
7
6
  use tokio::runtime::Runtime;
@@ -22,7 +21,7 @@ pub fn create_server(port: u16) -> Result<tiny_http::Server, String> {
22
21
 
23
22
  /// Convert a tiny_http::Request into a Tish Value object.
24
23
  pub fn request_to_value(request: &mut tiny_http::Request) -> Value {
25
- let mut obj: HashMap<Arc<str>, Value> = HashMap::with_capacity(6);
24
+ let mut obj: PropMap = PropMap::with_capacity(6);
26
25
 
27
26
  obj.insert(
28
27
  Arc::from("method"),
@@ -39,7 +38,7 @@ pub fn request_to_value(request: &mut tiny_http::Request) -> Value {
39
38
  let query_string = request.url().split('?').nth(1).unwrap_or("");
40
39
  obj.insert(Arc::from("query"), Value::String(query_string.into()));
41
40
 
42
- let mut headers_obj: HashMap<Arc<str>, Value> = HashMap::with_capacity(request.headers().len());
41
+ let mut headers_obj: PropMap = PropMap::with_capacity(request.headers().len());
43
42
  for header in request.headers() {
44
43
  headers_obj.insert(
45
44
  Arc::from(header.field.as_str().as_str()),
@@ -14,6 +14,7 @@ pub mod regex;
14
14
  mod value;
15
15
 
16
16
  pub use eval::Evaluator;
17
+ pub use value::PropMap;
17
18
  pub use value::Value;
18
19
 
19
20
  /// Trait for pluggable native modules (e.g. Polars). Implement to register
@@ -21,6 +22,12 @@ pub use value::Value;
21
22
  pub trait TishNativeModule: Send + Sync {
22
23
  fn name(&self) -> &'static str;
23
24
  fn register(&self) -> std::collections::HashMap<std::sync::Arc<str>, Value>;
25
+
26
+ /// Virtual `tish:*` modules for `import { x } from 'tish:…'` (e.g. `tish:polars`).
27
+ /// Return `(specifier, exports_object)` pairs. Default: none.
28
+ fn virtual_builtin_modules(&self) -> Vec<(&'static str, Value)> {
29
+ vec![]
30
+ }
24
31
  }
25
32
  #[cfg(feature = "regex")]
26
33
  pub use regex::TishRegExp;