@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.
- package/Cargo.toml +1 -0
- package/crates/js_to_tish/src/transform/expr.rs +15 -6
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish/src/main.rs +8 -55
- package/crates/tish/tests/integration_test.rs +4 -3
- package/crates/tish_ast/src/ast.rs +65 -2
- package/crates/tish_build_utils/src/lib.rs +10 -2
- package/crates/tish_builtins/src/construct.rs +177 -0
- package/crates/tish_builtins/src/globals.rs +3 -5
- package/crates/tish_builtins/src/helpers.rs +2 -3
- package/crates/tish_builtins/src/lib.rs +1 -0
- package/crates/tish_builtins/src/object.rs +3 -4
- package/crates/tish_bytecode/src/compiler.rs +85 -11
- package/crates/tish_bytecode/src/opcode.rs +7 -3
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +233 -71
- package/crates/tish_compile/src/lib.rs +35 -0
- package/crates/tish_compile_js/Cargo.toml +1 -1
- package/crates/tish_compile_js/src/codegen.rs +43 -147
- package/crates/tish_compile_js/src/lib.rs +4 -7
- package/crates/tish_compile_js/src/tests_jsx.rs +89 -19
- package/crates/tish_compiler_wasm/src/lib.rs +2 -3
- package/crates/tish_core/Cargo.toml +4 -0
- package/crates/tish_core/src/console_style.rs +7 -1
- package/crates/tish_core/src/json.rs +1 -2
- package/crates/tish_core/src/macros.rs +2 -3
- package/crates/tish_core/src/value.rs +10 -5
- package/crates/tish_eval/Cargo.toml +2 -0
- package/crates/tish_eval/src/eval.rs +149 -72
- package/crates/tish_eval/src/http.rs +3 -4
- package/crates/tish_eval/src/regex.rs +3 -2
- package/crates/tish_eval/src/value.rs +11 -13
- package/crates/tish_eval/src/value_convert.rs +4 -8
- package/crates/tish_fmt/src/lib.rs +49 -10
- package/crates/tish_jsx_web/Cargo.toml +1 -1
- package/crates/tish_jsx_web/README.md +3 -16
- package/crates/tish_jsx_web/src/lib.rs +2 -157
- package/crates/tish_lexer/src/token.rs +2 -0
- package/crates/tish_lint/src/lib.rs +9 -0
- package/crates/tish_lsp/README.md +1 -1
- package/crates/tish_native/src/build.rs +16 -2
- package/crates/tish_opt/src/lib.rs +15 -0
- package/crates/tish_parser/src/lib.rs +101 -1
- package/crates/tish_parser/src/parser.rs +161 -50
- package/crates/tish_runtime/src/http.rs +4 -5
- package/crates/tish_runtime/src/http_fetch.rs +9 -10
- package/crates/tish_runtime/src/lib.rs +9 -2
- package/crates/tish_runtime/src/promise.rs +2 -3
- package/crates/tish_runtime/src/promise_io.rs +2 -3
- package/crates/tish_runtime/src/ws.rs +7 -7
- package/crates/tish_ui/Cargo.toml +17 -0
- package/crates/tish_ui/src/jsx.rs +390 -0
- package/crates/tish_ui/src/lib.rs +16 -0
- package/crates/tish_ui/src/runtime/hooks.rs +122 -0
- package/crates/tish_ui/src/runtime/mod.rs +173 -0
- package/crates/tish_vm/src/vm.rs +121 -27
- package/justfile +3 -3
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
- package/crates/tish_compile_js/src/js_intrinsics.rs +0 -82
- package/crates/tish_jsx_web/vendor/Lattish.tish +0 -362
package/Cargo.toml
CHANGED
|
@@ -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<
|
|
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<
|
|
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
|
}
|
package/crates/tish/Cargo.toml
CHANGED
package/crates/tish/src/main.rs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
5
|
-
//! - Generate/update expected files: `REGENERATE_EXPECTED=1 cargo test -p
|
|
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
|
|
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<
|
|
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<
|
|
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(¤t) {
|
|
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:
|
|
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(
|
|
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 =
|
|
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
|
}
|
|
@@ -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(
|
|
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(
|
|
18
|
+
Value::Object(Rc::new(RefCell::new(ObjectMap::with_capacity(capacity))))
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
/// Get the keys of an object.
|