@tishlang/tish 1.0.29 → 1.0.33

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 (60) 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 +233 -71
  17. package/crates/tish_compile/src/lib.rs +35 -0
  18. package/crates/tish_compile_js/Cargo.toml +1 -0
  19. package/crates/tish_compile_js/src/codegen.rs +38 -94
  20. package/crates/tish_compile_js/src/lib.rs +0 -1
  21. package/crates/tish_compile_js/src/tests_jsx.rs +68 -0
  22. package/crates/tish_core/Cargo.toml +4 -0
  23. package/crates/tish_core/src/console_style.rs +7 -1
  24. package/crates/tish_core/src/json.rs +1 -2
  25. package/crates/tish_core/src/macros.rs +2 -3
  26. package/crates/tish_core/src/value.rs +10 -5
  27. package/crates/tish_eval/Cargo.toml +2 -0
  28. package/crates/tish_eval/src/eval.rs +149 -72
  29. package/crates/tish_eval/src/http.rs +3 -4
  30. package/crates/tish_eval/src/regex.rs +3 -2
  31. package/crates/tish_eval/src/value.rs +11 -13
  32. package/crates/tish_eval/src/value_convert.rs +4 -8
  33. package/crates/tish_fmt/src/lib.rs +49 -10
  34. package/crates/tish_lexer/src/token.rs +2 -0
  35. package/crates/tish_lint/src/lib.rs +9 -0
  36. package/crates/tish_lsp/README.md +1 -1
  37. package/crates/tish_native/src/build.rs +16 -2
  38. package/crates/tish_opt/src/lib.rs +15 -0
  39. package/crates/tish_parser/src/lib.rs +101 -1
  40. package/crates/tish_parser/src/parser.rs +161 -50
  41. package/crates/tish_runtime/src/http.rs +4 -5
  42. package/crates/tish_runtime/src/http_fetch.rs +9 -10
  43. package/crates/tish_runtime/src/lib.rs +9 -2
  44. package/crates/tish_runtime/src/promise.rs +2 -3
  45. package/crates/tish_runtime/src/promise_io.rs +2 -3
  46. package/crates/tish_runtime/src/ws.rs +7 -7
  47. package/crates/tish_ui/Cargo.toml +17 -0
  48. package/crates/tish_ui/src/jsx.rs +390 -0
  49. package/crates/tish_ui/src/lib.rs +16 -0
  50. package/crates/tish_ui/src/runtime/hooks.rs +122 -0
  51. package/crates/tish_ui/src/runtime/mod.rs +173 -0
  52. package/crates/tish_vm/src/vm.rs +121 -27
  53. package/justfile +3 -3
  54. package/package.json +1 -1
  55. package/platform/darwin-arm64/tish +0 -0
  56. package/platform/darwin-x64/tish +0 -0
  57. package/platform/linux-arm64/tish +0 -0
  58. package/platform/linux-x64/tish +0 -0
  59. package/platform/win32-x64/tish.exe +0 -0
  60. 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
  }))
@@ -85,7 +85,7 @@ impl Evaluator {
85
85
  let scope = Scope::new();
86
86
  {
87
87
  let mut s = scope.borrow_mut();
88
- let mut console = HashMap::with_capacity(5);
88
+ let mut console = PropMap::with_capacity(5);
89
89
  console.insert("debug".into(), Value::Native(natives::console_debug));
90
90
  console.insert("info".into(), Value::Native(natives::console_info));
91
91
  console.insert("log".into(), Value::Native(natives::console_log));
@@ -101,7 +101,7 @@ impl Evaluator {
101
101
  s.set("isNaN".into(), Value::Native(natives::is_nan), true);
102
102
  s.set("Infinity".into(), Value::Number(f64::INFINITY), true);
103
103
  s.set("NaN".into(), Value::Number(f64::NAN), true);
104
- let mut math = HashMap::with_capacity(18);
104
+ let mut math = PropMap::with_capacity(18);
105
105
  math.insert("abs".into(), Value::Native(natives::math_abs));
106
106
  math.insert("sqrt".into(), Value::Native(natives::math_sqrt));
107
107
  math.insert("min".into(), Value::Native(natives::math_min));
@@ -122,12 +122,12 @@ impl Evaluator {
122
122
  math.insert("E".into(), Value::Number(std::f64::consts::E));
123
123
  s.set("Math".into(), Value::Object(Rc::new(RefCell::new(math))), true);
124
124
 
125
- let mut json = HashMap::with_capacity(2);
125
+ let mut json = PropMap::with_capacity(2);
126
126
  json.insert("parse".into(), Value::Native(Self::json_parse_native));
127
127
  json.insert("stringify".into(), Value::Native(Self::json_stringify_native));
128
128
  s.set("JSON".into(), Value::Object(Rc::new(RefCell::new(json))), true);
129
129
 
130
- let mut object = HashMap::with_capacity(5);
130
+ let mut object = PropMap::with_capacity(5);
131
131
  object.insert("keys".into(), Value::Native(Self::object_keys));
132
132
  object.insert("values".into(), Value::Native(Self::object_values));
133
133
  object.insert("entries".into(), Value::Native(Self::object_entries));
@@ -135,18 +135,33 @@ impl Evaluator {
135
135
  object.insert("fromEntries".into(), Value::Native(Self::object_from_entries));
136
136
  s.set("Object".into(), Value::Object(Rc::new(RefCell::new(object))), true);
137
137
 
138
- let mut array_obj = HashMap::with_capacity(1);
138
+ let mut array_obj = PropMap::with_capacity(1);
139
139
  array_obj.insert("isArray".into(), Value::Native(natives::array_is_array));
140
140
  s.set("Array".into(), Value::Object(Rc::new(RefCell::new(array_obj))), true);
141
141
 
142
- let mut string_obj = HashMap::with_capacity(1);
142
+ let mut string_obj = PropMap::with_capacity(1);
143
143
  string_obj.insert("fromCharCode".into(), Value::Native(natives::string_from_char_code));
144
144
  s.set("String".into(), Value::Object(Rc::new(RefCell::new(string_obj))), true);
145
145
 
146
- let mut date = HashMap::with_capacity(1);
146
+ let mut date = PropMap::with_capacity(1);
147
147
  date.insert("now".into(), Value::Native(natives::date_now));
148
148
  s.set("Date".into(), Value::Object(Rc::new(RefCell::new(date))), true);
149
149
 
150
+ s.set(
151
+ "Uint8Array".into(),
152
+ crate::value_convert::core_to_eval(
153
+ tishlang_builtins::construct::uint8_array_constructor_value(),
154
+ ),
155
+ true,
156
+ );
157
+ s.set(
158
+ "AudioContext".into(),
159
+ crate::value_convert::core_to_eval(
160
+ tishlang_builtins::construct::audio_context_constructor_value(),
161
+ ),
162
+ true,
163
+ );
164
+
150
165
  #[cfg(feature = "regex")]
151
166
  {
152
167
  s.set("RegExp".into(), Value::Native(Self::regexp_constructor_native), true);
@@ -323,14 +338,11 @@ impl Evaluator {
323
338
  body,
324
339
  ..
325
340
  } => {
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();
341
+ let formals: Arc<[FunParam]> = Arc::from(params.clone());
329
342
  let rest_param_name = rest_param.as_ref().map(|p| Arc::clone(&p.name));
330
343
  let body = Arc::new(body.as_ref().clone());
331
344
  let func = Value::Function {
332
- params: param_names,
333
- defaults,
345
+ formals,
334
346
  rest_param: rest_param_name,
335
347
  body,
336
348
  };
@@ -536,7 +548,7 @@ impl Evaluator {
536
548
  let _ = self.eval_statement(stmt);
537
549
  }
538
550
  }
539
- let mut exports: HashMap<Arc<str>, Value> = HashMap::new();
551
+ let mut exports: PropMap = PropMap::default();
540
552
  for name in export_names {
541
553
  if let Some(v) = module_scope.borrow().get(&name) {
542
554
  exports.insert(Arc::from(name.as_str()), v);
@@ -576,7 +588,7 @@ impl Evaluator {
576
588
  "tish:fs" => {
577
589
  #[cfg(feature = "fs")]
578
590
  {
579
- let mut exports: HashMap<Arc<str>, Value> = HashMap::new();
591
+ let mut exports: PropMap = PropMap::default();
580
592
  exports.insert("readFile".into(), Value::Native(natives::read_file));
581
593
  exports.insert("writeFile".into(), Value::Native(natives::write_file));
582
594
  exports.insert("fileExists".into(), Value::Native(natives::file_exists));
@@ -595,7 +607,7 @@ impl Evaluator {
595
607
  "tish:http" => {
596
608
  #[cfg(feature = "http")]
597
609
  {
598
- let mut exports: HashMap<Arc<str>, Value> = HashMap::new();
610
+ let mut exports: PropMap = PropMap::default();
599
611
  exports.insert("fetch".into(), Value::Native(Self::fetch_native));
600
612
  exports.insert("fetchAll".into(), Value::Native(Self::fetch_all_native));
601
613
  exports.insert("serve".into(), Value::Serve);
@@ -616,7 +628,7 @@ impl Evaluator {
616
628
  "tish:ws" => {
617
629
  #[cfg(feature = "ws")]
618
630
  {
619
- let mut exports: HashMap<Arc<str>, Value> = HashMap::new();
631
+ let mut exports: PropMap = PropMap::default();
620
632
  exports.insert("WebSocket".into(), Value::Native(Self::ws_web_socket_native));
621
633
  exports.insert("Server".into(), Value::Native(Self::ws_server_native));
622
634
  exports.insert("wsSend".into(), Value::Native(Self::ws_send_native));
@@ -633,7 +645,7 @@ impl Evaluator {
633
645
  "tish:process" => {
634
646
  #[cfg(feature = "process")]
635
647
  {
636
- let mut exports: HashMap<Arc<str>, Value> = HashMap::new();
648
+ let mut exports: PropMap = PropMap::default();
637
649
  exports.insert("exit".into(), Value::Native(natives::process_exit));
638
650
  exports.insert("cwd".into(), Value::Native(natives::process_cwd));
639
651
  exports.insert("exec".into(), Value::Native(natives::process_exec));
@@ -641,11 +653,11 @@ impl Evaluator {
641
653
  .map(|s| Value::String(s.into()))
642
654
  .collect();
643
655
  exports.insert("argv".into(), Value::Array(Rc::new(RefCell::new(argv.clone()))));
644
- let env_obj: HashMap<Arc<str>, Value> = std::env::vars()
656
+ let env_obj: PropMap = std::env::vars()
645
657
  .map(|(key, value)| (Arc::from(key.as_str()), Value::String(value.into())))
646
658
  .collect();
647
659
  exports.insert("env".into(), Value::Object(Rc::new(RefCell::new(env_obj.clone()))));
648
- let mut process_obj = HashMap::new();
660
+ let mut process_obj = PropMap::default();
649
661
  process_obj.insert("exit".into(), Value::Native(natives::process_exit));
650
662
  process_obj.insert("cwd".into(), Value::Native(natives::process_cwd));
651
663
  process_obj.insert("exec".into(), Value::Native(natives::process_exec));
@@ -1534,7 +1546,7 @@ impl Evaluator {
1534
1546
  Ok(Value::Array(Rc::new(RefCell::new(vals))))
1535
1547
  }
1536
1548
  Expr::Object { props, .. } => {
1537
- let mut map = HashMap::new();
1549
+ let mut map = PropMap::default();
1538
1550
  for prop in props {
1539
1551
  match prop {
1540
1552
  tishlang_ast::ObjectProp::KeyValue(k, v) => {
@@ -1561,6 +1573,11 @@ impl Evaluator {
1561
1573
  }
1562
1574
  }
1563
1575
  Expr::Await { operand, .. } => self.eval_await(operand),
1576
+ Expr::New { callee, args, .. } => {
1577
+ let c = self.eval_expr(callee)?;
1578
+ let arg_vals = self.eval_call_args(args)?;
1579
+ self.construct_value(&c, &arg_vals)
1580
+ }
1564
1581
  Expr::JsxElement { .. } | Expr::JsxFragment { .. } => Err(EvalError::Error(
1565
1582
  "JSX is not supported in the interpreter. Use 'tish compile --target js' to compile to JavaScript.".to_string(),
1566
1583
  )),
@@ -1577,7 +1594,6 @@ impl Evaluator {
1577
1594
  Value::Array(_) => "object".into(),
1578
1595
  Value::Object(_) => "object".into(),
1579
1596
  Value::Function { .. } | Value::Native(_) => "function".into(),
1580
- #[cfg(any(feature = "http", feature = "ws"))]
1581
1597
  Value::CoreFn(_) => "function".into(),
1582
1598
  #[cfg(feature = "http")]
1583
1599
  Value::CorePromise(_) => "object".into(),
@@ -1755,9 +1771,7 @@ impl Evaluator {
1755
1771
  }
1756
1772
  Expr::ArrowFunction { params, body, .. } => {
1757
1773
  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();
1774
+ let formals: Arc<[FunParam]> = Arc::from(params.clone());
1761
1775
  let body_stmt = match body {
1762
1776
  ArrowBody::Expr(expr) => {
1763
1777
  // Expression body: wrap in implicit return
@@ -1769,8 +1783,7 @@ impl Evaluator {
1769
1783
  ArrowBody::Block(stmt) => stmt.as_ref().clone(),
1770
1784
  };
1771
1785
  Ok(Value::Function {
1772
- params: param_names,
1773
- defaults,
1786
+ formals,
1774
1787
  rest_param: None,
1775
1788
  body: Arc::new(body_stmt),
1776
1789
  })
@@ -1862,18 +1875,21 @@ impl Evaluator {
1862
1875
  /// descending = false: checks for `(a, b) => a - b`
1863
1876
  /// descending = true: checks for `(a, b) => b - a`
1864
1877
  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()) {
1878
+ if let Value::Function { formals, body, rest_param, .. } = f {
1879
+ // Must have exactly 2 simple params, no defaults, no rest
1880
+ if formals.len() != 2 || rest_param.is_some() {
1871
1881
  return false;
1872
1882
  }
1883
+ let (param_a, param_b) = match (&formals[0], &formals[1]) {
1884
+ (FunParam::Simple(a), FunParam::Simple(b))
1885
+ if a.default.is_none() && b.default.is_none() =>
1886
+ {
1887
+ (&a.name, &b.name)
1888
+ }
1889
+ _ => return false,
1890
+ };
1873
1891
 
1874
1892
  // 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
1893
 
1878
1894
  // Check for both Statement::Return and Statement::ExprStmt (arrow implicit return)
1879
1895
  let expr = match body.as_ref() {
@@ -1947,20 +1963,32 @@ impl Evaluator {
1947
1963
  /// Optimized callback invocation for array methods.
1948
1964
  /// Creates a reusable scope that can be updated for each iteration.
1949
1965
  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()) {
1966
+ if let Value::Function { formals, body, rest_param, .. } = f {
1967
+ if rest_param.is_some() {
1953
1968
  return None;
1954
1969
  }
1970
+ for fp in formals.iter() {
1971
+ match fp {
1972
+ FunParam::Simple(tp) if tp.default.is_none() => {}
1973
+ _ => return None,
1974
+ }
1975
+ }
1955
1976
  let scope = Scope::child(Rc::clone(&self.scope));
1956
- // Pre-initialize parameters to Null
1957
1977
  {
1958
1978
  let mut s = scope.borrow_mut();
1959
- for p in params.iter() {
1960
- s.set(Arc::clone(p), Value::Null, true);
1979
+ for fp in formals.iter() {
1980
+ for n in fp.bound_names() {
1981
+ s.set(n, Value::Null, true);
1982
+ }
1961
1983
  }
1962
1984
  }
1963
- return Some((scope, Arc::clone(params), Arc::clone(body)));
1985
+ let flat_names: Arc<[Arc<str>]> = Arc::from(
1986
+ formals
1987
+ .iter()
1988
+ .flat_map(|fp| fp.bound_names())
1989
+ .collect::<Vec<_>>(),
1990
+ );
1991
+ return Some((scope, flat_names, Arc::clone(body)));
1964
1992
  }
1965
1993
  None
1966
1994
  }
@@ -2002,12 +2030,14 @@ impl Evaluator {
2002
2030
  f: &Value,
2003
2031
  args: &[Value],
2004
2032
  ) -> 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()) {
2033
+ if let Value::Function { formals, body, rest_param, .. } = f {
2034
+ if formals.len() != 1 || rest_param.is_some() {
2008
2035
  return None;
2009
2036
  }
2010
- let param_name = &params[0];
2037
+ let param_name = match &formals[0] {
2038
+ FunParam::Simple(tp) if tp.default.is_none() => &tp.name,
2039
+ _ => return None,
2040
+ };
2011
2041
  let arg = args.first().cloned().unwrap_or(Value::Null);
2012
2042
 
2013
2043
  // Get the expression from the body
@@ -2078,6 +2108,27 @@ impl Evaluator {
2078
2108
  }
2079
2109
  }
2080
2110
 
2111
+ /// Host `new`: `__construct` on objects; otherwise same callables as `call_func`, else null.
2112
+ fn construct_value(&self, callee: &Value, args: &[Value]) -> Result<Value, EvalError> {
2113
+ if let Value::Object(o) = callee {
2114
+ if let Some(ctor) = o.borrow().get(&Arc::from("__construct")) {
2115
+ return self.call_func(ctor, args);
2116
+ }
2117
+ }
2118
+ match callee {
2119
+ Value::Native(_) | Value::Function { .. } | Value::CoreFn(_) => {
2120
+ self.call_func(callee, args)
2121
+ }
2122
+ #[cfg(feature = "http")]
2123
+ Value::PromiseConstructor
2124
+ | Value::Serve
2125
+ | Value::BoundPromiseMethod(_, _)
2126
+ | Value::TimerBuiltin(_) => self.call_func(callee, args),
2127
+ Value::OpaqueMethod(_, _) => self.call_func(callee, args),
2128
+ _ => Ok(Value::Null),
2129
+ }
2130
+ }
2131
+
2081
2132
  fn call_func(&self, f: &Value, args: &[Value]) -> Result<Value, EvalError> {
2082
2133
  match f {
2083
2134
  Value::Native(native_fn) => {
@@ -2142,7 +2193,6 @@ impl Evaluator {
2142
2193
  }
2143
2194
  #[cfg(feature = "http")]
2144
2195
  Value::Serve => self.run_http_server(args),
2145
- #[cfg(any(feature = "http", feature = "ws"))]
2146
2196
  Value::CoreFn(f) => {
2147
2197
  let ca: Result<Vec<tishlang_core::Value>, String> =
2148
2198
  args.iter().map(crate::value_convert::eval_to_core).collect();
@@ -2167,15 +2217,19 @@ impl Evaluator {
2167
2217
  let result = method(&core_args);
2168
2218
  Ok(crate::value_convert::core_to_eval(result))
2169
2219
  }
2170
- Value::Function { params, defaults, rest_param, body } => {
2220
+ Value::Function { formals, rest_param, body } => {
2171
2221
  let scope = Scope::child(Rc::clone(&self.scope));
2172
2222
  {
2173
2223
  let mut s = scope.borrow_mut();
2174
- for (i, p) in params.iter().enumerate() {
2224
+ for (i, formal) in formals.iter().enumerate() {
2175
2225
  let val = match args.get(i) {
2176
2226
  Some(v) => v.clone(),
2177
2227
  None => {
2178
- if let Some(Some(default_expr)) = defaults.get(i) {
2228
+ let def = match formal {
2229
+ FunParam::Simple(tp) => tp.default.as_ref(),
2230
+ FunParam::Destructure { default, .. } => default.as_ref(),
2231
+ };
2232
+ if let Some(default_expr) = def {
2179
2233
  drop(s);
2180
2234
  let default_val = self.eval_expr(default_expr)?;
2181
2235
  s = scope.borrow_mut();
@@ -2185,10 +2239,19 @@ impl Evaluator {
2185
2239
  }
2186
2240
  }
2187
2241
  };
2188
- s.set(Arc::clone(p), val, true);
2242
+ match formal {
2243
+ FunParam::Simple(tp) => {
2244
+ s.set(Arc::clone(&tp.name), val, true);
2245
+ }
2246
+ FunParam::Destructure { pattern, .. } => {
2247
+ drop(s);
2248
+ Self::bind_destruct_pattern_scoped(&scope, pattern, &val, true)?;
2249
+ s = scope.borrow_mut();
2250
+ }
2251
+ }
2189
2252
  }
2190
2253
  if let Some(ref rest_name) = rest_param {
2191
- let rest_vals: Vec<Value> = args.iter().skip(params.len()).cloned().collect();
2254
+ let rest_vals: Vec<Value> = args.iter().skip(formals.len()).cloned().collect();
2192
2255
  s.set(Arc::clone(rest_name), Value::Array(Rc::new(RefCell::new(rest_vals))), true);
2193
2256
  }
2194
2257
  }
@@ -2449,13 +2512,13 @@ impl Evaluator {
2449
2512
  let response_value = match self.call_func(&handler, &[req_value]) {
2450
2513
  Ok(v) => v,
2451
2514
  Err(EvalError::Throw(v)) => {
2452
- let mut err_obj: HashMap<Arc<str>, Value> = HashMap::with_capacity(2);
2515
+ let mut err_obj: PropMap = PropMap::with_capacity(2);
2453
2516
  err_obj.insert(Arc::from("status"), Value::Number(500.0));
2454
2517
  err_obj.insert(Arc::from("body"), Value::String(v.to_string().into()));
2455
2518
  Value::Object(Rc::new(RefCell::new(err_obj)))
2456
2519
  }
2457
2520
  Err(e) => {
2458
- let mut err_obj: HashMap<Arc<str>, Value> = HashMap::with_capacity(2);
2521
+ let mut err_obj: PropMap = PropMap::with_capacity(2);
2459
2522
  err_obj.insert(Arc::from("status"), Value::Number(500.0));
2460
2523
  err_obj.insert(Arc::from("body"), Value::String(e.to_string().into()));
2461
2524
  Value::Object(Rc::new(RefCell::new(err_obj)))
@@ -2491,28 +2554,37 @@ impl Evaluator {
2491
2554
  Ok(result)
2492
2555
  }
2493
2556
 
2494
- fn bind_destruct_pattern(&mut self, pattern: &tishlang_ast::DestructPattern, value: &Value, mutable: bool) -> Result<(), EvalError> {
2557
+ fn bind_destruct_pattern_scoped(
2558
+ scope: &Rc<RefCell<Scope>>,
2559
+ pattern: &tishlang_ast::DestructPattern,
2560
+ value: &Value,
2561
+ mutable: bool,
2562
+ ) -> Result<(), EvalError> {
2495
2563
  match pattern {
2496
2564
  tishlang_ast::DestructPattern::Array(elements) => {
2497
2565
  let arr = match value {
2498
2566
  Value::Array(a) => a.borrow().clone(),
2499
2567
  _ => return Err(EvalError::Error("Cannot destructure non-array value".to_string())),
2500
2568
  };
2501
-
2569
+
2502
2570
  for (i, elem) in elements.iter().enumerate() {
2503
2571
  if let Some(el) = elem {
2504
2572
  match el {
2505
2573
  tishlang_ast::DestructElement::Ident(name) => {
2506
2574
  let val = arr.get(i).cloned().unwrap_or(Value::Null);
2507
- self.scope.borrow_mut().set(Arc::clone(name), val, mutable);
2575
+ scope.borrow_mut().set(Arc::clone(name), val, mutable);
2508
2576
  }
2509
2577
  tishlang_ast::DestructElement::Pattern(nested) => {
2510
2578
  let val = arr.get(i).cloned().unwrap_or(Value::Null);
2511
- self.bind_destruct_pattern(nested, &val, mutable)?;
2579
+ Self::bind_destruct_pattern_scoped(scope, nested, &val, mutable)?;
2512
2580
  }
2513
2581
  tishlang_ast::DestructElement::Rest(name) => {
2514
2582
  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);
2583
+ scope.borrow_mut().set(
2584
+ Arc::clone(name),
2585
+ Value::Array(Rc::new(RefCell::new(rest))),
2586
+ mutable,
2587
+ );
2516
2588
  break;
2517
2589
  }
2518
2590
  }
@@ -2524,18 +2596,20 @@ impl Evaluator {
2524
2596
  Value::Object(o) => o.borrow().clone(),
2525
2597
  _ => return Err(EvalError::Error("Cannot destructure non-object value".to_string())),
2526
2598
  };
2527
-
2599
+
2528
2600
  for prop in props {
2529
2601
  let val = obj.get(&prop.key).cloned().unwrap_or(Value::Null);
2530
2602
  match &prop.value {
2531
2603
  tishlang_ast::DestructElement::Ident(name) => {
2532
- self.scope.borrow_mut().set(Arc::clone(name), val, mutable);
2604
+ scope.borrow_mut().set(Arc::clone(name), val, mutable);
2533
2605
  }
2534
2606
  tishlang_ast::DestructElement::Pattern(nested) => {
2535
- self.bind_destruct_pattern(nested, &val, mutable)?;
2607
+ Self::bind_destruct_pattern_scoped(scope, nested, &val, mutable)?;
2536
2608
  }
2537
2609
  tishlang_ast::DestructElement::Rest(_) => {
2538
- return Err(EvalError::Error("Rest not supported in object destructuring".to_string()));
2610
+ return Err(EvalError::Error(
2611
+ "Rest not supported in object destructuring".to_string(),
2612
+ ));
2539
2613
  }
2540
2614
  }
2541
2615
  }
@@ -2544,6 +2618,10 @@ impl Evaluator {
2544
2618
  Ok(())
2545
2619
  }
2546
2620
 
2621
+ fn bind_destruct_pattern(&mut self, pattern: &tishlang_ast::DestructPattern, value: &Value, mutable: bool) -> Result<(), EvalError> {
2622
+ Self::bind_destruct_pattern_scoped(&self.scope, pattern, value, mutable)
2623
+ }
2624
+
2547
2625
  fn get_prop(&self, obj: &Value, key: &str) -> Result<Value, String> {
2548
2626
  match obj {
2549
2627
  Value::Object(map) => Ok(map.borrow().get(key).cloned().unwrap_or(Value::Null)),
@@ -2745,9 +2823,9 @@ impl Evaluator {
2745
2823
  fn json_parse_object(s: &str) -> Result<Value, ()> {
2746
2824
  let s = s[1..].trim_start();
2747
2825
  if s.starts_with('}') {
2748
- return Ok(Value::Object(Rc::new(RefCell::new(HashMap::new()))));
2826
+ return Ok(Value::Object(Rc::new(RefCell::new(PropMap::default()))));
2749
2827
  }
2750
- let mut map = HashMap::new();
2828
+ let mut map = PropMap::default();
2751
2829
  let mut rest = s;
2752
2830
  loop {
2753
2831
  if !rest.starts_with('"') {
@@ -2873,7 +2951,6 @@ impl Evaluator {
2873
2951
  Value::Function { .. } | Value::Native(_) => "null".to_string(),
2874
2952
  #[cfg(feature = "http")]
2875
2953
  Value::CorePromise(_) => "null".to_string(),
2876
- #[cfg(any(feature = "http", feature = "ws"))]
2877
2954
  Value::CoreFn(_) => "null".to_string(),
2878
2955
  #[cfg(feature = "http")]
2879
2956
  Value::Serve
@@ -2949,7 +3026,7 @@ impl Evaluator {
2949
3026
 
2950
3027
  fn object_from_entries(args: &[Value]) -> Result<Value, String> {
2951
3028
  if let Some(Value::Array(arr)) = args.first() {
2952
- let mut map = HashMap::new();
3029
+ let mut map = PropMap::default();
2953
3030
  for entry in arr.borrow().iter() {
2954
3031
  if let Value::Array(pair) = entry {
2955
3032
  let pair = pair.borrow();
@@ -2961,7 +3038,7 @@ impl Evaluator {
2961
3038
  }
2962
3039
  Ok(Value::Object(Rc::new(RefCell::new(map))))
2963
3040
  } else {
2964
- Ok(Value::Object(Rc::new(RefCell::new(HashMap::new()))))
3041
+ Ok(Value::Object(Rc::new(RefCell::new(PropMap::default()))))
2965
3042
  }
2966
3043
  }
2967
3044
 
@@ -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()),
@@ -3,12 +3,13 @@
3
3
  //! Re-exports core types from tishlang_core and provides interpreter-specific functionality.
4
4
 
5
5
  use std::cell::RefCell;
6
- use std::collections::HashMap;
7
6
  use std::rc::Rc;
8
7
  use std::sync::Arc;
9
8
 
10
9
  pub use tishlang_core::{RegExpFlags, TishRegExp};
11
10
 
11
+ use crate::value::PropMap;
12
+
12
13
  use crate::value::Value;
13
14
 
14
15
  /// RegExp.prototype.exec(string) - returns match object (array-like with index) or null
@@ -42,7 +43,7 @@ pub fn regexp_exec(re: &mut TishRegExp, input: &str) -> Value {
42
43
  let match_byte_start = byte_start + full_match.start();
43
44
  let match_char_index = input[..match_byte_start].chars().count();
44
45
 
45
- let mut obj: HashMap<Arc<str>, Value> = HashMap::new();
46
+ let mut obj: PropMap = PropMap::default();
46
47
  obj.insert(Arc::from("0"), Value::String(full_match.as_str().into()));
47
48
  for i in 1..caps.len() {
48
49
  let val = match caps.get(i) {
@@ -6,13 +6,15 @@
6
6
  //! different shape (no AST-carrying variants). The split is intentional.
7
7
 
8
8
  use std::cell::RefCell;
9
- use std::collections::HashMap;
10
9
  use std::rc::Rc;
11
10
  use std::sync::Arc;
12
11
 
13
- use tishlang_ast::{Expr, Statement};
14
- #[cfg(any(feature = "http", feature = "ws"))]
12
+ use ahash::AHashMap;
13
+ use tishlang_ast::{FunParam, Statement};
15
14
  use tishlang_core::NativeFn as CoreNativeFn;
15
+
16
+ /// Property map for interpreter `Value::Object` (uses `eval::Value`, not `tishlang_core::Value`).
17
+ pub type PropMap = AHashMap<Arc<str>, Value>;
16
18
  #[cfg(feature = "http")]
17
19
  use tishlang_core::TishPromise;
18
20
  use tishlang_core::TishOpaque;
@@ -32,11 +34,10 @@ pub enum Value {
32
34
  Bool(bool),
33
35
  Null,
34
36
  Array(Rc<RefCell<Vec<Value>>>),
35
- Object(Rc<RefCell<HashMap<Arc<str>, Value>>>),
37
+ Object(Rc<RefCell<PropMap>>),
36
38
  /// User-defined function with AST body
37
39
  Function {
38
- params: Arc<[Arc<str>]>,
39
- defaults: Arc<[Option<Expr>]>,
40
+ formals: Arc<[FunParam]>,
40
41
  rest_param: Option<Arc<str>>,
41
42
  body: Arc<Statement>,
42
43
  },
@@ -65,8 +66,7 @@ pub enum Value {
65
66
  /// Native `tishlang_core` Promise (fetch / reader.read / response.text).
66
67
  #[cfg(feature = "http")]
67
68
  CorePromise(Arc<dyn TishPromise>),
68
- /// `tishlang_core::Value::Function` (e.g. response.text/json, ws send/receive) callable from eval.
69
- #[cfg(any(feature = "http", feature = "ws"))]
69
+ /// `tishlang_core::Value::Function` (native callbacks, `new` constructors, fetch/ws when enabled).
70
70
  CoreFn(CoreNativeFn),
71
71
  /// Opaque handle to a native Rust type (e.g. Polars DataFrame).
72
72
  Opaque(Arc<dyn TishOpaque>),
@@ -99,7 +99,6 @@ impl std::fmt::Debug for Value {
99
99
  Value::BoundPromiseMethod(_, _) | Value::TimerBuiltin(_) => write!(f, "[Function]"),
100
100
  #[cfg(feature = "http")]
101
101
  Value::CorePromise(_) => write!(f, "Promise"),
102
- #[cfg(any(feature = "http", feature = "ws"))]
103
102
  Value::CoreFn(_) => write!(f, "CoreFn"),
104
103
  Value::Opaque(o) => write!(f, "{}(opaque)", o.type_name()),
105
104
  Value::OpaqueMethod(_, _) => write!(f, "[Function]"),
@@ -155,7 +154,6 @@ impl std::fmt::Display for Value {
155
154
  Value::BoundPromiseMethod(_, _) | Value::TimerBuiltin(_) => write!(f, "[Function]"),
156
155
  #[cfg(feature = "http")]
157
156
  Value::CorePromise(_) => write!(f, "[Promise]"),
158
- #[cfg(any(feature = "http", feature = "ws"))]
159
157
  Value::CoreFn(_) => write!(f, "[Function]"),
160
158
  Value::Opaque(o) => write!(f, "[object {}]", o.type_name()),
161
159
  Value::OpaqueMethod(_, _) => write!(f, "[Function]"),
@@ -199,8 +197,8 @@ impl Value {
199
197
  Value::Array(Rc::new(RefCell::new(items)))
200
198
  }
201
199
 
202
- /// Create a new object Value from a HashMap.
203
- pub fn object(map: HashMap<Arc<str>, Value>) -> Self {
200
+ /// Create a new object Value from a property map.
201
+ pub fn object(map: PropMap) -> Self {
204
202
  Value::Object(Rc::new(RefCell::new(map)))
205
203
  }
206
204
 
@@ -211,7 +209,7 @@ impl Value {
211
209
 
212
210
  /// Create an empty object Value.
213
211
  pub fn empty_object() -> Self {
214
- Value::Object(Rc::new(RefCell::new(HashMap::new())))
212
+ Value::Object(Rc::new(RefCell::new(PropMap::default())))
215
213
  }
216
214
 
217
215
  /// Extract the number value, if this is a Number.