@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
package/Cargo.toml CHANGED
@@ -13,6 +13,7 @@ members = [
13
13
  "crates/tish_eval",
14
14
  "crates/tish_runtime",
15
15
  "crates/tish_compile",
16
+ "crates/tish_ui",
16
17
  "crates/tish_jsx_web",
17
18
  "crates/tish_compile_js",
18
19
  "crates/tish_bytecode",
@@ -6,7 +6,7 @@ use oxc::ast::ast::Expression as OxcExpr;
6
6
  use oxc::semantic::Semantic;
7
7
  use tishlang_ast::{
8
8
  ArrayElement, ArrowBody, BinOp, CompoundOp, DestructPattern, Expr, Literal, LogicalAssignOp,
9
- MemberProp, ObjectProp, TypedParam,
9
+ FunParam, MemberProp, ObjectProp, TypedParam,
10
10
  };
11
11
 
12
12
  use crate::error::{ConvertError, ConvertErrorKind};
@@ -73,6 +73,15 @@ pub fn convert_expr(expr: &OxcExpr<'_>, ctx: &Ctx<'_>) -> Result<Expr, ConvertEr
73
73
  .collect::<Result<Vec<_>, _>>()?;
74
74
  Ok(Expr::Call { callee, args, span })
75
75
  }
76
+ OxcExpr::NewExpression(n) => {
77
+ let callee = Box::new(convert_expr(&n.callee, ctx)?);
78
+ let args = n
79
+ .arguments
80
+ .iter()
81
+ .map(|a| convert_call_arg(a, ctx))
82
+ .collect::<Result<Vec<_>, _>>()?;
83
+ Ok(Expr::New { callee, args, span })
84
+ }
76
85
  OxcExpr::StaticMemberExpression(s) => {
77
86
  let object = Box::new(convert_expr(&s.object, ctx)?);
78
87
  Ok(Expr::Member {
@@ -518,7 +527,7 @@ fn convert_unary_op(
518
527
  pub fn convert_params(
519
528
  params: &oxc::ast::ast::FormalParameters<'_>,
520
529
  ctx: &Ctx<'_>,
521
- ) -> Result<(Vec<TypedParam>, Option<TypedParam>), ConvertError> {
530
+ ) -> Result<(Vec<FunParam>, Option<TypedParam>), ConvertError> {
522
531
  let mut typed_params = Vec::new();
523
532
  let mut rest_param = None;
524
533
  for (i, p) in params.items.iter().enumerate() {
@@ -558,11 +567,11 @@ pub fn convert_params(
558
567
  .as_ref()
559
568
  .map(|e| convert_expr(e, ctx))
560
569
  .transpose()?;
561
- typed_params.push(TypedParam {
570
+ typed_params.push(FunParam::Simple(TypedParam {
562
571
  name: Arc::from(name),
563
572
  type_ann: None,
564
573
  default,
565
- });
574
+ }));
566
575
  }
567
576
  }
568
577
  if rest_param.is_none() {
@@ -589,10 +598,10 @@ pub fn convert_params(
589
598
  fn convert_arrow_params(
590
599
  params: &oxc::ast::ast::FormalParameters<'_>,
591
600
  ctx: &Ctx<'_>,
592
- ) -> Result<Vec<TypedParam>, ConvertError> {
601
+ ) -> Result<Vec<FunParam>, ConvertError> {
593
602
  let (mut ps, rest) = convert_params(params, ctx)?;
594
603
  if let Some(r) = rest {
595
- ps.push(r);
604
+ ps.push(FunParam::Simple(r));
596
605
  }
597
606
  Ok(ps)
598
607
  }
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "tishlang"
3
- version = "1.0.29"
3
+ version = "1.0.33"
4
4
  edition = "2021"
5
5
  description = "Tish CLI - run, REPL, compile to native"
6
6
  license-file = { workspace = true }
@@ -28,7 +28,7 @@ struct RunArgs {
28
28
  #[arg(long, default_value = "vm")]
29
29
  backend: String,
30
30
  /// Enable capabilities (http, fs, process, regex, ws). Must match how tish was built.
31
- /// E.g. cargo run -p tish --features http,fs -- run script.tish --feature http,fs
31
+ /// E.g. cargo run -p tishlang--features http,fs -- run script.tish --feature http,fs
32
32
  #[arg(long = "feature", action = clap::ArgAction::Append)]
33
33
  features: Vec<String>,
34
34
  /// Disable AST and bytecode optimizations (for debugging)
@@ -1,8 +1,8 @@
1
1
  //! Full-stack integration tests: run .tish files with interpreter or each backend and compare
2
2
  //! stdout to static expected files (e.g. `fn_any.tish.expected`).
3
3
  //!
4
- //! - Run: `cargo test -p tish` (or `cargo nextest run -p tish`).
5
- //! - Generate/update expected files: `REGENERATE_EXPECTED=1 cargo test -p tish test_mvp_programs_interpreter`
4
+ //! - Run: `cargo test -p tishlang` (or `cargo nextest run -p tishlang`).
5
+ //! - Generate/update expected files: `REGENERATE_EXPECTED=1 cargo test -p tishlangtest_mvp_programs_interpreter`
6
6
  //! then commit the new/updated `tests/core/*.tish.expected` files.
7
7
  //! - Compiled outputs are cached under `target/integration_compile_cache/` per backend.
8
8
 
@@ -232,7 +232,7 @@ fn test_async_await_compile_via_binary() {
232
232
  /// Uses httpbin.org/delay/1 (1s each). 3 parallel ≈ 1s, 3 sequential ≈ 3s.
233
233
  #[test]
234
234
  #[cfg(feature = "http")]
235
- #[ignore = "timing and network sensitive; run manually: cargo test test_async_parallel_vs_sequential_timing -p tish --features http -- --ignored"]
235
+ #[ignore = "timing and network sensitive; run manually: cargo test test_async_parallel_vs_sequential_timing -p tishlang--features http -- --ignored"]
236
236
  fn test_async_parallel_vs_sequential_timing() {
237
237
  let bin = tish_bin();
238
238
  let parallel_src = workspace_root().join("examples").join("async-await").join("src").join("parallel.tish");
@@ -474,6 +474,7 @@ const MVP_TEST_FILES: &[&str] = &[
474
474
  "types.tish",
475
475
  "logical_assign.tish",
476
476
  "spread.tish",
477
+ "fn_param_destructuring.tish",
477
478
  ];
478
479
 
479
480
  /// Run each .tish file with interpreter and compare stdout to static expected.
@@ -34,6 +34,62 @@ pub struct TypedParam {
34
34
  pub default: Option<Expr>,
35
35
  }
36
36
 
37
+
38
+ /// Single formal parameter: simple identifier or destructuring pattern.
39
+ #[derive(Debug, Clone, PartialEq)]
40
+ pub enum FunParam {
41
+ Simple(TypedParam),
42
+ Destructure {
43
+ pattern: DestructPattern,
44
+ type_ann: Option<TypeAnnotation>,
45
+ default: Option<Expr>,
46
+ },
47
+ }
48
+
49
+
50
+ impl FunParam {
51
+ /// Variable names introduced by this formal parameter.
52
+ pub fn bound_names(&self) -> Vec<Arc<str>> {
53
+ let mut out = Vec::new();
54
+ match self {
55
+ FunParam::Simple(tp) => out.push(Arc::clone(&tp.name)),
56
+ FunParam::Destructure { pattern, .. } => {
57
+ Self::collect_pattern_binding_names(pattern, &mut out);
58
+ }
59
+ }
60
+ out
61
+ }
62
+
63
+ fn collect_pattern_binding_names(pattern: &DestructPattern, out: &mut Vec<Arc<str>>) {
64
+ match pattern {
65
+ DestructPattern::Array(elements) => {
66
+ for el in elements {
67
+ if let Some(el) = el {
68
+ match el {
69
+ DestructElement::Ident(n) => out.push(Arc::clone(n)),
70
+ DestructElement::Pattern(p) => {
71
+ Self::collect_pattern_binding_names(p, out);
72
+ }
73
+ DestructElement::Rest(n) => out.push(Arc::clone(n)),
74
+ }
75
+ }
76
+ }
77
+ }
78
+ DestructPattern::Object(props) => {
79
+ for prop in props {
80
+ match &prop.value {
81
+ DestructElement::Ident(n) => out.push(Arc::clone(n)),
82
+ DestructElement::Pattern(p) => {
83
+ Self::collect_pattern_binding_names(p, out);
84
+ }
85
+ DestructElement::Rest(n) => out.push(Arc::clone(n)),
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+
37
93
  /// Destructuring pattern for array or object destructuring
38
94
  #[derive(Debug, Clone, PartialEq)]
39
95
  pub enum DestructPattern {
@@ -149,7 +205,7 @@ pub enum Statement {
149
205
  FunDecl {
150
206
  async_: bool,
151
207
  name: Arc<str>,
152
- params: Vec<TypedParam>,
208
+ params: Vec<FunParam>,
153
209
  rest_param: Option<TypedParam>,
154
210
  return_type: Option<TypeAnnotation>,
155
211
  body: Box<Statement>,
@@ -214,6 +270,12 @@ pub enum Expr {
214
270
  args: Vec<CallArg>,
215
271
  span: Span,
216
272
  },
273
+ /// `new` expression (JavaScript target). `callee` is the constructor reference; `args` may be empty.
274
+ New {
275
+ callee: Box<Expr>,
276
+ args: Vec<CallArg>,
277
+ span: Span,
278
+ },
217
279
  Member {
218
280
  object: Box<Expr>,
219
281
  prop: MemberProp,
@@ -298,7 +360,7 @@ pub enum Expr {
298
360
  },
299
361
  /// Arrow function: (params) => body
300
362
  ArrowFunction {
301
- params: Vec<TypedParam>,
363
+ params: Vec<FunParam>,
302
364
  body: ArrowBody,
303
365
  span: Span,
304
366
  },
@@ -374,6 +436,7 @@ impl Expr {
374
436
  Expr::Binary { span, .. } => *span,
375
437
  Expr::Unary { span, .. } => *span,
376
438
  Expr::Call { span, .. } => *span,
439
+ Expr::New { span, .. } => *span,
377
440
  Expr::Member { span, .. } => *span,
378
441
  Expr::Index { span, .. } => *span,
379
442
  Expr::Conditional { span, .. } => *span,
@@ -7,6 +7,14 @@ use std::fs;
7
7
  use std::path::{Path, PathBuf};
8
8
  use std::process::Command;
9
9
 
10
+ /// True if `root` looks like the Tish language repo (has `crates/tish_runtime`).
11
+ ///
12
+ /// Used so we do not treat unrelated workspaces (e.g. a parent `zectre-platform` repo) as Tish
13
+ /// when `CARGO_MANIFEST_DIR` or cwd points at another Rust workspace.
14
+ fn is_tish_workspace_root(root: &Path) -> bool {
15
+ root.join("crates").join("tish_runtime").is_dir()
16
+ }
17
+
10
18
  /// Find the Tish workspace root using multiple strategies.
11
19
  ///
12
20
  /// Returns the directory containing the workspace Cargo.toml (with [workspace]).
@@ -18,7 +26,7 @@ pub fn find_workspace_root() -> Result<PathBuf, String> {
18
26
  // For crates/tish_*, workspace root is parent.parent()
19
27
  if let Some(root) = path.parent().and_then(|p| p.parent()) {
20
28
  let root_buf = root.to_path_buf();
21
- if root_buf.join("Cargo.toml").exists() {
29
+ if root_buf.join("Cargo.toml").exists() && is_tish_workspace_root(&root_buf) {
22
30
  return Ok(root_buf);
23
31
  }
24
32
  }
@@ -47,7 +55,7 @@ pub fn find_workspace_root() -> Result<PathBuf, String> {
47
55
  let cargo_toml = current.join("Cargo.toml");
48
56
  if cargo_toml.exists() {
49
57
  if let Ok(content) = std::fs::read_to_string(&cargo_toml) {
50
- if content.contains("[workspace]") {
58
+ if content.contains("[workspace]") && is_tish_workspace_root(&current) {
51
59
  return Ok(current);
52
60
  }
53
61
  }
@@ -0,0 +1,177 @@
1
+ //! `new` lowering for non-JS targets: `construct(callee, args)` approximates JS `[[Construct]]`.
2
+ //! Browser-exact behavior remains on `tish compile --target js`.
3
+
4
+ use std::cell::RefCell;
5
+ use std::rc::Rc;
6
+ use std::sync::Arc;
7
+
8
+ use tishlang_core::{ObjectMap, Value};
9
+
10
+ const CONSTRUCT: &str = "__construct";
11
+
12
+ /// Host `new`: `Object` with `__construct`, `Function` as plain call, else `Null`.
13
+ pub fn construct(callee: &Value, args: &[Value]) -> Value {
14
+ match callee {
15
+ Value::Function(f) => f(args),
16
+ Value::Object(o) => {
17
+ let b = o.borrow();
18
+ if let Some(Value::Function(ctor)) = b.get(&Arc::from(CONSTRUCT)) {
19
+ let c = Rc::clone(ctor);
20
+ drop(b);
21
+ return c(args);
22
+ }
23
+ Value::Null
24
+ }
25
+ _ => Value::Null,
26
+ }
27
+ }
28
+
29
+ fn param(initial: f64) -> Value {
30
+ let mut m = ObjectMap::default();
31
+ m.insert(Arc::from("value"), Value::Number(initial));
32
+ Value::Object(Rc::new(RefCell::new(m)))
33
+ }
34
+
35
+ fn connect_fn() -> Value {
36
+ Value::Function(Rc::new(|_| Value::Null))
37
+ }
38
+
39
+ /// Shared audio-node shape: connect, gain, optional filter fields.
40
+ fn audio_node_stub() -> Value {
41
+ let mut m = ObjectMap::default();
42
+ m.insert(Arc::from("connect"), connect_fn());
43
+ m.insert(Arc::from("gain"), param(0.0));
44
+ m.insert(Arc::from("frequency"), param(440.0));
45
+ m.insert(Arc::from("Q"), param(1.0));
46
+ m.insert(Arc::from("type"), Value::String("peaking".into()));
47
+ Value::Object(Rc::new(RefCell::new(m)))
48
+ }
49
+
50
+ fn analyser_stub() -> Value {
51
+ let mut m = ObjectMap::default();
52
+ m.insert(Arc::from("connect"), connect_fn());
53
+ m.insert(Arc::from("fftSize"), Value::Number(2048.0));
54
+ Value::Object(Rc::new(RefCell::new(m)))
55
+ }
56
+
57
+ fn stereo_panner_stub() -> Value {
58
+ let mut m = ObjectMap::default();
59
+ m.insert(Arc::from("connect"), connect_fn());
60
+ m.insert(Arc::from("pan"), param(0.0));
61
+ Value::Object(Rc::new(RefCell::new(m)))
62
+ }
63
+
64
+ fn audio_buffer_stub(len: usize) -> Value {
65
+ let n = len.max(1);
66
+ let data = Rc::new(RefCell::new(vec![Value::Number(0.0); n]));
67
+ let data2 = Rc::clone(&data);
68
+ let mut m = ObjectMap::default();
69
+ m.insert(
70
+ Arc::from("getChannelData"),
71
+ Value::Function(Rc::new(move |_args| Value::Array(Rc::clone(&data2)))),
72
+ );
73
+ Value::Object(Rc::new(RefCell::new(m)))
74
+ }
75
+
76
+ fn buffer_source_stub() -> Value {
77
+ let mut m = ObjectMap::default();
78
+ m.insert(Arc::from("buffer"), Value::Null);
79
+ m.insert(Arc::from("loop"), Value::Bool(false));
80
+ m.insert(Arc::from("connect"), connect_fn());
81
+ m.insert(
82
+ Arc::from("start"),
83
+ Value::Function(Rc::new(|_| Value::Null)),
84
+ );
85
+ m.insert(
86
+ Arc::from("stop"),
87
+ Value::Function(Rc::new(|_| Value::Null)),
88
+ );
89
+ Value::Object(Rc::new(RefCell::new(m)))
90
+ }
91
+
92
+ fn oscillator_stub() -> Value {
93
+ let mut m = ObjectMap::default();
94
+ m.insert(Arc::from("frequency"), param(440.0));
95
+ m.insert(Arc::from("type"), Value::String("sine".into()));
96
+ m.insert(Arc::from("connect"), connect_fn());
97
+ m.insert(
98
+ Arc::from("start"),
99
+ Value::Function(Rc::new(|_| Value::Null)),
100
+ );
101
+ m.insert(
102
+ Arc::from("stop"),
103
+ Value::Function(Rc::new(|_| Value::Null)),
104
+ );
105
+ Value::Object(Rc::new(RefCell::new(m)))
106
+ }
107
+
108
+ fn audio_context_instance() -> Value {
109
+ let mut ctx = ObjectMap::default();
110
+ ctx.insert(Arc::from("sampleRate"), Value::Number(48_000.0));
111
+ ctx.insert(Arc::from("destination"), audio_node_stub());
112
+
113
+ ctx.insert(
114
+ Arc::from("createGain"),
115
+ Value::Function(Rc::new(|_| audio_node_stub())),
116
+ );
117
+ ctx.insert(
118
+ Arc::from("createBiquadFilter"),
119
+ Value::Function(Rc::new(|_| audio_node_stub())),
120
+ );
121
+ ctx.insert(
122
+ Arc::from("createStereoPanner"),
123
+ Value::Function(Rc::new(|_| stereo_panner_stub())),
124
+ );
125
+ ctx.insert(
126
+ Arc::from("createAnalyser"),
127
+ Value::Function(Rc::new(|_| analyser_stub())),
128
+ );
129
+ ctx.insert(
130
+ Arc::from("createBuffer"),
131
+ Value::Function(Rc::new(|args: &[Value]| {
132
+ let len = args
133
+ .get(1)
134
+ .and_then(Value::as_number)
135
+ .unwrap_or(0.0)
136
+ .clamp(0.0, 1_000_000_000.0) as usize;
137
+ audio_buffer_stub(len)
138
+ })),
139
+ );
140
+ ctx.insert(
141
+ Arc::from("createBufferSource"),
142
+ Value::Function(Rc::new(|_| buffer_source_stub())),
143
+ );
144
+ ctx.insert(
145
+ Arc::from("createOscillator"),
146
+ Value::Function(Rc::new(|_| oscillator_stub())),
147
+ );
148
+ ctx.insert(
149
+ Arc::from("decodeAudioData"),
150
+ Value::Function(Rc::new(|_| Value::Null)),
151
+ );
152
+
153
+ Value::Object(Rc::new(RefCell::new(ctx)))
154
+ }
155
+
156
+ /// Global `Uint8Array` for native/VM: `new Uint8Array(n)` → numeric array of zeros (not real bytes).
157
+ pub fn uint8_array_constructor_value() -> Value {
158
+ let ctor = Rc::new(|args: &[Value]| {
159
+ let len = args
160
+ .first()
161
+ .and_then(Value::as_number)
162
+ .unwrap_or(0.0)
163
+ .clamp(0.0, 1_000_000_000.0) as usize;
164
+ Value::Array(Rc::new(RefCell::new(vec![Value::Number(0.0); len])))
165
+ });
166
+ let mut m = ObjectMap::default();
167
+ m.insert(Arc::from(CONSTRUCT), Value::Function(ctor));
168
+ Value::Object(Rc::new(RefCell::new(m)))
169
+ }
170
+
171
+ /// Global `AudioContext` for native/VM: stub graph (no real audio).
172
+ pub fn audio_context_constructor_value() -> Value {
173
+ let ctor = Rc::new(|_args: &[Value]| audio_context_instance());
174
+ let mut m = ObjectMap::default();
175
+ m.insert(Arc::from(CONSTRUCT), Value::Function(ctor));
176
+ Value::Object(Rc::new(RefCell::new(m)))
177
+ }
@@ -4,10 +4,9 @@
4
4
  //! independent of tishlang_runtime.
5
5
 
6
6
  use std::cell::RefCell;
7
- use std::collections::HashMap;
8
7
  use std::rc::Rc;
9
8
  use std::sync::Arc;
10
- use tishlang_core::{percent_decode, percent_encode, Value};
9
+ use tishlang_core::{percent_decode, percent_encode, ObjectMap, Value};
11
10
 
12
11
  /// Boolean(value) - coerce to bool
13
12
  pub fn boolean(args: &[Value]) -> Value {
@@ -174,8 +173,7 @@ pub fn parse_float(args: &[Value]) -> Value {
174
173
  pub fn object_from_entries(args: &[Value]) -> Value {
175
174
  if let Some(Value::Array(entries)) = args.first() {
176
175
  let entries_borrow = entries.borrow();
177
- let mut obj: HashMap<Arc<str>, Value> =
178
- HashMap::with_capacity(entries_borrow.len());
176
+ let mut obj: ObjectMap = ObjectMap::with_capacity(entries_borrow.len());
179
177
 
180
178
  for entry in entries_borrow.iter() {
181
179
  if let Value::Array(pair) = entry {
@@ -192,6 +190,6 @@ pub fn object_from_entries(args: &[Value]) -> Value {
192
190
 
193
191
  Value::Object(Rc::new(RefCell::new(obj)))
194
192
  } else {
195
- Value::Object(Rc::new(RefCell::new(HashMap::new())))
193
+ Value::Object(Rc::new(RefCell::new(ObjectMap::default())))
196
194
  }
197
195
  }
@@ -1,10 +1,9 @@
1
1
  //! Common helper functions used across builtin implementations.
2
2
 
3
3
  use std::cell::RefCell;
4
- use std::collections::HashMap;
5
4
  use std::rc::Rc;
6
5
  use std::sync::Arc;
7
- use tishlang_core::Value;
6
+ use tishlang_core::{ObjectMap, Value};
8
7
 
9
8
  /// Normalize an array index, handling negative indices.
10
9
  /// Returns a valid index within bounds or the default value.
@@ -24,7 +23,7 @@ pub fn normalize_index(idx: &Value, len: i64, default: usize) -> usize {
24
23
 
25
24
  /// Create an error object with a single "error" field.
26
25
  pub fn make_error_value(e: impl std::fmt::Display) -> Value {
27
- let mut obj = HashMap::with_capacity(1);
26
+ let mut obj = ObjectMap::with_capacity(1);
28
27
  obj.insert(Arc::from("error"), Value::String(e.to_string().into()));
29
28
  Value::Object(Rc::new(RefCell::new(obj)))
30
29
  }
@@ -10,5 +10,6 @@ pub mod object;
10
10
  pub mod math;
11
11
  pub mod helpers;
12
12
  pub mod globals;
13
+ pub mod construct;
13
14
 
14
15
  pub use tishlang_core::Value;
@@ -4,19 +4,18 @@
4
4
  //! Functions will be migrated here from tishlang_runtime and tishlang_eval.
5
5
 
6
6
  use std::cell::RefCell;
7
- use std::collections::HashMap;
8
7
  use std::rc::Rc;
9
8
  use std::sync::Arc;
10
- use tishlang_core::Value;
9
+ use tishlang_core::{ObjectMap, Value};
11
10
 
12
11
  /// Create a new empty object Value.
13
12
  pub fn new() -> Value {
14
- Value::Object(Rc::new(RefCell::new(HashMap::new())))
13
+ Value::Object(Rc::new(RefCell::new(ObjectMap::default())))
15
14
  }
16
15
 
17
16
  /// Create a new object Value with a given capacity.
18
17
  pub fn with_capacity(capacity: usize) -> Value {
19
- Value::Object(Rc::new(RefCell::new(HashMap::with_capacity(capacity))))
18
+ Value::Object(Rc::new(RefCell::new(ObjectMap::with_capacity(capacity))))
20
19
  }
21
20
 
22
21
  /// Get the keys of an object.
@@ -5,7 +5,8 @@ use std::sync::Arc;
5
5
 
6
6
  use tishlang_ast::{
7
7
  ArrayElement, ArrowBody, BinOp, CallArg, DestructElement, DestructPattern, Expr,
8
- JsxAttrValue, JsxChild, JsxProp, Literal, MemberProp, ObjectProp, Program, Span, Statement,
8
+ FunParam, JsxAttrValue, JsxChild, JsxProp, Literal, MemberProp, ObjectProp, Program, Span,
9
+ Statement,
9
10
  };
10
11
 
11
12
  use crate::chunk::{Chunk, Constant};
@@ -137,8 +138,14 @@ impl<'a> Compiler<'a> {
137
138
  if params.len() != 2 {
138
139
  return None;
139
140
  }
140
- let param_a = params[0].name.as_ref();
141
- let param_b = params[1].name.as_ref();
141
+ let (param_a, param_b) = match (&params[0], &params[1]) {
142
+ (FunParam::Simple(a), FunParam::Simple(b))
143
+ if a.default.is_none() && b.default.is_none() =>
144
+ {
145
+ (a.name.as_ref(), b.name.as_ref())
146
+ }
147
+ _ => return None,
148
+ };
142
149
  let body_expr = match body {
143
150
  ArrowBody::Expr(e) => e.as_ref(),
144
151
  ArrowBody::Block(stmt) => {
@@ -184,8 +191,14 @@ impl<'a> Compiler<'a> {
184
191
  if params.len() != 2 {
185
192
  return None;
186
193
  }
187
- let param_a = params[0].name.as_ref();
188
- let param_b = params[1].name.as_ref();
194
+ let (param_a, param_b) = match (&params[0], &params[1]) {
195
+ (FunParam::Simple(a), FunParam::Simple(b))
196
+ if a.default.is_none() && b.default.is_none() =>
197
+ {
198
+ (a.name.as_ref(), b.name.as_ref())
199
+ }
200
+ _ => return None,
201
+ };
189
202
  let body_expr = match body {
190
203
  ArrowBody::Expr(e) => e.as_ref(),
191
204
  ArrowBody::Block(stmt) => {
@@ -228,7 +241,10 @@ impl<'a> Compiler<'a> {
228
241
  if params.len() != 1 {
229
242
  return None;
230
243
  }
231
- let param_name = params[0].name.as_ref();
244
+ let param_name = match &params[0] {
245
+ FunParam::Simple(tp) if tp.default.is_none() => tp.name.as_ref(),
246
+ _ => return None,
247
+ };
232
248
  let expr_ref: &Expr = match body {
233
249
  ArrowBody::Expr(e) => e.as_ref(),
234
250
  ArrowBody::Block(stmt) => {
@@ -277,7 +293,10 @@ impl<'a> Compiler<'a> {
277
293
  if params.len() != 1 {
278
294
  return None;
279
295
  }
280
- let param_name = params[0].name.as_ref();
296
+ let param_name = match &params[0] {
297
+ FunParam::Simple(tp) if tp.default.is_none() => tp.name.as_ref(),
298
+ _ => return None,
299
+ };
281
300
  let expr_ref: &Expr = match body {
282
301
  ArrowBody::Expr(e) => e.as_ref(),
283
302
  ArrowBody::Block(stmt) => {
@@ -550,9 +569,22 @@ impl<'a> Compiler<'a> {
550
569
  async_: _,
551
570
  ..
552
571
  } => {
572
+ for p in params {
573
+ if matches!(p, FunParam::Destructure { .. }) {
574
+ return Err(CompileError {
575
+ message: "Destructuring parameters are not supported in bytecode"
576
+ .to_string(),
577
+ });
578
+ }
579
+ }
553
580
  let mut inner = Chunk::new();
554
- let mut param_names: Vec<Arc<str>> =
555
- params.iter().map(|p| Arc::clone(&p.name)).collect();
581
+ let mut param_names: Vec<Arc<str>> = params
582
+ .iter()
583
+ .map(|p| match p {
584
+ FunParam::Simple(tp) => Arc::clone(&tp.name),
585
+ _ => unreachable!(),
586
+ })
587
+ .collect();
556
588
  if let Some(rp) = rest_param {
557
589
  param_names.push(rp.name.clone());
558
590
  inner.rest_param_index = (param_names.len() as u16).saturating_sub(1);
@@ -1061,9 +1093,22 @@ impl<'a> Compiler<'a> {
1061
1093
  self.emit_u16(Opcode::Call, 1);
1062
1094
  }
1063
1095
  Expr::ArrowFunction { params, body, .. } => {
1096
+ for p in params {
1097
+ if matches!(p, FunParam::Destructure { .. }) {
1098
+ return Err(CompileError {
1099
+ message: "Destructuring parameters are not supported in bytecode"
1100
+ .to_string(),
1101
+ });
1102
+ }
1103
+ }
1064
1104
  let mut inner = Chunk::new();
1065
- let param_names: Vec<Arc<str>> =
1066
- params.iter().map(|p| Arc::clone(&p.name)).collect();
1105
+ let param_names: Vec<Arc<str>> = params
1106
+ .iter()
1107
+ .map(|p| match p {
1108
+ FunParam::Simple(tp) => Arc::clone(&tp.name),
1109
+ _ => unreachable!(),
1110
+ })
1111
+ .collect();
1067
1112
  for p in &param_names {
1068
1113
  inner.add_name(Arc::clone(p));
1069
1114
  }
@@ -1205,6 +1250,35 @@ impl<'a> Compiler<'a> {
1205
1250
  message: "Logical assignment (&&=, ||=, ??=) not yet supported in bytecode".to_string(),
1206
1251
  });
1207
1252
  }
1253
+ Expr::New { callee, args, .. } => {
1254
+ let has_spread = args.iter().any(|a| matches!(a, CallArg::Spread(_)));
1255
+ if has_spread {
1256
+ self.emit_u16(Opcode::NewArray, 0);
1257
+ for arg in args {
1258
+ match arg {
1259
+ CallArg::Expr(e) => {
1260
+ self.compile_expr(e)?;
1261
+ self.emit_u16(Opcode::NewArray, 1);
1262
+ self.emit(Opcode::ConcatArray);
1263
+ }
1264
+ CallArg::Spread(expr) => {
1265
+ self.compile_expr(expr)?;
1266
+ self.emit(Opcode::ConcatArray);
1267
+ }
1268
+ }
1269
+ }
1270
+ self.compile_expr(callee)?;
1271
+ self.emit(Opcode::ConstructSpread);
1272
+ } else {
1273
+ self.compile_expr(callee)?;
1274
+ for arg in args {
1275
+ if let CallArg::Expr(e) = arg {
1276
+ self.compile_expr(e)?;
1277
+ }
1278
+ }
1279
+ self.emit_u16(Opcode::Construct, args.len() as u16);
1280
+ }
1281
+ }
1208
1282
  }
1209
1283
  Ok(())
1210
1284
  }