@tishlang/tish 1.0.28 → 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 (65) 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 +8 -55
  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 -1
  19. package/crates/tish_compile_js/src/codegen.rs +43 -147
  20. package/crates/tish_compile_js/src/lib.rs +4 -7
  21. package/crates/tish_compile_js/src/tests_jsx.rs +89 -19
  22. package/crates/tish_compiler_wasm/src/lib.rs +2 -3
  23. package/crates/tish_core/Cargo.toml +4 -0
  24. package/crates/tish_core/src/console_style.rs +7 -1
  25. package/crates/tish_core/src/json.rs +1 -2
  26. package/crates/tish_core/src/macros.rs +2 -3
  27. package/crates/tish_core/src/value.rs +10 -5
  28. package/crates/tish_eval/Cargo.toml +2 -0
  29. package/crates/tish_eval/src/eval.rs +149 -72
  30. package/crates/tish_eval/src/http.rs +3 -4
  31. package/crates/tish_eval/src/regex.rs +3 -2
  32. package/crates/tish_eval/src/value.rs +11 -13
  33. package/crates/tish_eval/src/value_convert.rs +4 -8
  34. package/crates/tish_fmt/src/lib.rs +49 -10
  35. package/crates/tish_jsx_web/Cargo.toml +1 -1
  36. package/crates/tish_jsx_web/README.md +3 -16
  37. package/crates/tish_jsx_web/src/lib.rs +2 -157
  38. package/crates/tish_lexer/src/token.rs +2 -0
  39. package/crates/tish_lint/src/lib.rs +9 -0
  40. package/crates/tish_lsp/README.md +1 -1
  41. package/crates/tish_native/src/build.rs +16 -2
  42. package/crates/tish_opt/src/lib.rs +15 -0
  43. package/crates/tish_parser/src/lib.rs +101 -1
  44. package/crates/tish_parser/src/parser.rs +161 -50
  45. package/crates/tish_runtime/src/http.rs +4 -5
  46. package/crates/tish_runtime/src/http_fetch.rs +9 -10
  47. package/crates/tish_runtime/src/lib.rs +9 -2
  48. package/crates/tish_runtime/src/promise.rs +2 -3
  49. package/crates/tish_runtime/src/promise_io.rs +2 -3
  50. package/crates/tish_runtime/src/ws.rs +7 -7
  51. package/crates/tish_ui/Cargo.toml +17 -0
  52. package/crates/tish_ui/src/jsx.rs +390 -0
  53. package/crates/tish_ui/src/lib.rs +16 -0
  54. package/crates/tish_ui/src/runtime/hooks.rs +122 -0
  55. package/crates/tish_ui/src/runtime/mod.rs +173 -0
  56. package/crates/tish_vm/src/vm.rs +121 -27
  57. package/justfile +3 -3
  58. package/package.json +1 -1
  59. package/platform/darwin-arm64/tish +0 -0
  60. package/platform/darwin-x64/tish +0 -0
  61. package/platform/linux-arm64/tish +0 -0
  62. package/platform/linux-x64/tish +0 -0
  63. package/platform/win32-x64/tish.exe +0 -0
  64. package/crates/tish_compile_js/src/js_intrinsics.rs +0 -82
  65. package/crates/tish_jsx_web/vendor/Lattish.tish +0 -362
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.28"
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)
@@ -56,9 +56,6 @@ struct CompileArgs {
56
56
  features: Vec<String>,
57
57
  #[arg(long)]
58
58
  no_optimize: bool,
59
- /// JS target only: `lattish` (default), `vdom` (vnode + patch; use with Lattish createRoot).
60
- #[arg(long = "jsx", value_name = "MODE", default_value = "lattish")]
61
- jsx_mode: String,
62
59
  #[arg(required = true)]
63
60
  file: String,
64
61
  }
@@ -94,7 +91,6 @@ fn main() {
94
91
  &a.native_backend,
95
92
  &a.features,
96
93
  a.no_optimize || no_opt_env,
97
- &a.jsx_mode,
98
94
  ),
99
95
  Some(Commands::DumpAst { file }) => dump_ast(&file),
100
96
  None => run_repl("vm", false), // No args = REPL
@@ -339,29 +335,7 @@ fn tish_history_path() -> Option<PathBuf> {
339
335
  home.map(|h| PathBuf::from(h).join(".tish_history"))
340
336
  }
341
337
 
342
- fn parse_jsx_mode(s: &str) -> Result<tishlang_compile_js::JsxMode, String> {
343
- match s {
344
- "legacy" => Err(
345
- "--jsx legacy was removed. Use --jsx lattish (default) with lattish merged into your \
346
- bundle, or --jsx vdom with Lattish's createRoot."
347
- .to_string(),
348
- ),
349
- "vdom" => Ok(tishlang_compile_js::JsxMode::Vdom),
350
- "lattish" => Ok(tishlang_compile_js::JsxMode::LattishH),
351
- other => Err(format!(
352
- "Unknown --jsx {:?}: use lattish (default) or vdom.",
353
- other
354
- )),
355
- }
356
- }
357
-
358
- fn compile_to_js(
359
- input_path: &Path,
360
- output_path: &str,
361
- optimize: bool,
362
- jsx: &str,
363
- ) -> Result<(), String> {
364
- let jsx_mode = parse_jsx_mode(jsx)?;
338
+ fn compile_to_js(input_path: &Path, output_path: &str, optimize: bool) -> Result<(), String> {
365
339
  let project_root = input_path.parent().and_then(|p| {
366
340
  if p.file_name().and_then(|n| n.to_str()) == Some("src") {
367
341
  p.parent()
@@ -382,13 +356,13 @@ fn compile_to_js(
382
356
  } else {
383
357
  program
384
358
  };
385
- tishlang_compile_js::compile_with_jsx(&p, optimize, jsx_mode).map_err(|e| format!("{}", e))?
359
+ tishlang_compile_js::compile_with_jsx(&p, optimize).map_err(|e| format!("{}", e))?
386
360
  } else if input_path.extension().map(|e| e == "js") == Some(true) {
387
361
  let source = fs::read_to_string(input_path).map_err(|e| format!("{}", e))?;
388
362
  let program = tishlang_js_to_tish::convert(&source).map_err(|e| format!("{}", e))?;
389
- tishlang_compile_js::compile_with_jsx(&program, optimize, jsx_mode).map_err(|e| format!("{}", e))?
363
+ tishlang_compile_js::compile_with_jsx(&program, optimize).map_err(|e| format!("{}", e))?
390
364
  } else {
391
- tishlang_compile_js::compile_project_with_jsx(input_path, project_root, optimize, jsx_mode)
365
+ tishlang_compile_js::compile_project_with_jsx(input_path, project_root, optimize)
392
366
  .map_err(|e| format!("{}", e))?
393
367
  };
394
368
 
@@ -418,7 +392,6 @@ fn compile_file(
418
392
  native_backend: &str,
419
393
  cli_features: &[String],
420
394
  no_optimize: bool,
421
- jsx: &str,
422
395
  ) -> Result<(), String> {
423
396
  let optimize = !no_optimize;
424
397
  let input_path =
@@ -427,7 +400,7 @@ fn compile_file(
427
400
  let is_js = input_path.extension().map(|e| e == "js") == Some(true);
428
401
 
429
402
  if target == "js" {
430
- return compile_to_js(&input_path, output_path, optimize, jsx.trim());
403
+ return compile_to_js(&input_path, output_path, optimize);
431
404
  }
432
405
 
433
406
  if target == "wasm" && is_js {
@@ -539,7 +512,7 @@ mod cli_tests {
539
512
  use super::{Cli, Commands};
540
513
 
541
514
  #[test]
542
- fn compile_jsx_defaults_to_lattish() {
515
+ fn compile_js_target_parses() {
543
516
  let cli = Cli::try_parse_from([
544
517
  "tish",
545
518
  "compile",
@@ -551,27 +524,7 @@ mod cli_tests {
551
524
  ])
552
525
  .unwrap();
553
526
  match cli.command {
554
- Some(Commands::Compile(a)) => assert_eq!(a.jsx_mode.as_str(), "lattish"),
555
- _ => panic!("expected Compile"),
556
- }
557
- }
558
-
559
- #[test]
560
- fn compile_jsx_flag_vdom() {
561
- let cli = Cli::try_parse_from([
562
- "tish",
563
- "compile",
564
- "a.tish",
565
- "--target",
566
- "js",
567
- "--jsx",
568
- "vdom",
569
- "-o",
570
- "x.js",
571
- ])
572
- .unwrap();
573
- match cli.command {
574
- Some(Commands::Compile(a)) => assert_eq!(a.jsx_mode.as_str(), "vdom"),
527
+ Some(Commands::Compile(a)) => assert_eq!(a.file, "m.tish"),
575
528
  _ => panic!("expected Compile"),
576
529
  }
577
530
  }
@@ -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.