@tishlang/tish-format 1.0.12 → 2.0.1
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 +51 -0
- package/LICENSE +13 -0
- package/bin/tish-format +0 -0
- package/crates/js_to_tish/Cargo.toml +11 -0
- package/crates/js_to_tish/README.md +18 -0
- package/crates/js_to_tish/src/error.rs +55 -0
- package/crates/js_to_tish/src/lib.rs +11 -0
- package/crates/js_to_tish/src/span_util.rs +35 -0
- package/crates/js_to_tish/src/transform/expr.rs +611 -0
- package/crates/js_to_tish/src/transform/stmt.rs +503 -0
- package/crates/js_to_tish/src/transform.rs +60 -0
- package/crates/tish/Cargo.toml +62 -0
- package/crates/tish/build.rs +21 -0
- package/crates/tish/src/cargo_native_registry.rs +32 -0
- package/crates/tish/src/cli_help.rs +576 -0
- package/crates/tish/src/main.rs +853 -0
- package/crates/tish/src/repl_completion.rs +199 -0
- package/crates/tish/tests/cargo_example_compile.rs +67 -0
- package/crates/tish/tests/error_source_location.rs +36 -0
- package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
- package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
- package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
- package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
- package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
- package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
- package/crates/tish/tests/integration_test.rs +1406 -0
- package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
- package/crates/tish/tests/shortcircuit.rs +65 -0
- package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
- package/crates/tish/tests/tty_capability.rs +43 -0
- package/crates/tish_ast/Cargo.toml +9 -0
- package/crates/tish_ast/src/ast.rs +649 -0
- package/crates/tish_ast/src/lib.rs +5 -0
- package/crates/tish_build_utils/Cargo.toml +11 -0
- package/crates/tish_build_utils/src/lib.rs +577 -0
- package/crates/tish_builtins/Cargo.toml +22 -0
- package/crates/tish_builtins/src/array.rs +803 -0
- package/crates/tish_builtins/src/collections.rs +481 -0
- package/crates/tish_builtins/src/construct.rs +199 -0
- package/crates/tish_builtins/src/date.rs +538 -0
- package/crates/tish_builtins/src/globals.rs +293 -0
- package/crates/tish_builtins/src/helpers.rs +35 -0
- package/crates/tish_builtins/src/iterator.rs +129 -0
- package/crates/tish_builtins/src/lib.rs +21 -0
- package/crates/tish_builtins/src/math.rs +89 -0
- package/crates/tish_builtins/src/number.rs +96 -0
- package/crates/tish_builtins/src/object.rs +36 -0
- package/crates/tish_builtins/src/string.rs +646 -0
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_builtins/src/typedarrays.rs +298 -0
- package/crates/tish_bytecode/Cargo.toml +17 -0
- package/crates/tish_bytecode/src/chunk.rs +164 -0
- package/crates/tish_bytecode/src/compiler.rs +2604 -0
- package/crates/tish_bytecode/src/encoding.rs +102 -0
- package/crates/tish_bytecode/src/lib.rs +20 -0
- package/crates/tish_bytecode/src/opcode.rs +185 -0
- package/crates/tish_bytecode/src/peephole.rs +189 -0
- package/crates/tish_bytecode/src/serialize.rs +193 -0
- package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
- package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
- package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
- package/crates/tish_compile/Cargo.toml +27 -0
- package/crates/tish_compile/src/check.rs +774 -0
- package/crates/tish_compile/src/codegen.rs +7317 -0
- package/crates/tish_compile/src/infer.rs +1681 -0
- package/crates/tish_compile/src/lib.rs +206 -0
- package/crates/tish_compile/src/resolve.rs +1951 -0
- package/crates/tish_compile/src/types.rs +605 -0
- package/crates/tish_compile_js/Cargo.toml +18 -0
- package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
- package/crates/tish_compile_js/src/codegen.rs +938 -0
- package/crates/tish_compile_js/src/error.rs +20 -0
- package/crates/tish_compile_js/src/lib.rs +26 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +414 -0
- package/crates/tish_compiler_wasm/Cargo.toml +21 -0
- package/crates/tish_compiler_wasm/src/lib.rs +57 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
- package/crates/tish_core/Cargo.toml +32 -0
- package/crates/tish_core/src/console_style.rs +170 -0
- package/crates/tish_core/src/json.rs +430 -0
- package/crates/tish_core/src/lib.rs +20 -0
- package/crates/tish_core/src/macros.rs +36 -0
- package/crates/tish_core/src/shape.rs +85 -0
- package/crates/tish_core/src/uri.rs +118 -0
- package/crates/tish_core/src/value.rs +1350 -0
- package/crates/tish_core/src/vmref.rs +183 -0
- package/crates/tish_cranelift/Cargo.toml +19 -0
- package/crates/tish_cranelift/src/lib.rs +43 -0
- package/crates/tish_cranelift/src/link.rs +130 -0
- package/crates/tish_cranelift/src/lower.rs +85 -0
- package/crates/tish_cranelift_runtime/Cargo.toml +26 -0
- package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
- package/crates/tish_eval/Cargo.toml +51 -0
- package/crates/tish_eval/src/eval.rs +4265 -0
- package/crates/tish_eval/src/http.rs +191 -0
- package/crates/tish_eval/src/lib.rs +99 -0
- package/crates/tish_eval/src/natives.rs +551 -0
- package/crates/tish_eval/src/promise.rs +179 -0
- package/crates/tish_eval/src/regex.rs +299 -0
- package/crates/tish_eval/src/timers.rs +120 -0
- package/crates/tish_eval/src/value.rs +336 -0
- package/crates/tish_eval/src/value_convert.rs +117 -0
- package/crates/tish_ffi/Cargo.toml +26 -0
- package/crates/tish_ffi/src/lib.rs +518 -0
- package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
- package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
- package/crates/tish_ffi/tests/loader.rs +65 -0
- package/crates/tish_fmt/Cargo.toml +16 -0
- package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
- package/crates/tish_fmt/src/lib.rs +2157 -0
- package/crates/tish_jsx_web/Cargo.toml +9 -0
- package/crates/tish_jsx_web/README.md +5 -0
- package/crates/tish_jsx_web/src/lib.rs +2 -0
- package/crates/tish_lexer/Cargo.toml +9 -0
- package/crates/tish_lexer/src/lib.rs +1104 -0
- package/crates/tish_lexer/src/token.rs +170 -0
- package/crates/tish_lint/Cargo.toml +18 -0
- package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
- package/crates/tish_lint/src/lib.rs +281 -0
- package/crates/tish_llvm/Cargo.toml +13 -0
- package/crates/tish_llvm/src/lib.rs +115 -0
- package/crates/tish_lsp/Cargo.toml +25 -0
- package/crates/tish_lsp/README.md +26 -0
- package/crates/tish_lsp/src/builtin_goto.rs +362 -0
- package/crates/tish_lsp/src/import_goto.rs +564 -0
- package/crates/tish_lsp/src/main.rs +1459 -0
- package/crates/tish_native/Cargo.toml +16 -0
- package/crates/tish_native/src/build.rs +481 -0
- package/crates/tish_native/src/config.rs +48 -0
- package/crates/tish_native/src/lib.rs +416 -0
- package/crates/tish_opt/Cargo.toml +13 -0
- package/crates/tish_opt/src/lib.rs +1046 -0
- package/crates/tish_parser/Cargo.toml +11 -0
- package/crates/tish_parser/src/lib.rs +386 -0
- package/crates/tish_parser/src/parser.rs +2726 -0
- package/crates/tish_pg/Cargo.toml +34 -0
- package/crates/tish_pg/README.md +38 -0
- package/crates/tish_pg/src/error.rs +52 -0
- package/crates/tish_pg/src/lib.rs +955 -0
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3601 -0
- package/crates/tish_resolve/src/pos.rs +141 -0
- package/crates/tish_runtime/Cargo.toml +100 -0
- package/crates/tish_runtime/src/http.rs +1347 -0
- package/crates/tish_runtime/src/http_fetch.rs +492 -0
- package/crates/tish_runtime/src/http_hyper.rs +441 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +1447 -0
- package/crates/tish_runtime/src/native_promise.rs +15 -0
- package/crates/tish_runtime/src/promise.rs +558 -0
- package/crates/tish_runtime/src/promise_io.rs +38 -0
- package/crates/tish_runtime/src/timers.rs +172 -0
- package/crates/tish_runtime/src/tty.rs +226 -0
- package/crates/tish_runtime/src/ws.rs +778 -0
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
- package/crates/tish_ui/Cargo.toml +17 -0
- package/crates/tish_ui/src/jsx.rs +692 -0
- package/crates/tish_ui/src/lib.rs +20 -0
- package/crates/tish_ui/src/runtime/hooks.rs +573 -0
- package/crates/tish_ui/src/runtime/mod.rs +183 -0
- package/crates/tish_vm/Cargo.toml +60 -0
- package/crates/tish_vm/src/jit.rs +1050 -0
- package/crates/tish_vm/src/lib.rs +41 -0
- package/crates/tish_vm/src/vm.rs +3536 -0
- package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
- package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
- package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
- package/crates/tish_wasm/Cargo.toml +15 -0
- package/crates/tish_wasm/src/lib.rs +428 -0
- package/crates/tish_wasm_runtime/Cargo.toml +37 -0
- package/crates/tish_wasm_runtime/src/gpu.rs +429 -0
- package/crates/tish_wasm_runtime/src/lib.rs +42 -0
- package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +261 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
- package/justfile +276 -0
- package/package.json +2 -2
- package/platform/darwin-arm64/tish-fmt +0 -0
- package/platform/darwin-x64/tish-fmt +0 -0
- package/platform/linux-arm64/tish-fmt +0 -0
- package/platform/linux-x64/tish-fmt +0 -0
- package/platform/win32-x64/tish-fmt.exe +0 -0
|
@@ -0,0 +1,4265 @@
|
|
|
1
|
+
//! Tree-walk evaluator for Tish.
|
|
2
|
+
|
|
3
|
+
#![allow(clippy::type_complexity, clippy::cloned_ref_to_slice_refs)]
|
|
4
|
+
|
|
5
|
+
use std::cell::RefCell;
|
|
6
|
+
use std::collections::HashMap;
|
|
7
|
+
use std::path::{Path, PathBuf};
|
|
8
|
+
use std::rc::Rc;
|
|
9
|
+
use std::sync::Arc;
|
|
10
|
+
|
|
11
|
+
use tishlang_ast::{
|
|
12
|
+
BinOp, CompoundOp, ExportDeclaration, Expr, FunParam, ImportSpecifier, Literal,
|
|
13
|
+
LogicalAssignOp, MemberProp, Span, Statement, UnaryOp,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
#[cfg(any(feature = "fs", feature = "process"))]
|
|
17
|
+
use crate::natives;
|
|
18
|
+
use ahash::AHashMap;
|
|
19
|
+
|
|
20
|
+
use crate::value::{
|
|
21
|
+
eval_object_get, eval_object_has, eval_object_set, EvalObjectData, PropMap, Value,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
pub struct Scope {
|
|
25
|
+
// Scope vars: order is never observed (no Object.keys over a scope), so use a fast
|
|
26
|
+
// unordered aHash map — NOT the object-strings PropMap (an insertion-ordered IndexMap),
|
|
27
|
+
// which would pay SipHash + ordered-bucket overhead on every variable lookup.
|
|
28
|
+
vars: AHashMap<Arc<str>, Value>,
|
|
29
|
+
consts: ahash::AHashSet<Arc<str>>,
|
|
30
|
+
parent: Option<Rc<std::cell::RefCell<Scope>>>,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// A reference-counted lexical scope. A `Value::Function` captures one of these at creation
|
|
34
|
+
/// (the *defining* scope) so calls resolve free variables lexically — real closures.
|
|
35
|
+
pub type ScopeRef = Rc<std::cell::RefCell<Scope>>;
|
|
36
|
+
|
|
37
|
+
impl Scope {
|
|
38
|
+
fn new() -> Rc<std::cell::RefCell<Self>> {
|
|
39
|
+
Rc::new(std::cell::RefCell::new(Self {
|
|
40
|
+
vars: AHashMap::default(),
|
|
41
|
+
consts: ahash::AHashSet::default(),
|
|
42
|
+
parent: None,
|
|
43
|
+
}))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fn child(parent: Rc<std::cell::RefCell<Scope>>) -> Rc<std::cell::RefCell<Self>> {
|
|
47
|
+
Rc::new(std::cell::RefCell::new(Self {
|
|
48
|
+
vars: AHashMap::default(),
|
|
49
|
+
consts: ahash::AHashSet::default(),
|
|
50
|
+
parent: Some(parent),
|
|
51
|
+
}))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fn get(&self, name: &str) -> Option<Value> {
|
|
55
|
+
if let Some(v) = self.vars.get(name) {
|
|
56
|
+
return Some(v.clone());
|
|
57
|
+
}
|
|
58
|
+
if let Some(ref parent) = self.parent {
|
|
59
|
+
return parent.borrow().get(name);
|
|
60
|
+
}
|
|
61
|
+
None
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn set(&mut self, name: Arc<str>, value: Value, mutable: bool) {
|
|
65
|
+
if !mutable {
|
|
66
|
+
self.consts.insert(Arc::clone(&name));
|
|
67
|
+
}
|
|
68
|
+
self.vars.insert(name, value);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fn assign(&mut self, name: &str, value: Value) -> Result<bool, String> {
|
|
72
|
+
if let Some(existing) = self.vars.get_mut(name) {
|
|
73
|
+
if self.consts.contains(name) {
|
|
74
|
+
return Err(format!("Cannot assign to const variable: {}", name));
|
|
75
|
+
}
|
|
76
|
+
*existing = value;
|
|
77
|
+
return Ok(true);
|
|
78
|
+
}
|
|
79
|
+
if let Some(ref parent) = self.parent {
|
|
80
|
+
return parent.borrow_mut().assign(name, value);
|
|
81
|
+
}
|
|
82
|
+
Ok(false)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
pub struct Evaluator {
|
|
87
|
+
scope: Rc<std::cell::RefCell<Scope>>,
|
|
88
|
+
/// Cache of evaluated modules: canonical path -> exports object
|
|
89
|
+
module_cache: Rc<RefCell<HashMap<PathBuf, Value>>>,
|
|
90
|
+
/// Directory of the file currently being evaluated (for resolving relative imports)
|
|
91
|
+
current_dir: RefCell<Option<PathBuf>>,
|
|
92
|
+
/// Extra `tish:*` builtins from `TishNativeModule::virtual_builtin_modules` (shared across nested evaluators).
|
|
93
|
+
virtual_builtins: Rc<RefCell<HashMap<Arc<str>, Value>>>,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
impl Evaluator {
|
|
97
|
+
#[allow(clippy::new_without_default)]
|
|
98
|
+
pub fn new() -> Self {
|
|
99
|
+
use crate::natives;
|
|
100
|
+
|
|
101
|
+
let scope = Scope::new();
|
|
102
|
+
{
|
|
103
|
+
let mut s = scope.borrow_mut();
|
|
104
|
+
let mut console = PropMap::with_capacity(5);
|
|
105
|
+
console.insert("debug".into(), Value::Native(natives::console_debug));
|
|
106
|
+
console.insert("info".into(), Value::Native(natives::console_info));
|
|
107
|
+
console.insert("log".into(), Value::Native(natives::console_log));
|
|
108
|
+
console.insert("warn".into(), Value::Native(natives::console_warn));
|
|
109
|
+
console.insert("error".into(), Value::Native(natives::console_error));
|
|
110
|
+
s.set(
|
|
111
|
+
"console".into(),
|
|
112
|
+
Value::object(console),
|
|
113
|
+
true,
|
|
114
|
+
);
|
|
115
|
+
s.set("parseInt".into(), Value::Native(natives::parse_int), true);
|
|
116
|
+
s.set(
|
|
117
|
+
"parseFloat".into(),
|
|
118
|
+
Value::Native(natives::parse_float),
|
|
119
|
+
true,
|
|
120
|
+
);
|
|
121
|
+
s.set("decodeURI".into(), Value::Native(natives::decode_uri), true);
|
|
122
|
+
s.set("encodeURI".into(), Value::Native(natives::encode_uri), true);
|
|
123
|
+
s.set(
|
|
124
|
+
"htmlEscape".into(),
|
|
125
|
+
Value::Native(natives::html_escape),
|
|
126
|
+
true,
|
|
127
|
+
);
|
|
128
|
+
s.set(
|
|
129
|
+
"Boolean".into(),
|
|
130
|
+
Value::Native(natives::boolean_native),
|
|
131
|
+
true,
|
|
132
|
+
);
|
|
133
|
+
s.set("isFinite".into(), Value::Native(natives::is_finite), true);
|
|
134
|
+
s.set("isNaN".into(), Value::Native(natives::is_nan), true);
|
|
135
|
+
s.set("Infinity".into(), Value::Number(f64::INFINITY), true);
|
|
136
|
+
s.set("NaN".into(), Value::Number(f64::NAN), true);
|
|
137
|
+
let mut math = PropMap::with_capacity(18);
|
|
138
|
+
math.insert("abs".into(), Value::Native(natives::math_abs));
|
|
139
|
+
math.insert("sqrt".into(), Value::Native(natives::math_sqrt));
|
|
140
|
+
math.insert("min".into(), Value::Native(natives::math_min));
|
|
141
|
+
math.insert("max".into(), Value::Native(natives::math_max));
|
|
142
|
+
math.insert("floor".into(), Value::Native(natives::math_floor));
|
|
143
|
+
math.insert("ceil".into(), Value::Native(natives::math_ceil));
|
|
144
|
+
math.insert("round".into(), Value::Native(natives::math_round));
|
|
145
|
+
math.insert("random".into(), Value::Native(natives::math_random));
|
|
146
|
+
math.insert("pow".into(), Value::Native(natives::math_pow));
|
|
147
|
+
math.insert("sin".into(), Value::Native(natives::math_sin));
|
|
148
|
+
math.insert("cos".into(), Value::Native(natives::math_cos));
|
|
149
|
+
math.insert("tan".into(), Value::Native(natives::math_tan));
|
|
150
|
+
math.insert("log".into(), Value::Native(natives::math_log));
|
|
151
|
+
math.insert("exp".into(), Value::Native(natives::math_exp));
|
|
152
|
+
math.insert("sign".into(), Value::Native(natives::math_sign));
|
|
153
|
+
math.insert("trunc".into(), Value::Native(natives::math_trunc));
|
|
154
|
+
math.insert("sinh".into(), Value::Native(natives::math_sinh));
|
|
155
|
+
math.insert("cosh".into(), Value::Native(natives::math_cosh));
|
|
156
|
+
math.insert("tanh".into(), Value::Native(natives::math_tanh));
|
|
157
|
+
math.insert("asinh".into(), Value::Native(natives::math_asinh));
|
|
158
|
+
math.insert("acosh".into(), Value::Native(natives::math_acosh));
|
|
159
|
+
math.insert("atanh".into(), Value::Native(natives::math_atanh));
|
|
160
|
+
math.insert("cbrt".into(), Value::Native(natives::math_cbrt));
|
|
161
|
+
math.insert("log2".into(), Value::Native(natives::math_log2));
|
|
162
|
+
math.insert("log10".into(), Value::Native(natives::math_log10));
|
|
163
|
+
math.insert("PI".into(), Value::Number(std::f64::consts::PI));
|
|
164
|
+
math.insert("E".into(), Value::Number(std::f64::consts::E));
|
|
165
|
+
s.set(
|
|
166
|
+
"Math".into(),
|
|
167
|
+
Value::object(math),
|
|
168
|
+
true,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
let mut json = PropMap::with_capacity(2);
|
|
172
|
+
json.insert("parse".into(), Value::Native(Self::json_parse_native));
|
|
173
|
+
json.insert(
|
|
174
|
+
"stringify".into(),
|
|
175
|
+
Value::Native(Self::json_stringify_native),
|
|
176
|
+
);
|
|
177
|
+
s.set(
|
|
178
|
+
"JSON".into(),
|
|
179
|
+
Value::object(json),
|
|
180
|
+
true,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
let mut object = PropMap::with_capacity(5);
|
|
184
|
+
object.insert("keys".into(), Value::Native(Self::object_keys));
|
|
185
|
+
object.insert("values".into(), Value::Native(Self::object_values));
|
|
186
|
+
object.insert("entries".into(), Value::Native(Self::object_entries));
|
|
187
|
+
object.insert("assign".into(), Value::Native(Self::object_assign));
|
|
188
|
+
object.insert(
|
|
189
|
+
"fromEntries".into(),
|
|
190
|
+
Value::Native(Self::object_from_entries),
|
|
191
|
+
);
|
|
192
|
+
s.set(
|
|
193
|
+
"Object".into(),
|
|
194
|
+
Value::object(object),
|
|
195
|
+
true,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
let mut array_obj = PropMap::with_capacity(3);
|
|
199
|
+
array_obj.insert("isArray".into(), Value::Native(natives::array_is_array));
|
|
200
|
+
// `Array(n)` and `new Array(n)` constructor (issue #72).
|
|
201
|
+
array_obj.insert("__call".into(), Value::Native(natives::array_construct));
|
|
202
|
+
array_obj.insert("__construct".into(), Value::Native(natives::array_construct));
|
|
203
|
+
s.set(
|
|
204
|
+
"Array".into(),
|
|
205
|
+
Value::object(array_obj),
|
|
206
|
+
true,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// Error constructors (issue #60): callable + constructable via __call/__construct.
|
|
210
|
+
for (name, ctor) in [
|
|
211
|
+
("Error", natives::error_construct as fn(&[Value]) -> Result<Value, String>),
|
|
212
|
+
("TypeError", natives::type_error_construct),
|
|
213
|
+
("RangeError", natives::range_error_construct),
|
|
214
|
+
("SyntaxError", natives::syntax_error_construct),
|
|
215
|
+
] {
|
|
216
|
+
let mut err_obj = PropMap::with_capacity(2);
|
|
217
|
+
err_obj.insert("__call".into(), Value::Native(ctor));
|
|
218
|
+
err_obj.insert("__construct".into(), Value::Native(ctor));
|
|
219
|
+
s.set(name.into(), Value::object(err_obj), true);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let mut string_obj = PropMap::with_capacity(2);
|
|
223
|
+
string_obj.insert(
|
|
224
|
+
"fromCharCode".into(),
|
|
225
|
+
Value::Native(natives::string_from_char_code),
|
|
226
|
+
);
|
|
227
|
+
// `String(value)` callable: dispatched via `__call` in `call_func`, like `Symbol`.
|
|
228
|
+
string_obj.insert("__call".into(), Value::Native(natives::string_convert));
|
|
229
|
+
s.set(
|
|
230
|
+
"String".into(),
|
|
231
|
+
Value::object(string_obj),
|
|
232
|
+
true,
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// `Number(value)` coercion as a callable global (issue #36).
|
|
236
|
+
let mut number_obj = PropMap::with_capacity(1);
|
|
237
|
+
number_obj.insert("__call".into(), Value::Native(natives::number_convert));
|
|
238
|
+
s.set("Number".into(), Value::object(number_obj), true);
|
|
239
|
+
|
|
240
|
+
s.set(
|
|
241
|
+
"Date".into(),
|
|
242
|
+
crate::value_convert::core_to_eval(
|
|
243
|
+
tishlang_builtins::date::date_constructor_value(),
|
|
244
|
+
),
|
|
245
|
+
true,
|
|
246
|
+
);
|
|
247
|
+
s.set(
|
|
248
|
+
"Set".into(),
|
|
249
|
+
crate::value_convert::core_to_eval(
|
|
250
|
+
tishlang_builtins::collections::set_constructor_value(),
|
|
251
|
+
),
|
|
252
|
+
true,
|
|
253
|
+
);
|
|
254
|
+
s.set(
|
|
255
|
+
"Map".into(),
|
|
256
|
+
crate::value_convert::core_to_eval(
|
|
257
|
+
tishlang_builtins::collections::map_constructor_value(),
|
|
258
|
+
),
|
|
259
|
+
true,
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
s.set(
|
|
263
|
+
"Symbol".into(),
|
|
264
|
+
crate::value_convert::core_to_eval(tishlang_builtins::symbol::symbol_object()),
|
|
265
|
+
true,
|
|
266
|
+
);
|
|
267
|
+
for (name, ctor) in [
|
|
268
|
+
(
|
|
269
|
+
"Float64Array",
|
|
270
|
+
tishlang_builtins::typedarrays::float64_array_constructor_value
|
|
271
|
+
as fn() -> tishlang_core::Value,
|
|
272
|
+
),
|
|
273
|
+
("Float32Array", tishlang_builtins::typedarrays::float32_array_constructor_value),
|
|
274
|
+
("Int8Array", tishlang_builtins::typedarrays::int8_array_constructor_value),
|
|
275
|
+
("Uint8Array", tishlang_builtins::typedarrays::uint8_array_constructor_value),
|
|
276
|
+
(
|
|
277
|
+
"Uint8ClampedArray",
|
|
278
|
+
tishlang_builtins::typedarrays::uint8_clamped_array_constructor_value,
|
|
279
|
+
),
|
|
280
|
+
("Int16Array", tishlang_builtins::typedarrays::int16_array_constructor_value),
|
|
281
|
+
("Uint16Array", tishlang_builtins::typedarrays::uint16_array_constructor_value),
|
|
282
|
+
("Int32Array", tishlang_builtins::typedarrays::int32_array_constructor_value),
|
|
283
|
+
("Uint32Array", tishlang_builtins::typedarrays::uint32_array_constructor_value),
|
|
284
|
+
] {
|
|
285
|
+
s.set(name.into(), crate::value_convert::core_to_eval(ctor()), true);
|
|
286
|
+
}
|
|
287
|
+
s.set(
|
|
288
|
+
"AudioContext".into(),
|
|
289
|
+
crate::value_convert::core_to_eval(
|
|
290
|
+
tishlang_builtins::construct::audio_context_constructor_value(),
|
|
291
|
+
),
|
|
292
|
+
true,
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
#[cfg(feature = "regex")]
|
|
296
|
+
{
|
|
297
|
+
s.set(
|
|
298
|
+
"RegExp".into(),
|
|
299
|
+
Value::Native(Self::regexp_constructor_native),
|
|
300
|
+
true,
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// fs, process: prefer `import { x } from 'tish:fs'` etc.
|
|
305
|
+
#[cfg(feature = "timers")]
|
|
306
|
+
{
|
|
307
|
+
s.set(
|
|
308
|
+
"setTimeout".into(),
|
|
309
|
+
Value::TimerBuiltin(Arc::from("setTimeout")),
|
|
310
|
+
true,
|
|
311
|
+
);
|
|
312
|
+
s.set(
|
|
313
|
+
"setInterval".into(),
|
|
314
|
+
Value::TimerBuiltin(Arc::from("setInterval")),
|
|
315
|
+
true,
|
|
316
|
+
);
|
|
317
|
+
s.set(
|
|
318
|
+
"clearTimeout".into(),
|
|
319
|
+
Value::Native(Self::clear_timeout_native),
|
|
320
|
+
true,
|
|
321
|
+
);
|
|
322
|
+
s.set(
|
|
323
|
+
"clearInterval".into(),
|
|
324
|
+
Value::Native(Self::clear_interval_native),
|
|
325
|
+
true,
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
#[cfg(feature = "http")]
|
|
329
|
+
{
|
|
330
|
+
s.set("fetch".into(), Value::Native(Self::fetch_native), true);
|
|
331
|
+
s.set(
|
|
332
|
+
"fetchAll".into(),
|
|
333
|
+
Value::Native(Self::fetch_all_native),
|
|
334
|
+
true,
|
|
335
|
+
);
|
|
336
|
+
s.set("Promise".into(), Value::PromiseConstructor, true);
|
|
337
|
+
s.set("serve".into(), Value::Serve, true);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
Self {
|
|
341
|
+
scope,
|
|
342
|
+
module_cache: Rc::new(RefCell::new(HashMap::new())),
|
|
343
|
+
current_dir: RefCell::new(None),
|
|
344
|
+
virtual_builtins: Rc::new(RefCell::new(HashMap::new())),
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/// Create an evaluator with extra native modules (e.g. Polars) registered.
|
|
349
|
+
pub fn with_modules(modules: &[&dyn crate::TishNativeModule]) -> Self {
|
|
350
|
+
let eval = Self::new();
|
|
351
|
+
{
|
|
352
|
+
let mut s = eval.scope.borrow_mut();
|
|
353
|
+
for module in modules {
|
|
354
|
+
for (name, value) in module.register() {
|
|
355
|
+
s.set(name, value, true);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
{
|
|
360
|
+
let mut vb = eval.virtual_builtins.borrow_mut();
|
|
361
|
+
for module in modules {
|
|
362
|
+
for (spec, value) in module.virtual_builtin_modules() {
|
|
363
|
+
vb.insert(Arc::from(spec), value);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
eval
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
pub fn set_current_dir(&self, dir: Option<&Path>) {
|
|
371
|
+
*self.current_dir.borrow_mut() = dir.map(PathBuf::from);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
pub fn eval_program(&mut self, program: &tishlang_ast::Program) -> Result<Value, String> {
|
|
375
|
+
let mut last = Value::Null;
|
|
376
|
+
for stmt in &program.statements {
|
|
377
|
+
last = self.eval_statement(stmt).map_err(|e| e.to_string())?;
|
|
378
|
+
}
|
|
379
|
+
Ok(last)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
fn eval_statement(&mut self, stmt: &Statement) -> Result<Value, EvalError> {
|
|
383
|
+
match stmt {
|
|
384
|
+
Statement::Block { statements, .. } => {
|
|
385
|
+
let scope = Scope::child(Rc::clone(&self.scope));
|
|
386
|
+
let prev = std::mem::replace(&mut self.scope, scope);
|
|
387
|
+
let mut last = Value::Null;
|
|
388
|
+
for s in statements {
|
|
389
|
+
last = self.eval_statement(s)?;
|
|
390
|
+
}
|
|
391
|
+
self.scope = prev;
|
|
392
|
+
Ok(last)
|
|
393
|
+
}
|
|
394
|
+
// Comma-declarators: a transparent group — evaluate each declarator in
|
|
395
|
+
// the *current* scope (no child scope).
|
|
396
|
+
Statement::Multi { statements, .. } => {
|
|
397
|
+
let mut last = Value::Null;
|
|
398
|
+
for s in statements {
|
|
399
|
+
last = self.eval_statement(s)?;
|
|
400
|
+
}
|
|
401
|
+
Ok(last)
|
|
402
|
+
}
|
|
403
|
+
Statement::VarDecl {
|
|
404
|
+
name,
|
|
405
|
+
mutable,
|
|
406
|
+
init,
|
|
407
|
+
..
|
|
408
|
+
} => {
|
|
409
|
+
let value = init
|
|
410
|
+
.as_ref()
|
|
411
|
+
.map(|e| self.eval_expr(e))
|
|
412
|
+
.transpose()?
|
|
413
|
+
.unwrap_or(Value::Null);
|
|
414
|
+
self.scope
|
|
415
|
+
.borrow_mut()
|
|
416
|
+
.set(Arc::clone(name), value, *mutable);
|
|
417
|
+
Ok(Value::Null)
|
|
418
|
+
}
|
|
419
|
+
Statement::VarDeclDestructure {
|
|
420
|
+
pattern,
|
|
421
|
+
mutable,
|
|
422
|
+
init,
|
|
423
|
+
..
|
|
424
|
+
} => {
|
|
425
|
+
let value = self.eval_expr(init)?;
|
|
426
|
+
self.bind_destruct_pattern(pattern, &value, *mutable)?;
|
|
427
|
+
Ok(Value::Null)
|
|
428
|
+
}
|
|
429
|
+
Statement::ExprStmt { expr, .. } => self.eval_expr(expr),
|
|
430
|
+
Statement::If {
|
|
431
|
+
cond,
|
|
432
|
+
then_branch,
|
|
433
|
+
else_branch,
|
|
434
|
+
..
|
|
435
|
+
} => {
|
|
436
|
+
let c = self.eval_expr(cond)?;
|
|
437
|
+
if c.is_truthy() {
|
|
438
|
+
self.eval_statement(then_branch)
|
|
439
|
+
} else if let Some(eb) = else_branch {
|
|
440
|
+
self.eval_statement(eb)
|
|
441
|
+
} else {
|
|
442
|
+
Ok(Value::Null)
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
Statement::While { cond, body, .. } => {
|
|
446
|
+
loop {
|
|
447
|
+
if !self.eval_expr(cond)?.is_truthy() {
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
match self.eval_statement(body) {
|
|
451
|
+
Ok(_) => {}
|
|
452
|
+
Err(EvalError::Break) => break,
|
|
453
|
+
Err(EvalError::Continue) => continue,
|
|
454
|
+
Err(e) => return Err(e),
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
Ok(Value::Null)
|
|
458
|
+
}
|
|
459
|
+
Statement::ForOf {
|
|
460
|
+
name,
|
|
461
|
+
iterable,
|
|
462
|
+
body,
|
|
463
|
+
..
|
|
464
|
+
} => {
|
|
465
|
+
let iter_val = self.eval_expr(iterable)?;
|
|
466
|
+
let elements = match &iter_val {
|
|
467
|
+
crate::value::Value::Array(arr) => {
|
|
468
|
+
arr.borrow().iter().cloned().collect::<Vec<_>>()
|
|
469
|
+
}
|
|
470
|
+
crate::value::Value::String(s) => s
|
|
471
|
+
.chars()
|
|
472
|
+
.map(|c| crate::value::Value::String(Arc::from(c.to_string())))
|
|
473
|
+
.collect::<Vec<_>>(),
|
|
474
|
+
// Iterator protocol: an object with a callable `next()` returning
|
|
475
|
+
// `{ value, done }` — e.g. a Map/Set iterator from `.values()` /
|
|
476
|
+
// `.keys()` / `.entries()`. Drain it ONCE (draining advances the
|
|
477
|
+
// iterator's shared position, so it must not be re-run).
|
|
478
|
+
_ => match self.drain_eval_iterator(&iter_val) {
|
|
479
|
+
Some(elems) => elems,
|
|
480
|
+
None => {
|
|
481
|
+
return Err(EvalError::Error(format!(
|
|
482
|
+
"for-of requires iterable (array, string, or iterator), got {}",
|
|
483
|
+
iter_val
|
|
484
|
+
)));
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
};
|
|
488
|
+
// Each element gets a FRESH per-iteration binding (ES6 `for (let v of …)`), so a
|
|
489
|
+
// closure created in the body captures that element, not the last one.
|
|
490
|
+
let outer = Rc::clone(&self.scope);
|
|
491
|
+
let mut ret = Ok(Value::Null);
|
|
492
|
+
for elem in elements {
|
|
493
|
+
let iter_env = Scope::child(Rc::clone(&outer));
|
|
494
|
+
iter_env.borrow_mut().set(Arc::clone(name), elem, true);
|
|
495
|
+
self.scope = Rc::clone(&iter_env);
|
|
496
|
+
match self.eval_statement(body) {
|
|
497
|
+
Ok(_) => {}
|
|
498
|
+
Err(EvalError::Break) => break,
|
|
499
|
+
Err(EvalError::Continue) => continue,
|
|
500
|
+
Err(e) => {
|
|
501
|
+
ret = Err(e);
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
self.scope = outer;
|
|
507
|
+
ret
|
|
508
|
+
}
|
|
509
|
+
Statement::For {
|
|
510
|
+
init,
|
|
511
|
+
cond,
|
|
512
|
+
update,
|
|
513
|
+
body,
|
|
514
|
+
..
|
|
515
|
+
} => {
|
|
516
|
+
// `let`/`const` declared in `init` get a FRESH per-iteration binding (ES6), so a
|
|
517
|
+
// closure created in the body captures THAT iteration's value, not the final one.
|
|
518
|
+
// The canonical values live in `loop_env`; each iteration's body runs in a fresh
|
|
519
|
+
// `iter_env` copy, and mutations are copied back for the next test/update.
|
|
520
|
+
let outer = Rc::clone(&self.scope);
|
|
521
|
+
let loop_env = Scope::child(Rc::clone(&outer));
|
|
522
|
+
self.scope = Rc::clone(&loop_env);
|
|
523
|
+
if let Some(i) = init {
|
|
524
|
+
if let Err(e) = self.eval_statement(i) {
|
|
525
|
+
self.scope = outer;
|
|
526
|
+
return Err(e);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
let per_iter: Vec<Arc<str>> = loop_env.borrow().vars.keys().cloned().collect();
|
|
530
|
+
let copy_vars = |from: &ScopeRef, to: &ScopeRef, names: &[Arc<str>]| {
|
|
531
|
+
let src = from.borrow();
|
|
532
|
+
let mut dst = to.borrow_mut();
|
|
533
|
+
for n in names {
|
|
534
|
+
if let Some(v) = src.vars.get(n.as_ref()) {
|
|
535
|
+
dst.set(Arc::clone(n), v.clone(), true);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
let mut ret = Ok(Value::Null);
|
|
540
|
+
loop {
|
|
541
|
+
self.scope = Rc::clone(&loop_env);
|
|
542
|
+
let cond_ok = match cond.as_ref() {
|
|
543
|
+
Some(c) => match self.eval_expr(c) {
|
|
544
|
+
Ok(v) => v.is_truthy(),
|
|
545
|
+
Err(e) => {
|
|
546
|
+
ret = Err(e);
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
},
|
|
550
|
+
None => true,
|
|
551
|
+
};
|
|
552
|
+
if !cond_ok {
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
let iter_env = if per_iter.is_empty() {
|
|
556
|
+
Rc::clone(&loop_env)
|
|
557
|
+
} else {
|
|
558
|
+
let e = Scope::child(Rc::clone(&outer));
|
|
559
|
+
copy_vars(&loop_env, &e, &per_iter);
|
|
560
|
+
e
|
|
561
|
+
};
|
|
562
|
+
self.scope = Rc::clone(&iter_env);
|
|
563
|
+
let flow = self.eval_statement(body);
|
|
564
|
+
if !per_iter.is_empty() {
|
|
565
|
+
copy_vars(&iter_env, &loop_env, &per_iter);
|
|
566
|
+
}
|
|
567
|
+
match flow {
|
|
568
|
+
Ok(_) => {}
|
|
569
|
+
Err(EvalError::Break) => break,
|
|
570
|
+
Err(EvalError::Continue) => {}
|
|
571
|
+
Err(e) => {
|
|
572
|
+
ret = Err(e);
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
self.scope = Rc::clone(&loop_env);
|
|
577
|
+
if let Some(u) = update {
|
|
578
|
+
if let Err(e) = self.eval_expr(u) {
|
|
579
|
+
ret = Err(e);
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
self.scope = outer;
|
|
585
|
+
ret
|
|
586
|
+
}
|
|
587
|
+
Statement::Return { value, .. } => {
|
|
588
|
+
let v = value
|
|
589
|
+
.as_ref()
|
|
590
|
+
.map(|e| self.eval_expr(e))
|
|
591
|
+
.transpose()?
|
|
592
|
+
.unwrap_or(Value::Null);
|
|
593
|
+
Err(EvalError::Return(v))
|
|
594
|
+
}
|
|
595
|
+
Statement::Break { .. } => Err(EvalError::Break),
|
|
596
|
+
Statement::Continue { .. } => Err(EvalError::Continue),
|
|
597
|
+
Statement::FunDecl {
|
|
598
|
+
name,
|
|
599
|
+
params,
|
|
600
|
+
rest_param,
|
|
601
|
+
body,
|
|
602
|
+
..
|
|
603
|
+
} => {
|
|
604
|
+
let formals: Arc<[FunParam]> = Arc::from(params.clone());
|
|
605
|
+
let rest_param_name = rest_param.as_ref().map(|p| Arc::clone(&p.name));
|
|
606
|
+
let body = Arc::new(body.as_ref().clone());
|
|
607
|
+
let func = Value::Function {
|
|
608
|
+
formals,
|
|
609
|
+
rest_param: rest_param_name,
|
|
610
|
+
body,
|
|
611
|
+
// Capture the defining scope. It's the SAME Rc we insert into below, so the
|
|
612
|
+
// function sees itself → recursion works.
|
|
613
|
+
env: Rc::clone(&self.scope),
|
|
614
|
+
};
|
|
615
|
+
self.scope.borrow_mut().set(Arc::clone(name), func, true);
|
|
616
|
+
Ok(Value::Null)
|
|
617
|
+
}
|
|
618
|
+
Statement::Switch {
|
|
619
|
+
expr,
|
|
620
|
+
cases,
|
|
621
|
+
default_body,
|
|
622
|
+
..
|
|
623
|
+
} => {
|
|
624
|
+
let v = self.eval_expr(expr)?;
|
|
625
|
+
let mut matched = false;
|
|
626
|
+
for (case_expr, body) in cases {
|
|
627
|
+
if let Some(ce) = case_expr {
|
|
628
|
+
let cv = self.eval_expr(ce)?;
|
|
629
|
+
if v.strict_eq(&cv) {
|
|
630
|
+
matched = true;
|
|
631
|
+
let scope = Scope::child(Rc::clone(&self.scope));
|
|
632
|
+
let prev = std::mem::replace(&mut self.scope, scope);
|
|
633
|
+
for s in body {
|
|
634
|
+
match self.eval_statement(s) {
|
|
635
|
+
Ok(_) => {}
|
|
636
|
+
Err(EvalError::Break) => {
|
|
637
|
+
self.scope = prev;
|
|
638
|
+
return Ok(Value::Null);
|
|
639
|
+
}
|
|
640
|
+
Err(e) => {
|
|
641
|
+
self.scope = prev;
|
|
642
|
+
return Err(e);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
self.scope = prev;
|
|
647
|
+
break;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
if !matched {
|
|
652
|
+
if let Some(body) = default_body {
|
|
653
|
+
let scope = Scope::child(Rc::clone(&self.scope));
|
|
654
|
+
let prev = std::mem::replace(&mut self.scope, scope);
|
|
655
|
+
for s in body {
|
|
656
|
+
match self.eval_statement(s) {
|
|
657
|
+
Ok(_) => {}
|
|
658
|
+
Err(EvalError::Break) => break,
|
|
659
|
+
Err(e) => {
|
|
660
|
+
self.scope = prev;
|
|
661
|
+
return Err(e);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
self.scope = prev;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
Ok(Value::Null)
|
|
669
|
+
}
|
|
670
|
+
Statement::DoWhile { body, cond, .. } => {
|
|
671
|
+
loop {
|
|
672
|
+
match self.eval_statement(body) {
|
|
673
|
+
Ok(_) => {}
|
|
674
|
+
Err(EvalError::Break) => break,
|
|
675
|
+
Err(EvalError::Continue) => {
|
|
676
|
+
if !self.eval_expr(cond)?.is_truthy() {
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
Err(e) => return Err(e),
|
|
682
|
+
}
|
|
683
|
+
if !self.eval_expr(cond)?.is_truthy() {
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
Ok(Value::Null)
|
|
688
|
+
}
|
|
689
|
+
Statement::Throw { value, .. } => {
|
|
690
|
+
let v = self.eval_expr(value)?;
|
|
691
|
+
Err(EvalError::Throw(v))
|
|
692
|
+
}
|
|
693
|
+
Statement::Try {
|
|
694
|
+
body,
|
|
695
|
+
catch_param,
|
|
696
|
+
catch_body,
|
|
697
|
+
finally_body,
|
|
698
|
+
..
|
|
699
|
+
} => {
|
|
700
|
+
let try_result = self.eval_statement(body);
|
|
701
|
+
|
|
702
|
+
// Both a user `throw` and a runtime error (`null.foo()`, "not a function", …)
|
|
703
|
+
// are catchable (issue #60); a runtime error is boxed as a `{ name, message }`
|
|
704
|
+
// object so `catch (e) { e.message }` works. Break/Continue/Return propagate.
|
|
705
|
+
let caught: Option<Value> = match &try_result {
|
|
706
|
+
Err(EvalError::Throw(v)) => Some(v.clone()),
|
|
707
|
+
Err(EvalError::Error(msg)) => {
|
|
708
|
+
let mut m = crate::value::PropMap::with_capacity(2);
|
|
709
|
+
m.insert("name".into(), Value::String("TypeError".into()));
|
|
710
|
+
m.insert("message".into(), Value::String(msg.as_str().into()));
|
|
711
|
+
Some(Value::object(m))
|
|
712
|
+
}
|
|
713
|
+
_ => None,
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
let result = match caught {
|
|
717
|
+
Some(thrown) => {
|
|
718
|
+
if let Some(catch_stmt) = catch_body {
|
|
719
|
+
if let Some(param) = catch_param {
|
|
720
|
+
let scope = Scope::child(Rc::clone(&self.scope));
|
|
721
|
+
let prev = std::mem::replace(&mut self.scope, Rc::clone(&scope));
|
|
722
|
+
scope.borrow_mut().set(Arc::clone(param), thrown, true);
|
|
723
|
+
let res = self.eval_statement(catch_stmt);
|
|
724
|
+
self.scope = prev;
|
|
725
|
+
res
|
|
726
|
+
} else {
|
|
727
|
+
self.eval_statement(catch_stmt)
|
|
728
|
+
}
|
|
729
|
+
} else {
|
|
730
|
+
// No catch clause — re-raise the original error after `finally`.
|
|
731
|
+
try_result
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
None => try_result,
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
if let Some(finally_stmt) = finally_body {
|
|
738
|
+
// KNOWN BUG (shared with the VM/compiled backends): a throw/return/
|
|
739
|
+
// break/continue inside `finally` should supersede the try/catch
|
|
740
|
+
// outcome (JS completion semantics) but is swallowed here. Fixing it
|
|
741
|
+
// in the interp alone (`?`) breaks interp==vm parity because the VM has
|
|
742
|
+
// the same bug, and the VM fix is a bytecode-compiler-level
|
|
743
|
+
// finally-completion change. Deferred as a coordinated cross-backend fix.
|
|
744
|
+
let _ = self.eval_statement(finally_stmt);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
result
|
|
748
|
+
}
|
|
749
|
+
Statement::Import {
|
|
750
|
+
specifiers, from, ..
|
|
751
|
+
} => {
|
|
752
|
+
let exports_val = self.load_module(from)?;
|
|
753
|
+
let exports = match &exports_val {
|
|
754
|
+
Value::Object(m) => m.borrow().clone(),
|
|
755
|
+
_ => {
|
|
756
|
+
return Err(EvalError::Error(
|
|
757
|
+
"Module exports must be object".to_string(),
|
|
758
|
+
))
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
let mut scope = self.scope.borrow_mut();
|
|
762
|
+
for spec in specifiers {
|
|
763
|
+
match spec {
|
|
764
|
+
ImportSpecifier::Named { name, alias, .. } => {
|
|
765
|
+
let v = exports.strings.get(name.as_ref()).ok_or_else(|| {
|
|
766
|
+
EvalError::Error(format!("Module does not export '{}'", name))
|
|
767
|
+
})?;
|
|
768
|
+
let bind = alias.as_deref().unwrap_or(name.as_ref());
|
|
769
|
+
scope.set(Arc::from(bind), v.clone(), false);
|
|
770
|
+
}
|
|
771
|
+
ImportSpecifier::Namespace { name, .. } => {
|
|
772
|
+
scope.set(Arc::clone(name), exports_val.clone(), false);
|
|
773
|
+
}
|
|
774
|
+
ImportSpecifier::Default { name, .. } => {
|
|
775
|
+
let v = exports.strings.get("default").ok_or_else(|| {
|
|
776
|
+
EvalError::Error("Module does not have default export".to_string())
|
|
777
|
+
})?;
|
|
778
|
+
scope.set(Arc::clone(name), v.clone(), false);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
Ok(Value::Null)
|
|
783
|
+
}
|
|
784
|
+
Statement::Export { declaration, .. } => {
|
|
785
|
+
match declaration.as_ref() {
|
|
786
|
+
ExportDeclaration::Named(s) => {
|
|
787
|
+
let _ = self.eval_statement(s);
|
|
788
|
+
}
|
|
789
|
+
ExportDeclaration::Default(e) => {
|
|
790
|
+
let v = self.eval_expr(e)?;
|
|
791
|
+
self.scope.borrow_mut().set(Arc::from("default"), v, false);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
Ok(Value::Null)
|
|
795
|
+
}
|
|
796
|
+
Statement::TypeAlias { .. }
|
|
797
|
+
| Statement::DeclareVar { .. }
|
|
798
|
+
| Statement::DeclareFun { .. } => Ok(Value::Null),
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/// Load and evaluate a module, returning its exports object. Uses cache.
|
|
803
|
+
fn load_module(&mut self, from: &str) -> Result<Value, EvalError> {
|
|
804
|
+
if from.starts_with("cargo:") {
|
|
805
|
+
return Err(EvalError::Error(
|
|
806
|
+
"cargo:… imports are only supported by `tish build` with the Rust native backend."
|
|
807
|
+
.into(),
|
|
808
|
+
));
|
|
809
|
+
}
|
|
810
|
+
if from.starts_with("tish:") {
|
|
811
|
+
return self.load_builtin_module(from);
|
|
812
|
+
}
|
|
813
|
+
// Scoped native modules (e.g. `@tishlang/waterui`) registered via `TishNativeModule::virtual_builtin_modules`.
|
|
814
|
+
if self.virtual_builtins.borrow().get(from).is_some() {
|
|
815
|
+
return self.load_builtin_module(from);
|
|
816
|
+
}
|
|
817
|
+
let dir = self.current_dir.borrow().clone().ok_or_else(|| {
|
|
818
|
+
EvalError::Error(
|
|
819
|
+
"Cannot resolve imports: no current file directory (use run_file)".to_string(),
|
|
820
|
+
)
|
|
821
|
+
})?;
|
|
822
|
+
let path = Self::resolve_import_path(from, &dir)?;
|
|
823
|
+
let path = path
|
|
824
|
+
.canonicalize()
|
|
825
|
+
.map_err(|e| EvalError::Error(format!("Cannot resolve import '{}': {}", from, e)))?;
|
|
826
|
+
{
|
|
827
|
+
let cache = self.module_cache.borrow();
|
|
828
|
+
if let Some(m) = cache.get(&path) {
|
|
829
|
+
return Ok(m.clone());
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
let source = std::fs::read_to_string(&path)
|
|
833
|
+
.map_err(|e| EvalError::Error(format!("Cannot read {}: {}", path.display(), e)))?;
|
|
834
|
+
let program = tishlang_parser::parse(&source)
|
|
835
|
+
.map_err(|e| EvalError::Error(format!("Parse error in {}: {}", path.display(), e)))?;
|
|
836
|
+
let module_scope = Scope::child(Rc::clone(&self.scope));
|
|
837
|
+
let prev_scope = std::mem::replace(&mut self.scope, Rc::clone(&module_scope));
|
|
838
|
+
let parent_dir = self.current_dir.borrow().clone();
|
|
839
|
+
let module_dir = path.parent().map(PathBuf::from);
|
|
840
|
+
*self.current_dir.borrow_mut() = module_dir;
|
|
841
|
+
let mut export_names: Vec<String> = Vec::new();
|
|
842
|
+
for stmt in &program.statements {
|
|
843
|
+
if let Statement::Export { declaration, .. } = stmt {
|
|
844
|
+
match declaration.as_ref() {
|
|
845
|
+
ExportDeclaration::Named(s) => {
|
|
846
|
+
let _ = self.eval_statement(s);
|
|
847
|
+
if let Statement::VarDecl { name, .. } | Statement::FunDecl { name, .. } =
|
|
848
|
+
s.as_ref()
|
|
849
|
+
{
|
|
850
|
+
export_names.push(name.to_string());
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
ExportDeclaration::Default(e) => {
|
|
854
|
+
let v = self.eval_expr(e)?;
|
|
855
|
+
self.scope.borrow_mut().set(Arc::from("default"), v, false);
|
|
856
|
+
export_names.push("default".to_string());
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
} else {
|
|
860
|
+
let _ = self.eval_statement(stmt);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
let mut exports: PropMap = PropMap::default();
|
|
864
|
+
for name in export_names {
|
|
865
|
+
if let Some(v) = module_scope.borrow().get(&name) {
|
|
866
|
+
exports.insert(Arc::from(name.as_str()), v);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
*self.current_dir.borrow_mut() = parent_dir;
|
|
870
|
+
self.scope = prev_scope;
|
|
871
|
+
let exports_val = Value::object(exports);
|
|
872
|
+
self.module_cache
|
|
873
|
+
.borrow_mut()
|
|
874
|
+
.insert(path, exports_val.clone());
|
|
875
|
+
Ok(exports_val)
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
fn resolve_import_path(from: &str, dir: &Path) -> Result<PathBuf, EvalError> {
|
|
879
|
+
if !from.starts_with("./") && !from.starts_with("../") {
|
|
880
|
+
return Err(EvalError::Error(format!(
|
|
881
|
+
"Only relative imports supported (./ or ../), got: {}",
|
|
882
|
+
from
|
|
883
|
+
)));
|
|
884
|
+
}
|
|
885
|
+
let base = dir.join(from);
|
|
886
|
+
let path = if base.extension().is_none() {
|
|
887
|
+
let with_ext = base.with_extension("tish");
|
|
888
|
+
if with_ext.exists() {
|
|
889
|
+
with_ext
|
|
890
|
+
} else {
|
|
891
|
+
base
|
|
892
|
+
}
|
|
893
|
+
} else {
|
|
894
|
+
base
|
|
895
|
+
};
|
|
896
|
+
Ok(path)
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/// Load built-in module (tish:fs, tish:http, tish:process, …) or a virtual module from native crates.
|
|
900
|
+
fn load_builtin_module(&self, spec: &str) -> Result<Value, EvalError> {
|
|
901
|
+
if spec.starts_with("cargo:") {
|
|
902
|
+
return Err(EvalError::Error(
|
|
903
|
+
"cargo:… imports are only supported when compiling with `tish build` and the Rust native backend. They link Cargo crates via package.json tish.rustDependencies and a generated native wrapper — not the interpreter or VM.".into(),
|
|
904
|
+
));
|
|
905
|
+
}
|
|
906
|
+
if let Some(v) = self.virtual_builtins.borrow().get(spec) {
|
|
907
|
+
return Ok(v.clone());
|
|
908
|
+
}
|
|
909
|
+
match spec {
|
|
910
|
+
"tish:fs" => {
|
|
911
|
+
#[cfg(feature = "fs")]
|
|
912
|
+
{
|
|
913
|
+
let mut exports: PropMap = PropMap::default();
|
|
914
|
+
exports.insert("readFile".into(), Value::Native(natives::read_file));
|
|
915
|
+
exports.insert("writeFile".into(), Value::Native(natives::write_file));
|
|
916
|
+
exports.insert("fileExists".into(), Value::Native(natives::file_exists));
|
|
917
|
+
exports.insert("isDir".into(), Value::Native(natives::is_dir));
|
|
918
|
+
exports.insert("readDir".into(), Value::Native(natives::read_dir));
|
|
919
|
+
exports.insert("mkdir".into(), Value::Native(natives::mkdir));
|
|
920
|
+
Ok(Value::object(exports))
|
|
921
|
+
}
|
|
922
|
+
#[cfg(not(feature = "fs"))]
|
|
923
|
+
{
|
|
924
|
+
return Err(EvalError::Error(
|
|
925
|
+
"tish:fs requires the fs feature. Rebuild with: cargo build -p tishlang --features fs".into(),
|
|
926
|
+
));
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
"tish:http" => {
|
|
930
|
+
#[cfg(feature = "http")]
|
|
931
|
+
{
|
|
932
|
+
let mut exports: PropMap = PropMap::default();
|
|
933
|
+
exports.insert("fetch".into(), Value::Native(Self::fetch_native));
|
|
934
|
+
exports.insert("fetchAll".into(), Value::Native(Self::fetch_all_native));
|
|
935
|
+
exports.insert("serve".into(), Value::Serve);
|
|
936
|
+
exports.insert("Promise".into(), Value::PromiseConstructor);
|
|
937
|
+
Ok(Value::object(exports))
|
|
938
|
+
}
|
|
939
|
+
#[cfg(not(feature = "http"))]
|
|
940
|
+
{
|
|
941
|
+
return Err(EvalError::Error(
|
|
942
|
+
"tish:http requires the http feature. Rebuild with: cargo build -p tishlang --features http".into(),
|
|
943
|
+
));
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
"tish:timers" => {
|
|
947
|
+
#[cfg(feature = "timers")]
|
|
948
|
+
{
|
|
949
|
+
let mut exports: PropMap = PropMap::default();
|
|
950
|
+
exports.insert(
|
|
951
|
+
"setTimeout".into(),
|
|
952
|
+
Value::TimerBuiltin(Arc::from("setTimeout")),
|
|
953
|
+
);
|
|
954
|
+
exports.insert(
|
|
955
|
+
"setInterval".into(),
|
|
956
|
+
Value::TimerBuiltin(Arc::from("setInterval")),
|
|
957
|
+
);
|
|
958
|
+
exports.insert(
|
|
959
|
+
"clearTimeout".into(),
|
|
960
|
+
Value::Native(Self::clear_timeout_native),
|
|
961
|
+
);
|
|
962
|
+
exports.insert(
|
|
963
|
+
"clearInterval".into(),
|
|
964
|
+
Value::Native(Self::clear_interval_native),
|
|
965
|
+
);
|
|
966
|
+
Ok(Value::object(exports))
|
|
967
|
+
}
|
|
968
|
+
#[cfg(not(feature = "timers"))]
|
|
969
|
+
{
|
|
970
|
+
return Err(EvalError::Error(
|
|
971
|
+
"tish:timers requires the timers feature. Rebuild with: cargo build -p tishlang --features timers".into(),
|
|
972
|
+
));
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
"tish:ws" => {
|
|
976
|
+
#[cfg(feature = "ws")]
|
|
977
|
+
{
|
|
978
|
+
let mut exports: PropMap = PropMap::default();
|
|
979
|
+
exports.insert(
|
|
980
|
+
"WebSocket".into(),
|
|
981
|
+
Value::Native(Self::ws_web_socket_native),
|
|
982
|
+
);
|
|
983
|
+
exports.insert("Server".into(), Value::Native(Self::ws_server_native));
|
|
984
|
+
exports.insert("wsSend".into(), Value::Native(Self::ws_send_native));
|
|
985
|
+
exports.insert(
|
|
986
|
+
"wsBroadcast".into(),
|
|
987
|
+
Value::Native(Self::ws_broadcast_native),
|
|
988
|
+
);
|
|
989
|
+
Ok(Value::object(exports))
|
|
990
|
+
}
|
|
991
|
+
#[cfg(not(feature = "ws"))]
|
|
992
|
+
{
|
|
993
|
+
return Err(EvalError::Error(
|
|
994
|
+
"tish:ws requires the ws feature. Rebuild with: cargo build -p tishlang --features ws".into(),
|
|
995
|
+
));
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
"tish:tty" => {
|
|
999
|
+
#[cfg(feature = "tty")]
|
|
1000
|
+
{
|
|
1001
|
+
let mut exports: PropMap = PropMap::default();
|
|
1002
|
+
exports.insert("size".into(), Value::Native(natives::tty_size));
|
|
1003
|
+
exports.insert("isTTY".into(), Value::Native(natives::tty_is_tty));
|
|
1004
|
+
exports.insert("setRawMode".into(), Value::Native(natives::tty_set_raw_mode));
|
|
1005
|
+
exports.insert(
|
|
1006
|
+
"enterAltScreen".into(),
|
|
1007
|
+
Value::Native(natives::tty_enter_alt_screen),
|
|
1008
|
+
);
|
|
1009
|
+
exports.insert(
|
|
1010
|
+
"leaveAltScreen".into(),
|
|
1011
|
+
Value::Native(natives::tty_leave_alt_screen),
|
|
1012
|
+
);
|
|
1013
|
+
exports.insert("read".into(), Value::Native(natives::tty_read));
|
|
1014
|
+
exports.insert("readLine".into(), Value::Native(natives::tty_read_line));
|
|
1015
|
+
Ok(Value::object(exports))
|
|
1016
|
+
}
|
|
1017
|
+
#[cfg(not(feature = "tty"))]
|
|
1018
|
+
{
|
|
1019
|
+
return Err(EvalError::Error(
|
|
1020
|
+
"tish:tty requires the tty feature. Rebuild with: cargo build -p tishlang --features tty".into(),
|
|
1021
|
+
));
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
"tish:process" => {
|
|
1025
|
+
#[cfg(feature = "process")]
|
|
1026
|
+
{
|
|
1027
|
+
let mut exports: PropMap = PropMap::default();
|
|
1028
|
+
exports.insert("exit".into(), Value::Native(natives::process_exit));
|
|
1029
|
+
exports.insert("cwd".into(), Value::Native(natives::process_cwd));
|
|
1030
|
+
exports.insert("exec".into(), Value::Native(natives::process_exec));
|
|
1031
|
+
let argv: Vec<Value> =
|
|
1032
|
+
std::env::args().map(|s| Value::String(s.into())).collect();
|
|
1033
|
+
exports.insert(
|
|
1034
|
+
"argv".into(),
|
|
1035
|
+
Value::Array(Rc::new(RefCell::new(argv.clone()))),
|
|
1036
|
+
);
|
|
1037
|
+
let env_obj: PropMap = std::env::vars()
|
|
1038
|
+
.map(|(key, value)| (Arc::from(key.as_str()), Value::String(value.into())))
|
|
1039
|
+
.collect();
|
|
1040
|
+
exports.insert(
|
|
1041
|
+
"env".into(),
|
|
1042
|
+
Value::object(env_obj.clone()),
|
|
1043
|
+
);
|
|
1044
|
+
let mut process_obj = PropMap::default();
|
|
1045
|
+
process_obj.insert("exit".into(), Value::Native(natives::process_exit));
|
|
1046
|
+
process_obj.insert("cwd".into(), Value::Native(natives::process_cwd));
|
|
1047
|
+
process_obj.insert("exec".into(), Value::Native(natives::process_exec));
|
|
1048
|
+
process_obj.insert("argv".into(), Value::Array(Rc::new(RefCell::new(argv))));
|
|
1049
|
+
process_obj.insert("env".into(), Value::object(env_obj));
|
|
1050
|
+
exports.insert(
|
|
1051
|
+
"process".into(),
|
|
1052
|
+
Value::object(process_obj),
|
|
1053
|
+
);
|
|
1054
|
+
Ok(Value::object(exports))
|
|
1055
|
+
}
|
|
1056
|
+
#[cfg(not(feature = "process"))]
|
|
1057
|
+
{
|
|
1058
|
+
return Err(EvalError::Error(
|
|
1059
|
+
"tish:process requires the process feature. Rebuild with: cargo build -p tishlang --features process".into(),
|
|
1060
|
+
));
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
_ => {
|
|
1064
|
+
Err(EvalError::Error(format!(
|
|
1065
|
+
"Unknown built-in module: {}. Supported: tish:fs, tish:http, tish:timers, tish:process, tish:ws (plus any registered by native modules)",
|
|
1066
|
+
spec
|
|
1067
|
+
)))
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
fn load_builtin_export(&self, spec: &str, export_name: &str) -> Result<Value, EvalError> {
|
|
1073
|
+
let module = self.load_builtin_module(spec)?;
|
|
1074
|
+
let exports = match &module {
|
|
1075
|
+
Value::Object(m) => m.borrow().clone(),
|
|
1076
|
+
_ => return Err(EvalError::Error("Built-in module must be object".into())),
|
|
1077
|
+
};
|
|
1078
|
+
exports
|
|
1079
|
+
.strings
|
|
1080
|
+
.get(export_name)
|
|
1081
|
+
.cloned()
|
|
1082
|
+
.ok_or_else(|| {
|
|
1083
|
+
EvalError::Error(format!("Module {} does not export '{}'", spec, export_name))
|
|
1084
|
+
})
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
fn eval_expr(&self, expr: &Expr) -> Result<Value, EvalError> {
|
|
1088
|
+
match expr {
|
|
1089
|
+
Expr::Literal { value, .. } => Ok(match value {
|
|
1090
|
+
Literal::Number(n) => Value::Number(*n),
|
|
1091
|
+
Literal::String(s) => Value::String(Arc::clone(s)),
|
|
1092
|
+
Literal::Bool(b) => Value::Bool(*b),
|
|
1093
|
+
Literal::Null => Value::Null,
|
|
1094
|
+
}),
|
|
1095
|
+
Expr::Ident { name, .. } => self
|
|
1096
|
+
.scope
|
|
1097
|
+
.borrow()
|
|
1098
|
+
.get(name.as_ref())
|
|
1099
|
+
.ok_or_else(|| EvalError::Error(format!("Undefined variable: {}", name))),
|
|
1100
|
+
Expr::Binary {
|
|
1101
|
+
left,
|
|
1102
|
+
op,
|
|
1103
|
+
right,
|
|
1104
|
+
..
|
|
1105
|
+
} => {
|
|
1106
|
+
let l = self.eval_expr(left)?;
|
|
1107
|
+
let r = self.eval_expr(right)?;
|
|
1108
|
+
self.eval_binop(&l, *op, &r).map_err(EvalError::Error)
|
|
1109
|
+
}
|
|
1110
|
+
Expr::Unary { op, operand, .. } => {
|
|
1111
|
+
let o = self.eval_expr(operand)?;
|
|
1112
|
+
self.eval_unary(*op, &o).map_err(EvalError::Error)
|
|
1113
|
+
}
|
|
1114
|
+
Expr::Call { callee, args, .. } => {
|
|
1115
|
+
// Check for built-in method calls on arrays/strings
|
|
1116
|
+
if let Expr::Member {
|
|
1117
|
+
object,
|
|
1118
|
+
prop: MemberProp::Name { name: method_name, .. },
|
|
1119
|
+
..
|
|
1120
|
+
} = callee.as_ref()
|
|
1121
|
+
{
|
|
1122
|
+
let obj = self.eval_expr(object)?;
|
|
1123
|
+
let arg_vals = self.eval_call_args(args)?;
|
|
1124
|
+
|
|
1125
|
+
// Array methods
|
|
1126
|
+
if let Value::Array(arr) = &obj {
|
|
1127
|
+
match method_name.as_ref() {
|
|
1128
|
+
"push" => {
|
|
1129
|
+
let mut arr_mut = arr.borrow_mut();
|
|
1130
|
+
for v in &arg_vals {
|
|
1131
|
+
arr_mut.push(v.clone());
|
|
1132
|
+
}
|
|
1133
|
+
return Ok(Value::Number(arr_mut.len() as f64));
|
|
1134
|
+
}
|
|
1135
|
+
"pop" => {
|
|
1136
|
+
return Ok(arr.borrow_mut().pop().unwrap_or(Value::Null));
|
|
1137
|
+
}
|
|
1138
|
+
"shift" => {
|
|
1139
|
+
let mut arr_mut = arr.borrow_mut();
|
|
1140
|
+
if arr_mut.is_empty() {
|
|
1141
|
+
return Ok(Value::Null);
|
|
1142
|
+
}
|
|
1143
|
+
return Ok(arr_mut.remove(0));
|
|
1144
|
+
}
|
|
1145
|
+
"unshift" => {
|
|
1146
|
+
let mut arr_mut = arr.borrow_mut();
|
|
1147
|
+
for (i, v) in arg_vals.iter().enumerate() {
|
|
1148
|
+
arr_mut.insert(i, v.clone());
|
|
1149
|
+
}
|
|
1150
|
+
return Ok(Value::Number(arr_mut.len() as f64));
|
|
1151
|
+
}
|
|
1152
|
+
"indexOf" => {
|
|
1153
|
+
let search = arg_vals.first().cloned().unwrap_or(Value::Null);
|
|
1154
|
+
let arr_borrow = arr.borrow();
|
|
1155
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
1156
|
+
if v.strict_eq(&search) {
|
|
1157
|
+
return Ok(Value::Number(i as f64));
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
return Ok(Value::Number(-1.0));
|
|
1161
|
+
}
|
|
1162
|
+
"includes" => {
|
|
1163
|
+
let search = arg_vals.first().cloned().unwrap_or(Value::Null);
|
|
1164
|
+
let arr_borrow = arr.borrow();
|
|
1165
|
+
let len = arr_borrow.len() as i64;
|
|
1166
|
+
let start = match arg_vals.get(1) {
|
|
1167
|
+
Some(Value::Number(n)) if *n >= 0.0 => (*n as i64).min(len).max(0) as usize,
|
|
1168
|
+
Some(Value::Number(n)) if *n < 0.0 => ((len + *n as i64).max(0)) as usize,
|
|
1169
|
+
_ => 0,
|
|
1170
|
+
};
|
|
1171
|
+
for v in arr_borrow.iter().skip(start) {
|
|
1172
|
+
if v.strict_eq(&search) {
|
|
1173
|
+
return Ok(Value::Bool(true));
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
return Ok(Value::Bool(false));
|
|
1177
|
+
}
|
|
1178
|
+
"join" => {
|
|
1179
|
+
let sep = match arg_vals.first() {
|
|
1180
|
+
Some(Value::String(s)) => s.to_string(),
|
|
1181
|
+
_ => ",".to_string(),
|
|
1182
|
+
};
|
|
1183
|
+
let arr_borrow = arr.borrow();
|
|
1184
|
+
// JS join: null/undefined → "", else JS ToString (nested arrays
|
|
1185
|
+
// recurse to a comma-join, objects → "[object Object]").
|
|
1186
|
+
let parts: Vec<String> = arr_borrow
|
|
1187
|
+
.iter()
|
|
1188
|
+
.map(|v| match v {
|
|
1189
|
+
Value::Null => String::new(),
|
|
1190
|
+
other => other.to_js_string(),
|
|
1191
|
+
})
|
|
1192
|
+
.collect();
|
|
1193
|
+
return Ok(Value::String(parts.join(&sep).into()));
|
|
1194
|
+
}
|
|
1195
|
+
"reverse" => {
|
|
1196
|
+
arr.borrow_mut().reverse();
|
|
1197
|
+
return Ok(obj.clone());
|
|
1198
|
+
}
|
|
1199
|
+
"fill" => {
|
|
1200
|
+
// Array.prototype.fill(value, start?, end?) — in place (issue #76).
|
|
1201
|
+
let value = arg_vals.first().cloned().unwrap_or(Value::Null);
|
|
1202
|
+
let mut arr_mut = arr.borrow_mut();
|
|
1203
|
+
let len = arr_mut.len() as i64;
|
|
1204
|
+
let norm = |v: Option<&Value>, dflt: usize| -> usize {
|
|
1205
|
+
match v {
|
|
1206
|
+
Some(Value::Number(n)) => {
|
|
1207
|
+
let n = *n as i64;
|
|
1208
|
+
if n < 0 { (len + n).max(0) as usize } else { (n as usize).min(len as usize) }
|
|
1209
|
+
}
|
|
1210
|
+
_ => dflt,
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
let start = norm(arg_vals.get(1), 0);
|
|
1214
|
+
let end = norm(arg_vals.get(2), len as usize);
|
|
1215
|
+
let mut i = start;
|
|
1216
|
+
while i < end && i < arr_mut.len() {
|
|
1217
|
+
arr_mut[i] = value.clone();
|
|
1218
|
+
i += 1;
|
|
1219
|
+
}
|
|
1220
|
+
drop(arr_mut);
|
|
1221
|
+
return Ok(obj.clone());
|
|
1222
|
+
}
|
|
1223
|
+
"shuffle" => {
|
|
1224
|
+
let mut v = arr.borrow().clone();
|
|
1225
|
+
use rand::seq::SliceRandom;
|
|
1226
|
+
v.shuffle(&mut rand::rng());
|
|
1227
|
+
return Ok(Value::Array(Rc::new(RefCell::new(v))));
|
|
1228
|
+
}
|
|
1229
|
+
"sort" => {
|
|
1230
|
+
let comparator = arg_vals.into_iter().next();
|
|
1231
|
+
let mut arr_mut = arr.borrow_mut();
|
|
1232
|
+
|
|
1233
|
+
if let Some(cmp_fn) = comparator {
|
|
1234
|
+
// Check for fast path: (a, b) => a - b numeric ascending
|
|
1235
|
+
let is_numeric_asc = Self::is_numeric_sort_comparator(&cmp_fn, false);
|
|
1236
|
+
let is_numeric_desc = !is_numeric_asc && Self::is_numeric_sort_comparator(&cmp_fn, true);
|
|
1237
|
+
|
|
1238
|
+
if is_numeric_asc {
|
|
1239
|
+
// Fast path: numeric ascending sort
|
|
1240
|
+
arr_mut.sort_by(|a, b| {
|
|
1241
|
+
let na = match a { Value::Number(n) => *n, _ => f64::NAN };
|
|
1242
|
+
let nb = match b { Value::Number(n) => *n, _ => f64::NAN };
|
|
1243
|
+
na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal)
|
|
1244
|
+
});
|
|
1245
|
+
} else if is_numeric_desc {
|
|
1246
|
+
// Fast path: numeric descending sort
|
|
1247
|
+
arr_mut.sort_by(|a, b| {
|
|
1248
|
+
let na = match a { Value::Number(n) => *n, _ => f64::NAN };
|
|
1249
|
+
let nb = match b { Value::Number(n) => *n, _ => f64::NAN };
|
|
1250
|
+
nb.partial_cmp(&na).unwrap_or(std::cmp::Ordering::Equal)
|
|
1251
|
+
});
|
|
1252
|
+
} else {
|
|
1253
|
+
// General case: use comparator function with optimized scope reuse
|
|
1254
|
+
let len = arr_mut.len();
|
|
1255
|
+
let mut indices: Vec<usize> = (0..len).collect();
|
|
1256
|
+
let arr_values: Vec<Value> = std::mem::take(&mut *arr_mut);
|
|
1257
|
+
|
|
1258
|
+
if let Some((scope, params, body)) = self.create_callback_scope(&cmp_fn) {
|
|
1259
|
+
indices.sort_by(|&i, &j| {
|
|
1260
|
+
let result = self.call_with_scope(&scope, ¶ms, &body, &[arr_values[i].clone(), arr_values[j].clone()]);
|
|
1261
|
+
match result {
|
|
1262
|
+
Ok(Value::Number(n)) if n < 0.0 => std::cmp::Ordering::Less,
|
|
1263
|
+
Ok(Value::Number(n)) if n > 0.0 => std::cmp::Ordering::Greater,
|
|
1264
|
+
_ => std::cmp::Ordering::Equal,
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
} else {
|
|
1268
|
+
indices.sort_by(|&i, &j| {
|
|
1269
|
+
let result = self.call_func(&cmp_fn, &[arr_values[i].clone(), arr_values[j].clone()]);
|
|
1270
|
+
match result {
|
|
1271
|
+
Ok(Value::Number(n)) if n < 0.0 => std::cmp::Ordering::Less,
|
|
1272
|
+
Ok(Value::Number(n)) if n > 0.0 => std::cmp::Ordering::Greater,
|
|
1273
|
+
_ => std::cmp::Ordering::Equal,
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
*arr_mut = indices.into_iter().map(|i| arr_values[i].clone()).collect();
|
|
1279
|
+
}
|
|
1280
|
+
} else {
|
|
1281
|
+
// Default string sort - precompute strings once
|
|
1282
|
+
let mut pairs: Vec<(String, usize)> = arr_mut
|
|
1283
|
+
.iter()
|
|
1284
|
+
.enumerate()
|
|
1285
|
+
.map(|(i, v)| (v.to_string(), i))
|
|
1286
|
+
.collect();
|
|
1287
|
+
pairs.sort_by(|a, b| a.0.cmp(&b.0));
|
|
1288
|
+
let arr_values: Vec<Value> = std::mem::take(&mut *arr_mut);
|
|
1289
|
+
*arr_mut = pairs.into_iter().map(|(_, i)| arr_values[i].clone()).collect();
|
|
1290
|
+
}
|
|
1291
|
+
drop(arr_mut);
|
|
1292
|
+
return Ok(obj.clone());
|
|
1293
|
+
}
|
|
1294
|
+
"splice" => {
|
|
1295
|
+
let mut arr_mut = arr.borrow_mut();
|
|
1296
|
+
let len = arr_mut.len() as i64;
|
|
1297
|
+
|
|
1298
|
+
let start = match arg_vals.first() {
|
|
1299
|
+
Some(Value::Number(n)) => {
|
|
1300
|
+
let n = *n as i64;
|
|
1301
|
+
if n < 0 { (len + n).max(0) as usize } else { n.min(len) as usize }
|
|
1302
|
+
}
|
|
1303
|
+
_ => 0,
|
|
1304
|
+
};
|
|
1305
|
+
|
|
1306
|
+
let delete_count = match arg_vals.get(1) {
|
|
1307
|
+
Some(Value::Number(n)) => (*n as i64).max(0) as usize,
|
|
1308
|
+
_ => (len as usize).saturating_sub(start),
|
|
1309
|
+
};
|
|
1310
|
+
|
|
1311
|
+
let actual_delete = delete_count.min(arr_mut.len().saturating_sub(start));
|
|
1312
|
+
let removed: Vec<Value> = arr_mut.drain(start..start + actual_delete).collect();
|
|
1313
|
+
|
|
1314
|
+
if arg_vals.len() > 2 {
|
|
1315
|
+
let items_to_insert: Vec<Value> = arg_vals[2..].to_vec();
|
|
1316
|
+
for (i, item) in items_to_insert.into_iter().enumerate() {
|
|
1317
|
+
arr_mut.insert(start + i, item);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
return Ok(Value::Array(Rc::new(RefCell::new(removed))));
|
|
1322
|
+
}
|
|
1323
|
+
"slice" => {
|
|
1324
|
+
let arr_borrow = arr.borrow();
|
|
1325
|
+
let len = arr_borrow.len() as i64;
|
|
1326
|
+
let start = match arg_vals.first() {
|
|
1327
|
+
Some(Value::Number(n)) => {
|
|
1328
|
+
let n = *n as i64;
|
|
1329
|
+
if n < 0 { (len + n).max(0) as usize } else { n.min(len) as usize }
|
|
1330
|
+
}
|
|
1331
|
+
_ => 0,
|
|
1332
|
+
};
|
|
1333
|
+
let end = match arg_vals.get(1) {
|
|
1334
|
+
Some(Value::Number(n)) => {
|
|
1335
|
+
let n = *n as i64;
|
|
1336
|
+
if n < 0 { (len + n).max(0) as usize } else { n.min(len) as usize }
|
|
1337
|
+
}
|
|
1338
|
+
_ => len as usize,
|
|
1339
|
+
};
|
|
1340
|
+
let sliced: Vec<Value> = if start < end {
|
|
1341
|
+
arr_borrow[start..end].to_vec()
|
|
1342
|
+
} else {
|
|
1343
|
+
vec![]
|
|
1344
|
+
};
|
|
1345
|
+
return Ok(Value::Array(Rc::new(RefCell::new(sliced))));
|
|
1346
|
+
}
|
|
1347
|
+
"concat" => {
|
|
1348
|
+
let mut result = arr.borrow().clone();
|
|
1349
|
+
for v in &arg_vals {
|
|
1350
|
+
if let Value::Array(other) = v {
|
|
1351
|
+
result.extend(other.borrow().iter().cloned());
|
|
1352
|
+
} else {
|
|
1353
|
+
result.push(v.clone());
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
return Ok(Value::Array(Rc::new(RefCell::new(result))));
|
|
1357
|
+
}
|
|
1358
|
+
"map" => {
|
|
1359
|
+
let callback = arg_vals.first().cloned().unwrap_or(Value::Null);
|
|
1360
|
+
let arr_borrow = arr.borrow();
|
|
1361
|
+
let mut result = Vec::with_capacity(arr_borrow.len());
|
|
1362
|
+
// Try fastest path: simple single-expression callbacks
|
|
1363
|
+
let first_result = self.eval_simple_callback(&callback, &[arr_borrow.first().cloned().unwrap_or(Value::Null)]);
|
|
1364
|
+
if first_result.is_some() {
|
|
1365
|
+
// Simple callback path - inline evaluation
|
|
1366
|
+
for v in arr_borrow.iter() {
|
|
1367
|
+
if let Some(r) = self.eval_simple_callback(&callback, &[v.clone()]) {
|
|
1368
|
+
result.push(r?);
|
|
1369
|
+
} else {
|
|
1370
|
+
// Shouldn't happen, but fall back
|
|
1371
|
+
result.push(self.call_func(&callback, &[v.clone()])?);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
} else if let Some((scope, params, body)) = self.create_callback_scope(&callback) {
|
|
1375
|
+
// Reusable scope path
|
|
1376
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
1377
|
+
let mapped = self.call_with_scope(&scope, ¶ms, &body, &[v.clone(), Value::Number(i as f64)])?;
|
|
1378
|
+
result.push(mapped);
|
|
1379
|
+
}
|
|
1380
|
+
} else {
|
|
1381
|
+
// Full call_func path
|
|
1382
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
1383
|
+
let mapped = self.call_func(&callback, &[v.clone(), Value::Number(i as f64)])?;
|
|
1384
|
+
result.push(mapped);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
return Ok(Value::Array(Rc::new(RefCell::new(result))));
|
|
1388
|
+
}
|
|
1389
|
+
"filter" => {
|
|
1390
|
+
let callback = arg_vals.first().cloned().unwrap_or(Value::Null);
|
|
1391
|
+
let arr_borrow = arr.borrow();
|
|
1392
|
+
let mut result = Vec::new();
|
|
1393
|
+
// Try simple callback fast path
|
|
1394
|
+
let use_simple = arr_borrow.first().map(|v| {
|
|
1395
|
+
self.eval_simple_callback(&callback, &[v.clone()]).is_some()
|
|
1396
|
+
}).unwrap_or(false);
|
|
1397
|
+
if use_simple {
|
|
1398
|
+
for v in arr_borrow.iter() {
|
|
1399
|
+
if let Some(keep) = self.eval_simple_callback(&callback, &[v.clone()]) {
|
|
1400
|
+
if keep?.is_truthy() {
|
|
1401
|
+
result.push(v.clone());
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
} else if let Some((scope, params, body)) = self.create_callback_scope(&callback) {
|
|
1406
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
1407
|
+
let keep = self.call_with_scope(&scope, ¶ms, &body, &[v.clone(), Value::Number(i as f64)])?;
|
|
1408
|
+
if keep.is_truthy() {
|
|
1409
|
+
result.push(v.clone());
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
} else {
|
|
1413
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
1414
|
+
let keep = self.call_func(&callback, &[v.clone(), Value::Number(i as f64)])?;
|
|
1415
|
+
if keep.is_truthy() {
|
|
1416
|
+
result.push(v.clone());
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
return Ok(Value::Array(Rc::new(RefCell::new(result))));
|
|
1421
|
+
}
|
|
1422
|
+
"reduce" => {
|
|
1423
|
+
let callback = arg_vals.first().cloned().unwrap_or(Value::Null);
|
|
1424
|
+
let arr_borrow = arr.borrow();
|
|
1425
|
+
let (mut acc, start_idx) = if arg_vals.len() > 1 {
|
|
1426
|
+
(arg_vals[1].clone(), 0)
|
|
1427
|
+
} else if !arr_borrow.is_empty() {
|
|
1428
|
+
(arr_borrow[0].clone(), 1)
|
|
1429
|
+
} else {
|
|
1430
|
+
return Err(EvalError::Error("Reduce of empty array with no initial value".to_string()));
|
|
1431
|
+
};
|
|
1432
|
+
if let Some((scope, params, body)) = self.create_callback_scope(&callback) {
|
|
1433
|
+
for (i, v) in arr_borrow.iter().enumerate().skip(start_idx) {
|
|
1434
|
+
acc = self.call_with_scope(&scope, ¶ms, &body, &[acc, v.clone(), Value::Number(i as f64)])?;
|
|
1435
|
+
}
|
|
1436
|
+
} else {
|
|
1437
|
+
for (i, v) in arr_borrow.iter().enumerate().skip(start_idx) {
|
|
1438
|
+
acc = self.call_func(&callback, &[acc, v.clone(), Value::Number(i as f64)])?;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
return Ok(acc);
|
|
1442
|
+
}
|
|
1443
|
+
"find" => {
|
|
1444
|
+
let callback = arg_vals.first().cloned().unwrap_or(Value::Null);
|
|
1445
|
+
let arr_borrow = arr.borrow();
|
|
1446
|
+
// Try simple callback fast path
|
|
1447
|
+
let use_simple = arr_borrow.first().map(|v| {
|
|
1448
|
+
self.eval_simple_callback(&callback, &[v.clone()]).is_some()
|
|
1449
|
+
}).unwrap_or(false);
|
|
1450
|
+
if use_simple {
|
|
1451
|
+
for v in arr_borrow.iter() {
|
|
1452
|
+
if let Some(found) = self.eval_simple_callback(&callback, &[v.clone()]) {
|
|
1453
|
+
if found?.is_truthy() {
|
|
1454
|
+
return Ok(v.clone());
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
} else if let Some((scope, params, body)) = self.create_callback_scope(&callback) {
|
|
1459
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
1460
|
+
let found = self.call_with_scope(&scope, ¶ms, &body, &[v.clone(), Value::Number(i as f64)])?;
|
|
1461
|
+
if found.is_truthy() {
|
|
1462
|
+
return Ok(v.clone());
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
} else {
|
|
1466
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
1467
|
+
let found = self.call_func(&callback, &[v.clone(), Value::Number(i as f64)])?;
|
|
1468
|
+
if found.is_truthy() {
|
|
1469
|
+
return Ok(v.clone());
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
return Ok(Value::Null);
|
|
1474
|
+
}
|
|
1475
|
+
"findIndex" => {
|
|
1476
|
+
let callback = arg_vals.first().cloned().unwrap_or(Value::Null);
|
|
1477
|
+
let arr_borrow = arr.borrow();
|
|
1478
|
+
if let Some((scope, params, body)) = self.create_callback_scope(&callback) {
|
|
1479
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
1480
|
+
let found = self.call_with_scope(&scope, ¶ms, &body, &[v.clone(), Value::Number(i as f64)])?;
|
|
1481
|
+
if found.is_truthy() {
|
|
1482
|
+
return Ok(Value::Number(i as f64));
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
} else {
|
|
1486
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
1487
|
+
let found = self.call_func(&callback, &[v.clone(), Value::Number(i as f64)])?;
|
|
1488
|
+
if found.is_truthy() {
|
|
1489
|
+
return Ok(Value::Number(i as f64));
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
return Ok(Value::Number(-1.0));
|
|
1494
|
+
}
|
|
1495
|
+
"forEach" => {
|
|
1496
|
+
let callback = arg_vals.first().cloned().unwrap_or(Value::Null);
|
|
1497
|
+
let arr_borrow = arr.borrow();
|
|
1498
|
+
if let Some((scope, params, body)) = self.create_callback_scope(&callback) {
|
|
1499
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
1500
|
+
self.call_with_scope(&scope, ¶ms, &body, &[v.clone(), Value::Number(i as f64)])?;
|
|
1501
|
+
}
|
|
1502
|
+
} else {
|
|
1503
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
1504
|
+
self.call_func(&callback, &[v.clone(), Value::Number(i as f64)])?;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
return Ok(Value::Null);
|
|
1508
|
+
}
|
|
1509
|
+
"some" => {
|
|
1510
|
+
let callback = arg_vals.first().cloned().unwrap_or(Value::Null);
|
|
1511
|
+
let arr_borrow = arr.borrow();
|
|
1512
|
+
// Try simple callback fast path
|
|
1513
|
+
let use_simple = arr_borrow.first().map(|v| {
|
|
1514
|
+
self.eval_simple_callback(&callback, &[v.clone()]).is_some()
|
|
1515
|
+
}).unwrap_or(false);
|
|
1516
|
+
if use_simple {
|
|
1517
|
+
for v in arr_borrow.iter() {
|
|
1518
|
+
if let Some(result) = self.eval_simple_callback(&callback, &[v.clone()]) {
|
|
1519
|
+
if result?.is_truthy() {
|
|
1520
|
+
return Ok(Value::Bool(true));
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
} else if let Some((scope, params, body)) = self.create_callback_scope(&callback) {
|
|
1525
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
1526
|
+
let result = self.call_with_scope(&scope, ¶ms, &body, &[v.clone(), Value::Number(i as f64)])?;
|
|
1527
|
+
if result.is_truthy() {
|
|
1528
|
+
return Ok(Value::Bool(true));
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
} else {
|
|
1532
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
1533
|
+
let result = self.call_func(&callback, &[v.clone(), Value::Number(i as f64)])?;
|
|
1534
|
+
if result.is_truthy() {
|
|
1535
|
+
return Ok(Value::Bool(true));
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
return Ok(Value::Bool(false));
|
|
1540
|
+
}
|
|
1541
|
+
"every" => {
|
|
1542
|
+
let callback = arg_vals.first().cloned().unwrap_or(Value::Null);
|
|
1543
|
+
let arr_borrow = arr.borrow();
|
|
1544
|
+
// Try simple callback fast path
|
|
1545
|
+
let use_simple = arr_borrow.first().map(|v| {
|
|
1546
|
+
self.eval_simple_callback(&callback, &[v.clone()]).is_some()
|
|
1547
|
+
}).unwrap_or(false);
|
|
1548
|
+
if use_simple {
|
|
1549
|
+
for v in arr_borrow.iter() {
|
|
1550
|
+
if let Some(result) = self.eval_simple_callback(&callback, &[v.clone()]) {
|
|
1551
|
+
if !result?.is_truthy() {
|
|
1552
|
+
return Ok(Value::Bool(false));
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
} else if let Some((scope, params, body)) = self.create_callback_scope(&callback) {
|
|
1557
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
1558
|
+
let result = self.call_with_scope(&scope, ¶ms, &body, &[v.clone(), Value::Number(i as f64)])?;
|
|
1559
|
+
if !result.is_truthy() {
|
|
1560
|
+
return Ok(Value::Bool(false));
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
} else {
|
|
1564
|
+
for (i, v) in arr_borrow.iter().enumerate() {
|
|
1565
|
+
let result = self.call_func(&callback, &[v.clone(), Value::Number(i as f64)])?;
|
|
1566
|
+
if !result.is_truthy() {
|
|
1567
|
+
return Ok(Value::Bool(false));
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
return Ok(Value::Bool(true));
|
|
1572
|
+
}
|
|
1573
|
+
"flat" => {
|
|
1574
|
+
let depth = match arg_vals.first() {
|
|
1575
|
+
Some(Value::Number(n)) => *n as usize,
|
|
1576
|
+
_ => 1,
|
|
1577
|
+
};
|
|
1578
|
+
fn flatten(arr: &[Value], depth: usize) -> Vec<Value> {
|
|
1579
|
+
let mut result = Vec::new();
|
|
1580
|
+
for v in arr {
|
|
1581
|
+
if depth > 0 {
|
|
1582
|
+
if let Value::Array(inner) = v {
|
|
1583
|
+
result.extend(flatten(&inner.borrow(), depth - 1));
|
|
1584
|
+
continue;
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
result.push(v.clone());
|
|
1588
|
+
}
|
|
1589
|
+
result
|
|
1590
|
+
}
|
|
1591
|
+
let flattened = flatten(&arr.borrow(), depth);
|
|
1592
|
+
return Ok(Value::Array(Rc::new(RefCell::new(flattened))));
|
|
1593
|
+
}
|
|
1594
|
+
_ => {}
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// String methods
|
|
1599
|
+
if let Value::String(s) = &obj {
|
|
1600
|
+
match method_name.as_ref() {
|
|
1601
|
+
"indexOf" => {
|
|
1602
|
+
let search = match arg_vals.first() {
|
|
1603
|
+
Some(Value::String(ss)) => ss.as_ref(),
|
|
1604
|
+
_ => return Ok(Value::Number(-1.0)),
|
|
1605
|
+
};
|
|
1606
|
+
let from_char = match arg_vals.get(1) {
|
|
1607
|
+
Some(Value::Number(n)) if *n >= 0.0 => {
|
|
1608
|
+
(*n as usize).min(s.chars().count())
|
|
1609
|
+
}
|
|
1610
|
+
_ => 0,
|
|
1611
|
+
};
|
|
1612
|
+
let byte_start: usize = s.chars().take(from_char).map(|c| c.len_utf8()).sum();
|
|
1613
|
+
let found = s[byte_start..].find(search).map(|byte_pos| {
|
|
1614
|
+
let char_idx = from_char
|
|
1615
|
+
+ s[byte_start..][..byte_pos].chars().count();
|
|
1616
|
+
char_idx as f64
|
|
1617
|
+
});
|
|
1618
|
+
return Ok(Value::Number(found.unwrap_or(-1.0)));
|
|
1619
|
+
}
|
|
1620
|
+
"lastIndexOf" => {
|
|
1621
|
+
return Ok(Self::string_last_index_of_eval(&arg_vals, s));
|
|
1622
|
+
}
|
|
1623
|
+
"includes" => {
|
|
1624
|
+
let search = match arg_vals.first() {
|
|
1625
|
+
Some(Value::String(ss)) => ss.as_ref(),
|
|
1626
|
+
_ => return Ok(Value::Bool(false)),
|
|
1627
|
+
};
|
|
1628
|
+
let from_char = match arg_vals.get(1) {
|
|
1629
|
+
Some(Value::Number(n)) if *n >= 0.0 => (*n as usize).min(s.chars().count()),
|
|
1630
|
+
Some(Value::Number(n)) if *n < 0.0 => {
|
|
1631
|
+
let len = s.chars().count() as i64;
|
|
1632
|
+
((len + *n as i64).max(0)) as usize
|
|
1633
|
+
}
|
|
1634
|
+
_ => 0,
|
|
1635
|
+
};
|
|
1636
|
+
let byte_start: usize = s.chars().take(from_char).map(|c| c.len_utf8()).sum();
|
|
1637
|
+
return Ok(Value::Bool(s[byte_start..].contains(search)));
|
|
1638
|
+
}
|
|
1639
|
+
"slice" => {
|
|
1640
|
+
let chars: Vec<char> = s.chars().collect();
|
|
1641
|
+
let len = chars.len() as i64;
|
|
1642
|
+
let start = match arg_vals.first() {
|
|
1643
|
+
Some(Value::Number(n)) => {
|
|
1644
|
+
let n = *n as i64;
|
|
1645
|
+
if n < 0 { (len + n).max(0) as usize } else { n.min(len) as usize }
|
|
1646
|
+
}
|
|
1647
|
+
_ => 0,
|
|
1648
|
+
};
|
|
1649
|
+
let end = match arg_vals.get(1) {
|
|
1650
|
+
Some(Value::Number(n)) => {
|
|
1651
|
+
let n = *n as i64;
|
|
1652
|
+
if n < 0 { (len + n).max(0) as usize } else { n.min(len) as usize }
|
|
1653
|
+
}
|
|
1654
|
+
_ => len as usize,
|
|
1655
|
+
};
|
|
1656
|
+
let sliced: String = if start < end {
|
|
1657
|
+
chars[start..end].iter().collect()
|
|
1658
|
+
} else {
|
|
1659
|
+
String::new()
|
|
1660
|
+
};
|
|
1661
|
+
return Ok(Value::String(sliced.into()));
|
|
1662
|
+
}
|
|
1663
|
+
"substring" => {
|
|
1664
|
+
let chars: Vec<char> = s.chars().collect();
|
|
1665
|
+
let len = chars.len();
|
|
1666
|
+
let start = match arg_vals.first() {
|
|
1667
|
+
Some(Value::Number(n)) => (*n as usize).min(len),
|
|
1668
|
+
_ => 0,
|
|
1669
|
+
};
|
|
1670
|
+
let end = match arg_vals.get(1) {
|
|
1671
|
+
Some(Value::Number(n)) => (*n as usize).min(len),
|
|
1672
|
+
_ => len,
|
|
1673
|
+
};
|
|
1674
|
+
let (s, e) = (start.min(end), start.max(end));
|
|
1675
|
+
return Ok(Value::String(chars[s..e].iter().collect::<String>().into()));
|
|
1676
|
+
}
|
|
1677
|
+
"split" => {
|
|
1678
|
+
#[cfg(feature = "regex")]
|
|
1679
|
+
if let Some(sep) = arg_vals.first() {
|
|
1680
|
+
let limit = arg_vals.get(1).and_then(|v| match v {
|
|
1681
|
+
Value::Number(n) => Some(*n as usize),
|
|
1682
|
+
_ => None,
|
|
1683
|
+
});
|
|
1684
|
+
return Ok(crate::regex::string_split(s, sep, limit));
|
|
1685
|
+
}
|
|
1686
|
+
#[cfg(not(feature = "regex"))]
|
|
1687
|
+
{
|
|
1688
|
+
let sep = match arg_vals.first() {
|
|
1689
|
+
Some(Value::String(ss)) => ss.as_ref(),
|
|
1690
|
+
_ => return Ok(Value::Array(Rc::new(RefCell::new(vec![obj.clone()])))),
|
|
1691
|
+
};
|
|
1692
|
+
let parts: Vec<Value> = s.split(sep)
|
|
1693
|
+
.map(|p| Value::String(p.into()))
|
|
1694
|
+
.collect();
|
|
1695
|
+
return Ok(Value::Array(Rc::new(RefCell::new(parts))));
|
|
1696
|
+
}
|
|
1697
|
+
#[cfg(feature = "regex")]
|
|
1698
|
+
return Ok(Value::Array(Rc::new(RefCell::new(vec![obj.clone()]))));
|
|
1699
|
+
}
|
|
1700
|
+
"trim" => {
|
|
1701
|
+
return Ok(Value::String(s.trim().into()));
|
|
1702
|
+
}
|
|
1703
|
+
"toUpperCase" => {
|
|
1704
|
+
return Ok(Value::String(s.to_uppercase().into()));
|
|
1705
|
+
}
|
|
1706
|
+
"toLowerCase" => {
|
|
1707
|
+
return Ok(Value::String(s.to_lowercase().into()));
|
|
1708
|
+
}
|
|
1709
|
+
"startsWith" => {
|
|
1710
|
+
let search = match arg_vals.first() {
|
|
1711
|
+
Some(Value::String(ss)) => ss.as_ref(),
|
|
1712
|
+
_ => return Ok(Value::Bool(false)),
|
|
1713
|
+
};
|
|
1714
|
+
return Ok(Value::Bool(s.starts_with(search)));
|
|
1715
|
+
}
|
|
1716
|
+
"endsWith" => {
|
|
1717
|
+
let search = match arg_vals.first() {
|
|
1718
|
+
Some(Value::String(ss)) => ss.as_ref(),
|
|
1719
|
+
_ => return Ok(Value::Bool(false)),
|
|
1720
|
+
};
|
|
1721
|
+
return Ok(Value::Bool(s.ends_with(search)));
|
|
1722
|
+
}
|
|
1723
|
+
"replace" => {
|
|
1724
|
+
#[cfg(feature = "regex")]
|
|
1725
|
+
if let (Some(search), Some(replace)) = (arg_vals.first(), arg_vals.get(1)) {
|
|
1726
|
+
let is_fn = matches!(replace, Value::Function { .. } | Value::Native(_));
|
|
1727
|
+
if matches!(search, Value::RegExp(_)) && is_fn {
|
|
1728
|
+
let re = match search {
|
|
1729
|
+
Value::RegExp(r) => r.clone(),
|
|
1730
|
+
_ => unreachable!(),
|
|
1731
|
+
};
|
|
1732
|
+
let re_guard = re.borrow();
|
|
1733
|
+
let replace_fn = replace.clone();
|
|
1734
|
+
let input_str = s.as_ref();
|
|
1735
|
+
let mut invoke = |args: &[Value]| {
|
|
1736
|
+
self.call_func(&replace_fn, args)
|
|
1737
|
+
.map(|v| v.to_string())
|
|
1738
|
+
.map_err(|e: EvalError| e.to_string())
|
|
1739
|
+
};
|
|
1740
|
+
match crate::regex::string_replace_regex_with_fn(
|
|
1741
|
+
input_str,
|
|
1742
|
+
&re_guard,
|
|
1743
|
+
&mut invoke,
|
|
1744
|
+
) {
|
|
1745
|
+
Ok(v) => return Ok(v),
|
|
1746
|
+
Err(_) => return Ok(Value::String(Arc::clone(s))),
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
return Ok(crate::regex::string_replace(s.as_ref(), search, replace));
|
|
1750
|
+
}
|
|
1751
|
+
#[cfg(not(feature = "regex"))]
|
|
1752
|
+
{
|
|
1753
|
+
let search = match arg_vals.first() {
|
|
1754
|
+
Some(Value::String(ss)) => ss.to_string(),
|
|
1755
|
+
_ => return Ok(obj.clone()),
|
|
1756
|
+
};
|
|
1757
|
+
let replacement = match arg_vals.get(1) {
|
|
1758
|
+
Some(Value::String(ss)) => ss.to_string(),
|
|
1759
|
+
_ => String::new(),
|
|
1760
|
+
};
|
|
1761
|
+
return Ok(Value::String(s.replacen(&search, &replacement, 1).into()));
|
|
1762
|
+
}
|
|
1763
|
+
#[cfg(feature = "regex")]
|
|
1764
|
+
return Ok(obj.clone());
|
|
1765
|
+
}
|
|
1766
|
+
"replaceAll" => {
|
|
1767
|
+
let search = match arg_vals.first() {
|
|
1768
|
+
Some(Value::String(ss)) => ss.to_string(),
|
|
1769
|
+
_ => return Ok(obj.clone()),
|
|
1770
|
+
};
|
|
1771
|
+
let replacement = match arg_vals.get(1) {
|
|
1772
|
+
Some(Value::String(ss)) => ss.to_string(),
|
|
1773
|
+
_ => String::new(),
|
|
1774
|
+
};
|
|
1775
|
+
return Ok(Value::String(s.replace(&search, &replacement).into()));
|
|
1776
|
+
}
|
|
1777
|
+
"charAt" => {
|
|
1778
|
+
let idx = match arg_vals.first() {
|
|
1779
|
+
Some(Value::Number(n)) => *n as usize,
|
|
1780
|
+
_ => 0,
|
|
1781
|
+
};
|
|
1782
|
+
let chars: Vec<char> = s.chars().collect();
|
|
1783
|
+
return Ok(chars.get(idx)
|
|
1784
|
+
.map(|c| Value::String(c.to_string().into()))
|
|
1785
|
+
.unwrap_or(Value::String("".into())));
|
|
1786
|
+
}
|
|
1787
|
+
"charCodeAt" => {
|
|
1788
|
+
let idx = match arg_vals.first() {
|
|
1789
|
+
Some(Value::Number(n)) => *n as usize,
|
|
1790
|
+
_ => 0,
|
|
1791
|
+
};
|
|
1792
|
+
let chars: Vec<char> = s.chars().collect();
|
|
1793
|
+
return Ok(chars.get(idx)
|
|
1794
|
+
.map(|c| Value::Number(*c as u32 as f64))
|
|
1795
|
+
.unwrap_or(Value::Number(f64::NAN)));
|
|
1796
|
+
}
|
|
1797
|
+
"repeat" => {
|
|
1798
|
+
let count = match arg_vals.first() {
|
|
1799
|
+
Some(Value::Number(n)) if *n >= 0.0 => *n as usize,
|
|
1800
|
+
_ => 0,
|
|
1801
|
+
};
|
|
1802
|
+
return Ok(Value::String(s.repeat(count).into()));
|
|
1803
|
+
}
|
|
1804
|
+
"padStart" => {
|
|
1805
|
+
let target_len = match arg_vals.first() {
|
|
1806
|
+
Some(Value::Number(n)) => *n as usize,
|
|
1807
|
+
_ => return Ok(obj.clone()),
|
|
1808
|
+
};
|
|
1809
|
+
let pad = match arg_vals.get(1) {
|
|
1810
|
+
Some(Value::String(p)) => p.to_string(),
|
|
1811
|
+
_ => " ".to_string(),
|
|
1812
|
+
};
|
|
1813
|
+
let char_count = s.chars().count();
|
|
1814
|
+
if char_count >= target_len || pad.is_empty() {
|
|
1815
|
+
return Ok(obj.clone());
|
|
1816
|
+
}
|
|
1817
|
+
let needed = target_len - char_count;
|
|
1818
|
+
let padding: String = pad.chars().cycle().take(needed).collect();
|
|
1819
|
+
return Ok(Value::String(format!("{}{}", padding, s).into()));
|
|
1820
|
+
}
|
|
1821
|
+
"padEnd" => {
|
|
1822
|
+
let target_len = match arg_vals.first() {
|
|
1823
|
+
Some(Value::Number(n)) => *n as usize,
|
|
1824
|
+
_ => return Ok(obj.clone()),
|
|
1825
|
+
};
|
|
1826
|
+
let pad = match arg_vals.get(1) {
|
|
1827
|
+
Some(Value::String(p)) => p.to_string(),
|
|
1828
|
+
_ => " ".to_string(),
|
|
1829
|
+
};
|
|
1830
|
+
let char_count = s.chars().count();
|
|
1831
|
+
if char_count >= target_len || pad.is_empty() {
|
|
1832
|
+
return Ok(obj.clone());
|
|
1833
|
+
}
|
|
1834
|
+
let needed = target_len - char_count;
|
|
1835
|
+
let padding: String = pad.chars().cycle().take(needed).collect();
|
|
1836
|
+
return Ok(Value::String(format!("{}{}", s, padding).into()));
|
|
1837
|
+
}
|
|
1838
|
+
#[cfg(feature = "regex")]
|
|
1839
|
+
"match" => {
|
|
1840
|
+
if let Some(regexp) = arg_vals.first() {
|
|
1841
|
+
return Ok(crate::regex::string_match(s, regexp));
|
|
1842
|
+
}
|
|
1843
|
+
return Ok(Value::Null);
|
|
1844
|
+
}
|
|
1845
|
+
#[cfg(feature = "regex")]
|
|
1846
|
+
"search" => {
|
|
1847
|
+
if let Some(regexp) = arg_vals.first() {
|
|
1848
|
+
return Ok(crate::regex::string_search(s, regexp));
|
|
1849
|
+
}
|
|
1850
|
+
return Ok(Value::Number(-1.0));
|
|
1851
|
+
}
|
|
1852
|
+
_ => {}
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
// Number methods
|
|
1857
|
+
if let Value::Number(n) = &obj {
|
|
1858
|
+
if method_name.as_ref() == "toFixed" {
|
|
1859
|
+
let digits = arg_vals
|
|
1860
|
+
.first()
|
|
1861
|
+
.and_then(|v| match v {
|
|
1862
|
+
Value::Number(d) => Some(*d as i32),
|
|
1863
|
+
_ => None,
|
|
1864
|
+
})
|
|
1865
|
+
.unwrap_or(0)
|
|
1866
|
+
.clamp(0, 20); // ECMA-262: 0–20
|
|
1867
|
+
let formatted = format!("{:.*}", digits as usize, n);
|
|
1868
|
+
return Ok(Value::String(formatted.into()));
|
|
1869
|
+
}
|
|
1870
|
+
if method_name.as_ref() == "toString" {
|
|
1871
|
+
// Shares the VM/native formatting via the backend-agnostic helper
|
|
1872
|
+
// (issue #59). Radix defaults to 10; 2–36 supported, else RangeError.
|
|
1873
|
+
let radix = arg_vals
|
|
1874
|
+
.first()
|
|
1875
|
+
.and_then(|v| match v {
|
|
1876
|
+
Value::Number(d) => Some(*d as i64),
|
|
1877
|
+
_ => None,
|
|
1878
|
+
})
|
|
1879
|
+
.unwrap_or(10);
|
|
1880
|
+
return match tishlang_builtins::number::number_to_string_radix(*n, radix)
|
|
1881
|
+
{
|
|
1882
|
+
Some(s) => Ok(Value::String(s.into())),
|
|
1883
|
+
None => Err(EvalError::Error(
|
|
1884
|
+
"toString() radix must be between 2 and 36".to_string(),
|
|
1885
|
+
)),
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
// RegExp methods
|
|
1891
|
+
#[cfg(feature = "regex")]
|
|
1892
|
+
if let Value::RegExp(re) = &obj {
|
|
1893
|
+
match method_name.as_ref() {
|
|
1894
|
+
"test" => {
|
|
1895
|
+
let input = arg_vals.first()
|
|
1896
|
+
.map(|v| v.to_string())
|
|
1897
|
+
.unwrap_or_default();
|
|
1898
|
+
let result = re.borrow_mut().test(&input);
|
|
1899
|
+
return Ok(Value::Bool(result));
|
|
1900
|
+
}
|
|
1901
|
+
"exec" => {
|
|
1902
|
+
let input = arg_vals.first()
|
|
1903
|
+
.map(|v| v.to_string())
|
|
1904
|
+
.unwrap_or_default();
|
|
1905
|
+
let result = crate::regex::regexp_exec(&mut re.borrow_mut(), &input);
|
|
1906
|
+
return Ok(result);
|
|
1907
|
+
}
|
|
1908
|
+
_ => {}
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
// Fall through to normal function call. `get_prop` only implements `length` on
|
|
1913
|
+
// strings, so method calls would otherwise become `call_func(Null)` → Not a function.
|
|
1914
|
+
if let Value::String(s) = &obj {
|
|
1915
|
+
if method_name.as_ref() == "lastIndexOf" {
|
|
1916
|
+
return Ok(Self::string_last_index_of_eval(&arg_vals, s));
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
let f = self.get_prop(&obj, method_name).map_err(EvalError::Error)?;
|
|
1920
|
+
return self.call_func(&f, &arg_vals);
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
let f = self.eval_expr(callee)?;
|
|
1924
|
+
let arg_vals = self.eval_call_args(args)?;
|
|
1925
|
+
self.call_func(&f, &arg_vals)
|
|
1926
|
+
}
|
|
1927
|
+
Expr::Member {
|
|
1928
|
+
object,
|
|
1929
|
+
prop,
|
|
1930
|
+
optional,
|
|
1931
|
+
..
|
|
1932
|
+
} => {
|
|
1933
|
+
let obj = self.eval_expr(object)?;
|
|
1934
|
+
if *optional && matches!(obj, Value::Null) {
|
|
1935
|
+
return Ok(Value::Null);
|
|
1936
|
+
}
|
|
1937
|
+
let key = match prop {
|
|
1938
|
+
MemberProp::Name { name, .. } => Arc::clone(name),
|
|
1939
|
+
MemberProp::Expr(e) => {
|
|
1940
|
+
let v = self.eval_expr(e)?;
|
|
1941
|
+
match v {
|
|
1942
|
+
Value::String(s) => s,
|
|
1943
|
+
_ => return Err(EvalError::Error("Property key must be string".to_string())),
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
};
|
|
1947
|
+
match self.get_prop(&obj, &key) {
|
|
1948
|
+
Ok(v) => Ok(v),
|
|
1949
|
+
Err(_) if *optional => Ok(Value::Null),
|
|
1950
|
+
Err(e) => Err(EvalError::Error(e)),
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
Expr::Index {
|
|
1954
|
+
object,
|
|
1955
|
+
index,
|
|
1956
|
+
optional,
|
|
1957
|
+
..
|
|
1958
|
+
} => {
|
|
1959
|
+
let obj = self.eval_expr(object)?;
|
|
1960
|
+
if *optional && matches!(obj, Value::Null) {
|
|
1961
|
+
return Ok(Value::Null);
|
|
1962
|
+
}
|
|
1963
|
+
let idx = self.eval_expr(index)?;
|
|
1964
|
+
self.get_index(&obj, &idx).map_err(EvalError::Error)
|
|
1965
|
+
}
|
|
1966
|
+
Expr::Conditional {
|
|
1967
|
+
cond,
|
|
1968
|
+
then_branch,
|
|
1969
|
+
else_branch,
|
|
1970
|
+
..
|
|
1971
|
+
} => {
|
|
1972
|
+
if self.eval_expr(cond)?.is_truthy() {
|
|
1973
|
+
self.eval_expr(then_branch)
|
|
1974
|
+
} else {
|
|
1975
|
+
self.eval_expr(else_branch)
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
Expr::NullishCoalesce { left, right, .. } => {
|
|
1979
|
+
let l = self.eval_expr(left)?;
|
|
1980
|
+
if matches!(l, Value::Null) {
|
|
1981
|
+
self.eval_expr(right)
|
|
1982
|
+
} else {
|
|
1983
|
+
Ok(l)
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
Expr::Array { elements, .. } => {
|
|
1987
|
+
let mut vals = Vec::with_capacity(elements.len());
|
|
1988
|
+
for elem in elements {
|
|
1989
|
+
match elem {
|
|
1990
|
+
tishlang_ast::ArrayElement::Expr(e) => {
|
|
1991
|
+
vals.push(self.eval_expr(e)?);
|
|
1992
|
+
}
|
|
1993
|
+
tishlang_ast::ArrayElement::Spread(e) => {
|
|
1994
|
+
let spread_val = self.eval_expr(e)?;
|
|
1995
|
+
if let Value::Array(arr) = &spread_val {
|
|
1996
|
+
vals.extend(arr.borrow().iter().cloned());
|
|
1997
|
+
} else if let Some(items) = self.drain_eval_iterator(&spread_val) {
|
|
1998
|
+
// Spread a Map/Set iterator (`[...m.values()]`).
|
|
1999
|
+
vals.extend(items);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
Ok(Value::Array(Rc::new(RefCell::new(vals))))
|
|
2005
|
+
}
|
|
2006
|
+
Expr::Object { props, .. } => {
|
|
2007
|
+
let mut data = EvalObjectData::default();
|
|
2008
|
+
for prop in props {
|
|
2009
|
+
match prop {
|
|
2010
|
+
tishlang_ast::ObjectProp::KeyValue(k, v) => {
|
|
2011
|
+
data
|
|
2012
|
+
.strings
|
|
2013
|
+
.insert(Arc::clone(k), self.eval_expr(v)?);
|
|
2014
|
+
}
|
|
2015
|
+
tishlang_ast::ObjectProp::Spread(e) => {
|
|
2016
|
+
let spread_val = self.eval_expr(e)?;
|
|
2017
|
+
if let Value::Object(obj) = spread_val {
|
|
2018
|
+
let b = obj.borrow();
|
|
2019
|
+
for (k, v) in b.strings.iter() {
|
|
2020
|
+
data.strings.insert(Arc::clone(k), v.clone());
|
|
2021
|
+
}
|
|
2022
|
+
if let Some(ref sm) = b.symbols {
|
|
2023
|
+
if data.symbols.is_none() {
|
|
2024
|
+
data.symbols = Some(AHashMap::default());
|
|
2025
|
+
}
|
|
2026
|
+
let dm = data.symbols.as_mut().unwrap();
|
|
2027
|
+
for (id, v) in sm.iter() {
|
|
2028
|
+
dm.insert(*id, v.clone());
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
Ok(Value::Object(Rc::new(RefCell::new(data))))
|
|
2036
|
+
}
|
|
2037
|
+
Expr::Assign { name, value, .. } => {
|
|
2038
|
+
let v = self.eval_expr(value)?;
|
|
2039
|
+
match self.scope.borrow_mut().assign(name.as_ref(), v.clone()) {
|
|
2040
|
+
Ok(true) => Ok(v),
|
|
2041
|
+
Ok(false) => Err(EvalError::Error(format!("Undefined variable: {}", name))),
|
|
2042
|
+
Err(e) => Err(EvalError::Error(e)),
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
Expr::Await { operand, .. } => self.eval_await(operand),
|
|
2046
|
+
Expr::New { callee, args, .. } => {
|
|
2047
|
+
let c = self.eval_expr(callee)?;
|
|
2048
|
+
let arg_vals = self.eval_call_args(args)?;
|
|
2049
|
+
self.construct_value(&c, &arg_vals)
|
|
2050
|
+
}
|
|
2051
|
+
Expr::JsxElement { .. } | Expr::JsxFragment { .. } => Err(EvalError::Error(
|
|
2052
|
+
"JSX is not supported in the interpreter. Use 'tish build --target js' to compile to JavaScript.".to_string(),
|
|
2053
|
+
)),
|
|
2054
|
+
Expr::NativeModuleLoad { spec, export_name, .. } => {
|
|
2055
|
+
self.load_builtin_export(spec.as_ref(), export_name.as_ref())
|
|
2056
|
+
}
|
|
2057
|
+
Expr::TypeOf { operand, .. } => {
|
|
2058
|
+
let v = self.eval_expr(operand)?;
|
|
2059
|
+
Ok(Value::String(match &v {
|
|
2060
|
+
Value::Number(_) => "number".into(),
|
|
2061
|
+
Value::String(_) => "string".into(),
|
|
2062
|
+
Value::Bool(_) => "boolean".into(),
|
|
2063
|
+
Value::Null => "null".into(),
|
|
2064
|
+
Value::Array(_) => "object".into(),
|
|
2065
|
+
Value::Object(_) => "object".into(),
|
|
2066
|
+
Value::Symbol(_) => "symbol".into(),
|
|
2067
|
+
Value::Function { .. } | Value::Native(_) => "function".into(),
|
|
2068
|
+
Value::CoreFn(_) => "function".into(),
|
|
2069
|
+
#[cfg(feature = "http")]
|
|
2070
|
+
Value::CorePromise(_) => "object".into(),
|
|
2071
|
+
#[cfg(feature = "http")]
|
|
2072
|
+
Value::Serve
|
|
2073
|
+
| Value::PromiseResolver(_)
|
|
2074
|
+
| Value::PromiseConstructor
|
|
2075
|
+
| Value::BoundPromiseMethod(_, _) => "function".into(),
|
|
2076
|
+
#[cfg(feature = "timers")]
|
|
2077
|
+
Value::TimerBuiltin(_) => "function".into(),
|
|
2078
|
+
#[cfg(feature = "http")]
|
|
2079
|
+
Value::Promise(_) => "object".into(),
|
|
2080
|
+
#[cfg(feature = "regex")]
|
|
2081
|
+
Value::RegExp(_) => "object".into(),
|
|
2082
|
+
Value::Opaque(_) => "object".into(),
|
|
2083
|
+
Value::OpaqueMethod(_, _) => "function".into(),
|
|
2084
|
+
}))
|
|
2085
|
+
}
|
|
2086
|
+
// `delete obj.prop` / `delete obj[key]` (issue #40): remove the property and
|
|
2087
|
+
// evaluate to `true`. Objects drop the key; arrays clear a numeric index to a
|
|
2088
|
+
// null hole (length preserved). Deleting a non-reference is a no-op (still `true`).
|
|
2089
|
+
Expr::Delete { target, .. } => {
|
|
2090
|
+
// Resolve the target to (object value, key value); then remove the key.
|
|
2091
|
+
let resolved = match target.as_ref() {
|
|
2092
|
+
Expr::Member { object, prop: MemberProp::Name { name, .. }, .. } => {
|
|
2093
|
+
Some((self.eval_expr(object)?, Value::String(name.as_ref().into())))
|
|
2094
|
+
}
|
|
2095
|
+
Expr::Member { object, prop: MemberProp::Expr(key), .. } => {
|
|
2096
|
+
Some((self.eval_expr(object)?, self.eval_expr(key)?))
|
|
2097
|
+
}
|
|
2098
|
+
Expr::Index { object, index, .. } => {
|
|
2099
|
+
Some((self.eval_expr(object)?, self.eval_expr(index)?))
|
|
2100
|
+
}
|
|
2101
|
+
_ => None,
|
|
2102
|
+
};
|
|
2103
|
+
if let Some((obj, key)) = resolved {
|
|
2104
|
+
match &obj {
|
|
2105
|
+
Value::Object(map) => {
|
|
2106
|
+
let key_s = match &key {
|
|
2107
|
+
Value::String(s) => s.to_string(),
|
|
2108
|
+
Value::Number(n) => n.to_string(),
|
|
2109
|
+
other => other.to_string(),
|
|
2110
|
+
};
|
|
2111
|
+
// shift_remove preserves the insertion order of the remaining keys
|
|
2112
|
+
// (JS delete semantics); plain remove() is deprecated on IndexMap.
|
|
2113
|
+
map.borrow_mut().strings.shift_remove(key_s.as_str());
|
|
2114
|
+
}
|
|
2115
|
+
Value::Array(arr) => {
|
|
2116
|
+
if let Value::Number(n) = &key {
|
|
2117
|
+
let n = *n;
|
|
2118
|
+
if n >= 0.0 && n.fract() == 0.0 {
|
|
2119
|
+
let i = n as usize;
|
|
2120
|
+
let mut a = arr.borrow_mut();
|
|
2121
|
+
if i < a.len() {
|
|
2122
|
+
a[i] = Value::Null;
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
_ => {}
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
Ok(Value::Bool(true))
|
|
2131
|
+
}
|
|
2132
|
+
Expr::PostfixInc { name, .. } => {
|
|
2133
|
+
let v = self.scope.borrow().get(name.as_ref())
|
|
2134
|
+
.ok_or_else(|| EvalError::Error(format!("Undefined variable: {}", name)))?;
|
|
2135
|
+
let n = match &v {
|
|
2136
|
+
Value::Number(x) => *x,
|
|
2137
|
+
_ => return Err(EvalError::Error(format!("Cannot apply ++ to {:?}", v))),
|
|
2138
|
+
};
|
|
2139
|
+
match self.scope.borrow_mut().assign(name.as_ref(), Value::Number(n + 1.0)) {
|
|
2140
|
+
Ok(true) => Ok(Value::Number(n)),
|
|
2141
|
+
Ok(false) => Err(EvalError::Error(format!("Undefined variable: {}", name))),
|
|
2142
|
+
Err(e) => Err(EvalError::Error(e)),
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
Expr::PostfixDec { name, .. } => {
|
|
2146
|
+
let v = self.scope.borrow().get(name.as_ref())
|
|
2147
|
+
.ok_or_else(|| EvalError::Error(format!("Undefined variable: {}", name)))?;
|
|
2148
|
+
let n = match &v {
|
|
2149
|
+
Value::Number(x) => *x,
|
|
2150
|
+
_ => return Err(EvalError::Error(format!("Cannot apply -- to {:?}", v))),
|
|
2151
|
+
};
|
|
2152
|
+
match self.scope.borrow_mut().assign(name.as_ref(), Value::Number(n - 1.0)) {
|
|
2153
|
+
Ok(true) => Ok(Value::Number(n)),
|
|
2154
|
+
Ok(false) => Err(EvalError::Error(format!("Undefined variable: {}", name))),
|
|
2155
|
+
Err(e) => Err(EvalError::Error(e)),
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
Expr::PrefixInc { name, .. } => {
|
|
2159
|
+
let v = self.scope.borrow().get(name.as_ref())
|
|
2160
|
+
.ok_or_else(|| EvalError::Error(format!("Undefined variable: {}", name)))?;
|
|
2161
|
+
let n = match &v {
|
|
2162
|
+
Value::Number(x) => *x,
|
|
2163
|
+
_ => return Err(EvalError::Error(format!("Cannot apply ++ to {:?}", v))),
|
|
2164
|
+
};
|
|
2165
|
+
let new_val = Value::Number(n + 1.0);
|
|
2166
|
+
match self.scope.borrow_mut().assign(name.as_ref(), new_val.clone()) {
|
|
2167
|
+
Ok(true) => Ok(new_val),
|
|
2168
|
+
Ok(false) => Err(EvalError::Error(format!("Undefined variable: {}", name))),
|
|
2169
|
+
Err(e) => Err(EvalError::Error(e)),
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
Expr::PrefixDec { name, .. } => {
|
|
2173
|
+
let v = self.scope.borrow().get(name.as_ref())
|
|
2174
|
+
.ok_or_else(|| EvalError::Error(format!("Undefined variable: {}", name)))?;
|
|
2175
|
+
let n = match &v {
|
|
2176
|
+
Value::Number(x) => *x,
|
|
2177
|
+
_ => return Err(EvalError::Error(format!("Cannot apply -- to {:?}", v))),
|
|
2178
|
+
};
|
|
2179
|
+
let new_val = Value::Number(n - 1.0);
|
|
2180
|
+
match self.scope.borrow_mut().assign(name.as_ref(), new_val.clone()) {
|
|
2181
|
+
Ok(true) => Ok(new_val),
|
|
2182
|
+
Ok(false) => Err(EvalError::Error(format!("Undefined variable: {}", name))),
|
|
2183
|
+
Err(e) => Err(EvalError::Error(e)),
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
Expr::CompoundAssign { name, op, value, .. } => {
|
|
2187
|
+
let current = self.scope.borrow().get(name.as_ref())
|
|
2188
|
+
.ok_or_else(|| EvalError::Error(format!("Undefined variable: {}", name)))?;
|
|
2189
|
+
let rhs = self.eval_expr(value)?;
|
|
2190
|
+
let bin_op = match op {
|
|
2191
|
+
CompoundOp::Add => BinOp::Add,
|
|
2192
|
+
CompoundOp::Sub => BinOp::Sub,
|
|
2193
|
+
CompoundOp::Mul => BinOp::Mul,
|
|
2194
|
+
CompoundOp::Div => BinOp::Div,
|
|
2195
|
+
CompoundOp::Mod => BinOp::Mod,
|
|
2196
|
+
};
|
|
2197
|
+
let result = self.eval_binop(¤t, bin_op, &rhs).map_err(EvalError::Error)?;
|
|
2198
|
+
match self.scope.borrow_mut().assign(name.as_ref(), result.clone()) {
|
|
2199
|
+
Ok(true) => Ok(result),
|
|
2200
|
+
Ok(false) => Err(EvalError::Error(format!("Undefined variable: {}", name))),
|
|
2201
|
+
Err(e) => Err(EvalError::Error(e)),
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
Expr::LogicalAssign { name, op, value, .. } => {
|
|
2205
|
+
let current = self.scope.borrow().get(name.as_ref())
|
|
2206
|
+
.ok_or_else(|| EvalError::Error(format!("Undefined variable: {}", name)))?;
|
|
2207
|
+
let result = match op {
|
|
2208
|
+
LogicalAssignOp::AndAnd => {
|
|
2209
|
+
if current.is_truthy() {
|
|
2210
|
+
let rhs = self.eval_expr(value)?;
|
|
2211
|
+
let _ = self.scope.borrow_mut().assign(name.as_ref(), rhs.clone());
|
|
2212
|
+
rhs
|
|
2213
|
+
} else {
|
|
2214
|
+
current.clone()
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
LogicalAssignOp::OrOr => {
|
|
2218
|
+
if !current.is_truthy() {
|
|
2219
|
+
let rhs = self.eval_expr(value)?;
|
|
2220
|
+
let _ = self.scope.borrow_mut().assign(name.as_ref(), rhs.clone());
|
|
2221
|
+
rhs
|
|
2222
|
+
} else {
|
|
2223
|
+
current.clone()
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
LogicalAssignOp::Nullish => {
|
|
2227
|
+
if matches!(current, Value::Null) {
|
|
2228
|
+
let rhs = self.eval_expr(value)?;
|
|
2229
|
+
let _ = self.scope.borrow_mut().assign(name.as_ref(), rhs.clone());
|
|
2230
|
+
rhs
|
|
2231
|
+
} else {
|
|
2232
|
+
current.clone()
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
};
|
|
2236
|
+
Ok(result)
|
|
2237
|
+
}
|
|
2238
|
+
Expr::MemberAssign { object, prop, value, .. } => {
|
|
2239
|
+
let obj_val = self.eval_expr(object)?;
|
|
2240
|
+
let val = self.eval_expr(value)?;
|
|
2241
|
+
match obj_val {
|
|
2242
|
+
Value::Object(map) => {
|
|
2243
|
+
map.borrow_mut()
|
|
2244
|
+
.strings
|
|
2245
|
+
.insert(Arc::clone(prop), val.clone());
|
|
2246
|
+
Ok(val)
|
|
2247
|
+
}
|
|
2248
|
+
// `arr.length = k` truncates / grows the array (holes read back as Null),
|
|
2249
|
+
// matching JS and the bytecode VM (issue #62).
|
|
2250
|
+
Value::Array(arr) if prop.as_ref() == "length" => {
|
|
2251
|
+
let n = match &val {
|
|
2252
|
+
Value::Number(n) => *n,
|
|
2253
|
+
_ => f64::NAN,
|
|
2254
|
+
};
|
|
2255
|
+
if n.is_nan() || n < 0.0 || n.fract() != 0.0 || n > 4_294_967_295.0 {
|
|
2256
|
+
return Err(EvalError::Error("Invalid array length".to_string()));
|
|
2257
|
+
}
|
|
2258
|
+
arr.borrow_mut().resize(n as usize, Value::Null);
|
|
2259
|
+
Ok(val)
|
|
2260
|
+
}
|
|
2261
|
+
_ => Err(EvalError::Error(format!(
|
|
2262
|
+
"Cannot assign property '{}' on non-object: {:?}",
|
|
2263
|
+
prop, obj_val
|
|
2264
|
+
))),
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
Expr::IndexAssign { object, index, value, .. } => {
|
|
2268
|
+
let obj_val = self.eval_expr(object)?;
|
|
2269
|
+
let idx_val = self.eval_expr(index)?;
|
|
2270
|
+
let val = self.eval_expr(value)?;
|
|
2271
|
+
match obj_val {
|
|
2272
|
+
Value::Array(arr) => {
|
|
2273
|
+
let idx = match &idx_val {
|
|
2274
|
+
Value::Number(n) => *n as usize,
|
|
2275
|
+
_ => return Err(EvalError::Error(format!(
|
|
2276
|
+
"Array index must be a number, got {:?}",
|
|
2277
|
+
idx_val
|
|
2278
|
+
))),
|
|
2279
|
+
};
|
|
2280
|
+
let mut arr_mut = arr.borrow_mut();
|
|
2281
|
+
// Extend array if necessary (JS behavior)
|
|
2282
|
+
while arr_mut.len() <= idx {
|
|
2283
|
+
arr_mut.push(Value::Null);
|
|
2284
|
+
}
|
|
2285
|
+
arr_mut[idx] = val.clone();
|
|
2286
|
+
Ok(val)
|
|
2287
|
+
}
|
|
2288
|
+
Value::Object(_) => {
|
|
2289
|
+
eval_object_set(&obj_val, &idx_val, val.clone())
|
|
2290
|
+
.map_err(EvalError::Error)?;
|
|
2291
|
+
Ok(val)
|
|
2292
|
+
}
|
|
2293
|
+
_ => Err(EvalError::Error(format!(
|
|
2294
|
+
"Cannot assign index on non-array/object: {:?}",
|
|
2295
|
+
obj_val
|
|
2296
|
+
))),
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
Expr::ArrowFunction { params, body, .. } => {
|
|
2300
|
+
use tishlang_ast::ArrowBody;
|
|
2301
|
+
let formals: Arc<[FunParam]> = Arc::from(params.clone());
|
|
2302
|
+
let body_stmt = match body {
|
|
2303
|
+
ArrowBody::Expr(expr) => {
|
|
2304
|
+
// Expression body: wrap in implicit return
|
|
2305
|
+
Statement::Return {
|
|
2306
|
+
value: Some(expr.as_ref().clone()),
|
|
2307
|
+
span: Span { start: (0, 0), end: (0, 0) },
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
ArrowBody::Block(stmt) => stmt.as_ref().clone(),
|
|
2311
|
+
};
|
|
2312
|
+
Ok(Value::Function {
|
|
2313
|
+
formals,
|
|
2314
|
+
rest_param: None,
|
|
2315
|
+
body: Arc::new(body_stmt),
|
|
2316
|
+
env: Rc::clone(&self.scope),
|
|
2317
|
+
})
|
|
2318
|
+
}
|
|
2319
|
+
Expr::TemplateLiteral { quasis, exprs, .. } => {
|
|
2320
|
+
// Build the string by interleaving quasis and evaluated expressions
|
|
2321
|
+
let mut result = String::new();
|
|
2322
|
+
for (i, quasi) in quasis.iter().enumerate() {
|
|
2323
|
+
result.push_str(quasi);
|
|
2324
|
+
if i < exprs.len() {
|
|
2325
|
+
let val = self.eval_expr(&exprs[i])?;
|
|
2326
|
+
result.push_str(&val.to_js_string());
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
Ok(Value::String(result.into()))
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
fn eval_binop(&self, l: &Value, op: BinOp, r: &Value) -> Result<Value, String> {
|
|
2335
|
+
match op {
|
|
2336
|
+
BinOp::Add => match (l, r) {
|
|
2337
|
+
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)),
|
|
2338
|
+
(Value::String(a), Value::String(b)) => {
|
|
2339
|
+
let mut s = String::with_capacity(a.len() + b.len());
|
|
2340
|
+
s.push_str(a);
|
|
2341
|
+
s.push_str(b);
|
|
2342
|
+
Ok(Value::String(s.into()))
|
|
2343
|
+
}
|
|
2344
|
+
(Value::String(a), b) => {
|
|
2345
|
+
let b_str = b.to_js_string();
|
|
2346
|
+
let mut s = String::with_capacity(a.len() + b_str.len());
|
|
2347
|
+
s.push_str(a);
|
|
2348
|
+
s.push_str(&b_str);
|
|
2349
|
+
Ok(Value::String(s.into()))
|
|
2350
|
+
}
|
|
2351
|
+
(a, Value::String(b)) => {
|
|
2352
|
+
let a_str = a.to_js_string();
|
|
2353
|
+
let mut s = String::with_capacity(a_str.len() + b.len());
|
|
2354
|
+
s.push_str(&a_str);
|
|
2355
|
+
s.push_str(b);
|
|
2356
|
+
Ok(Value::String(s.into()))
|
|
2357
|
+
}
|
|
2358
|
+
// Neither operand is a string: numeric add, coercing non-numbers
|
|
2359
|
+
// (Null/Bool/Object/…) to NaN exactly like the VM's
|
|
2360
|
+
// `as_number().unwrap_or(NaN)` (vm.rs eval_binop). e.g. an out-of-bounds
|
|
2361
|
+
// array read is `Null` (JS `undefined`), so `15 + arr[oob]` → NaN, not an error.
|
|
2362
|
+
_ => Ok(Value::Number(
|
|
2363
|
+
l.as_number().unwrap_or(f64::NAN) + r.as_number().unwrap_or(f64::NAN),
|
|
2364
|
+
)),
|
|
2365
|
+
},
|
|
2366
|
+
BinOp::Sub => self.binop_number(l, r, |a, b| Value::Number(a - b)),
|
|
2367
|
+
BinOp::Mul => self.binop_number(l, r, |a, b| Value::Number(a * b)),
|
|
2368
|
+
BinOp::Div => self.binop_number(l, r, |a, b| Value::Number(a / b)),
|
|
2369
|
+
BinOp::Mod => self.binop_number(l, r, |a, b| Value::Number(a % b)),
|
|
2370
|
+
BinOp::Pow => self.binop_number(l, r, |a, b| Value::Number(a.powf(b))),
|
|
2371
|
+
BinOp::StrictEq => Ok(Value::Bool(l.strict_eq(r))),
|
|
2372
|
+
BinOp::StrictNe => Ok(Value::Bool(!l.strict_eq(r))),
|
|
2373
|
+
// Relational ops compare strings lexicographically when BOTH operands
|
|
2374
|
+
// are strings (JS semantics); otherwise coerce to numbers via binop_number.
|
|
2375
|
+
BinOp::Lt => self.binop_relational(l, r, |o| o.is_lt()),
|
|
2376
|
+
BinOp::Le => self.binop_relational(l, r, |o| o.is_le()),
|
|
2377
|
+
BinOp::Gt => self.binop_relational(l, r, |o| o.is_gt()),
|
|
2378
|
+
BinOp::Ge => self.binop_relational(l, r, |o| o.is_ge()),
|
|
2379
|
+
BinOp::And => Ok(Value::Bool(l.is_truthy() && r.is_truthy())),
|
|
2380
|
+
BinOp::Or => Ok(Value::Bool(l.is_truthy() || r.is_truthy())),
|
|
2381
|
+
BinOp::BitAnd => self.binop_int32(l, r, |a, b| Value::Number((a & b) as f64)),
|
|
2382
|
+
BinOp::BitOr => self.binop_int32(l, r, |a, b| Value::Number((a | b) as f64)),
|
|
2383
|
+
BinOp::BitXor => self.binop_int32(l, r, |a, b| Value::Number((a ^ b) as f64)),
|
|
2384
|
+
// JS shifts mask the count to the low 5 bits; `wrapping_sh*` does exactly
|
|
2385
|
+
// that and never panics (plain `<<`/`>>` panic in debug for count >= 32).
|
|
2386
|
+
BinOp::Shl => {
|
|
2387
|
+
self.binop_int32(l, r, |a, b| Value::Number(a.wrapping_shl(b as u32) as f64))
|
|
2388
|
+
}
|
|
2389
|
+
BinOp::Shr => {
|
|
2390
|
+
self.binop_int32(l, r, |a, b| Value::Number(a.wrapping_shr(b as u32) as f64))
|
|
2391
|
+
}
|
|
2392
|
+
// `>>>` — unsigned (logical) right shift: ToUint32(a) >>> (b & 31).
|
|
2393
|
+
BinOp::UShr => self.binop_int32(l, r, |a, b| {
|
|
2394
|
+
Value::Number((a as u32).wrapping_shr(b as u32) as f64)
|
|
2395
|
+
}),
|
|
2396
|
+
BinOp::In => {
|
|
2397
|
+
let ok = match r {
|
|
2398
|
+
Value::Object(_) => eval_object_has(r, l),
|
|
2399
|
+
Value::Array(arr) => {
|
|
2400
|
+
let key: Arc<str> = match l {
|
|
2401
|
+
Value::String(s) => Arc::clone(s),
|
|
2402
|
+
Value::Number(n) => n.to_string().into(),
|
|
2403
|
+
_ => {
|
|
2404
|
+
return Err(format!(
|
|
2405
|
+
"'in' requires string or number key on array, got {:?}",
|
|
2406
|
+
l
|
|
2407
|
+
))
|
|
2408
|
+
}
|
|
2409
|
+
};
|
|
2410
|
+
key.as_ref() == "length"
|
|
2411
|
+
|| key
|
|
2412
|
+
.parse::<usize>()
|
|
2413
|
+
.ok()
|
|
2414
|
+
.map(|i| i < arr.borrow().len())
|
|
2415
|
+
.unwrap_or(false)
|
|
2416
|
+
}
|
|
2417
|
+
_ => return Err(format!("'in' requires object or array, got {:?}", r)),
|
|
2418
|
+
};
|
|
2419
|
+
Ok(Value::Bool(ok))
|
|
2420
|
+
}
|
|
2421
|
+
// Loose ==/!= : match the VM (vm.rs maps Eq/Ne to strict_eq) so interp == vm ==
|
|
2422
|
+
// compiled. Previously the interpreter alone errored on `==`.
|
|
2423
|
+
BinOp::Eq => Ok(Value::Bool(l.strict_eq(r))),
|
|
2424
|
+
BinOp::Ne => Ok(Value::Bool(!l.strict_eq(r))),
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
/// Check if a function value is the common numeric sort comparator pattern.
|
|
2429
|
+
/// descending = false: checks for `(a, b) => a - b`
|
|
2430
|
+
/// descending = true: checks for `(a, b) => b - a`
|
|
2431
|
+
fn is_numeric_sort_comparator(f: &Value, descending: bool) -> bool {
|
|
2432
|
+
if let Value::Function {
|
|
2433
|
+
formals,
|
|
2434
|
+
body,
|
|
2435
|
+
rest_param,
|
|
2436
|
+
..
|
|
2437
|
+
} = f
|
|
2438
|
+
{
|
|
2439
|
+
// Must have exactly 2 simple params, no defaults, no rest
|
|
2440
|
+
if formals.len() != 2 || rest_param.is_some() {
|
|
2441
|
+
return false;
|
|
2442
|
+
}
|
|
2443
|
+
let (param_a, param_b) = match (&formals[0], &formals[1]) {
|
|
2444
|
+
(FunParam::Simple(a), FunParam::Simple(b))
|
|
2445
|
+
if a.default.is_none() && b.default.is_none() =>
|
|
2446
|
+
{
|
|
2447
|
+
(&a.name, &b.name)
|
|
2448
|
+
}
|
|
2449
|
+
_ => return false,
|
|
2450
|
+
};
|
|
2451
|
+
|
|
2452
|
+
// Body must be a return of a - b (or b - a for descending)
|
|
2453
|
+
|
|
2454
|
+
// Check for both Statement::Return and Statement::ExprStmt (arrow implicit return)
|
|
2455
|
+
let expr = match body.as_ref() {
|
|
2456
|
+
Statement::Return { value: Some(e), .. } => e,
|
|
2457
|
+
Statement::ExprStmt { expr: e, .. } => e,
|
|
2458
|
+
_ => return false,
|
|
2459
|
+
};
|
|
2460
|
+
|
|
2461
|
+
// Check for binary subtraction
|
|
2462
|
+
if let Expr::Binary {
|
|
2463
|
+
left,
|
|
2464
|
+
op: BinOp::Sub,
|
|
2465
|
+
right,
|
|
2466
|
+
..
|
|
2467
|
+
} = expr
|
|
2468
|
+
{
|
|
2469
|
+
// Check left is Ident(a) and right is Ident(b)
|
|
2470
|
+
let (expected_left, expected_right) = if descending {
|
|
2471
|
+
(param_b, param_a) // b - a
|
|
2472
|
+
} else {
|
|
2473
|
+
(param_a, param_b) // a - b
|
|
2474
|
+
};
|
|
2475
|
+
|
|
2476
|
+
if let (
|
|
2477
|
+
Expr::Ident {
|
|
2478
|
+
name: left_name, ..
|
|
2479
|
+
},
|
|
2480
|
+
Expr::Ident {
|
|
2481
|
+
name: right_name, ..
|
|
2482
|
+
},
|
|
2483
|
+
) = (left.as_ref(), right.as_ref())
|
|
2484
|
+
{
|
|
2485
|
+
return left_name == expected_left && right_name == expected_right;
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
false
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
/// JS ToInt32 coercion. Non-numbers coerce to NaN → 0. Going through `i64`
|
|
2493
|
+
/// (not a direct `as i32`) gives modulo-2³² truncation instead of a saturating
|
|
2494
|
+
/// cast, so out-of-i32-range values (e.g. a `0..2³²` hash) wrap exactly like JS:
|
|
2495
|
+
/// `4294967295 | 0 === -1`, not the saturated `i32::MAX`. Realistic values are
|
|
2496
|
+
/// `< 2⁵³` so they fit `i64` exactly; the two casts stay cheap.
|
|
2497
|
+
fn to_int32(v: &Value) -> i32 {
|
|
2498
|
+
// NaN / ±Infinity → 0 (the `is_finite` guard): `f64 as i64` *saturates* (`+∞ → i64::MAX
|
|
2499
|
+
// → -1`), which is not the JS ToInt32 result. Finite values use the cheap modulo cast.
|
|
2500
|
+
let x = v.as_number().unwrap_or(f64::NAN);
|
|
2501
|
+
if x.is_finite() {
|
|
2502
|
+
x as i64 as i32
|
|
2503
|
+
} else {
|
|
2504
|
+
0
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
fn binop_int32<F>(&self, l: &Value, r: &Value, f: F) -> Result<Value, String>
|
|
2509
|
+
where
|
|
2510
|
+
F: FnOnce(i32, i32) -> Value,
|
|
2511
|
+
{
|
|
2512
|
+
Ok(f(Self::to_int32(l), Self::to_int32(r)))
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
/// Numeric binop, coercing each operand to a number the way the VM does
|
|
2516
|
+
/// (`as_number().unwrap_or(NaN)`): non-numbers (Null/Bool/Object/…) become NaN rather
|
|
2517
|
+
/// than erroring. Keeps the interpreter in parity with the VM and Node on out-of-bounds
|
|
2518
|
+
/// reads and other `undefined`-like operands.
|
|
2519
|
+
fn binop_number<F>(&self, l: &Value, r: &Value, f: F) -> Result<Value, String>
|
|
2520
|
+
where
|
|
2521
|
+
F: FnOnce(f64, f64) -> Value,
|
|
2522
|
+
{
|
|
2523
|
+
let a = l.as_number().unwrap_or(f64::NAN);
|
|
2524
|
+
let b = r.as_number().unwrap_or(f64::NAN);
|
|
2525
|
+
Ok(f(a, b))
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
/// Relational comparison (`<` `<=` `>` `>=`). When both operands are strings,
|
|
2529
|
+
/// compare lexicographically; otherwise coerce to numbers. `pred` maps the
|
|
2530
|
+
/// resulting `Ordering` to a bool. A NaN-involved numeric comparison yields no
|
|
2531
|
+
/// ordering and is always `false`, matching JS (`NaN < 5` → false).
|
|
2532
|
+
fn binop_relational<F>(&self, l: &Value, r: &Value, pred: F) -> Result<Value, String>
|
|
2533
|
+
where
|
|
2534
|
+
F: FnOnce(std::cmp::Ordering) -> bool,
|
|
2535
|
+
{
|
|
2536
|
+
let ord = match (l, r) {
|
|
2537
|
+
(Value::String(a), Value::String(b)) => Some(a.as_ref().cmp(b.as_ref())),
|
|
2538
|
+
_ => {
|
|
2539
|
+
let a = l.as_number().unwrap_or(f64::NAN);
|
|
2540
|
+
let b = r.as_number().unwrap_or(f64::NAN);
|
|
2541
|
+
a.partial_cmp(&b)
|
|
2542
|
+
}
|
|
2543
|
+
};
|
|
2544
|
+
Ok(Value::Bool(ord.map(pred).unwrap_or(false)))
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
fn eval_unary(&self, op: UnaryOp, v: &Value) -> Result<Value, String> {
|
|
2548
|
+
match op {
|
|
2549
|
+
UnaryOp::Not => Ok(Value::Bool(!v.is_truthy())),
|
|
2550
|
+
UnaryOp::Neg => match v {
|
|
2551
|
+
Value::Number(n) => Ok(Value::Number(-n)),
|
|
2552
|
+
_ => Err(format!("Cannot negate {:?}", v)),
|
|
2553
|
+
},
|
|
2554
|
+
UnaryOp::Pos => match v {
|
|
2555
|
+
Value::Number(n) => Ok(Value::Number(*n)),
|
|
2556
|
+
_ => Err(format!("Cannot apply unary + to {:?}", v)),
|
|
2557
|
+
},
|
|
2558
|
+
UnaryOp::BitNot => {
|
|
2559
|
+
let n = Self::to_int32(v);
|
|
2560
|
+
Ok(Value::Number((!n) as f64))
|
|
2561
|
+
}
|
|
2562
|
+
UnaryOp::Void => Ok(Value::Null),
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
/// Optimized callback invocation for array methods.
|
|
2567
|
+
/// Creates a reusable scope that can be updated for each iteration.
|
|
2568
|
+
fn create_callback_scope(
|
|
2569
|
+
&self,
|
|
2570
|
+
f: &Value,
|
|
2571
|
+
) -> Option<(Rc<RefCell<Scope>>, Arc<[Arc<str>]>, Arc<Statement>)> {
|
|
2572
|
+
if let Value::Function {
|
|
2573
|
+
formals,
|
|
2574
|
+
body,
|
|
2575
|
+
rest_param,
|
|
2576
|
+
..
|
|
2577
|
+
} = f
|
|
2578
|
+
{
|
|
2579
|
+
if rest_param.is_some() {
|
|
2580
|
+
return None;
|
|
2581
|
+
}
|
|
2582
|
+
for fp in formals.iter() {
|
|
2583
|
+
match fp {
|
|
2584
|
+
FunParam::Simple(tp) if tp.default.is_none() => {}
|
|
2585
|
+
_ => return None,
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
let scope = Scope::child(Rc::clone(&self.scope));
|
|
2589
|
+
{
|
|
2590
|
+
let mut s = scope.borrow_mut();
|
|
2591
|
+
for fp in formals.iter() {
|
|
2592
|
+
for n in fp.bound_names() {
|
|
2593
|
+
s.set(n, Value::Null, true);
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
let flat_names: Arc<[Arc<str>]> = Arc::from(
|
|
2598
|
+
formals
|
|
2599
|
+
.iter()
|
|
2600
|
+
.flat_map(|fp| fp.bound_names())
|
|
2601
|
+
.collect::<Vec<_>>(),
|
|
2602
|
+
);
|
|
2603
|
+
return Some((scope, flat_names, Arc::clone(body)));
|
|
2604
|
+
}
|
|
2605
|
+
None
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
/// Fast callback invocation that reuses an existing scope.
|
|
2609
|
+
fn call_with_scope(
|
|
2610
|
+
&self,
|
|
2611
|
+
scope: &Rc<RefCell<Scope>>,
|
|
2612
|
+
params: &[Arc<str>],
|
|
2613
|
+
body: &Statement,
|
|
2614
|
+
args: &[Value],
|
|
2615
|
+
) -> Result<Value, EvalError> {
|
|
2616
|
+
{
|
|
2617
|
+
let mut s = scope.borrow_mut();
|
|
2618
|
+
for (i, p) in params.iter().enumerate() {
|
|
2619
|
+
let val = args.get(i).cloned().unwrap_or(Value::Null);
|
|
2620
|
+
// Direct assignment - we know these vars exist and are mutable
|
|
2621
|
+
if let Some(existing) = s.vars.get_mut(p.as_ref()) {
|
|
2622
|
+
*existing = val;
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
let mut eval = Evaluator {
|
|
2627
|
+
scope: Rc::clone(scope),
|
|
2628
|
+
module_cache: Rc::clone(&self.module_cache),
|
|
2629
|
+
current_dir: RefCell::new(self.current_dir.borrow().clone()),
|
|
2630
|
+
virtual_builtins: Rc::clone(&self.virtual_builtins),
|
|
2631
|
+
};
|
|
2632
|
+
match eval.eval_statement(body) {
|
|
2633
|
+
Ok(v) => Ok(v),
|
|
2634
|
+
Err(EvalError::Return(v)) => Ok(v),
|
|
2635
|
+
Err(e) => Err(e),
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
/// Try to evaluate a simple callback expression directly without creating a scope.
|
|
2640
|
+
/// Returns Some(result) for simple patterns like `x => x * 2` or `x => x > 5`.
|
|
2641
|
+
fn eval_simple_callback(&self, f: &Value, args: &[Value]) -> Option<Result<Value, EvalError>> {
|
|
2642
|
+
if let Value::Function {
|
|
2643
|
+
formals,
|
|
2644
|
+
body,
|
|
2645
|
+
rest_param,
|
|
2646
|
+
..
|
|
2647
|
+
} = f
|
|
2648
|
+
{
|
|
2649
|
+
if formals.len() != 1 || rest_param.is_some() {
|
|
2650
|
+
return None;
|
|
2651
|
+
}
|
|
2652
|
+
let param_name = match &formals[0] {
|
|
2653
|
+
FunParam::Simple(tp) if tp.default.is_none() => &tp.name,
|
|
2654
|
+
_ => return None,
|
|
2655
|
+
};
|
|
2656
|
+
let arg = args.first().cloned().unwrap_or(Value::Null);
|
|
2657
|
+
|
|
2658
|
+
// Get the expression from the body
|
|
2659
|
+
let expr = match body.as_ref() {
|
|
2660
|
+
Statement::Return { value: Some(e), .. } => e,
|
|
2661
|
+
Statement::ExprStmt { expr: e, .. } => e,
|
|
2662
|
+
_ => return None,
|
|
2663
|
+
};
|
|
2664
|
+
|
|
2665
|
+
// Fast path for common patterns
|
|
2666
|
+
match expr {
|
|
2667
|
+
// x * constant or x + constant, etc.
|
|
2668
|
+
Expr::Binary {
|
|
2669
|
+
left, op, right, ..
|
|
2670
|
+
} => {
|
|
2671
|
+
let left_val = self.eval_simple_operand(left, param_name, &arg)?;
|
|
2672
|
+
let right_val = self.eval_simple_operand(right, param_name, &arg)?;
|
|
2673
|
+
Some(
|
|
2674
|
+
self.eval_binop(&left_val, *op, &right_val)
|
|
2675
|
+
.map_err(EvalError::Error),
|
|
2676
|
+
)
|
|
2677
|
+
}
|
|
2678
|
+
// Just return the parameter
|
|
2679
|
+
Expr::Ident { name, .. } if name == param_name => Some(Ok(arg)),
|
|
2680
|
+
// Property access: x.prop
|
|
2681
|
+
Expr::Member {
|
|
2682
|
+
object,
|
|
2683
|
+
prop,
|
|
2684
|
+
optional,
|
|
2685
|
+
..
|
|
2686
|
+
} => {
|
|
2687
|
+
if let Expr::Ident { name, .. } = object.as_ref() {
|
|
2688
|
+
if name == param_name {
|
|
2689
|
+
return self.eval_simple_member(&arg, prop, *optional);
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
None
|
|
2693
|
+
}
|
|
2694
|
+
_ => None,
|
|
2695
|
+
}
|
|
2696
|
+
} else {
|
|
2697
|
+
None
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
/// Evaluate a simple operand (identifier or literal).
|
|
2702
|
+
fn eval_simple_operand(
|
|
2703
|
+
&self,
|
|
2704
|
+
expr: &Expr,
|
|
2705
|
+
param_name: &Arc<str>,
|
|
2706
|
+
param_val: &Value,
|
|
2707
|
+
) -> Option<Value> {
|
|
2708
|
+
match expr {
|
|
2709
|
+
Expr::Ident { name, .. } if name == param_name => Some(param_val.clone()),
|
|
2710
|
+
Expr::Literal { value, .. } => match value {
|
|
2711
|
+
Literal::Number(n) => Some(Value::Number(*n)),
|
|
2712
|
+
Literal::String(s) => Some(Value::String(Arc::clone(s))),
|
|
2713
|
+
Literal::Bool(b) => Some(Value::Bool(*b)),
|
|
2714
|
+
Literal::Null => Some(Value::Null),
|
|
2715
|
+
},
|
|
2716
|
+
_ => None,
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
/// Evaluate simple member access.
|
|
2721
|
+
fn eval_simple_member(
|
|
2722
|
+
&self,
|
|
2723
|
+
obj: &Value,
|
|
2724
|
+
property: &MemberProp,
|
|
2725
|
+
_optional: bool,
|
|
2726
|
+
) -> Option<Result<Value, EvalError>> {
|
|
2727
|
+
match property {
|
|
2728
|
+
MemberProp::Name { name, .. } => match obj {
|
|
2729
|
+
Value::Object(o) => {
|
|
2730
|
+
let result = o
|
|
2731
|
+
.borrow()
|
|
2732
|
+
.strings
|
|
2733
|
+
.get(name.as_ref())
|
|
2734
|
+
.cloned()
|
|
2735
|
+
.unwrap_or(Value::Null);
|
|
2736
|
+
Some(Ok(result))
|
|
2737
|
+
}
|
|
2738
|
+
Value::Array(arr) if name.as_ref() == "length" => {
|
|
2739
|
+
Some(Ok(Value::Number(arr.borrow().len() as f64)))
|
|
2740
|
+
}
|
|
2741
|
+
_ => None,
|
|
2742
|
+
},
|
|
2743
|
+
_ => None,
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
/// Host `new`: `__construct` on objects; otherwise same callables as `call_func`, else null.
|
|
2748
|
+
fn construct_value(&self, callee: &Value, args: &[Value]) -> Result<Value, EvalError> {
|
|
2749
|
+
if let Value::Object(o) = callee {
|
|
2750
|
+
if let Some(ctor) = o
|
|
2751
|
+
.borrow()
|
|
2752
|
+
.strings
|
|
2753
|
+
.get("__construct")
|
|
2754
|
+
.cloned()
|
|
2755
|
+
{
|
|
2756
|
+
return self.call_func(&ctor, args);
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
match callee {
|
|
2760
|
+
Value::Native(_) | Value::Function { .. } | Value::CoreFn(_) => {
|
|
2761
|
+
self.call_func(callee, args)
|
|
2762
|
+
}
|
|
2763
|
+
#[cfg(feature = "http")]
|
|
2764
|
+
Value::PromiseConstructor | Value::Serve | Value::BoundPromiseMethod(_, _) => {
|
|
2765
|
+
self.call_func(callee, args)
|
|
2766
|
+
}
|
|
2767
|
+
#[cfg(feature = "timers")]
|
|
2768
|
+
Value::TimerBuiltin(_) => self.call_func(callee, args),
|
|
2769
|
+
Value::OpaqueMethod(_, _) => self.call_func(callee, args),
|
|
2770
|
+
_ => Ok(Value::Null),
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
fn call_func(&self, f: &Value, args: &[Value]) -> Result<Value, EvalError> {
|
|
2775
|
+
match f {
|
|
2776
|
+
Value::Object(o) => {
|
|
2777
|
+
if let Some(call) = o.borrow().strings.get("__call").cloned() {
|
|
2778
|
+
return self.call_func(&call, args);
|
|
2779
|
+
}
|
|
2780
|
+
Err(EvalError::Error("Not a function".to_string()))
|
|
2781
|
+
}
|
|
2782
|
+
Value::Native(native_fn) => native_fn(args).map_err(EvalError::Error),
|
|
2783
|
+
#[cfg(feature = "http")]
|
|
2784
|
+
Value::PromiseResolver(r) => {
|
|
2785
|
+
let value = args.first().cloned().unwrap_or(Value::Null);
|
|
2786
|
+
let (val, is_fulfilled, reactions) =
|
|
2787
|
+
crate::promise::settle_promise(r, value, r.is_resolve)
|
|
2788
|
+
.map_err(EvalError::Error)?;
|
|
2789
|
+
for reaction in reactions {
|
|
2790
|
+
match reaction {
|
|
2791
|
+
crate::promise::Reaction::Then(
|
|
2792
|
+
on_fulfilled,
|
|
2793
|
+
on_rejected,
|
|
2794
|
+
ref resolve,
|
|
2795
|
+
ref reject,
|
|
2796
|
+
) => {
|
|
2797
|
+
let handler_result = if is_fulfilled {
|
|
2798
|
+
if let Some(ref h) = on_fulfilled {
|
|
2799
|
+
self.call_func(h, &[val.clone()])
|
|
2800
|
+
} else {
|
|
2801
|
+
Ok(val.clone())
|
|
2802
|
+
}
|
|
2803
|
+
} else {
|
|
2804
|
+
if let Some(ref h) = on_rejected {
|
|
2805
|
+
self.call_func(h, &[val.clone()])
|
|
2806
|
+
} else {
|
|
2807
|
+
Err(EvalError::Throw(val.clone()))
|
|
2808
|
+
}
|
|
2809
|
+
};
|
|
2810
|
+
match handler_result {
|
|
2811
|
+
Ok(v) => {
|
|
2812
|
+
crate::promise::settle_promise(resolve, v, true)
|
|
2813
|
+
.map_err(EvalError::Error)?;
|
|
2814
|
+
}
|
|
2815
|
+
Err(EvalError::Throw(v)) => {
|
|
2816
|
+
crate::promise::settle_promise(reject, v, false)
|
|
2817
|
+
.map_err(EvalError::Error)?;
|
|
2818
|
+
}
|
|
2819
|
+
Err(e) => return Err(e),
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
crate::promise::Reaction::Finally(on_finally, ref resolve, ref reject) => {
|
|
2823
|
+
let _ = self.call_func(&on_finally, &[]);
|
|
2824
|
+
if is_fulfilled {
|
|
2825
|
+
crate::promise::settle_promise(resolve, val.clone(), true)
|
|
2826
|
+
.map_err(EvalError::Error)?;
|
|
2827
|
+
} else {
|
|
2828
|
+
crate::promise::settle_promise(reject, val.clone(), false)
|
|
2829
|
+
.map_err(EvalError::Error)?;
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
Ok(Value::Null)
|
|
2835
|
+
}
|
|
2836
|
+
#[cfg(feature = "http")]
|
|
2837
|
+
Value::PromiseConstructor => {
|
|
2838
|
+
let executor = args.first().ok_or_else(|| {
|
|
2839
|
+
EvalError::Error("Promise requires an executor function".to_string())
|
|
2840
|
+
})?;
|
|
2841
|
+
let (promise, resolve, reject) = crate::promise::create_promise();
|
|
2842
|
+
self.call_func(executor, &[resolve, reject])?;
|
|
2843
|
+
Ok(promise)
|
|
2844
|
+
}
|
|
2845
|
+
#[cfg(feature = "http")]
|
|
2846
|
+
Value::Serve => self.run_http_server(args),
|
|
2847
|
+
Value::CoreFn(f) => {
|
|
2848
|
+
let ca: Result<Vec<tishlang_core::Value>, String> = args
|
|
2849
|
+
.iter()
|
|
2850
|
+
.map(crate::value_convert::eval_to_core)
|
|
2851
|
+
.collect();
|
|
2852
|
+
let ca = ca.map_err(EvalError::Error)?;
|
|
2853
|
+
Ok(crate::value_convert::core_to_eval(f.call(&ca)))
|
|
2854
|
+
}
|
|
2855
|
+
#[cfg(feature = "regex")]
|
|
2856
|
+
Value::RegExp(_) => Err(EvalError::Error("RegExp is not callable".to_string())),
|
|
2857
|
+
#[cfg(feature = "http")]
|
|
2858
|
+
Value::BoundPromiseMethod(promise_ref, method) => {
|
|
2859
|
+
self.run_promise_method(promise_ref, method.as_ref(), args)
|
|
2860
|
+
}
|
|
2861
|
+
#[cfg(feature = "timers")]
|
|
2862
|
+
Value::TimerBuiltin(name) => self.run_timer_builtin(name.as_ref(), args),
|
|
2863
|
+
Value::OpaqueMethod(opaque, method_name) => {
|
|
2864
|
+
let method = opaque.get_method(method_name.as_ref()).ok_or_else(|| {
|
|
2865
|
+
EvalError::Error(format!(
|
|
2866
|
+
"Method {} not found on {}",
|
|
2867
|
+
method_name,
|
|
2868
|
+
opaque.type_name()
|
|
2869
|
+
))
|
|
2870
|
+
})?;
|
|
2871
|
+
let core_args: Result<Vec<tishlang_core::Value>, String> = args
|
|
2872
|
+
.iter()
|
|
2873
|
+
.map(crate::value_convert::eval_to_core)
|
|
2874
|
+
.collect();
|
|
2875
|
+
let core_args = core_args.map_err(EvalError::Error)?;
|
|
2876
|
+
let result = method.call(&core_args);
|
|
2877
|
+
Ok(crate::value_convert::core_to_eval(result))
|
|
2878
|
+
}
|
|
2879
|
+
Value::Function {
|
|
2880
|
+
formals,
|
|
2881
|
+
rest_param,
|
|
2882
|
+
body,
|
|
2883
|
+
env,
|
|
2884
|
+
} => {
|
|
2885
|
+
// A real closure: the call frame's parent is the function's DEFINING scope (env),
|
|
2886
|
+
// not the call site — so free variables resolve lexically.
|
|
2887
|
+
let scope = Scope::child(Rc::clone(env));
|
|
2888
|
+
// The call-frame evaluator, built up front so default-parameter expressions
|
|
2889
|
+
// evaluate in this *call* scope — where earlier params are already bound (so a
|
|
2890
|
+
// default like `b = a + 1` can see `a`) and free vars still resolve lexically
|
|
2891
|
+
// through the closure's `env`. Evaluating against `self.scope` (the call *site*)
|
|
2892
|
+
// would see neither, matching the bytecode VM's ArgMissing prologue, which runs
|
|
2893
|
+
// defaults in the frame after the supplied args are bound.
|
|
2894
|
+
let mut eval = Evaluator {
|
|
2895
|
+
scope: Rc::clone(&scope),
|
|
2896
|
+
module_cache: Rc::clone(&self.module_cache),
|
|
2897
|
+
current_dir: RefCell::new(self.current_dir.borrow().clone()),
|
|
2898
|
+
virtual_builtins: Rc::clone(&self.virtual_builtins),
|
|
2899
|
+
};
|
|
2900
|
+
{
|
|
2901
|
+
let mut s = scope.borrow_mut();
|
|
2902
|
+
for (i, formal) in formals.iter().enumerate() {
|
|
2903
|
+
let val = match args.get(i) {
|
|
2904
|
+
Some(v) => v.clone(),
|
|
2905
|
+
None => {
|
|
2906
|
+
let def = match formal {
|
|
2907
|
+
FunParam::Simple(tp) => tp.default.as_ref(),
|
|
2908
|
+
FunParam::Destructure { default, .. } => default.as_ref(),
|
|
2909
|
+
};
|
|
2910
|
+
if let Some(default_expr) = def {
|
|
2911
|
+
drop(s);
|
|
2912
|
+
let default_val = eval.eval_expr(default_expr)?;
|
|
2913
|
+
s = scope.borrow_mut();
|
|
2914
|
+
default_val
|
|
2915
|
+
} else {
|
|
2916
|
+
Value::Null
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
};
|
|
2920
|
+
match formal {
|
|
2921
|
+
FunParam::Simple(tp) => {
|
|
2922
|
+
s.set(Arc::clone(&tp.name), val, true);
|
|
2923
|
+
}
|
|
2924
|
+
FunParam::Destructure { pattern, .. } => {
|
|
2925
|
+
drop(s);
|
|
2926
|
+
Self::bind_destruct_pattern_scoped(&scope, pattern, &val, true)?;
|
|
2927
|
+
s = scope.borrow_mut();
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
if let Some(ref rest_name) = rest_param {
|
|
2932
|
+
let rest_vals: Vec<Value> =
|
|
2933
|
+
args.iter().skip(formals.len()).cloned().collect();
|
|
2934
|
+
s.set(
|
|
2935
|
+
Arc::clone(rest_name),
|
|
2936
|
+
Value::Array(Rc::new(RefCell::new(rest_vals))),
|
|
2937
|
+
true,
|
|
2938
|
+
);
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
// Grow the native stack on demand so deep (non-tail) recursion doesn't overflow
|
|
2942
|
+
// the OS thread stack — same idea as the bytecode VM's `stacker::maybe_grow` around
|
|
2943
|
+
// recursive `run_chunk` (vm.rs:1138). Without it the tree-walker aborts (SIGABRT,
|
|
2944
|
+
// "stack overflow") on deep recursion, which the cross-backend parity run surfaced
|
|
2945
|
+
// on `recursion_stress`. This is the recursion ACCUMULATOR (every user-function call
|
|
2946
|
+
// lands here); the per-element HOF path (`call_with_scope`) is deliberately NOT
|
|
2947
|
+
// guarded — it never nests deeply, so it avoids the per-call check on hot map/filter.
|
|
2948
|
+
//
|
|
2949
|
+
// Red zone = 1 MiB, NOT the VM's 128 KiB: one tree-walker recursion level spans a
|
|
2950
|
+
// long eval chain (eval_statement → eval_expr(if) → eval_expr(binary) → eval_expr(call)
|
|
2951
|
+
// → eval_call_args → call_func → …), each frame large — far more per level than the
|
|
2952
|
+
// VM's single `run_chunk` re-entry. 128 KiB is smaller than one level's chain, so the
|
|
2953
|
+
// stack overflows BETWEEN checks; 1 MiB comfortably covers a level (verified to depth
|
|
2954
|
+
// 20000 in both debug and release). 16 MiB segments keep grow frequency low.
|
|
2955
|
+
let body_result = {
|
|
2956
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
2957
|
+
{
|
|
2958
|
+
stacker::maybe_grow(1024 * 1024, 16 * 1024 * 1024, || {
|
|
2959
|
+
eval.eval_statement(body)
|
|
2960
|
+
})
|
|
2961
|
+
}
|
|
2962
|
+
#[cfg(target_arch = "wasm32")]
|
|
2963
|
+
{
|
|
2964
|
+
eval.eval_statement(body)
|
|
2965
|
+
}
|
|
2966
|
+
};
|
|
2967
|
+
match body_result {
|
|
2968
|
+
Ok(v) => Ok(v),
|
|
2969
|
+
Err(EvalError::Return(v)) => Ok(v),
|
|
2970
|
+
Err(EvalError::Throw(v)) => Err(EvalError::Throw(v)),
|
|
2971
|
+
Err(EvalError::Error(s)) => Err(EvalError::Error(s)),
|
|
2972
|
+
Err(EvalError::Break) => {
|
|
2973
|
+
Err(EvalError::Error("break outside loop".to_string()))
|
|
2974
|
+
}
|
|
2975
|
+
Err(EvalError::Continue) => {
|
|
2976
|
+
Err(EvalError::Error("continue outside loop".to_string()))
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
_ => Err(EvalError::Error("Not a function".to_string())),
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
#[cfg(feature = "http")]
|
|
2985
|
+
fn run_promise_method(
|
|
2986
|
+
&self,
|
|
2987
|
+
promise_ref: &crate::promise::PromiseRef,
|
|
2988
|
+
method: &str,
|
|
2989
|
+
args: &[Value],
|
|
2990
|
+
) -> Result<Value, EvalError> {
|
|
2991
|
+
match method {
|
|
2992
|
+
"then" => {
|
|
2993
|
+
self.run_promise_then_core(promise_ref, args.first().cloned(), args.get(1).cloned())
|
|
2994
|
+
}
|
|
2995
|
+
"catch" => self.run_promise_then_core(promise_ref, None, args.first().cloned()),
|
|
2996
|
+
"finally" => self.run_promise_finally(promise_ref, args.first().cloned()),
|
|
2997
|
+
_ => Err(EvalError::Error(format!(
|
|
2998
|
+
"Unknown promise method: {}",
|
|
2999
|
+
method
|
|
3000
|
+
))),
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
|
|
3004
|
+
#[cfg(feature = "http")]
|
|
3005
|
+
fn run_promise_finally(
|
|
3006
|
+
&self,
|
|
3007
|
+
promise_ref: &crate::promise::PromiseRef,
|
|
3008
|
+
on_finally: Option<Value>,
|
|
3009
|
+
) -> Result<Value, EvalError> {
|
|
3010
|
+
let (promise, resolve_val, reject_val) = crate::promise::create_promise();
|
|
3011
|
+
let (resolve, reject) = crate::promise::extract_resolvers(&resolve_val, &reject_val);
|
|
3012
|
+
let state = &promise_ref.state;
|
|
3013
|
+
{
|
|
3014
|
+
let s = state.borrow();
|
|
3015
|
+
match &*s {
|
|
3016
|
+
crate::promise::PromiseState::Fulfilled(v) => {
|
|
3017
|
+
let v = v.clone();
|
|
3018
|
+
drop(s);
|
|
3019
|
+
if let Some(ref f) = on_finally {
|
|
3020
|
+
let _ = self.call_func(f, &[]);
|
|
3021
|
+
}
|
|
3022
|
+
crate::promise::settle_promise(&resolve, v, true).map_err(EvalError::Error)?;
|
|
3023
|
+
}
|
|
3024
|
+
crate::promise::PromiseState::Rejected(v) => {
|
|
3025
|
+
let v = v.clone();
|
|
3026
|
+
drop(s);
|
|
3027
|
+
if let Some(ref f) = on_finally {
|
|
3028
|
+
let _ = self.call_func(f, &[]);
|
|
3029
|
+
}
|
|
3030
|
+
crate::promise::settle_promise(&reject, v, false).map_err(EvalError::Error)?;
|
|
3031
|
+
}
|
|
3032
|
+
crate::promise::PromiseState::Pending { .. } => {
|
|
3033
|
+
let reaction = if let Some(ref f) = on_finally {
|
|
3034
|
+
crate::promise::Reaction::Finally(f.clone(), resolve, reject)
|
|
3035
|
+
} else {
|
|
3036
|
+
crate::promise::Reaction::Then(None, None, resolve, reject)
|
|
3037
|
+
};
|
|
3038
|
+
crate::promise::add_reaction(state, reaction);
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
Ok(promise)
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
#[cfg(feature = "http")]
|
|
3046
|
+
fn run_promise_then_core(
|
|
3047
|
+
&self,
|
|
3048
|
+
promise_ref: &crate::promise::PromiseRef,
|
|
3049
|
+
on_fulfilled: Option<Value>,
|
|
3050
|
+
on_rejected: Option<Value>,
|
|
3051
|
+
) -> Result<Value, EvalError> {
|
|
3052
|
+
let (promise, resolve_val, reject_val) = crate::promise::create_promise();
|
|
3053
|
+
let (resolve, reject) = crate::promise::extract_resolvers(&resolve_val, &reject_val);
|
|
3054
|
+
let state = &promise_ref.state;
|
|
3055
|
+
{
|
|
3056
|
+
let s = state.borrow();
|
|
3057
|
+
match &*s {
|
|
3058
|
+
crate::promise::PromiseState::Fulfilled(v) => {
|
|
3059
|
+
let v = v.clone();
|
|
3060
|
+
drop(s);
|
|
3061
|
+
let result = if let Some(ref h) = on_fulfilled {
|
|
3062
|
+
self.call_func(h, &[v])
|
|
3063
|
+
} else {
|
|
3064
|
+
Ok(v)
|
|
3065
|
+
};
|
|
3066
|
+
match result {
|
|
3067
|
+
Ok(val) => {
|
|
3068
|
+
crate::promise::settle_promise(&resolve, val, true)
|
|
3069
|
+
.map_err(EvalError::Error)?;
|
|
3070
|
+
}
|
|
3071
|
+
Err(EvalError::Throw(val)) => {
|
|
3072
|
+
crate::promise::settle_promise(&reject, val, false)
|
|
3073
|
+
.map_err(EvalError::Error)?;
|
|
3074
|
+
}
|
|
3075
|
+
Err(e) => return Err(e),
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
crate::promise::PromiseState::Rejected(v) => {
|
|
3079
|
+
let v = v.clone();
|
|
3080
|
+
drop(s);
|
|
3081
|
+
let result = if let Some(ref h) = on_rejected {
|
|
3082
|
+
self.call_func(h, &[v.clone()])
|
|
3083
|
+
} else {
|
|
3084
|
+
Err(EvalError::Throw(v))
|
|
3085
|
+
};
|
|
3086
|
+
match result {
|
|
3087
|
+
Ok(val) => {
|
|
3088
|
+
crate::promise::settle_promise(&resolve, val, true)
|
|
3089
|
+
.map_err(EvalError::Error)?;
|
|
3090
|
+
}
|
|
3091
|
+
Err(EvalError::Throw(val)) => {
|
|
3092
|
+
crate::promise::settle_promise(&reject, val, false)
|
|
3093
|
+
.map_err(EvalError::Error)?;
|
|
3094
|
+
}
|
|
3095
|
+
Err(e) => return Err(e),
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
crate::promise::PromiseState::Pending { .. } => {
|
|
3099
|
+
crate::promise::add_reaction(
|
|
3100
|
+
state,
|
|
3101
|
+
crate::promise::Reaction::Then(
|
|
3102
|
+
on_fulfilled,
|
|
3103
|
+
on_rejected,
|
|
3104
|
+
resolve.clone(),
|
|
3105
|
+
reject.clone(),
|
|
3106
|
+
),
|
|
3107
|
+
);
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
Ok(promise)
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
#[cfg(feature = "timers")]
|
|
3115
|
+
fn run_timer_builtin(&self, name: &str, args: &[Value]) -> Result<Value, EvalError> {
|
|
3116
|
+
let callback = args
|
|
3117
|
+
.first()
|
|
3118
|
+
.ok_or_else(|| EvalError::Error(format!("{} requires a callback", name)))?
|
|
3119
|
+
.clone();
|
|
3120
|
+
let delay_ms = args
|
|
3121
|
+
.get(1)
|
|
3122
|
+
.and_then(|v| v.as_number())
|
|
3123
|
+
.unwrap_or(0.0)
|
|
3124
|
+
.max(0.0) as u64;
|
|
3125
|
+
let extra_args: Vec<Value> = args.iter().skip(2).cloned().collect();
|
|
3126
|
+
|
|
3127
|
+
let id = match name {
|
|
3128
|
+
"setTimeout" => crate::timers::setTimeout(callback, extra_args, delay_ms),
|
|
3129
|
+
"setInterval" => crate::timers::setInterval(callback, extra_args, delay_ms),
|
|
3130
|
+
_ => return Err(EvalError::Error(format!("Unknown timer: {}", name))),
|
|
3131
|
+
};
|
|
3132
|
+
Ok(Value::Number(id as f64))
|
|
3133
|
+
}
|
|
3134
|
+
|
|
3135
|
+
#[cfg(feature = "timers")]
|
|
3136
|
+
fn clear_timeout_native(args: &[Value]) -> Result<Value, String> {
|
|
3137
|
+
if let Some(Value::Number(n)) = args.first() {
|
|
3138
|
+
crate::timers::clearTimer(*n as u64);
|
|
3139
|
+
}
|
|
3140
|
+
Ok(Value::Null)
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
#[cfg(feature = "timers")]
|
|
3144
|
+
fn clear_interval_native(args: &[Value]) -> Result<Value, String> {
|
|
3145
|
+
if let Some(Value::Number(n)) = args.first() {
|
|
3146
|
+
crate::timers::clearTimer(*n as u64);
|
|
3147
|
+
}
|
|
3148
|
+
Ok(Value::Null)
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
/// Run all due timer callbacks. Called after the script completes so setTimeout/setInterval
|
|
3152
|
+
/// callbacks run without blocking the main script. Loops until no timers are due.
|
|
3153
|
+
#[cfg(feature = "timers")]
|
|
3154
|
+
pub fn run_timer_phase(&mut self) -> Result<(), String> {
|
|
3155
|
+
const MAX_ITERATIONS: u32 = 1_000_000; // avoid infinite loop if setInterval never cleared
|
|
3156
|
+
let mut iterations = 0;
|
|
3157
|
+
while crate::timers::has_pending_timers() && iterations < MAX_ITERATIONS {
|
|
3158
|
+
iterations += 1;
|
|
3159
|
+
let due = crate::timers::take_due_timers();
|
|
3160
|
+
if due.is_empty() {
|
|
3161
|
+
// None due yet; sleep until next timer
|
|
3162
|
+
let next = crate::timers::next_due_instant();
|
|
3163
|
+
if let Some(instant) = next {
|
|
3164
|
+
let now = std::time::Instant::now();
|
|
3165
|
+
if instant > now {
|
|
3166
|
+
std::thread::sleep(instant.duration_since(now));
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
continue;
|
|
3170
|
+
}
|
|
3171
|
+
for (id, callback, args, interval_ms) in due {
|
|
3172
|
+
self.call_func(&callback, &args).map_err(|e| match e {
|
|
3173
|
+
EvalError::Error(s) => s,
|
|
3174
|
+
EvalError::Throw(v) => v.to_string(),
|
|
3175
|
+
_ => "timer callback error".to_string(),
|
|
3176
|
+
})?;
|
|
3177
|
+
if interval_ms > 0 {
|
|
3178
|
+
crate::timers::re_register_interval(id, callback, args, interval_ms);
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
Ok(())
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
#[cfg(feature = "http")]
|
|
3186
|
+
fn run_http_server(&self, args: &[Value]) -> Result<Value, EvalError> {
|
|
3187
|
+
use std::io::Write;
|
|
3188
|
+
|
|
3189
|
+
let port = match args.first() {
|
|
3190
|
+
Some(Value::Number(n)) => *n as u16,
|
|
3191
|
+
_ => return Err(EvalError::Error("serve requires a port number".to_string())),
|
|
3192
|
+
};
|
|
3193
|
+
|
|
3194
|
+
let max_requests: Option<usize> = args.get(2).and_then(|v| match v {
|
|
3195
|
+
Value::Number(n) if *n >= 1.0 => Some(*n as usize),
|
|
3196
|
+
_ => None,
|
|
3197
|
+
});
|
|
3198
|
+
|
|
3199
|
+
let handler = match args.get(1) {
|
|
3200
|
+
Some(f @ Value::Function { .. }) | Some(f @ Value::Native(_)) => f.clone(),
|
|
3201
|
+
_ => {
|
|
3202
|
+
return Err(EvalError::Error(
|
|
3203
|
+
"serve requires a handler function".to_string(),
|
|
3204
|
+
))
|
|
3205
|
+
}
|
|
3206
|
+
};
|
|
3207
|
+
|
|
3208
|
+
let server = crate::http::create_server(port).map_err(EvalError::Error)?;
|
|
3209
|
+
println!("Server listening on http://0.0.0.0:{}", port);
|
|
3210
|
+
|
|
3211
|
+
if max_requests == Some(1) {
|
|
3212
|
+
std::thread::spawn(move || {
|
|
3213
|
+
std::thread::sleep(std::time::Duration::from_millis(50));
|
|
3214
|
+
if let Ok(mut stream) = std::net::TcpStream::connect(format!("127.0.0.1:{}", port))
|
|
3215
|
+
{
|
|
3216
|
+
let _ = stream.write_all(
|
|
3217
|
+
b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n",
|
|
3218
|
+
);
|
|
3219
|
+
let _ = stream.shutdown(std::net::Shutdown::Write);
|
|
3220
|
+
}
|
|
3221
|
+
});
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
for (count, mut request) in server.incoming_requests().enumerate() {
|
|
3225
|
+
let req_value = crate::http::request_to_value(&mut request);
|
|
3226
|
+
|
|
3227
|
+
let response_value = match self.call_func(&handler, &[req_value]) {
|
|
3228
|
+
Ok(v) => v,
|
|
3229
|
+
Err(EvalError::Throw(v)) => {
|
|
3230
|
+
let mut err_obj: PropMap = PropMap::with_capacity(2);
|
|
3231
|
+
err_obj.insert(Arc::from("status"), Value::Number(500.0));
|
|
3232
|
+
err_obj.insert(Arc::from("body"), Value::String(v.to_string().into()));
|
|
3233
|
+
Value::object(err_obj)
|
|
3234
|
+
}
|
|
3235
|
+
Err(e) => {
|
|
3236
|
+
let mut err_obj: PropMap = PropMap::with_capacity(2);
|
|
3237
|
+
err_obj.insert(Arc::from("status"), Value::Number(500.0));
|
|
3238
|
+
err_obj.insert(Arc::from("body"), Value::String(e.to_string().into()));
|
|
3239
|
+
Value::object(err_obj)
|
|
3240
|
+
}
|
|
3241
|
+
};
|
|
3242
|
+
|
|
3243
|
+
if let Some((status, headers, file_path)) =
|
|
3244
|
+
crate::http::extract_file_from_response(&response_value)
|
|
3245
|
+
{
|
|
3246
|
+
crate::http::send_file_response(request, status, headers, file_path);
|
|
3247
|
+
} else {
|
|
3248
|
+
let (status, headers, body) = crate::http::value_to_response(&response_value);
|
|
3249
|
+
crate::http::send_response(request, status, headers, body);
|
|
3250
|
+
}
|
|
3251
|
+
if max_requests.map(|m| count + 1 >= m).unwrap_or(false) {
|
|
3252
|
+
break;
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
Ok(Value::Null)
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
fn eval_call_args(&self, args: &[tishlang_ast::CallArg]) -> Result<Vec<Value>, EvalError> {
|
|
3260
|
+
let mut result = Vec::with_capacity(args.len());
|
|
3261
|
+
for arg in args {
|
|
3262
|
+
match arg {
|
|
3263
|
+
tishlang_ast::CallArg::Expr(e) => {
|
|
3264
|
+
result.push(self.eval_expr(e)?);
|
|
3265
|
+
}
|
|
3266
|
+
tishlang_ast::CallArg::Spread(e) => {
|
|
3267
|
+
let spread_val = self.eval_expr(e)?;
|
|
3268
|
+
if let Value::Array(arr) = &spread_val {
|
|
3269
|
+
result.extend(arr.borrow().iter().cloned());
|
|
3270
|
+
} else if let Some(items) = self.drain_eval_iterator(&spread_val) {
|
|
3271
|
+
// Spread a Map/Set iterator into call args (`f(...m.values())`).
|
|
3272
|
+
result.extend(items);
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
Ok(result)
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
fn bind_destruct_pattern_scoped(
|
|
3281
|
+
scope: &Rc<RefCell<Scope>>,
|
|
3282
|
+
pattern: &tishlang_ast::DestructPattern,
|
|
3283
|
+
value: &Value,
|
|
3284
|
+
mutable: bool,
|
|
3285
|
+
) -> Result<(), EvalError> {
|
|
3286
|
+
match pattern {
|
|
3287
|
+
tishlang_ast::DestructPattern::Array(elements) => {
|
|
3288
|
+
let arr = match value {
|
|
3289
|
+
Value::Array(a) => a.borrow().clone(),
|
|
3290
|
+
_ => {
|
|
3291
|
+
return Err(EvalError::Error(
|
|
3292
|
+
"Cannot destructure non-array value".to_string(),
|
|
3293
|
+
))
|
|
3294
|
+
}
|
|
3295
|
+
};
|
|
3296
|
+
|
|
3297
|
+
for (i, elem) in elements.iter().enumerate() {
|
|
3298
|
+
if let Some(el) = elem {
|
|
3299
|
+
match el {
|
|
3300
|
+
tishlang_ast::DestructElement::Ident(name, _) => {
|
|
3301
|
+
let val = arr.get(i).cloned().unwrap_or(Value::Null);
|
|
3302
|
+
scope.borrow_mut().set(Arc::clone(name), val, mutable);
|
|
3303
|
+
}
|
|
3304
|
+
tishlang_ast::DestructElement::Pattern(nested) => {
|
|
3305
|
+
let val = arr.get(i).cloned().unwrap_or(Value::Null);
|
|
3306
|
+
Self::bind_destruct_pattern_scoped(scope, nested, &val, mutable)?;
|
|
3307
|
+
}
|
|
3308
|
+
tishlang_ast::DestructElement::Rest(name, _) => {
|
|
3309
|
+
let rest: Vec<Value> = arr.iter().skip(i).cloned().collect();
|
|
3310
|
+
scope.borrow_mut().set(
|
|
3311
|
+
Arc::clone(name),
|
|
3312
|
+
Value::Array(Rc::new(RefCell::new(rest))),
|
|
3313
|
+
mutable,
|
|
3314
|
+
);
|
|
3315
|
+
break;
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
tishlang_ast::DestructPattern::Object(props) => {
|
|
3322
|
+
let obj = match value {
|
|
3323
|
+
Value::Object(o) => o.borrow().clone(),
|
|
3324
|
+
_ => {
|
|
3325
|
+
return Err(EvalError::Error(
|
|
3326
|
+
"Cannot destructure non-object value".to_string(),
|
|
3327
|
+
))
|
|
3328
|
+
}
|
|
3329
|
+
};
|
|
3330
|
+
|
|
3331
|
+
for prop in props {
|
|
3332
|
+
let val = obj
|
|
3333
|
+
.strings
|
|
3334
|
+
.get(prop.key.as_ref())
|
|
3335
|
+
.cloned()
|
|
3336
|
+
.unwrap_or(Value::Null);
|
|
3337
|
+
match &prop.value {
|
|
3338
|
+
tishlang_ast::DestructElement::Ident(name, _) => {
|
|
3339
|
+
scope.borrow_mut().set(Arc::clone(name), val, mutable);
|
|
3340
|
+
}
|
|
3341
|
+
tishlang_ast::DestructElement::Pattern(nested) => {
|
|
3342
|
+
Self::bind_destruct_pattern_scoped(scope, nested, &val, mutable)?;
|
|
3343
|
+
}
|
|
3344
|
+
tishlang_ast::DestructElement::Rest(_, _) => {
|
|
3345
|
+
return Err(EvalError::Error(
|
|
3346
|
+
"Rest not supported in object destructuring".to_string(),
|
|
3347
|
+
));
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
Ok(())
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
fn bind_destruct_pattern(
|
|
3357
|
+
&mut self,
|
|
3358
|
+
pattern: &tishlang_ast::DestructPattern,
|
|
3359
|
+
value: &Value,
|
|
3360
|
+
mutable: bool,
|
|
3361
|
+
) -> Result<(), EvalError> {
|
|
3362
|
+
Self::bind_destruct_pattern_scoped(&self.scope, pattern, value, mutable)
|
|
3363
|
+
}
|
|
3364
|
+
|
|
3365
|
+
/// `String.prototype.lastIndexOf` (interpreter). Kept as a helper so dispatch cannot fall
|
|
3366
|
+
/// through to [`Self::get_prop`] + [`Self::call_func`] for string receivers.
|
|
3367
|
+
fn string_last_index_of_eval(arg_vals: &[Value], receiver: &Arc<str>) -> Value {
|
|
3368
|
+
let search = match arg_vals.first() {
|
|
3369
|
+
Some(Value::String(ss)) => ss.as_ref(),
|
|
3370
|
+
_ => return Value::Number(-1.0),
|
|
3371
|
+
};
|
|
3372
|
+
let position_core: tishlang_core::Value = match arg_vals.get(1) {
|
|
3373
|
+
None => tishlang_core::Value::Number(f64::INFINITY),
|
|
3374
|
+
Some(Value::Null) => tishlang_core::Value::Null,
|
|
3375
|
+
Some(Value::Number(n)) => tishlang_core::Value::Number(*n),
|
|
3376
|
+
Some(Value::Bool(b)) => tishlang_core::Value::Bool(*b),
|
|
3377
|
+
Some(_) => tishlang_core::Value::Number(0.0),
|
|
3378
|
+
};
|
|
3379
|
+
let out =
|
|
3380
|
+
tishlang_builtins::string::last_index_of_str(receiver.as_ref(), search, &position_core);
|
|
3381
|
+
match out {
|
|
3382
|
+
tishlang_core::Value::Number(n) => Value::Number(n),
|
|
3383
|
+
_ => Value::Number(-1.0),
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
/// Drain a JS iterator object — one whose `next()` is a bridged core fn (`CoreFn`)
|
|
3388
|
+
/// returning `{ value, done }`, e.g. a Map/Set iterator from `.values()` / `.keys()` /
|
|
3389
|
+
/// `.entries()` — into a `Vec` by calling `next()` until `done`. Returns `None` when `v`
|
|
3390
|
+
/// is not such an object. Shared by `for…of` and spread so both treat iterators like JS.
|
|
3391
|
+
fn drain_eval_iterator(&self, v: &Value) -> Option<Vec<Value>> {
|
|
3392
|
+
if !matches!(v, Value::Object(_)) {
|
|
3393
|
+
return None;
|
|
3394
|
+
}
|
|
3395
|
+
// Fast path: tish's Map/Set iterators expose `__drain__`, returning all remaining items as
|
|
3396
|
+
// one array — skips the per-element bridge + `{ value, done }` alloc of the generic loop.
|
|
3397
|
+
if let Ok(Value::CoreFn(drain)) = self.get_prop(v, "__drain__") {
|
|
3398
|
+
if let Value::Array(arr) = crate::value_convert::core_to_eval(drain.call(&[])) {
|
|
3399
|
+
return Some(arr.borrow().clone());
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
let Ok(Value::CoreFn(next)) = self.get_prop(v, "next") else {
|
|
3403
|
+
return None;
|
|
3404
|
+
};
|
|
3405
|
+
let mut out = Vec::new();
|
|
3406
|
+
loop {
|
|
3407
|
+
let res = crate::value_convert::core_to_eval(next.call(&[]));
|
|
3408
|
+
let done = self
|
|
3409
|
+
.get_prop(&res, "done")
|
|
3410
|
+
.map(|x| x.is_truthy())
|
|
3411
|
+
.unwrap_or(true);
|
|
3412
|
+
if done {
|
|
3413
|
+
break;
|
|
3414
|
+
}
|
|
3415
|
+
out.push(self.get_prop(&res, "value").unwrap_or(Value::Null));
|
|
3416
|
+
}
|
|
3417
|
+
Some(out)
|
|
3418
|
+
}
|
|
3419
|
+
|
|
3420
|
+
fn get_prop(&self, obj: &Value, key: &str) -> Result<Value, String> {
|
|
3421
|
+
match obj {
|
|
3422
|
+
Value::Object(map) => {
|
|
3423
|
+
// `Set`/`Map` instances expose a computed `.size` via a hidden `SizeProbe` opaque
|
|
3424
|
+
// (shared, not copied, across the value bridge — so it reflects the live store).
|
|
3425
|
+
if key == "size" {
|
|
3426
|
+
if let Some(Value::Opaque(op)) =
|
|
3427
|
+
map.borrow().strings.get(tishlang_builtins::collections::SIZE_SLOT)
|
|
3428
|
+
{
|
|
3429
|
+
if let Some(n) = tishlang_builtins::collections::size_probe_len(op.as_ref()) {
|
|
3430
|
+
return Ok(Value::Number(n));
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
Ok(map.borrow().strings.get(key).cloned().unwrap_or(Value::Null))
|
|
3435
|
+
}
|
|
3436
|
+
Value::Array(arr) => {
|
|
3437
|
+
if key == "length" {
|
|
3438
|
+
Ok(Value::Number(arr.borrow().len() as f64))
|
|
3439
|
+
} else if let Ok(idx) = key.parse::<usize>() {
|
|
3440
|
+
Ok(arr.borrow().get(idx).cloned().unwrap_or(Value::Null))
|
|
3441
|
+
} else {
|
|
3442
|
+
Ok(Value::Null)
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
Value::String(s) => {
|
|
3446
|
+
if key == "length" {
|
|
3447
|
+
Ok(Value::Number(s.chars().count() as f64))
|
|
3448
|
+
} else {
|
|
3449
|
+
Ok(Value::Null)
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
#[cfg(feature = "http")]
|
|
3453
|
+
Value::Promise(promise_ref) => match key {
|
|
3454
|
+
"then" => Ok(Value::BoundPromiseMethod(
|
|
3455
|
+
promise_ref.clone(),
|
|
3456
|
+
Arc::from("then"),
|
|
3457
|
+
)),
|
|
3458
|
+
"catch" => Ok(Value::BoundPromiseMethod(
|
|
3459
|
+
promise_ref.clone(),
|
|
3460
|
+
Arc::from("catch"),
|
|
3461
|
+
)),
|
|
3462
|
+
"finally" => Ok(Value::BoundPromiseMethod(
|
|
3463
|
+
promise_ref.clone(),
|
|
3464
|
+
Arc::from("finally"),
|
|
3465
|
+
)),
|
|
3466
|
+
_ => Ok(Value::Null),
|
|
3467
|
+
},
|
|
3468
|
+
#[cfg(feature = "http")]
|
|
3469
|
+
Value::CorePromise(_) => Ok(Value::Null),
|
|
3470
|
+
#[cfg(feature = "http")]
|
|
3471
|
+
Value::PromiseConstructor => match key {
|
|
3472
|
+
"resolve" => Ok(Value::Native(Self::promise_resolve)),
|
|
3473
|
+
"reject" => Ok(Value::Native(Self::promise_reject)),
|
|
3474
|
+
"all" => Ok(Value::Native(Self::promise_all)),
|
|
3475
|
+
"race" => Ok(Value::Native(Self::promise_race)),
|
|
3476
|
+
"any" => Ok(Value::Native(Self::promise_any)),
|
|
3477
|
+
"allSettled" => Ok(Value::Native(Self::promise_all_settled)),
|
|
3478
|
+
"spawn" => Ok(Value::Native(Self::promise_spawn_interp)),
|
|
3479
|
+
_ => Ok(Value::Null),
|
|
3480
|
+
},
|
|
3481
|
+
Value::Opaque(o) => {
|
|
3482
|
+
if o.get_method(key).is_some() {
|
|
3483
|
+
Ok(Value::OpaqueMethod(Arc::clone(o), Arc::from(key)))
|
|
3484
|
+
} else {
|
|
3485
|
+
Ok(Value::Null)
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
#[cfg(feature = "regex")]
|
|
3489
|
+
Value::RegExp(re) => {
|
|
3490
|
+
let re = re.borrow();
|
|
3491
|
+
match key {
|
|
3492
|
+
"source" => Ok(Value::String(re.source.clone().into())),
|
|
3493
|
+
"flags" => Ok(Value::String(re.flags_string().into())),
|
|
3494
|
+
"lastIndex" => Ok(Value::Number(re.last_index as f64)),
|
|
3495
|
+
"global" => Ok(Value::Bool(re.flags.global)),
|
|
3496
|
+
"ignoreCase" => Ok(Value::Bool(re.flags.ignore_case)),
|
|
3497
|
+
"multiline" => Ok(Value::Bool(re.flags.multiline)),
|
|
3498
|
+
"dotAll" => Ok(Value::Bool(re.flags.dot_all)),
|
|
3499
|
+
"unicode" => Ok(Value::Bool(re.flags.unicode)),
|
|
3500
|
+
"sticky" => Ok(Value::Bool(re.flags.sticky)),
|
|
3501
|
+
_ => Ok(Value::Null),
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
_ => Ok(Value::Null),
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
fn get_index(&self, obj: &Value, index: &Value) -> Result<Value, String> {
|
|
3509
|
+
match obj {
|
|
3510
|
+
Value::Array(arr) => {
|
|
3511
|
+
let idx = match index {
|
|
3512
|
+
Value::Number(n) => *n as usize,
|
|
3513
|
+
_ => return Ok(Value::Null),
|
|
3514
|
+
};
|
|
3515
|
+
Ok(arr.borrow().get(idx).cloned().unwrap_or(Value::Null))
|
|
3516
|
+
}
|
|
3517
|
+
// `str[i]` returns the character at index `i` (issue #17). The VM already does
|
|
3518
|
+
// this; the interpreter previously fell through to `null`, a silent divergence.
|
|
3519
|
+
// Out-of-bounds / negative / non-integer indices yield tish's nullish value.
|
|
3520
|
+
Value::String(s) => {
|
|
3521
|
+
let idx = match index {
|
|
3522
|
+
Value::Number(n) if *n >= 0.0 && n.fract() == 0.0 => *n as usize,
|
|
3523
|
+
_ => return Ok(Value::Null),
|
|
3524
|
+
};
|
|
3525
|
+
Ok(s
|
|
3526
|
+
.chars()
|
|
3527
|
+
.nth(idx)
|
|
3528
|
+
.map(|c| Value::String(c.to_string().into()))
|
|
3529
|
+
.unwrap_or(Value::Null))
|
|
3530
|
+
}
|
|
3531
|
+
Value::Object(_) => Ok(eval_object_get(obj, index).unwrap_or(Value::Null)),
|
|
3532
|
+
#[cfg(feature = "http")]
|
|
3533
|
+
Value::Promise(_) | Value::CorePromise(_) => {
|
|
3534
|
+
let key = match index {
|
|
3535
|
+
Value::String(s) => s.as_ref(),
|
|
3536
|
+
_ => return Ok(Value::Null),
|
|
3537
|
+
};
|
|
3538
|
+
self.get_prop(obj, key)
|
|
3539
|
+
}
|
|
3540
|
+
_ => Ok(Value::Null),
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3544
|
+
fn json_parse(s: &str) -> Value {
|
|
3545
|
+
let s = s.trim();
|
|
3546
|
+
if s.is_empty() {
|
|
3547
|
+
return Value::Null;
|
|
3548
|
+
}
|
|
3549
|
+
match Self::json_parse_str(s) {
|
|
3550
|
+
Ok(v) => v,
|
|
3551
|
+
Err(()) => Value::Null,
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3555
|
+
fn json_parse_str(s: &str) -> Result<Value, ()> {
|
|
3556
|
+
let s = s.trim();
|
|
3557
|
+
if s.is_empty() {
|
|
3558
|
+
return Err(());
|
|
3559
|
+
}
|
|
3560
|
+
if s == "null" {
|
|
3561
|
+
return Ok(Value::Null);
|
|
3562
|
+
}
|
|
3563
|
+
if s == "true" {
|
|
3564
|
+
return Ok(Value::Bool(true));
|
|
3565
|
+
}
|
|
3566
|
+
if s == "false" {
|
|
3567
|
+
return Ok(Value::Bool(false));
|
|
3568
|
+
}
|
|
3569
|
+
if s.starts_with('"') {
|
|
3570
|
+
return Self::json_parse_string_full(s);
|
|
3571
|
+
}
|
|
3572
|
+
if s.starts_with('[') {
|
|
3573
|
+
return Self::json_parse_array(s);
|
|
3574
|
+
}
|
|
3575
|
+
if s.starts_with('{') {
|
|
3576
|
+
return Self::json_parse_object(s);
|
|
3577
|
+
}
|
|
3578
|
+
if let Ok(n) = s.parse::<f64>() {
|
|
3579
|
+
return Ok(Value::Number(n));
|
|
3580
|
+
}
|
|
3581
|
+
Err(())
|
|
3582
|
+
}
|
|
3583
|
+
|
|
3584
|
+
fn json_parse_string(s: &str) -> Result<(Value, &str), ()> {
|
|
3585
|
+
let s = &s[1..];
|
|
3586
|
+
let mut out = String::new();
|
|
3587
|
+
let mut i = 0;
|
|
3588
|
+
let chars: Vec<char> = s.chars().collect();
|
|
3589
|
+
while i < chars.len() {
|
|
3590
|
+
if chars[i] == '"' {
|
|
3591
|
+
let rest_start = s.chars().take(i + 1).map(|c| c.len_utf8()).sum::<usize>();
|
|
3592
|
+
return Ok((Value::String(out.into()), &s[rest_start..]));
|
|
3593
|
+
}
|
|
3594
|
+
if chars[i] == '\\' {
|
|
3595
|
+
i += 1;
|
|
3596
|
+
if i >= chars.len() {
|
|
3597
|
+
return Err(());
|
|
3598
|
+
}
|
|
3599
|
+
match chars[i] {
|
|
3600
|
+
'"' => out.push('"'),
|
|
3601
|
+
'\\' => out.push('\\'),
|
|
3602
|
+
'n' => out.push('\n'),
|
|
3603
|
+
'r' => out.push('\r'),
|
|
3604
|
+
't' => out.push('\t'),
|
|
3605
|
+
_ => return Err(()),
|
|
3606
|
+
}
|
|
3607
|
+
} else {
|
|
3608
|
+
out.push(chars[i]);
|
|
3609
|
+
}
|
|
3610
|
+
i += 1;
|
|
3611
|
+
}
|
|
3612
|
+
Err(())
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
fn json_parse_string_full(s: &str) -> Result<Value, ()> {
|
|
3616
|
+
Self::json_parse_string(s).map(|(v, _)| v)
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
fn json_parse_array(s: &str) -> Result<Value, ()> {
|
|
3620
|
+
let s = s[1..].trim_start();
|
|
3621
|
+
if s.starts_with(']') {
|
|
3622
|
+
return Ok(Value::Array(Rc::new(RefCell::new(vec![]))));
|
|
3623
|
+
}
|
|
3624
|
+
let mut vals = Vec::new();
|
|
3625
|
+
let mut rest = s;
|
|
3626
|
+
loop {
|
|
3627
|
+
let (v, next) = Self::json_parse_one(rest)?;
|
|
3628
|
+
vals.push(v);
|
|
3629
|
+
rest = next.trim_start();
|
|
3630
|
+
if rest.starts_with(']') {
|
|
3631
|
+
break;
|
|
3632
|
+
}
|
|
3633
|
+
if !rest.starts_with(',') {
|
|
3634
|
+
return Err(());
|
|
3635
|
+
}
|
|
3636
|
+
rest = rest[1..].trim_start();
|
|
3637
|
+
}
|
|
3638
|
+
Ok(Value::Array(Rc::new(RefCell::new(vals))))
|
|
3639
|
+
}
|
|
3640
|
+
|
|
3641
|
+
fn json_parse_object(s: &str) -> Result<Value, ()> {
|
|
3642
|
+
let s = s[1..].trim_start();
|
|
3643
|
+
if s.starts_with('}') {
|
|
3644
|
+
return Ok(Value::object(PropMap::default()));
|
|
3645
|
+
}
|
|
3646
|
+
let mut map = PropMap::default();
|
|
3647
|
+
let mut rest = s;
|
|
3648
|
+
loop {
|
|
3649
|
+
if !rest.starts_with('"') {
|
|
3650
|
+
return Err(());
|
|
3651
|
+
}
|
|
3652
|
+
let (key_val, next) = Self::json_parse_string(rest)?;
|
|
3653
|
+
let key = match &key_val {
|
|
3654
|
+
Value::String(k) => Arc::clone(k),
|
|
3655
|
+
_ => return Err(()),
|
|
3656
|
+
};
|
|
3657
|
+
rest = next.trim_start();
|
|
3658
|
+
if !rest.starts_with(':') {
|
|
3659
|
+
return Err(());
|
|
3660
|
+
}
|
|
3661
|
+
rest = rest[1..].trim_start();
|
|
3662
|
+
let (val, next) = Self::json_parse_one(rest)?;
|
|
3663
|
+
map.insert(key, val);
|
|
3664
|
+
rest = next.trim_start();
|
|
3665
|
+
if rest.starts_with('}') {
|
|
3666
|
+
break;
|
|
3667
|
+
}
|
|
3668
|
+
if !rest.starts_with(',') {
|
|
3669
|
+
return Err(());
|
|
3670
|
+
}
|
|
3671
|
+
rest = rest[1..].trim_start();
|
|
3672
|
+
}
|
|
3673
|
+
Ok(Value::object(map))
|
|
3674
|
+
}
|
|
3675
|
+
|
|
3676
|
+
fn json_parse_one(s: &str) -> Result<(Value, &str), ()> {
|
|
3677
|
+
let s = s.trim();
|
|
3678
|
+
if s.is_empty() {
|
|
3679
|
+
return Err(());
|
|
3680
|
+
}
|
|
3681
|
+
if s.starts_with('"') {
|
|
3682
|
+
let (v, rest) = Self::json_parse_string(s)?;
|
|
3683
|
+
Ok((v, rest))
|
|
3684
|
+
} else if s.starts_with('[') {
|
|
3685
|
+
let mut depth = 0;
|
|
3686
|
+
for (i, c) in s.char_indices() {
|
|
3687
|
+
if c == '[' {
|
|
3688
|
+
depth += 1;
|
|
3689
|
+
} else if c == ']' {
|
|
3690
|
+
depth -= 1;
|
|
3691
|
+
if depth == 0 {
|
|
3692
|
+
let v = Self::json_parse_array(&s[..=i])?;
|
|
3693
|
+
return Ok((v, &s[i + c.len_utf8()..]));
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
Err(())
|
|
3698
|
+
} else if s.starts_with('{') {
|
|
3699
|
+
let mut depth = 0;
|
|
3700
|
+
for (i, c) in s.char_indices() {
|
|
3701
|
+
if c == '{' {
|
|
3702
|
+
depth += 1;
|
|
3703
|
+
} else if c == '}' {
|
|
3704
|
+
depth -= 1;
|
|
3705
|
+
if depth == 0 {
|
|
3706
|
+
let v = Self::json_parse_object(&s[..=i])?;
|
|
3707
|
+
return Ok((v, &s[i + c.len_utf8()..]));
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
Err(())
|
|
3712
|
+
} else if let Some(rest) = s.strip_prefix("null") {
|
|
3713
|
+
Ok((Value::Null, rest))
|
|
3714
|
+
} else if let Some(rest) = s.strip_prefix("true") {
|
|
3715
|
+
Ok((Value::Bool(true), rest))
|
|
3716
|
+
} else if let Some(rest) = s.strip_prefix("false") {
|
|
3717
|
+
Ok((Value::Bool(false), rest))
|
|
3718
|
+
} else {
|
|
3719
|
+
let end = s
|
|
3720
|
+
.find(|c: char| {
|
|
3721
|
+
!c.is_ascii_digit() && c != '-' && c != '+' && c != '.' && c != 'e' && c != 'E'
|
|
3722
|
+
})
|
|
3723
|
+
.unwrap_or(s.len());
|
|
3724
|
+
let num_str = &s[..end];
|
|
3725
|
+
let n: f64 = num_str.parse().map_err(|_| ())?;
|
|
3726
|
+
Ok((Value::Number(n), &s[end..]))
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3730
|
+
fn json_stringify_value(v: &Value) -> String {
|
|
3731
|
+
match v {
|
|
3732
|
+
Value::Null => "null".to_string(),
|
|
3733
|
+
Value::Bool(b) => b.to_string(),
|
|
3734
|
+
Value::Number(n) => {
|
|
3735
|
+
if n.is_finite() {
|
|
3736
|
+
n.to_string()
|
|
3737
|
+
} else {
|
|
3738
|
+
"null".to_string()
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
Value::String(s) => format!(
|
|
3742
|
+
"\"{}\"",
|
|
3743
|
+
s.replace('\\', "\\\\")
|
|
3744
|
+
.replace('"', "\\\"")
|
|
3745
|
+
.replace('\n', "\\n")
|
|
3746
|
+
.replace('\r', "\\r")
|
|
3747
|
+
.replace('\t', "\\t")
|
|
3748
|
+
),
|
|
3749
|
+
Value::Array(arr) => {
|
|
3750
|
+
let inner: Vec<String> = arr
|
|
3751
|
+
.borrow()
|
|
3752
|
+
.iter()
|
|
3753
|
+
.map(Self::json_stringify_value)
|
|
3754
|
+
.collect();
|
|
3755
|
+
format!("[{}]", inner.join(","))
|
|
3756
|
+
}
|
|
3757
|
+
Value::Object(map) => {
|
|
3758
|
+
// Insertion order (PropMap is an IndexMap) — matches JS/Node and the
|
|
3759
|
+
// VM/rust backends. No key sort.
|
|
3760
|
+
let entries: Vec<String> = map
|
|
3761
|
+
.borrow()
|
|
3762
|
+
.strings
|
|
3763
|
+
.iter()
|
|
3764
|
+
.map(|(k, v)| {
|
|
3765
|
+
format!(
|
|
3766
|
+
"\"{}\":{}",
|
|
3767
|
+
k.replace('\\', "\\\\").replace('"', "\\\""),
|
|
3768
|
+
Self::json_stringify_value(v)
|
|
3769
|
+
)
|
|
3770
|
+
})
|
|
3771
|
+
.collect();
|
|
3772
|
+
format!("{{{}}}", entries.join(","))
|
|
3773
|
+
}
|
|
3774
|
+
Value::Symbol(_) => "null".to_string(),
|
|
3775
|
+
Value::Function { .. } | Value::Native(_) => "null".to_string(),
|
|
3776
|
+
#[cfg(feature = "http")]
|
|
3777
|
+
Value::CorePromise(_) => "null".to_string(),
|
|
3778
|
+
Value::CoreFn(_) => "null".to_string(),
|
|
3779
|
+
#[cfg(feature = "http")]
|
|
3780
|
+
Value::Serve
|
|
3781
|
+
| Value::Promise(_)
|
|
3782
|
+
| Value::PromiseResolver(_)
|
|
3783
|
+
| Value::PromiseConstructor
|
|
3784
|
+
| Value::BoundPromiseMethod(_, _) => "null".to_string(),
|
|
3785
|
+
#[cfg(feature = "timers")]
|
|
3786
|
+
Value::TimerBuiltin(_) => "null".to_string(),
|
|
3787
|
+
#[cfg(feature = "regex")]
|
|
3788
|
+
Value::RegExp(_) => "null".to_string(),
|
|
3789
|
+
Value::Opaque(_) | Value::OpaqueMethod(_, _) => "null".to_string(),
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
// Static native wrapper functions (these need to be fn pointers, not closures with &self)
|
|
3794
|
+
fn json_parse_native(args: &[Value]) -> Result<Value, String> {
|
|
3795
|
+
let s = args.first().map(|v| v.to_string()).unwrap_or_default();
|
|
3796
|
+
Ok(Self::json_parse(&s))
|
|
3797
|
+
}
|
|
3798
|
+
|
|
3799
|
+
fn json_stringify_native(args: &[Value]) -> Result<Value, String> {
|
|
3800
|
+
let v = args.first().cloned().unwrap_or(Value::Null);
|
|
3801
|
+
Ok(Value::String(Self::json_stringify_value(&v).into()))
|
|
3802
|
+
}
|
|
3803
|
+
|
|
3804
|
+
fn object_keys(args: &[Value]) -> Result<Value, String> {
|
|
3805
|
+
if let Some(Value::Object(obj)) = args.first() {
|
|
3806
|
+
let keys: Vec<Value> = obj
|
|
3807
|
+
.borrow()
|
|
3808
|
+
.strings
|
|
3809
|
+
.keys()
|
|
3810
|
+
.map(|k| Value::String(Arc::clone(k)))
|
|
3811
|
+
.collect();
|
|
3812
|
+
Ok(Value::Array(Rc::new(RefCell::new(keys))))
|
|
3813
|
+
} else {
|
|
3814
|
+
Ok(Value::Array(Rc::new(RefCell::new(Vec::new()))))
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
|
|
3818
|
+
fn object_values(args: &[Value]) -> Result<Value, String> {
|
|
3819
|
+
if let Some(Value::Object(obj)) = args.first() {
|
|
3820
|
+
let values: Vec<Value> = obj.borrow().strings.values().cloned().collect();
|
|
3821
|
+
Ok(Value::Array(Rc::new(RefCell::new(values))))
|
|
3822
|
+
} else {
|
|
3823
|
+
Ok(Value::Array(Rc::new(RefCell::new(Vec::new()))))
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
fn object_entries(args: &[Value]) -> Result<Value, String> {
|
|
3828
|
+
if let Some(Value::Object(obj)) = args.first() {
|
|
3829
|
+
let entries: Vec<Value> = obj
|
|
3830
|
+
.borrow()
|
|
3831
|
+
.strings
|
|
3832
|
+
.iter()
|
|
3833
|
+
.map(|(k, v)| {
|
|
3834
|
+
Value::Array(Rc::new(RefCell::new(vec![
|
|
3835
|
+
Value::String(Arc::clone(k)),
|
|
3836
|
+
v.clone(),
|
|
3837
|
+
])))
|
|
3838
|
+
})
|
|
3839
|
+
.collect();
|
|
3840
|
+
Ok(Value::Array(Rc::new(RefCell::new(entries))))
|
|
3841
|
+
} else {
|
|
3842
|
+
Ok(Value::Array(Rc::new(RefCell::new(Vec::new()))))
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3846
|
+
fn object_assign(args: &[Value]) -> Result<Value, String> {
|
|
3847
|
+
if let Some(Value::Object(target)) = args.first() {
|
|
3848
|
+
let mut t = target.borrow_mut();
|
|
3849
|
+
for src in args.iter().skip(1) {
|
|
3850
|
+
if let Value::Object(src_obj) = src {
|
|
3851
|
+
let s = src_obj.borrow();
|
|
3852
|
+
for (k, v) in s.strings.iter() {
|
|
3853
|
+
t.strings.insert(Arc::clone(k), v.clone());
|
|
3854
|
+
}
|
|
3855
|
+
if let Some(ref sm) = s.symbols {
|
|
3856
|
+
if t.symbols.is_none() {
|
|
3857
|
+
t.symbols = Some(AHashMap::default());
|
|
3858
|
+
}
|
|
3859
|
+
let tm = t.symbols.as_mut().unwrap();
|
|
3860
|
+
for (id, v) in sm.iter() {
|
|
3861
|
+
tm.insert(*id, v.clone());
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
drop(t);
|
|
3867
|
+
Ok(args.first().cloned().unwrap())
|
|
3868
|
+
} else {
|
|
3869
|
+
Ok(Value::Null)
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3873
|
+
fn object_from_entries(args: &[Value]) -> Result<Value, String> {
|
|
3874
|
+
if let Some(Value::Array(arr)) = args.first() {
|
|
3875
|
+
let mut map = PropMap::default();
|
|
3876
|
+
for entry in arr.borrow().iter() {
|
|
3877
|
+
if let Value::Array(pair) = entry {
|
|
3878
|
+
let pair = pair.borrow();
|
|
3879
|
+
if let (Some(key), Some(value)) = (pair.first(), pair.get(1)) {
|
|
3880
|
+
let key_str: Arc<str> = key.to_string().into();
|
|
3881
|
+
map.insert(key_str, value.clone());
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
Ok(Value::object(map))
|
|
3886
|
+
} else {
|
|
3887
|
+
Ok(Value::object(PropMap::default()))
|
|
3888
|
+
}
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3891
|
+
#[cfg(feature = "regex")]
|
|
3892
|
+
fn regexp_constructor_native(args: &[Value]) -> Result<Value, String> {
|
|
3893
|
+
crate::regex::regexp_constructor(args)
|
|
3894
|
+
}
|
|
3895
|
+
|
|
3896
|
+
#[cfg(feature = "http")]
|
|
3897
|
+
fn promise_resolve(args: &[Value]) -> Result<Value, String> {
|
|
3898
|
+
let x = args.first().cloned().unwrap_or(Value::Null);
|
|
3899
|
+
let (promise, resolve_val, reject_val) = crate::promise::create_promise();
|
|
3900
|
+
let (resolve, _) = crate::promise::extract_resolvers(&resolve_val, &reject_val);
|
|
3901
|
+
crate::promise::settle_promise(&resolve, x, true)?;
|
|
3902
|
+
Ok(promise)
|
|
3903
|
+
}
|
|
3904
|
+
|
|
3905
|
+
#[cfg(feature = "http")]
|
|
3906
|
+
fn promise_reject(args: &[Value]) -> Result<Value, String> {
|
|
3907
|
+
let r = args.first().cloned().unwrap_or(Value::Null);
|
|
3908
|
+
let (promise, resolve_val, reject_val) = crate::promise::create_promise();
|
|
3909
|
+
let (_, reject) = crate::promise::extract_resolvers(&resolve_val, &reject_val);
|
|
3910
|
+
crate::promise::settle_promise(&reject, r, false)?;
|
|
3911
|
+
Ok(promise)
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
#[cfg(feature = "http")]
|
|
3915
|
+
fn promise_all(args: &[Value]) -> Result<Value, String> {
|
|
3916
|
+
let iterable = args
|
|
3917
|
+
.first()
|
|
3918
|
+
.ok_or_else(|| "Promise.all requires an iterable".to_string())?;
|
|
3919
|
+
let values: Vec<Value> = match iterable {
|
|
3920
|
+
Value::Array(arr) => arr.borrow().clone(),
|
|
3921
|
+
Value::String(s) => s
|
|
3922
|
+
.chars()
|
|
3923
|
+
.map(|c| Value::String(c.to_string().into()))
|
|
3924
|
+
.collect(),
|
|
3925
|
+
_ => return Err("Promise.all requires array or iterable".to_string()),
|
|
3926
|
+
};
|
|
3927
|
+
let mut results = Vec::with_capacity(values.len());
|
|
3928
|
+
for v in values {
|
|
3929
|
+
if let Value::Promise(ref p) = v {
|
|
3930
|
+
match crate::promise::block_until_settled(p) {
|
|
3931
|
+
crate::promise::PromiseAwaitResult::Fulfilled(x) => results.push(x),
|
|
3932
|
+
crate::promise::PromiseAwaitResult::Rejected(x) => {
|
|
3933
|
+
let (promise, resolve_val, reject_val) = crate::promise::create_promise();
|
|
3934
|
+
let (_, reject) =
|
|
3935
|
+
crate::promise::extract_resolvers(&resolve_val, &reject_val);
|
|
3936
|
+
let _ = crate::promise::settle_promise(&reject, x, false);
|
|
3937
|
+
return Ok(promise);
|
|
3938
|
+
}
|
|
3939
|
+
crate::promise::PromiseAwaitResult::Error(e) => return Err(e),
|
|
3940
|
+
}
|
|
3941
|
+
} else if let Value::CorePromise(ref p) = v {
|
|
3942
|
+
match p.block_until_settled() {
|
|
3943
|
+
Ok(x) => results.push(crate::value_convert::core_to_eval(x)),
|
|
3944
|
+
Err(x) => {
|
|
3945
|
+
let (promise, resolve_val, reject_val) = crate::promise::create_promise();
|
|
3946
|
+
let (_, reject) =
|
|
3947
|
+
crate::promise::extract_resolvers(&resolve_val, &reject_val);
|
|
3948
|
+
let _ = crate::promise::settle_promise(
|
|
3949
|
+
&reject,
|
|
3950
|
+
crate::value_convert::core_to_eval(x),
|
|
3951
|
+
false,
|
|
3952
|
+
);
|
|
3953
|
+
return Ok(promise);
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
} else {
|
|
3957
|
+
results.push(v);
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
let (promise, resolve_val, reject_val) = crate::promise::create_promise();
|
|
3961
|
+
let (resolve, _) = crate::promise::extract_resolvers(&resolve_val, &reject_val);
|
|
3962
|
+
let arr = Value::Array(Rc::new(RefCell::new(results)));
|
|
3963
|
+
crate::promise::settle_promise(&resolve, arr, true)?;
|
|
3964
|
+
Ok(promise)
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3967
|
+
#[cfg(feature = "http")]
|
|
3968
|
+
fn promise_race(args: &[Value]) -> Result<Value, String> {
|
|
3969
|
+
let iterable = args
|
|
3970
|
+
.first()
|
|
3971
|
+
.ok_or_else(|| "Promise.race requires an iterable".to_string())?;
|
|
3972
|
+
let values: Vec<Value> = match iterable {
|
|
3973
|
+
Value::Array(arr) => arr.borrow().clone(),
|
|
3974
|
+
Value::String(s) => s
|
|
3975
|
+
.chars()
|
|
3976
|
+
.map(|c| Value::String(c.to_string().into()))
|
|
3977
|
+
.collect(),
|
|
3978
|
+
_ => return Err("Promise.race requires array or iterable".to_string()),
|
|
3979
|
+
};
|
|
3980
|
+
for v in values {
|
|
3981
|
+
if let Value::CorePromise(ref p) = v {
|
|
3982
|
+
match p.block_until_settled() {
|
|
3983
|
+
Ok(x) => {
|
|
3984
|
+
let (promise, resolve_val, reject_val) = crate::promise::create_promise();
|
|
3985
|
+
let (resolve, _) =
|
|
3986
|
+
crate::promise::extract_resolvers(&resolve_val, &reject_val);
|
|
3987
|
+
crate::promise::settle_promise(
|
|
3988
|
+
&resolve,
|
|
3989
|
+
crate::value_convert::core_to_eval(x),
|
|
3990
|
+
true,
|
|
3991
|
+
)?;
|
|
3992
|
+
return Ok(promise);
|
|
3993
|
+
}
|
|
3994
|
+
Err(x) => {
|
|
3995
|
+
let (promise, resolve_val, reject_val) = crate::promise::create_promise();
|
|
3996
|
+
let (_, reject) =
|
|
3997
|
+
crate::promise::extract_resolvers(&resolve_val, &reject_val);
|
|
3998
|
+
crate::promise::settle_promise(
|
|
3999
|
+
&reject,
|
|
4000
|
+
crate::value_convert::core_to_eval(x),
|
|
4001
|
+
false,
|
|
4002
|
+
)?;
|
|
4003
|
+
return Ok(promise);
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
if let Value::Promise(ref p) = v {
|
|
4008
|
+
match crate::promise::block_until_settled(p) {
|
|
4009
|
+
crate::promise::PromiseAwaitResult::Fulfilled(x) => {
|
|
4010
|
+
let (promise, resolve_val, reject_val) = crate::promise::create_promise();
|
|
4011
|
+
let (resolve, _) =
|
|
4012
|
+
crate::promise::extract_resolvers(&resolve_val, &reject_val);
|
|
4013
|
+
crate::promise::settle_promise(&resolve, x, true)?;
|
|
4014
|
+
return Ok(promise);
|
|
4015
|
+
}
|
|
4016
|
+
crate::promise::PromiseAwaitResult::Rejected(x) => {
|
|
4017
|
+
let (promise, resolve_val, reject_val) = crate::promise::create_promise();
|
|
4018
|
+
let (_, reject) =
|
|
4019
|
+
crate::promise::extract_resolvers(&resolve_val, &reject_val);
|
|
4020
|
+
crate::promise::settle_promise(&reject, x, false)?;
|
|
4021
|
+
return Ok(promise);
|
|
4022
|
+
}
|
|
4023
|
+
crate::promise::PromiseAwaitResult::Error(e) => return Err(e),
|
|
4024
|
+
}
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
Err("Promise.race requires at least one promise".to_string())
|
|
4028
|
+
}
|
|
4029
|
+
|
|
4030
|
+
/// Helper: settle a new promise fulfilled with `v` (interp Value).
|
|
4031
|
+
#[cfg(feature = "http")]
|
|
4032
|
+
fn eval_fulfilled(v: Value) -> Result<Value, String> {
|
|
4033
|
+
let (promise, resolve_val, reject_val) = crate::promise::create_promise();
|
|
4034
|
+
let (resolve, _) = crate::promise::extract_resolvers(&resolve_val, &reject_val);
|
|
4035
|
+
crate::promise::settle_promise(&resolve, v, true)?;
|
|
4036
|
+
Ok(promise)
|
|
4037
|
+
}
|
|
4038
|
+
|
|
4039
|
+
/// Helper: settle a new promise rejected with `v` (interp Value).
|
|
4040
|
+
#[cfg(feature = "http")]
|
|
4041
|
+
fn eval_rejected(v: Value) -> Result<Value, String> {
|
|
4042
|
+
let (promise, resolve_val, reject_val) = crate::promise::create_promise();
|
|
4043
|
+
let (_, reject) = crate::promise::extract_resolvers(&resolve_val, &reject_val);
|
|
4044
|
+
crate::promise::settle_promise(&reject, v, false)?;
|
|
4045
|
+
Ok(promise)
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
/// Await one interp promise/core-promise/value → `Result<Value, Value>`.
|
|
4049
|
+
#[cfg(feature = "http")]
|
|
4050
|
+
fn settle_one(v: Value) -> Result<Value, Value> {
|
|
4051
|
+
match v {
|
|
4052
|
+
Value::Promise(ref p) => match crate::promise::block_until_settled(p) {
|
|
4053
|
+
crate::promise::PromiseAwaitResult::Fulfilled(x) => Ok(x),
|
|
4054
|
+
crate::promise::PromiseAwaitResult::Rejected(x) => Err(x),
|
|
4055
|
+
crate::promise::PromiseAwaitResult::Error(e) => {
|
|
4056
|
+
Err(Value::String(e.into()))
|
|
4057
|
+
}
|
|
4058
|
+
},
|
|
4059
|
+
Value::CorePromise(ref p) => match p.block_until_settled() {
|
|
4060
|
+
Ok(x) => Ok(crate::value_convert::core_to_eval(x)),
|
|
4061
|
+
Err(x) => Err(crate::value_convert::core_to_eval(x)),
|
|
4062
|
+
},
|
|
4063
|
+
other => Ok(other),
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
|
|
4067
|
+
/// `Promise.any(iterable)` — first fulfilled wins; rejects with array of reasons if all reject.
|
|
4068
|
+
#[cfg(feature = "http")]
|
|
4069
|
+
fn promise_any(args: &[Value]) -> Result<Value, String> {
|
|
4070
|
+
let iterable = args
|
|
4071
|
+
.first()
|
|
4072
|
+
.ok_or_else(|| "Promise.any requires an iterable".to_string())?;
|
|
4073
|
+
let values: Vec<Value> = match iterable {
|
|
4074
|
+
Value::Array(arr) => arr.borrow().clone(),
|
|
4075
|
+
_ => return Err("Promise.any requires an array".to_string()),
|
|
4076
|
+
};
|
|
4077
|
+
let n = values.len();
|
|
4078
|
+
if n == 0 {
|
|
4079
|
+
return Self::eval_rejected(Value::Array(Rc::new(RefCell::new(vec![]))));
|
|
4080
|
+
}
|
|
4081
|
+
let mut errors = Vec::with_capacity(n);
|
|
4082
|
+
for v in values {
|
|
4083
|
+
match Self::settle_one(v) {
|
|
4084
|
+
Ok(x) => return Self::eval_fulfilled(x),
|
|
4085
|
+
Err(e) => errors.push(e),
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
Self::eval_rejected(Value::Array(Rc::new(RefCell::new(errors))))
|
|
4089
|
+
}
|
|
4090
|
+
|
|
4091
|
+
/// `Promise.allSettled(iterable)` — always fulfills with array of `{status,value|reason}`.
|
|
4092
|
+
#[cfg(feature = "http")]
|
|
4093
|
+
fn promise_all_settled(args: &[Value]) -> Result<Value, String> {
|
|
4094
|
+
use crate::value::EvalObjectData;
|
|
4095
|
+
let iterable = args
|
|
4096
|
+
.first()
|
|
4097
|
+
.ok_or_else(|| "Promise.allSettled requires an iterable".to_string())?;
|
|
4098
|
+
let values: Vec<Value> = match iterable {
|
|
4099
|
+
Value::Array(arr) => arr.borrow().clone(),
|
|
4100
|
+
_ => return Err("Promise.allSettled requires an array".to_string()),
|
|
4101
|
+
};
|
|
4102
|
+
let mut out = Vec::with_capacity(values.len());
|
|
4103
|
+
for v in values {
|
|
4104
|
+
let r = Self::settle_one(v);
|
|
4105
|
+
let mut data = EvalObjectData::default();
|
|
4106
|
+
match r {
|
|
4107
|
+
Ok(x) => {
|
|
4108
|
+
data.strings.insert(std::sync::Arc::from("status"), Value::String("fulfilled".into()));
|
|
4109
|
+
data.strings.insert(std::sync::Arc::from("value"), x);
|
|
4110
|
+
}
|
|
4111
|
+
Err(e) => {
|
|
4112
|
+
data.strings.insert(std::sync::Arc::from("status"), Value::String("rejected".into()));
|
|
4113
|
+
data.strings.insert(std::sync::Arc::from("reason"), e);
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
out.push(Value::Object(Rc::new(RefCell::new(data))));
|
|
4117
|
+
}
|
|
4118
|
+
Self::eval_fulfilled(Value::Array(Rc::new(RefCell::new(out))))
|
|
4119
|
+
}
|
|
4120
|
+
|
|
4121
|
+
/// `Promise.spawn(fn)` — on the interpreter, runs the function synchronously and wraps
|
|
4122
|
+
/// the result in an immediate promise. The interpreter uses `Rc<RefCell<…>>` for closures,
|
|
4123
|
+
/// which is `!Send`, so we cannot move the function to a background thread here. Real
|
|
4124
|
+
/// cross-thread parallelism via spawn is available on the bytecode VM (which uses the
|
|
4125
|
+
/// `send-values` / Arc path for the shipped `full` build). For the interpreter, `any` and
|
|
4126
|
+
/// `race` over spawn-created promises still work correctly — they just don't run concurrently.
|
|
4127
|
+
#[cfg(feature = "http")]
|
|
4128
|
+
fn promise_spawn_interp(args: &[Value]) -> Result<Value, String> {
|
|
4129
|
+
let callable = match args.first() {
|
|
4130
|
+
Some(v @ (Value::Native(_) | Value::Function { .. })) => v.clone(),
|
|
4131
|
+
_ => return Err("Promise.spawn: expected a function argument".to_string()),
|
|
4132
|
+
};
|
|
4133
|
+
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
|
4134
|
+
match &callable {
|
|
4135
|
+
Value::Native(f) => f(&[]).map_err(|e| e.to_string()),
|
|
4136
|
+
// Interpreter closures (Value::Function) can't be called from a static native fn
|
|
4137
|
+
// (no evaluator state / Rc captures). Use the VM backend for concurrent CPU spawn.
|
|
4138
|
+
_ => Err("Promise.spawn: tish closures are not supported on the interpreter backend; use the vm backend (tish run) or pass a native module function".to_string()),
|
|
4139
|
+
}
|
|
4140
|
+
}));
|
|
4141
|
+
match result {
|
|
4142
|
+
Ok(Ok(v)) => Self::eval_fulfilled(v),
|
|
4143
|
+
Ok(Err(e)) => Self::eval_rejected(Value::String(e.into())),
|
|
4144
|
+
Err(_) => Self::eval_rejected(Value::String("Promise.spawn: task panicked".into())),
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
|
|
4148
|
+
#[cfg(feature = "ws")]
|
|
4149
|
+
fn ws_web_socket_native(args: &[Value]) -> Result<Value, String> {
|
|
4150
|
+
let mut cv = Vec::new();
|
|
4151
|
+
for a in args {
|
|
4152
|
+
cv.push(crate::value_convert::eval_to_core(a)?);
|
|
4153
|
+
}
|
|
4154
|
+
Ok(crate::value_convert::core_to_eval(
|
|
4155
|
+
tishlang_runtime::web_socket_client(&cv),
|
|
4156
|
+
))
|
|
4157
|
+
}
|
|
4158
|
+
|
|
4159
|
+
#[cfg(feature = "ws")]
|
|
4160
|
+
fn ws_server_native(args: &[Value]) -> Result<Value, String> {
|
|
4161
|
+
let mut cv = Vec::new();
|
|
4162
|
+
for a in args {
|
|
4163
|
+
cv.push(crate::value_convert::eval_to_core(a)?);
|
|
4164
|
+
}
|
|
4165
|
+
Ok(crate::value_convert::core_to_eval(
|
|
4166
|
+
tishlang_runtime::web_socket_server_construct(&cv),
|
|
4167
|
+
))
|
|
4168
|
+
}
|
|
4169
|
+
|
|
4170
|
+
#[cfg(feature = "ws")]
|
|
4171
|
+
fn ws_send_native(args: &[Value]) -> Result<Value, String> {
|
|
4172
|
+
let conn = args.first().ok_or("wsSend(conn, data) requires conn")?;
|
|
4173
|
+
let conn_core = crate::value_convert::eval_to_core(conn)?;
|
|
4174
|
+
let data = args.get(1).map(|v| v.to_string()).unwrap_or_default();
|
|
4175
|
+
Ok(Value::Bool(tishlang_runtime::ws_send_native(
|
|
4176
|
+
&conn_core, &data,
|
|
4177
|
+
)))
|
|
4178
|
+
}
|
|
4179
|
+
|
|
4180
|
+
#[cfg(feature = "ws")]
|
|
4181
|
+
fn ws_broadcast_native(args: &[Value]) -> Result<Value, String> {
|
|
4182
|
+
let mut cv = Vec::new();
|
|
4183
|
+
for a in args {
|
|
4184
|
+
cv.push(crate::value_convert::eval_to_core(a)?);
|
|
4185
|
+
}
|
|
4186
|
+
Ok(crate::value_convert::core_to_eval(
|
|
4187
|
+
tishlang_runtime::ws_broadcast_native(&cv),
|
|
4188
|
+
))
|
|
4189
|
+
}
|
|
4190
|
+
|
|
4191
|
+
#[cfg(feature = "http")]
|
|
4192
|
+
fn fetch_native(args: &[Value]) -> Result<Value, String> {
|
|
4193
|
+
let mut cv = Vec::new();
|
|
4194
|
+
for a in args {
|
|
4195
|
+
cv.push(crate::value_convert::eval_to_core(a)?);
|
|
4196
|
+
}
|
|
4197
|
+
match tishlang_runtime::fetch_promise(cv) {
|
|
4198
|
+
tishlang_core::Value::Promise(p) => Ok(Value::CorePromise(p)),
|
|
4199
|
+
_ => Err("internal: fetch did not return Promise".into()),
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
|
|
4203
|
+
#[cfg(feature = "http")]
|
|
4204
|
+
fn fetch_all_native(args: &[Value]) -> Result<Value, String> {
|
|
4205
|
+
let mut cv = Vec::new();
|
|
4206
|
+
for a in args {
|
|
4207
|
+
cv.push(crate::value_convert::eval_to_core(a)?);
|
|
4208
|
+
}
|
|
4209
|
+
match tishlang_runtime::fetch_all_promise(cv) {
|
|
4210
|
+
tishlang_core::Value::Promise(p) => Ok(Value::CorePromise(p)),
|
|
4211
|
+
_ => Err("internal: fetchAll did not return Promise".into()),
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4214
|
+
|
|
4215
|
+
#[cfg(feature = "http")]
|
|
4216
|
+
fn eval_await(&self, operand: &Expr) -> Result<Value, EvalError> {
|
|
4217
|
+
let val = self.eval_expr(operand)?;
|
|
4218
|
+
if let Value::Promise(ref p) = val {
|
|
4219
|
+
match crate::promise::block_until_settled(p) {
|
|
4220
|
+
crate::promise::PromiseAwaitResult::Fulfilled(v) => Ok(v),
|
|
4221
|
+
crate::promise::PromiseAwaitResult::Rejected(v) => Err(EvalError::Throw(v)),
|
|
4222
|
+
crate::promise::PromiseAwaitResult::Error(e) => Err(EvalError::Error(e)),
|
|
4223
|
+
}
|
|
4224
|
+
} else if let Value::CorePromise(ref p) = val {
|
|
4225
|
+
match p.block_until_settled() {
|
|
4226
|
+
Ok(v) => Ok(crate::value_convert::core_to_eval(v)),
|
|
4227
|
+
Err(v) => Err(EvalError::Throw(crate::value_convert::core_to_eval(v))),
|
|
4228
|
+
}
|
|
4229
|
+
} else {
|
|
4230
|
+
Err(EvalError::Error(
|
|
4231
|
+
"await requires a Promise (use await fetch(...), await reader.read(), etc.)".into(),
|
|
4232
|
+
))
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
|
|
4236
|
+
#[cfg(not(feature = "http"))]
|
|
4237
|
+
fn eval_await(&self, _operand: &Expr) -> Result<Value, EvalError> {
|
|
4238
|
+
Err(EvalError::Error(
|
|
4239
|
+
"await requires the http feature".to_string(),
|
|
4240
|
+
))
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
|
|
4244
|
+
#[derive(Debug)]
|
|
4245
|
+
enum EvalError {
|
|
4246
|
+
Return(Value),
|
|
4247
|
+
Break,
|
|
4248
|
+
Continue,
|
|
4249
|
+
Throw(Value),
|
|
4250
|
+
Error(String),
|
|
4251
|
+
}
|
|
4252
|
+
|
|
4253
|
+
impl std::fmt::Display for EvalError {
|
|
4254
|
+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
4255
|
+
match self {
|
|
4256
|
+
EvalError::Return(_) => write!(f, "return"),
|
|
4257
|
+
EvalError::Break => write!(f, "break"),
|
|
4258
|
+
EvalError::Continue => write!(f, "continue"),
|
|
4259
|
+
EvalError::Throw(v) => write!(f, "{}", v),
|
|
4260
|
+
EvalError::Error(s) => write!(f, "{}", s),
|
|
4261
|
+
}
|
|
4262
|
+
}
|
|
4263
|
+
}
|
|
4264
|
+
|
|
4265
|
+
impl std::error::Error for EvalError {}
|