@tishlang/tish-format 1.0.12 → 1.0.13
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 +49 -0
- package/LICENSE +13 -0
- package/README.md +138 -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 +610 -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 +54 -0
- package/crates/tish/src/cargo_native_registry.rs +32 -0
- package/crates/tish/src/cli_help.rs +565 -0
- package/crates/tish/src/main.rs +781 -0
- package/crates/tish/src/repl_completion.rs +200 -0
- package/crates/tish/tests/cargo_example_compile.rs +67 -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/integration_test.rs +1095 -0
- package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
- package/crates/tish/tests/shortcircuit.rs +65 -0
- package/crates/tish_ast/Cargo.toml +9 -0
- package/crates/tish_ast/src/ast.rs +620 -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 +20 -0
- package/crates/tish_builtins/src/array.rs +441 -0
- package/crates/tish_builtins/src/construct.rs +159 -0
- package/crates/tish_builtins/src/globals.rs +213 -0
- package/crates/tish_builtins/src/helpers.rs +35 -0
- package/crates/tish_builtins/src/lib.rs +16 -0
- package/crates/tish_builtins/src/math.rs +89 -0
- package/crates/tish_builtins/src/object.rs +36 -0
- package/crates/tish_builtins/src/string.rs +647 -0
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_bytecode/Cargo.toml +17 -0
- package/crates/tish_bytecode/src/chunk.rs +96 -0
- package/crates/tish_bytecode/src/compiler.rs +1760 -0
- package/crates/tish_bytecode/src/encoding.rs +100 -0
- package/crates/tish_bytecode/src/lib.rs +19 -0
- package/crates/tish_bytecode/src/opcode.rs +142 -0
- package/crates/tish_bytecode/src/peephole.rs +189 -0
- package/crates/tish_bytecode/src/serialize.rs +163 -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 +26 -0
- package/crates/tish_compile/src/codegen.rs +5332 -0
- package/crates/tish_compile/src/infer.rs +292 -0
- package/crates/tish_compile/src/lib.rs +164 -0
- package/crates/tish_compile/src/resolve.rs +1388 -0
- package/crates/tish_compile/src/types.rs +501 -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 +871 -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 +350 -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 +26 -0
- package/crates/tish_core/src/console_style.rs +160 -0
- package/crates/tish_core/src/json.rs +387 -0
- package/crates/tish_core/src/lib.rs +17 -0
- package/crates/tish_core/src/macros.rs +36 -0
- package/crates/tish_core/src/uri.rs +118 -0
- package/crates/tish_core/src/value.rs +696 -0
- package/crates/tish_core/src/vmref.rs +178 -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 +117 -0
- package/crates/tish_cranelift/src/lower.rs +85 -0
- package/crates/tish_cranelift_runtime/Cargo.toml +25 -0
- package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
- package/crates/tish_eval/Cargo.toml +45 -0
- package/crates/tish_eval/src/eval.rs +3717 -0
- package/crates/tish_eval/src/http.rs +188 -0
- package/crates/tish_eval/src/lib.rs +99 -0
- package/crates/tish_eval/src/natives.rs +399 -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 +318 -0
- package/crates/tish_eval/src/value_convert.rs +111 -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 +2101 -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 +716 -0
- package/crates/tish_lexer/src/token.rs +163 -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 +289 -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 +562 -0
- package/crates/tish_lsp/src/main.rs +1046 -0
- package/crates/tish_native/Cargo.toml +16 -0
- package/crates/tish_native/src/build.rs +427 -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 +943 -0
- package/crates/tish_parser/Cargo.toml +11 -0
- package/crates/tish_parser/src/lib.rs +332 -0
- package/crates/tish_parser/src/parser.rs +2304 -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 +3561 -0
- package/crates/tish_resolve/src/pos.rs +141 -0
- package/crates/tish_runtime/Cargo.toml +96 -0
- package/crates/tish_runtime/src/http.rs +1298 -0
- package/crates/tish_runtime/src/http_fetch.rs +471 -0
- package/crates/tish_runtime/src/http_hyper.rs +418 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +1192 -0
- package/crates/tish_runtime/src/native_promise.rs +15 -0
- package/crates/tish_runtime/src/promise.rs +248 -0
- package/crates/tish_runtime/src/promise_io.rs +38 -0
- package/crates/tish_runtime/src/timers.rs +166 -0
- package/crates/tish_runtime/src/ws.rs +761 -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 +682 -0
- package/crates/tish_ui/src/lib.rs +20 -0
- package/crates/tish_ui/src/runtime/hooks.rs +569 -0
- package/crates/tish_ui/src/runtime/mod.rs +180 -0
- package/crates/tish_vm/Cargo.toml +47 -0
- package/crates/tish_vm/src/lib.rs +39 -0
- package/crates/tish_vm/src/vm.rs +2192 -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 +424 -0
- package/crates/tish_wasm_runtime/Cargo.toml +37 -0
- package/crates/tish_wasm_runtime/src/gpu.rs +413 -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 +263 -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 +268 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-fmt +0 -0
|
@@ -0,0 +1,2192 @@
|
|
|
1
|
+
//! Stack-based bytecode VM.
|
|
2
|
+
|
|
3
|
+
use std::collections::{HashMap, HashSet};
|
|
4
|
+
use std::sync::Arc;
|
|
5
|
+
|
|
6
|
+
#[cfg(not(feature = "send-values"))]
|
|
7
|
+
use std::rc::Rc;
|
|
8
|
+
use tishlang_core::VmRef;
|
|
9
|
+
|
|
10
|
+
use tishlang_ast::{BinOp, UnaryOp};
|
|
11
|
+
use tishlang_builtins::array as arr_builtins;
|
|
12
|
+
use tishlang_builtins::construct as construct_builtin;
|
|
13
|
+
use tishlang_builtins::globals as globals_builtins;
|
|
14
|
+
use tishlang_builtins::math as math_builtins;
|
|
15
|
+
use tishlang_builtins::string as str_builtins;
|
|
16
|
+
use tishlang_bytecode::{u8_to_binop, u8_to_unaryop, Chunk, Constant, Opcode, NO_REST_PARAM};
|
|
17
|
+
use tishlang_core::{
|
|
18
|
+
merge_object_data, object_get, object_has, object_set, NativeFn, ObjectData, ObjectMap, Value,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/// Wrap a closure in the right shared pointer for the current build.
|
|
22
|
+
/// Under `send-values` that's `Arc<dyn Fn + Send + Sync>`; otherwise it's
|
|
23
|
+
/// plain `Rc<dyn Fn>`. Call sites can stay ignorant of the distinction.
|
|
24
|
+
#[cfg(feature = "send-values")]
|
|
25
|
+
#[inline]
|
|
26
|
+
fn make_native_fn<F>(f: F) -> NativeFn
|
|
27
|
+
where
|
|
28
|
+
F: Fn(&[Value]) -> Value + Send + Sync + 'static,
|
|
29
|
+
{
|
|
30
|
+
Arc::new(f)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#[cfg(not(feature = "send-values"))]
|
|
34
|
+
#[inline]
|
|
35
|
+
fn make_native_fn<F>(f: F) -> NativeFn
|
|
36
|
+
where
|
|
37
|
+
F: Fn(&[Value]) -> Value + 'static,
|
|
38
|
+
{
|
|
39
|
+
Rc::new(f)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Array / string / object methods have the same shape as `NativeFn`, which
|
|
43
|
+
// is already feature-gated (`Rc<dyn Fn>` vs `Arc<dyn Fn + Send + Sync>`).
|
|
44
|
+
// Alias to that so the VM picks the right pointer type automatically.
|
|
45
|
+
type ArrayMethodFn = NativeFn;
|
|
46
|
+
|
|
47
|
+
/// Feature names enabled for this VM run (`tish run --feature …`). `full` enables every optional capability.
|
|
48
|
+
#[cfg_attr(
|
|
49
|
+
not(any(
|
|
50
|
+
feature = "fs",
|
|
51
|
+
feature = "http",
|
|
52
|
+
feature = "promise",
|
|
53
|
+
feature = "timers",
|
|
54
|
+
feature = "process",
|
|
55
|
+
feature = "ws"
|
|
56
|
+
)),
|
|
57
|
+
allow(dead_code)
|
|
58
|
+
)]
|
|
59
|
+
#[inline]
|
|
60
|
+
fn value_object_from_map(m: ObjectMap) -> Value {
|
|
61
|
+
Value::Object(VmRef::new(ObjectData::from_strings(m)))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#[cfg(any(
|
|
65
|
+
feature = "fs",
|
|
66
|
+
feature = "http",
|
|
67
|
+
feature = "promise",
|
|
68
|
+
feature = "timers",
|
|
69
|
+
feature = "process",
|
|
70
|
+
feature = "ws"
|
|
71
|
+
))]
|
|
72
|
+
#[inline]
|
|
73
|
+
fn cap_allows(enabled: &HashSet<String>, name: &str) -> bool {
|
|
74
|
+
enabled.contains("full") || enabled.contains(name)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Capabilities linked into this `tishlang_vm` binary (compile-time). Used by [`Vm::new`] and `run()`.
|
|
78
|
+
pub fn all_compiled_capabilities() -> HashSet<String> {
|
|
79
|
+
#[allow(unused_mut)]
|
|
80
|
+
let mut s = HashSet::new();
|
|
81
|
+
#[cfg(feature = "http")]
|
|
82
|
+
s.insert("http".to_string());
|
|
83
|
+
#[cfg(feature = "promise")]
|
|
84
|
+
s.insert("promise".to_string());
|
|
85
|
+
#[cfg(feature = "timers")]
|
|
86
|
+
s.insert("timers".to_string());
|
|
87
|
+
#[cfg(feature = "fs")]
|
|
88
|
+
s.insert("fs".to_string());
|
|
89
|
+
#[cfg(feature = "process")]
|
|
90
|
+
s.insert("process".to_string());
|
|
91
|
+
#[cfg(feature = "regex")]
|
|
92
|
+
s.insert("regex".to_string());
|
|
93
|
+
#[cfg(feature = "ws")]
|
|
94
|
+
s.insert("ws".to_string());
|
|
95
|
+
s
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Look up built-in module export for LoadNativeExport. Returns None if unknown or feature disabled.
|
|
99
|
+
#[cfg_attr(
|
|
100
|
+
not(any(
|
|
101
|
+
feature = "fs",
|
|
102
|
+
feature = "http",
|
|
103
|
+
feature = "promise",
|
|
104
|
+
feature = "timers",
|
|
105
|
+
feature = "process",
|
|
106
|
+
feature = "ws"
|
|
107
|
+
)),
|
|
108
|
+
allow(unused_variables)
|
|
109
|
+
)]
|
|
110
|
+
fn get_builtin_export(enabled: &HashSet<String>, spec: &str, export_name: &str) -> Option<Value> {
|
|
111
|
+
#[cfg(feature = "fs")]
|
|
112
|
+
if spec == "tish:fs" && cap_allows(enabled, "fs") {
|
|
113
|
+
return match export_name {
|
|
114
|
+
"readFile" => Some(Value::native(|args: &[Value]| {
|
|
115
|
+
tishlang_runtime::read_file(args)
|
|
116
|
+
})),
|
|
117
|
+
"writeFile" => Some(Value::native(|args: &[Value]| {
|
|
118
|
+
tishlang_runtime::write_file(args)
|
|
119
|
+
})),
|
|
120
|
+
"fileExists" => Some(Value::native(|args: &[Value]| {
|
|
121
|
+
tishlang_runtime::file_exists(args)
|
|
122
|
+
})),
|
|
123
|
+
"isDir" => Some(Value::native(|args: &[Value]| {
|
|
124
|
+
tishlang_runtime::is_dir(args)
|
|
125
|
+
})),
|
|
126
|
+
"readDir" => Some(Value::native(|args: &[Value]| {
|
|
127
|
+
tishlang_runtime::read_dir(args)
|
|
128
|
+
})),
|
|
129
|
+
"mkdir" => Some(Value::native(|args: &[Value]| {
|
|
130
|
+
tishlang_runtime::mkdir(args)
|
|
131
|
+
})),
|
|
132
|
+
_ => None,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
#[cfg(feature = "http")]
|
|
136
|
+
if spec == "tish:http" && cap_allows(enabled, "http") {
|
|
137
|
+
return match export_name {
|
|
138
|
+
// Bytecode compiler lowers `await expr` to `tish:http.await(promise)` (see tish_bytecode compiler).
|
|
139
|
+
"await" => Some(Value::native(|args: &[Value]| {
|
|
140
|
+
tishlang_runtime::await_promise(args.first().cloned().unwrap_or(Value::Null))
|
|
141
|
+
})),
|
|
142
|
+
"fetch" => Some(Value::native(|args: &[Value]| {
|
|
143
|
+
tishlang_runtime::fetch_promise(args.to_vec())
|
|
144
|
+
})),
|
|
145
|
+
"fetchAll" => Some(Value::native(|args: &[Value]| {
|
|
146
|
+
tishlang_runtime::fetch_all_promise(args.to_vec())
|
|
147
|
+
})),
|
|
148
|
+
"Promise" => Some(tishlang_runtime::promise_object()),
|
|
149
|
+
"serve" => Some(Value::native(|args: &[Value]| {
|
|
150
|
+
// Phase-1 item 2: support `serve(port, { handler, onWorker })`
|
|
151
|
+
// in addition to `serve(port, handler)`. When an options
|
|
152
|
+
// object is given and onWorker is a function, invoke it with
|
|
153
|
+
// worker id 0 and expect it to return the request handler.
|
|
154
|
+
let raw = args.get(1).cloned().unwrap_or(Value::Null);
|
|
155
|
+
let handler_value = match raw {
|
|
156
|
+
Value::Function(_) => raw,
|
|
157
|
+
Value::Object(ref obj) => {
|
|
158
|
+
let obj_ref = obj.borrow();
|
|
159
|
+
if let Some(Value::Function(on_worker)) =
|
|
160
|
+
obj_ref.strings.get(&std::sync::Arc::from("onWorker")).cloned()
|
|
161
|
+
{
|
|
162
|
+
let args_for_init = [Value::Number(0.0)];
|
|
163
|
+
on_worker(&args_for_init)
|
|
164
|
+
} else if let Some(h) =
|
|
165
|
+
obj_ref.strings.get(&std::sync::Arc::from("handler")).cloned()
|
|
166
|
+
{
|
|
167
|
+
h
|
|
168
|
+
} else {
|
|
169
|
+
Value::Null
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
_ => Value::Null,
|
|
173
|
+
};
|
|
174
|
+
if let Value::Function(f) = handler_value {
|
|
175
|
+
tishlang_runtime::http_serve(args, move |req_args| f(req_args))
|
|
176
|
+
} else {
|
|
177
|
+
Value::Null
|
|
178
|
+
}
|
|
179
|
+
})),
|
|
180
|
+
_ => None,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
#[cfg(all(feature = "promise", not(feature = "http")))]
|
|
184
|
+
if spec == "tish:http" && cap_allows(enabled, "promise") {
|
|
185
|
+
return match export_name {
|
|
186
|
+
"Promise" => Some(tishlang_runtime::promise_object()),
|
|
187
|
+
"await" => Some(Value::native(|args: &[Value]| {
|
|
188
|
+
tishlang_runtime::await_promise(args.first().cloned().unwrap_or(Value::Null))
|
|
189
|
+
})),
|
|
190
|
+
_ => None,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
#[cfg(feature = "timers")]
|
|
194
|
+
if spec == "tish:timers" && cap_allows(enabled, "timers") {
|
|
195
|
+
return match export_name {
|
|
196
|
+
"setTimeout" => Some(Value::native(|args: &[Value]| {
|
|
197
|
+
tishlang_runtime::timer_set_timeout(args)
|
|
198
|
+
})),
|
|
199
|
+
"setInterval" => Some(Value::native(|args: &[Value]| {
|
|
200
|
+
tishlang_runtime::timer_set_interval(args)
|
|
201
|
+
})),
|
|
202
|
+
"clearTimeout" => Some(Value::native(|args: &[Value]| {
|
|
203
|
+
tishlang_runtime::timer_clear_timeout(args)
|
|
204
|
+
})),
|
|
205
|
+
"clearInterval" => Some(Value::native(|args: &[Value]| {
|
|
206
|
+
tishlang_runtime::timer_clear_interval(args)
|
|
207
|
+
})),
|
|
208
|
+
_ => None,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
#[cfg(feature = "process")]
|
|
212
|
+
if spec == "tish:process" && cap_allows(enabled, "process") {
|
|
213
|
+
return match export_name {
|
|
214
|
+
"exit" => Some(Value::native(|args: &[Value]| {
|
|
215
|
+
tishlang_runtime::process_exit(args)
|
|
216
|
+
})),
|
|
217
|
+
"cwd" => Some(Value::native(|args: &[Value]| {
|
|
218
|
+
tishlang_runtime::process_cwd(args)
|
|
219
|
+
})),
|
|
220
|
+
"exec" => Some(Value::native(|args: &[Value]| {
|
|
221
|
+
tishlang_runtime::process_exec(args)
|
|
222
|
+
})),
|
|
223
|
+
"argv" => Some(Value::Array(VmRef::new(
|
|
224
|
+
std::env::args().map(|s| Value::String(s.into())).collect(),
|
|
225
|
+
))),
|
|
226
|
+
"env" => Some(value_object_from_map(
|
|
227
|
+
std::env::vars()
|
|
228
|
+
.map(|(k, v)| (Arc::from(k.as_str()), Value::String(v.into())))
|
|
229
|
+
.collect(),
|
|
230
|
+
)),
|
|
231
|
+
"process" => {
|
|
232
|
+
let mut m = ObjectMap::default();
|
|
233
|
+
m.insert(
|
|
234
|
+
"exit".into(),
|
|
235
|
+
Value::native(|args: &[Value]| tishlang_runtime::process_exit(args)),
|
|
236
|
+
);
|
|
237
|
+
m.insert(
|
|
238
|
+
"cwd".into(),
|
|
239
|
+
Value::native(|args: &[Value]| tishlang_runtime::process_cwd(args)),
|
|
240
|
+
);
|
|
241
|
+
m.insert(
|
|
242
|
+
"exec".into(),
|
|
243
|
+
Value::native(|args: &[Value]| tishlang_runtime::process_exec(args)),
|
|
244
|
+
);
|
|
245
|
+
m.insert(
|
|
246
|
+
"argv".into(),
|
|
247
|
+
Value::Array(VmRef::new(
|
|
248
|
+
std::env::args().map(|s| Value::String(s.into())).collect(),
|
|
249
|
+
)),
|
|
250
|
+
);
|
|
251
|
+
m.insert(
|
|
252
|
+
"env".into(),
|
|
253
|
+
value_object_from_map(
|
|
254
|
+
std::env::vars()
|
|
255
|
+
.map(|(k, v)| (Arc::from(k.as_str()), Value::String(v.into())))
|
|
256
|
+
.collect(),
|
|
257
|
+
),
|
|
258
|
+
);
|
|
259
|
+
Some(value_object_from_map(m))
|
|
260
|
+
}
|
|
261
|
+
_ => None,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
#[cfg(feature = "ws")]
|
|
265
|
+
if spec == "tish:ws" && cap_allows(enabled, "ws") {
|
|
266
|
+
return match export_name {
|
|
267
|
+
"WebSocket" => Some(Value::native(|args: &[Value]| {
|
|
268
|
+
tishlang_runtime::web_socket_client(args)
|
|
269
|
+
})),
|
|
270
|
+
"Server" => Some(Value::native(|args: &[Value]| {
|
|
271
|
+
tishlang_runtime::web_socket_server_construct(args)
|
|
272
|
+
})),
|
|
273
|
+
"wsSend" => Some(Value::native(|args: &[Value]| {
|
|
274
|
+
Value::Bool(tishlang_runtime::ws_send_native(
|
|
275
|
+
args.first().unwrap_or(&Value::Null),
|
|
276
|
+
&args
|
|
277
|
+
.get(1)
|
|
278
|
+
.map(|v| v.to_display_string())
|
|
279
|
+
.unwrap_or_default(),
|
|
280
|
+
))
|
|
281
|
+
})),
|
|
282
|
+
"wsBroadcast" => Some(Value::native(|args: &[Value]| {
|
|
283
|
+
tishlang_runtime::ws_broadcast_native(args)
|
|
284
|
+
})),
|
|
285
|
+
_ => None,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
None
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/// Console output: println! on native, web_sys::console on wasm
|
|
292
|
+
#[cfg(not(feature = "wasm"))]
|
|
293
|
+
fn vm_log(s: &str) {
|
|
294
|
+
println!("{}", s);
|
|
295
|
+
}
|
|
296
|
+
#[cfg(not(feature = "wasm"))]
|
|
297
|
+
fn vm_log_err(s: &str) {
|
|
298
|
+
eprintln!("{}", s);
|
|
299
|
+
}
|
|
300
|
+
#[cfg(feature = "wasm")]
|
|
301
|
+
fn vm_log(s: &str) {
|
|
302
|
+
#[wasm_bindgen::prelude::wasm_bindgen]
|
|
303
|
+
extern "C" {
|
|
304
|
+
#[wasm_bindgen(js_namespace = console)]
|
|
305
|
+
fn log(s: &str);
|
|
306
|
+
}
|
|
307
|
+
log(s);
|
|
308
|
+
}
|
|
309
|
+
#[cfg(feature = "wasm")]
|
|
310
|
+
fn vm_log_err(s: &str) {
|
|
311
|
+
#[wasm_bindgen::prelude::wasm_bindgen]
|
|
312
|
+
extern "C" {
|
|
313
|
+
#[wasm_bindgen(js_namespace = console)]
|
|
314
|
+
fn error(s: &str);
|
|
315
|
+
}
|
|
316
|
+
error(s);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/// Initialize default globals (console, Math, JSON, etc.)
|
|
320
|
+
#[allow(unused_variables)]
|
|
321
|
+
fn init_globals(enabled: &HashSet<String>) -> ObjectMap {
|
|
322
|
+
let mut g = ObjectMap::default();
|
|
323
|
+
|
|
324
|
+
let mut console = ObjectMap::default();
|
|
325
|
+
console.insert(
|
|
326
|
+
"debug".into(),
|
|
327
|
+
Value::native(|args: &[Value]| {
|
|
328
|
+
let s =
|
|
329
|
+
tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
|
|
330
|
+
vm_log(&s);
|
|
331
|
+
Value::Null
|
|
332
|
+
}),
|
|
333
|
+
);
|
|
334
|
+
console.insert(
|
|
335
|
+
"log".into(),
|
|
336
|
+
Value::native(|args: &[Value]| {
|
|
337
|
+
let s =
|
|
338
|
+
tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
|
|
339
|
+
vm_log(&s);
|
|
340
|
+
Value::Null
|
|
341
|
+
}),
|
|
342
|
+
);
|
|
343
|
+
console.insert(
|
|
344
|
+
"info".into(),
|
|
345
|
+
Value::native(|args: &[Value]| {
|
|
346
|
+
let s =
|
|
347
|
+
tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
|
|
348
|
+
vm_log(&s);
|
|
349
|
+
Value::Null
|
|
350
|
+
}),
|
|
351
|
+
);
|
|
352
|
+
console.insert(
|
|
353
|
+
"warn".into(),
|
|
354
|
+
Value::native(|args: &[Value]| {
|
|
355
|
+
let s =
|
|
356
|
+
tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
|
|
357
|
+
vm_log_err(&s);
|
|
358
|
+
Value::Null
|
|
359
|
+
}),
|
|
360
|
+
);
|
|
361
|
+
console.insert(
|
|
362
|
+
"error".into(),
|
|
363
|
+
Value::native(|args: &[Value]| {
|
|
364
|
+
let s =
|
|
365
|
+
tishlang_core::format_values_for_console(args, tishlang_core::use_console_colors());
|
|
366
|
+
vm_log_err(&s);
|
|
367
|
+
Value::Null
|
|
368
|
+
}),
|
|
369
|
+
);
|
|
370
|
+
g.insert("console".into(), value_object_from_map(console));
|
|
371
|
+
|
|
372
|
+
let mut math = ObjectMap::default();
|
|
373
|
+
math.insert(
|
|
374
|
+
"abs".into(),
|
|
375
|
+
Value::native(|args: &[Value]| {
|
|
376
|
+
let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
|
|
377
|
+
Value::Number(n.abs())
|
|
378
|
+
}),
|
|
379
|
+
);
|
|
380
|
+
math.insert(
|
|
381
|
+
"sqrt".into(),
|
|
382
|
+
Value::native(|args: &[Value]| {
|
|
383
|
+
let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
|
|
384
|
+
Value::Number(n.sqrt())
|
|
385
|
+
}),
|
|
386
|
+
);
|
|
387
|
+
math.insert(
|
|
388
|
+
"floor".into(),
|
|
389
|
+
Value::native(|args: &[Value]| {
|
|
390
|
+
let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
|
|
391
|
+
Value::Number(n.floor())
|
|
392
|
+
}),
|
|
393
|
+
);
|
|
394
|
+
math.insert(
|
|
395
|
+
"ceil".into(),
|
|
396
|
+
Value::native(|args: &[Value]| {
|
|
397
|
+
let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
|
|
398
|
+
Value::Number(n.ceil())
|
|
399
|
+
}),
|
|
400
|
+
);
|
|
401
|
+
math.insert(
|
|
402
|
+
"round".into(),
|
|
403
|
+
Value::native(|args: &[Value]| {
|
|
404
|
+
let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
|
|
405
|
+
Value::Number(n.round())
|
|
406
|
+
}),
|
|
407
|
+
);
|
|
408
|
+
math.insert(
|
|
409
|
+
"random".into(),
|
|
410
|
+
Value::native(|_| Value::Number(rand::random::<f64>())),
|
|
411
|
+
);
|
|
412
|
+
math.insert(
|
|
413
|
+
"min".into(),
|
|
414
|
+
Value::native(|args: &[Value]| {
|
|
415
|
+
let nums: Vec<f64> = args.iter().filter_map(|v| v.as_number()).collect();
|
|
416
|
+
Value::Number(nums.into_iter().fold(f64::NAN, |a, b| a.min(b)))
|
|
417
|
+
}),
|
|
418
|
+
);
|
|
419
|
+
math.insert(
|
|
420
|
+
"max".into(),
|
|
421
|
+
Value::native(|args: &[Value]| {
|
|
422
|
+
let nums: Vec<f64> = args.iter().filter_map(|v| v.as_number()).collect();
|
|
423
|
+
Value::Number(nums.into_iter().fold(f64::NAN, |a, b| a.max(b)))
|
|
424
|
+
}),
|
|
425
|
+
);
|
|
426
|
+
math.insert(
|
|
427
|
+
"pow".into(),
|
|
428
|
+
Value::native(|args: &[Value]| math_builtins::pow(args)),
|
|
429
|
+
);
|
|
430
|
+
math.insert(
|
|
431
|
+
"sin".into(),
|
|
432
|
+
Value::native(|args: &[Value]| math_builtins::sin(args)),
|
|
433
|
+
);
|
|
434
|
+
math.insert(
|
|
435
|
+
"cos".into(),
|
|
436
|
+
Value::native(|args: &[Value]| math_builtins::cos(args)),
|
|
437
|
+
);
|
|
438
|
+
math.insert(
|
|
439
|
+
"tan".into(),
|
|
440
|
+
Value::native(|args: &[Value]| math_builtins::tan(args)),
|
|
441
|
+
);
|
|
442
|
+
math.insert(
|
|
443
|
+
"log".into(),
|
|
444
|
+
Value::native(|args: &[Value]| math_builtins::log(args)),
|
|
445
|
+
);
|
|
446
|
+
math.insert(
|
|
447
|
+
"exp".into(),
|
|
448
|
+
Value::native(|args: &[Value]| math_builtins::exp(args)),
|
|
449
|
+
);
|
|
450
|
+
math.insert(
|
|
451
|
+
"sign".into(),
|
|
452
|
+
Value::native(|args: &[Value]| math_builtins::sign(args)),
|
|
453
|
+
);
|
|
454
|
+
math.insert(
|
|
455
|
+
"trunc".into(),
|
|
456
|
+
Value::native(|args: &[Value]| math_builtins::trunc(args)),
|
|
457
|
+
);
|
|
458
|
+
// Trig/hypot not covered by `math_builtins`; needed by the 3D engine's
|
|
459
|
+
// camera + character-controller math (atan2/hypot) on the wasm VM, where
|
|
460
|
+
// (unlike `--target js`) there is no host `Math` to fall through to.
|
|
461
|
+
math.insert(
|
|
462
|
+
"atan2".into(),
|
|
463
|
+
Value::native(|args: &[Value]| {
|
|
464
|
+
let y = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
|
|
465
|
+
let x = args.get(1).and_then(|v| v.as_number()).unwrap_or(f64::NAN);
|
|
466
|
+
Value::Number(y.atan2(x))
|
|
467
|
+
}),
|
|
468
|
+
);
|
|
469
|
+
math.insert(
|
|
470
|
+
"atan".into(),
|
|
471
|
+
Value::native(|args: &[Value]| {
|
|
472
|
+
let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
|
|
473
|
+
Value::Number(n.atan())
|
|
474
|
+
}),
|
|
475
|
+
);
|
|
476
|
+
math.insert(
|
|
477
|
+
"asin".into(),
|
|
478
|
+
Value::native(|args: &[Value]| {
|
|
479
|
+
let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
|
|
480
|
+
Value::Number(n.asin())
|
|
481
|
+
}),
|
|
482
|
+
);
|
|
483
|
+
math.insert(
|
|
484
|
+
"acos".into(),
|
|
485
|
+
Value::native(|args: &[Value]| {
|
|
486
|
+
let n = args.first().and_then(|v| v.as_number()).unwrap_or(f64::NAN);
|
|
487
|
+
Value::Number(n.acos())
|
|
488
|
+
}),
|
|
489
|
+
);
|
|
490
|
+
math.insert(
|
|
491
|
+
"hypot".into(),
|
|
492
|
+
Value::native(|args: &[Value]| {
|
|
493
|
+
let nums: Vec<f64> = args.iter().filter_map(|v| v.as_number()).collect();
|
|
494
|
+
let sum_sq: f64 = nums.iter().map(|n| n * n).sum();
|
|
495
|
+
Value::Number(sum_sq.sqrt())
|
|
496
|
+
}),
|
|
497
|
+
);
|
|
498
|
+
math.insert("PI".into(), Value::Number(std::f64::consts::PI));
|
|
499
|
+
math.insert("E".into(), Value::Number(std::f64::consts::E));
|
|
500
|
+
g.insert("Math".into(), value_object_from_map(math));
|
|
501
|
+
|
|
502
|
+
let mut json = ObjectMap::default();
|
|
503
|
+
json.insert(
|
|
504
|
+
"parse".into(),
|
|
505
|
+
Value::native(|args: &[Value]| {
|
|
506
|
+
let s = args
|
|
507
|
+
.first()
|
|
508
|
+
.map(|v| v.to_display_string())
|
|
509
|
+
.unwrap_or_default();
|
|
510
|
+
tishlang_core::json_parse(&s).unwrap_or(Value::Null)
|
|
511
|
+
}),
|
|
512
|
+
);
|
|
513
|
+
json.insert(
|
|
514
|
+
"stringify".into(),
|
|
515
|
+
Value::native(|args: &[Value]| {
|
|
516
|
+
let v = args.first().unwrap_or(&Value::Null);
|
|
517
|
+
Value::String(tishlang_core::json_stringify(v).into())
|
|
518
|
+
}),
|
|
519
|
+
);
|
|
520
|
+
g.insert("JSON".into(), value_object_from_map(json));
|
|
521
|
+
|
|
522
|
+
g.insert(
|
|
523
|
+
"parseInt".into(),
|
|
524
|
+
Value::native(|args: &[Value]| globals_builtins::parse_int(args)),
|
|
525
|
+
);
|
|
526
|
+
g.insert(
|
|
527
|
+
"parseFloat".into(),
|
|
528
|
+
Value::native(|args: &[Value]| globals_builtins::parse_float(args)),
|
|
529
|
+
);
|
|
530
|
+
g.insert(
|
|
531
|
+
"encodeURI".into(),
|
|
532
|
+
Value::native(|args: &[Value]| globals_builtins::encode_uri(args)),
|
|
533
|
+
);
|
|
534
|
+
g.insert(
|
|
535
|
+
"decodeURI".into(),
|
|
536
|
+
Value::native(|args: &[Value]| globals_builtins::decode_uri(args)),
|
|
537
|
+
);
|
|
538
|
+
g.insert(
|
|
539
|
+
"htmlEscape".into(),
|
|
540
|
+
Value::native(|args: &[Value]| {
|
|
541
|
+
tishlang_builtins::string::escape_html(args.first().unwrap_or(&Value::Null))
|
|
542
|
+
}),
|
|
543
|
+
);
|
|
544
|
+
g.insert(
|
|
545
|
+
"Boolean".into(),
|
|
546
|
+
Value::native(|args: &[Value]| globals_builtins::boolean(args)),
|
|
547
|
+
);
|
|
548
|
+
g.insert(
|
|
549
|
+
"isFinite".into(),
|
|
550
|
+
Value::native(|args: &[Value]| globals_builtins::is_finite(args)),
|
|
551
|
+
);
|
|
552
|
+
g.insert(
|
|
553
|
+
"isNaN".into(),
|
|
554
|
+
Value::native(|args: &[Value]| globals_builtins::is_nan(args)),
|
|
555
|
+
);
|
|
556
|
+
g.insert("Infinity".into(), Value::Number(f64::INFINITY));
|
|
557
|
+
g.insert("NaN".into(), Value::Number(f64::NAN));
|
|
558
|
+
g.insert(
|
|
559
|
+
"typeof".into(),
|
|
560
|
+
Value::native(|args: &[Value]| {
|
|
561
|
+
let v = args.first().unwrap_or(&Value::Null);
|
|
562
|
+
Value::String(v.type_name().into())
|
|
563
|
+
}),
|
|
564
|
+
);
|
|
565
|
+
g.insert(
|
|
566
|
+
"Symbol".into(),
|
|
567
|
+
tishlang_builtins::symbol::symbol_object(),
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
// Date - at minimum Date.now() for timing
|
|
571
|
+
let mut date = ObjectMap::default();
|
|
572
|
+
date.insert(
|
|
573
|
+
"now".into(),
|
|
574
|
+
Value::native(|_args: &[Value]| {
|
|
575
|
+
let ms = std::time::SystemTime::now()
|
|
576
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
577
|
+
.unwrap_or_default()
|
|
578
|
+
.as_millis() as f64;
|
|
579
|
+
Value::Number(ms)
|
|
580
|
+
}),
|
|
581
|
+
);
|
|
582
|
+
g.insert("Date".into(), value_object_from_map(date));
|
|
583
|
+
|
|
584
|
+
g.insert(
|
|
585
|
+
"Uint8Array".into(),
|
|
586
|
+
construct_builtin::uint8_array_constructor_value(),
|
|
587
|
+
);
|
|
588
|
+
g.insert(
|
|
589
|
+
"AudioContext".into(),
|
|
590
|
+
construct_builtin::audio_context_constructor_value(),
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
// Object methods - delegate to tishlang_builtins::globals
|
|
594
|
+
let mut object_methods = ObjectMap::default();
|
|
595
|
+
object_methods.insert(
|
|
596
|
+
"assign".into(),
|
|
597
|
+
Value::native(|args: &[Value]| globals_builtins::object_assign(args)),
|
|
598
|
+
);
|
|
599
|
+
object_methods.insert(
|
|
600
|
+
"fromEntries".into(),
|
|
601
|
+
Value::native(|args: &[Value]| globals_builtins::object_from_entries(args)),
|
|
602
|
+
);
|
|
603
|
+
object_methods.insert(
|
|
604
|
+
"keys".into(),
|
|
605
|
+
Value::native(|args: &[Value]| globals_builtins::object_keys(args)),
|
|
606
|
+
);
|
|
607
|
+
object_methods.insert(
|
|
608
|
+
"values".into(),
|
|
609
|
+
Value::native(|args: &[Value]| globals_builtins::object_values(args)),
|
|
610
|
+
);
|
|
611
|
+
object_methods.insert(
|
|
612
|
+
"entries".into(),
|
|
613
|
+
Value::native(|args: &[Value]| globals_builtins::object_entries(args)),
|
|
614
|
+
);
|
|
615
|
+
g.insert("Object".into(), value_object_from_map(object_methods));
|
|
616
|
+
|
|
617
|
+
// Array.isArray
|
|
618
|
+
let mut array_static = ObjectMap::default();
|
|
619
|
+
array_static.insert(
|
|
620
|
+
"isArray".into(),
|
|
621
|
+
Value::native(|args: &[Value]| globals_builtins::array_is_array(args)),
|
|
622
|
+
);
|
|
623
|
+
g.insert("Array".into(), value_object_from_map(array_static));
|
|
624
|
+
|
|
625
|
+
// String(value) as callable + String.fromCharCode
|
|
626
|
+
let string_convert_fn = Value::native(|args: &[Value]| globals_builtins::string_convert(args));
|
|
627
|
+
let mut string_static = ObjectMap::default();
|
|
628
|
+
string_static.insert(
|
|
629
|
+
"fromCharCode".into(),
|
|
630
|
+
Value::native(|args: &[Value]| globals_builtins::string_from_char_code(args)),
|
|
631
|
+
);
|
|
632
|
+
string_static.insert(Arc::from("__call"), string_convert_fn);
|
|
633
|
+
g.insert("String".into(), value_object_from_map(string_static));
|
|
634
|
+
|
|
635
|
+
// JSX / Lattish: stubs for bytecode VM when no DOM (e.g. console). Override via set_global in browser.
|
|
636
|
+
g.insert("h".into(), Value::native(|_args: &[Value]| Value::Null));
|
|
637
|
+
g.insert(
|
|
638
|
+
"Fragment".into(),
|
|
639
|
+
value_object_from_map(ObjectMap::default()),
|
|
640
|
+
);
|
|
641
|
+
g.insert(
|
|
642
|
+
"createRoot".into(),
|
|
643
|
+
Value::native(|_args: &[Value]| {
|
|
644
|
+
let mut render_obj = ObjectMap::default();
|
|
645
|
+
render_obj.insert(
|
|
646
|
+
"render".into(),
|
|
647
|
+
Value::native(|_args: &[Value]| Value::Null),
|
|
648
|
+
);
|
|
649
|
+
value_object_from_map(render_obj)
|
|
650
|
+
}),
|
|
651
|
+
);
|
|
652
|
+
g.insert(
|
|
653
|
+
"useState".into(),
|
|
654
|
+
Value::native(|args: &[Value]| {
|
|
655
|
+
let init = args.first().cloned().unwrap_or(Value::Null);
|
|
656
|
+
let arr = vec![init, Value::native(|_| Value::Null)];
|
|
657
|
+
Value::Array(VmRef::new(arr))
|
|
658
|
+
}),
|
|
659
|
+
);
|
|
660
|
+
let mut document_obj = ObjectMap::default();
|
|
661
|
+
document_obj.insert("body".into(), Value::Null);
|
|
662
|
+
g.insert("document".into(), value_object_from_map(document_obj));
|
|
663
|
+
|
|
664
|
+
#[cfg(feature = "process")]
|
|
665
|
+
if cap_allows(enabled, "process") {
|
|
666
|
+
let mut process_obj = ObjectMap::default();
|
|
667
|
+
process_obj.insert(
|
|
668
|
+
"exit".into(),
|
|
669
|
+
Value::native(|args: &[Value]| tishlang_runtime::process_exit(args)),
|
|
670
|
+
);
|
|
671
|
+
process_obj.insert(
|
|
672
|
+
"cwd".into(),
|
|
673
|
+
Value::native(|args: &[Value]| tishlang_runtime::process_cwd(args)),
|
|
674
|
+
);
|
|
675
|
+
process_obj.insert(
|
|
676
|
+
"exec".into(),
|
|
677
|
+
Value::native(|args: &[Value]| tishlang_runtime::process_exec(args)),
|
|
678
|
+
);
|
|
679
|
+
process_obj.insert(
|
|
680
|
+
"argv".into(),
|
|
681
|
+
Value::Array(VmRef::new(
|
|
682
|
+
std::env::args().map(|s| Value::String(s.into())).collect(),
|
|
683
|
+
)),
|
|
684
|
+
);
|
|
685
|
+
process_obj.insert(
|
|
686
|
+
"env".into(),
|
|
687
|
+
value_object_from_map(
|
|
688
|
+
std::env::vars()
|
|
689
|
+
.map(|(k, v)| (Arc::from(k.as_str()), Value::String(v.into())))
|
|
690
|
+
.collect(),
|
|
691
|
+
),
|
|
692
|
+
);
|
|
693
|
+
g.insert("process".into(), value_object_from_map(process_obj));
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
#[cfg(feature = "timers")]
|
|
697
|
+
if cap_allows(enabled, "timers") {
|
|
698
|
+
g.insert(
|
|
699
|
+
"setTimeout".into(),
|
|
700
|
+
Value::native(|args: &[Value]| tishlang_runtime::timer_set_timeout(args)),
|
|
701
|
+
);
|
|
702
|
+
g.insert(
|
|
703
|
+
"clearTimeout".into(),
|
|
704
|
+
Value::native(|args: &[Value]| tishlang_runtime::timer_clear_timeout(args)),
|
|
705
|
+
);
|
|
706
|
+
g.insert(
|
|
707
|
+
"setInterval".into(),
|
|
708
|
+
Value::native(|args: &[Value]| tishlang_runtime::timer_set_interval(args)),
|
|
709
|
+
);
|
|
710
|
+
g.insert(
|
|
711
|
+
"clearInterval".into(),
|
|
712
|
+
Value::native(|args: &[Value]| tishlang_runtime::timer_clear_interval(args)),
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
#[cfg(feature = "http")]
|
|
717
|
+
if cap_allows(enabled, "http") {
|
|
718
|
+
g.insert(
|
|
719
|
+
"fetch".into(),
|
|
720
|
+
Value::native(|args: &[Value]| tishlang_runtime::fetch_promise(args.to_vec())),
|
|
721
|
+
);
|
|
722
|
+
g.insert(
|
|
723
|
+
"fetchAll".into(),
|
|
724
|
+
Value::native(|args: &[Value]| tishlang_runtime::fetch_all_promise(args.to_vec())),
|
|
725
|
+
);
|
|
726
|
+
g.insert(
|
|
727
|
+
"registerStaticRoute".into(),
|
|
728
|
+
Value::native(|args: &[Value]| {
|
|
729
|
+
let path = match args.first() {
|
|
730
|
+
Some(Value::String(s)) => s.to_string(),
|
|
731
|
+
_ => return Value::Null,
|
|
732
|
+
};
|
|
733
|
+
let body = match args.get(1) {
|
|
734
|
+
Some(Value::String(s)) => s.as_bytes().to_vec(),
|
|
735
|
+
_ => return Value::Null,
|
|
736
|
+
};
|
|
737
|
+
let ct = match args.get(2) {
|
|
738
|
+
Some(Value::String(s)) => s.to_string(),
|
|
739
|
+
_ => "application/octet-stream".to_string(),
|
|
740
|
+
};
|
|
741
|
+
tishlang_runtime::register_static_route(&path, &body, &ct);
|
|
742
|
+
Value::Null
|
|
743
|
+
}),
|
|
744
|
+
);
|
|
745
|
+
g.insert(
|
|
746
|
+
"serve".into(),
|
|
747
|
+
Value::native(|args: &[Value]| {
|
|
748
|
+
// Phase-1 item 2 (see tish:http.serve above for full docs).
|
|
749
|
+
let raw = args.get(1).cloned().unwrap_or(Value::Null);
|
|
750
|
+
let handler_value = match raw {
|
|
751
|
+
Value::Function(_) => raw,
|
|
752
|
+
Value::Object(ref obj) => {
|
|
753
|
+
let obj_ref = obj.borrow();
|
|
754
|
+
if let Some(Value::Function(on_worker)) =
|
|
755
|
+
obj_ref.strings.get(&std::sync::Arc::from("onWorker")).cloned()
|
|
756
|
+
{
|
|
757
|
+
let args_for_init = [Value::Number(0.0)];
|
|
758
|
+
on_worker(&args_for_init)
|
|
759
|
+
} else if let Some(h) =
|
|
760
|
+
obj_ref.strings.get(&std::sync::Arc::from("handler")).cloned()
|
|
761
|
+
{
|
|
762
|
+
h
|
|
763
|
+
} else {
|
|
764
|
+
Value::Null
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
_ => Value::Null,
|
|
768
|
+
};
|
|
769
|
+
if let Value::Function(f) = handler_value {
|
|
770
|
+
tishlang_runtime::http_serve(args, move |req_args| f(req_args))
|
|
771
|
+
} else {
|
|
772
|
+
Value::Null
|
|
773
|
+
}
|
|
774
|
+
}),
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
#[cfg(any(feature = "http", feature = "promise"))]
|
|
779
|
+
if cap_allows(enabled, "http") || cap_allows(enabled, "promise") {
|
|
780
|
+
g.insert("Promise".into(), tishlang_runtime::promise_object());
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
g
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/// Shared scope for closure capture (parent frame's locals).
|
|
787
|
+
type ScopeMap = VmRef<ObjectMap>;
|
|
788
|
+
|
|
789
|
+
/// Options for the convenience [`run_with_options`] helper (one-shot VM run from the CLI).
|
|
790
|
+
#[derive(Clone, Debug, Default)]
|
|
791
|
+
pub struct VmRunOptions {
|
|
792
|
+
/// When true and not inside a nested chunk (`enclosing` is `None`), top-level [`Opcode::DeclareVar`]
|
|
793
|
+
/// also writes to globals so the REPL keeps bindings across input lines.
|
|
794
|
+
pub repl_mode: bool,
|
|
795
|
+
/// Enabled capabilities for this run (e.g. `fs`, `http`, `full`). Empty = none (secure default).
|
|
796
|
+
pub capabilities: HashSet<String>,
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
pub struct Vm {
|
|
800
|
+
stack: Vec<Value>,
|
|
801
|
+
scope: ObjectMap,
|
|
802
|
+
/// Enclosing scope for closures (captured parent frame locals).
|
|
803
|
+
enclosing: Option<ScopeMap>,
|
|
804
|
+
globals: VmRef<ObjectMap>,
|
|
805
|
+
/// Capabilities for `LoadNativeExport` and globals such as `process` / `serve`.
|
|
806
|
+
capabilities: Arc<HashSet<String>>,
|
|
807
|
+
/// Externally registered native modules, keyed by import spec (e.g.
|
|
808
|
+
/// `"cargo:tish_pg"`). Populated by embedders before `run` (see
|
|
809
|
+
/// [`register_native_module`]). Phase-2 item 11: unblocks `cargo:`
|
|
810
|
+
/// imports on the cranelift and llvm backends which run this VM.
|
|
811
|
+
native_modules: VmRef<HashMap<String, VmRef<ObjectMap>>>,
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
impl Vm {
|
|
815
|
+
/// VM with every capability that exists in this `tishlang_vm` build (embedders, tests, `run()`).
|
|
816
|
+
pub fn new() -> Self {
|
|
817
|
+
Self::with_capabilities_arc(Arc::new(all_compiled_capabilities()))
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/// VM with an explicit capability set (e.g. from `tish run --feature …`).
|
|
821
|
+
pub fn with_capabilities(capabilities: HashSet<String>) -> Self {
|
|
822
|
+
Self::with_capabilities_arc(Arc::new(capabilities))
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
fn with_capabilities_arc(capabilities: Arc<HashSet<String>>) -> Self {
|
|
826
|
+
Self {
|
|
827
|
+
stack: Vec::new(),
|
|
828
|
+
scope: ObjectMap::default(),
|
|
829
|
+
enclosing: None,
|
|
830
|
+
globals: VmRef::new(init_globals(capabilities.as_ref())),
|
|
831
|
+
capabilities,
|
|
832
|
+
native_modules: VmRef::new(HashMap::new()),
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/// Register an externally-supplied native module under a `cargo:`-style
|
|
837
|
+
/// spec (e.g. `"cargo:tish_pg"`). The `exports` map is what
|
|
838
|
+
/// `LoadNativeExport` will index into when user code imports from this
|
|
839
|
+
/// spec. Intended to be called by the `tishlang_cranelift_runtime` /
|
|
840
|
+
/// `tishlang_llvm` link step, or by external embedders that want to
|
|
841
|
+
/// expose Rust crates to `.tish` programs running on the bytecode VM.
|
|
842
|
+
pub fn register_native_module(&mut self, spec: impl Into<String>, exports: ObjectMap) {
|
|
843
|
+
self.native_modules
|
|
844
|
+
.borrow_mut()
|
|
845
|
+
.insert(spec.into(), VmRef::new(exports));
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
pub fn get_global(&self, name: &str) -> Option<Value> {
|
|
849
|
+
self.globals.borrow().get(name).cloned()
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
pub fn set_global(&mut self, name: Arc<str>, value: Value) {
|
|
853
|
+
self.globals.borrow_mut().insert(name, value);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/// Names of all globals (for REPL bare-word tab completion).
|
|
857
|
+
pub fn global_names(&self) -> Vec<String> {
|
|
858
|
+
self.globals
|
|
859
|
+
.borrow()
|
|
860
|
+
.keys()
|
|
861
|
+
.map(|k| k.as_ref().to_string())
|
|
862
|
+
.collect()
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
fn read_u16(code: &[u8], ip: &mut usize) -> u16 {
|
|
866
|
+
let a = code[*ip] as u16;
|
|
867
|
+
let b = code[*ip + 1] as u16;
|
|
868
|
+
*ip += 2;
|
|
869
|
+
(a << 8) | b
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
fn read_i16(code: &[u8], ip: &mut usize) -> i16 {
|
|
873
|
+
Self::read_u16(code, ip) as i16
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/// Pop innermost try handler, truncate stack, push thrown value, jump to catch.
|
|
877
|
+
fn unwind_throw(
|
|
878
|
+
try_handlers: &mut Vec<(usize, usize)>,
|
|
879
|
+
stack: &mut Vec<Value>,
|
|
880
|
+
ip: &mut usize,
|
|
881
|
+
v: Value,
|
|
882
|
+
) -> Result<(), String> {
|
|
883
|
+
let (catch_ip, stack_len) = try_handlers
|
|
884
|
+
.pop()
|
|
885
|
+
.ok_or_else(|| format!("Uncaught throw: {}", v.to_display_string()))?;
|
|
886
|
+
stack.truncate(stack_len);
|
|
887
|
+
stack.push(v);
|
|
888
|
+
*ip = catch_ip;
|
|
889
|
+
Ok(())
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
pub fn run(&mut self, chunk: &Chunk) -> Result<Value, String> {
|
|
893
|
+
self.run_with_options(chunk, false)
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/// Run a chunk using this VM's capability set. `repl_mode` persists top-level `let` across REPL lines.
|
|
897
|
+
pub fn run_with_options(&mut self, chunk: &Chunk, repl_mode: bool) -> Result<Value, String> {
|
|
898
|
+
self.run_chunk(chunk, &chunk.nested, &[], repl_mode)
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
fn run_chunk(
|
|
902
|
+
&mut self,
|
|
903
|
+
chunk: &Chunk,
|
|
904
|
+
nested: &[Chunk],
|
|
905
|
+
args: &[Value],
|
|
906
|
+
repl_mode: bool,
|
|
907
|
+
) -> Result<Value, String> {
|
|
908
|
+
let code = &chunk.code;
|
|
909
|
+
let constants = &chunk.constants;
|
|
910
|
+
let names = &chunk.names;
|
|
911
|
+
|
|
912
|
+
let mut ip = 0;
|
|
913
|
+
let local_scope: ScopeMap = VmRef::new(ObjectMap::default());
|
|
914
|
+
{
|
|
915
|
+
let mut ls = local_scope.borrow_mut();
|
|
916
|
+
let param_count = chunk.param_count as usize;
|
|
917
|
+
if chunk.rest_param_index != NO_REST_PARAM {
|
|
918
|
+
let ri = chunk.rest_param_index as usize;
|
|
919
|
+
for (i, name) in chunk.names.iter().take(param_count).enumerate() {
|
|
920
|
+
if i < ri {
|
|
921
|
+
let v = args.get(i).cloned().unwrap_or(Value::Null);
|
|
922
|
+
ls.insert(Arc::clone(name), v);
|
|
923
|
+
} else if i == ri {
|
|
924
|
+
let rest_arr: Vec<Value> = args.iter().skip(ri).cloned().collect();
|
|
925
|
+
ls.insert(Arc::clone(name), Value::Array(VmRef::new(rest_arr)));
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
} else {
|
|
929
|
+
for (i, name) in chunk.names.iter().take(param_count).enumerate() {
|
|
930
|
+
if let Some(v) = args.get(i) {
|
|
931
|
+
ls.insert(Arc::clone(name), v.clone());
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
let mut try_handlers: Vec<(usize, usize)> = vec![];
|
|
937
|
+
let mut block_undo_stack: Vec<Vec<(Arc<str>, Option<Value>)>> = vec![];
|
|
938
|
+
|
|
939
|
+
loop {
|
|
940
|
+
if ip >= code.len() {
|
|
941
|
+
break;
|
|
942
|
+
}
|
|
943
|
+
let op = code[ip];
|
|
944
|
+
ip += 1;
|
|
945
|
+
if op == Opcode::Nop as u8 {
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
let opcode = Opcode::from_u8(op).ok_or_else(|| format!("Unknown opcode: {}", op))?;
|
|
949
|
+
|
|
950
|
+
match opcode {
|
|
951
|
+
Opcode::Nop => {}
|
|
952
|
+
Opcode::LoadConst => {
|
|
953
|
+
let idx = Self::read_u16(code, &mut ip);
|
|
954
|
+
let c = constants
|
|
955
|
+
.get(idx as usize)
|
|
956
|
+
.ok_or_else(|| format!("Constant index out of bounds: {}", idx))?;
|
|
957
|
+
let v = match c {
|
|
958
|
+
Constant::Number(n) => Value::Number(*n),
|
|
959
|
+
Constant::String(s) => Value::String(Arc::clone(s)),
|
|
960
|
+
Constant::Bool(b) => Value::Bool(*b),
|
|
961
|
+
Constant::Null => Value::Null,
|
|
962
|
+
Constant::Closure(nested_idx) => {
|
|
963
|
+
let inner = nested
|
|
964
|
+
.get(*nested_idx)
|
|
965
|
+
.ok_or_else(|| "Nested chunk index out of bounds".to_string())?;
|
|
966
|
+
let inner_clone = inner.clone();
|
|
967
|
+
let globals = self.globals.clone();
|
|
968
|
+
let enclosing = Some(local_scope.clone());
|
|
969
|
+
let capabilities = Arc::clone(&self.capabilities);
|
|
970
|
+
let native_modules = self.native_modules.clone();
|
|
971
|
+
Value::native(move |args: &[Value]| {
|
|
972
|
+
let mut vm = Vm {
|
|
973
|
+
stack: Vec::new(),
|
|
974
|
+
scope: ObjectMap::default(),
|
|
975
|
+
enclosing: enclosing.clone(),
|
|
976
|
+
globals: globals.clone(),
|
|
977
|
+
capabilities: Arc::clone(&capabilities),
|
|
978
|
+
native_modules: native_modules.clone(),
|
|
979
|
+
};
|
|
980
|
+
vm.run_chunk(&inner_clone, &inner_clone.nested, args, false)
|
|
981
|
+
.unwrap_or(Value::Null)
|
|
982
|
+
})
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
self.stack.push(v);
|
|
986
|
+
}
|
|
987
|
+
Opcode::LoadVar => {
|
|
988
|
+
let idx = Self::read_u16(code, &mut ip);
|
|
989
|
+
let name = names
|
|
990
|
+
.get(idx as usize)
|
|
991
|
+
.ok_or_else(|| format!("Name index out of bounds: {}", idx))?;
|
|
992
|
+
let v = local_scope
|
|
993
|
+
.borrow()
|
|
994
|
+
.get(name.as_ref())
|
|
995
|
+
.cloned()
|
|
996
|
+
.or_else(|| {
|
|
997
|
+
self.enclosing
|
|
998
|
+
.as_ref()
|
|
999
|
+
.and_then(|e| e.borrow().get(name.as_ref()).cloned())
|
|
1000
|
+
})
|
|
1001
|
+
.or_else(|| self.scope.get(name.as_ref()).cloned())
|
|
1002
|
+
.or_else(|| self.globals.borrow().get(name.as_ref()).cloned())
|
|
1003
|
+
.ok_or_else(|| format!("Undefined variable: {}", name))?;
|
|
1004
|
+
self.stack.push(v);
|
|
1005
|
+
}
|
|
1006
|
+
Opcode::StoreVar => {
|
|
1007
|
+
let idx = Self::read_u16(code, &mut ip);
|
|
1008
|
+
let name = names
|
|
1009
|
+
.get(idx as usize)
|
|
1010
|
+
.ok_or_else(|| format!("Name index out of bounds: {}", idx))?;
|
|
1011
|
+
let v = self
|
|
1012
|
+
.stack
|
|
1013
|
+
.pop()
|
|
1014
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1015
|
+
// Update innermost scope that has the variable (matches interpreter Scope.assign)
|
|
1016
|
+
if local_scope.borrow().contains_key(name.as_ref()) {
|
|
1017
|
+
local_scope.borrow_mut().insert(Arc::clone(name), v);
|
|
1018
|
+
} else if self
|
|
1019
|
+
.enclosing
|
|
1020
|
+
.as_ref()
|
|
1021
|
+
.map(|e| e.borrow().contains_key(name.as_ref()))
|
|
1022
|
+
.unwrap_or(false)
|
|
1023
|
+
{
|
|
1024
|
+
let en = self.enclosing.as_ref().unwrap();
|
|
1025
|
+
en.borrow_mut().insert(Arc::clone(name), v);
|
|
1026
|
+
} else if self.scope.contains_key(name.as_ref()) {
|
|
1027
|
+
self.scope.insert(Arc::clone(name), v);
|
|
1028
|
+
} else if self.globals.borrow().contains_key(name.as_ref()) {
|
|
1029
|
+
self.globals.borrow_mut().insert(Arc::clone(name), v);
|
|
1030
|
+
} else {
|
|
1031
|
+
// New variable: at top level (no enclosing) store in globals so REPL persists across lines
|
|
1032
|
+
if self.enclosing.is_none() {
|
|
1033
|
+
self.globals.borrow_mut().insert(Arc::clone(name), v);
|
|
1034
|
+
} else {
|
|
1035
|
+
local_scope.borrow_mut().insert(Arc::clone(name), v);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
Opcode::DeclareVar => {
|
|
1040
|
+
let idx = Self::read_u16(code, &mut ip);
|
|
1041
|
+
let name = names
|
|
1042
|
+
.get(idx as usize)
|
|
1043
|
+
.ok_or_else(|| format!("Name index out of bounds: {}", idx))?;
|
|
1044
|
+
let v = self
|
|
1045
|
+
.stack
|
|
1046
|
+
.pop()
|
|
1047
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1048
|
+
if let Some(frame) = block_undo_stack.last_mut() {
|
|
1049
|
+
let old = local_scope.borrow().get(name.as_ref()).cloned();
|
|
1050
|
+
frame.push((Arc::clone(name), old));
|
|
1051
|
+
}
|
|
1052
|
+
// REPL: persist top-level bindings only (not block-locals shadowing globals).
|
|
1053
|
+
if repl_mode && self.enclosing.is_none() && block_undo_stack.is_empty() {
|
|
1054
|
+
self.globals
|
|
1055
|
+
.borrow_mut()
|
|
1056
|
+
.insert(Arc::clone(name), v.clone());
|
|
1057
|
+
}
|
|
1058
|
+
local_scope.borrow_mut().insert(Arc::clone(name), v);
|
|
1059
|
+
}
|
|
1060
|
+
Opcode::DeclareVarPlain => {
|
|
1061
|
+
let idx = Self::read_u16(code, &mut ip);
|
|
1062
|
+
let name = names
|
|
1063
|
+
.get(idx as usize)
|
|
1064
|
+
.ok_or_else(|| format!("Name index out of bounds: {}", idx))?;
|
|
1065
|
+
let v = self
|
|
1066
|
+
.stack
|
|
1067
|
+
.pop()
|
|
1068
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1069
|
+
if repl_mode && self.enclosing.is_none() && block_undo_stack.is_empty() {
|
|
1070
|
+
self.globals
|
|
1071
|
+
.borrow_mut()
|
|
1072
|
+
.insert(Arc::clone(name), v.clone());
|
|
1073
|
+
}
|
|
1074
|
+
local_scope.borrow_mut().insert(Arc::clone(name), v);
|
|
1075
|
+
}
|
|
1076
|
+
Opcode::EnterBlock => {
|
|
1077
|
+
block_undo_stack.push(Vec::new());
|
|
1078
|
+
}
|
|
1079
|
+
Opcode::ExitBlock => {
|
|
1080
|
+
let frame = block_undo_stack
|
|
1081
|
+
.pop()
|
|
1082
|
+
.ok_or_else(|| "ExitBlock without matching EnterBlock".to_string())?;
|
|
1083
|
+
for (name, old) in frame.into_iter().rev() {
|
|
1084
|
+
let mut ls = local_scope.borrow_mut();
|
|
1085
|
+
match old {
|
|
1086
|
+
Some(prev) => {
|
|
1087
|
+
ls.insert(name, prev);
|
|
1088
|
+
}
|
|
1089
|
+
None => {
|
|
1090
|
+
ls.remove(name.as_ref());
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
Opcode::LoadGlobal => {
|
|
1096
|
+
let idx = Self::read_u16(code, &mut ip);
|
|
1097
|
+
let name = names
|
|
1098
|
+
.get(idx as usize)
|
|
1099
|
+
.ok_or_else(|| format!("Name index out of bounds: {}", idx))?;
|
|
1100
|
+
let v = self
|
|
1101
|
+
.globals
|
|
1102
|
+
.borrow()
|
|
1103
|
+
.get(name.as_ref())
|
|
1104
|
+
.cloned()
|
|
1105
|
+
.ok_or_else(|| format!("Undefined global: {}", name))?;
|
|
1106
|
+
self.stack.push(v);
|
|
1107
|
+
}
|
|
1108
|
+
Opcode::StoreGlobal => {
|
|
1109
|
+
let idx = Self::read_u16(code, &mut ip);
|
|
1110
|
+
let name = names
|
|
1111
|
+
.get(idx as usize)
|
|
1112
|
+
.ok_or_else(|| format!("Name index out of bounds: {}", idx))?;
|
|
1113
|
+
let v = self
|
|
1114
|
+
.stack
|
|
1115
|
+
.pop()
|
|
1116
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1117
|
+
self.globals.borrow_mut().insert(Arc::clone(name), v);
|
|
1118
|
+
}
|
|
1119
|
+
Opcode::Pop => {
|
|
1120
|
+
self.stack
|
|
1121
|
+
.pop()
|
|
1122
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1123
|
+
}
|
|
1124
|
+
Opcode::PopN => {
|
|
1125
|
+
let n = Self::read_u16(code, &mut ip) as usize;
|
|
1126
|
+
for _ in 0..n {
|
|
1127
|
+
self.stack
|
|
1128
|
+
.pop()
|
|
1129
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
Opcode::Dup => {
|
|
1133
|
+
let v = self
|
|
1134
|
+
.stack
|
|
1135
|
+
.last()
|
|
1136
|
+
.ok_or_else(|| "Stack underflow".to_string())?
|
|
1137
|
+
.clone();
|
|
1138
|
+
self.stack.push(v);
|
|
1139
|
+
}
|
|
1140
|
+
Opcode::Call => {
|
|
1141
|
+
let argc = Self::read_u16(code, &mut ip) as usize;
|
|
1142
|
+
let mut args = Vec::with_capacity(argc);
|
|
1143
|
+
for _ in 0..argc {
|
|
1144
|
+
args.push(
|
|
1145
|
+
self.stack
|
|
1146
|
+
.pop()
|
|
1147
|
+
.ok_or_else(|| "Stack underflow in call".to_string())?,
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
args.reverse();
|
|
1151
|
+
let callee = self
|
|
1152
|
+
.stack
|
|
1153
|
+
.pop()
|
|
1154
|
+
.ok_or_else(|| "Stack underflow: no callee".to_string())?;
|
|
1155
|
+
let f = match &callee {
|
|
1156
|
+
Value::Function(f) => f.clone(),
|
|
1157
|
+
Value::Object(o) => {
|
|
1158
|
+
if let Some(Value::Function(call_fn)) =
|
|
1159
|
+
o.borrow().strings.get("__call")
|
|
1160
|
+
{
|
|
1161
|
+
call_fn.clone()
|
|
1162
|
+
} else {
|
|
1163
|
+
return Err(format!(
|
|
1164
|
+
"Call of non-function: {}",
|
|
1165
|
+
callee.type_name()
|
|
1166
|
+
));
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
_ => {
|
|
1170
|
+
return Err(format!("Call of non-function: {}", callee.type_name()));
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
let result = f(&args);
|
|
1174
|
+
self.stack.push(result);
|
|
1175
|
+
}
|
|
1176
|
+
Opcode::CallSpread => {
|
|
1177
|
+
let callee = self
|
|
1178
|
+
.stack
|
|
1179
|
+
.pop()
|
|
1180
|
+
.ok_or_else(|| "Stack underflow: no callee in CallSpread".to_string())?;
|
|
1181
|
+
let args_array = self
|
|
1182
|
+
.stack
|
|
1183
|
+
.pop()
|
|
1184
|
+
.ok_or_else(|| "Stack underflow in CallSpread".to_string())?;
|
|
1185
|
+
let args: Vec<Value> = match &args_array {
|
|
1186
|
+
Value::Array(a) => a.borrow().clone(),
|
|
1187
|
+
_ => {
|
|
1188
|
+
return Err(format!(
|
|
1189
|
+
"CallSpread: args must be array, got {}",
|
|
1190
|
+
args_array.to_display_string()
|
|
1191
|
+
));
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
let f = match &callee {
|
|
1195
|
+
Value::Function(f) => f.clone(),
|
|
1196
|
+
Value::Object(o) => {
|
|
1197
|
+
if let Some(Value::Function(call_fn)) =
|
|
1198
|
+
o.borrow().strings.get("__call")
|
|
1199
|
+
{
|
|
1200
|
+
call_fn.clone()
|
|
1201
|
+
} else {
|
|
1202
|
+
return Err(format!(
|
|
1203
|
+
"Call of non-function: {}",
|
|
1204
|
+
callee.type_name()
|
|
1205
|
+
));
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
_ => {
|
|
1209
|
+
return Err(format!("Call of non-function: {}", callee.type_name()));
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
let result = f(&args);
|
|
1213
|
+
self.stack.push(result);
|
|
1214
|
+
}
|
|
1215
|
+
Opcode::Construct => {
|
|
1216
|
+
let argc = Self::read_u16(code, &mut ip) as usize;
|
|
1217
|
+
let mut args = Vec::with_capacity(argc);
|
|
1218
|
+
for _ in 0..argc {
|
|
1219
|
+
args.push(
|
|
1220
|
+
self.stack
|
|
1221
|
+
.pop()
|
|
1222
|
+
.ok_or_else(|| "Stack underflow in construct".to_string())?,
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
args.reverse();
|
|
1226
|
+
let callee = self
|
|
1227
|
+
.stack
|
|
1228
|
+
.pop()
|
|
1229
|
+
.ok_or_else(|| "Stack underflow: no callee for construct".to_string())?;
|
|
1230
|
+
let result = construct_builtin::construct(&callee, &args);
|
|
1231
|
+
self.stack.push(result);
|
|
1232
|
+
}
|
|
1233
|
+
Opcode::ConstructSpread => {
|
|
1234
|
+
let callee = self
|
|
1235
|
+
.stack
|
|
1236
|
+
.pop()
|
|
1237
|
+
.ok_or_else(|| "Stack underflow: callee in ConstructSpread".to_string())?;
|
|
1238
|
+
let args_array = self
|
|
1239
|
+
.stack
|
|
1240
|
+
.pop()
|
|
1241
|
+
.ok_or_else(|| "Stack underflow in ConstructSpread".to_string())?;
|
|
1242
|
+
let args: Vec<Value> = match &args_array {
|
|
1243
|
+
Value::Array(a) => a.borrow().clone(),
|
|
1244
|
+
_ => {
|
|
1245
|
+
return Err(format!(
|
|
1246
|
+
"ConstructSpread: args must be array, got {}",
|
|
1247
|
+
args_array.to_display_string()
|
|
1248
|
+
));
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1251
|
+
let result = construct_builtin::construct(&callee, &args);
|
|
1252
|
+
self.stack.push(result);
|
|
1253
|
+
}
|
|
1254
|
+
Opcode::Return => {
|
|
1255
|
+
let v = self.stack.pop().unwrap_or(Value::Null);
|
|
1256
|
+
return Ok(v);
|
|
1257
|
+
}
|
|
1258
|
+
Opcode::Jump => {
|
|
1259
|
+
let offset = Self::read_i16(code, &mut ip) as isize;
|
|
1260
|
+
ip = (ip as isize + offset).max(0) as usize;
|
|
1261
|
+
}
|
|
1262
|
+
Opcode::JumpIfFalse => {
|
|
1263
|
+
let offset = Self::read_i16(code, &mut ip) as isize;
|
|
1264
|
+
let v = self
|
|
1265
|
+
.stack
|
|
1266
|
+
.pop()
|
|
1267
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1268
|
+
if !v.is_truthy() {
|
|
1269
|
+
ip = (ip as isize + offset).max(0) as usize;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
Opcode::JumpBack => {
|
|
1273
|
+
let dist = Self::read_u16(code, &mut ip) as usize;
|
|
1274
|
+
ip = ip.saturating_sub(dist);
|
|
1275
|
+
}
|
|
1276
|
+
Opcode::BinOp => {
|
|
1277
|
+
let op_u8 = Self::read_u16(code, &mut ip) as u8;
|
|
1278
|
+
let r = self
|
|
1279
|
+
.stack
|
|
1280
|
+
.pop()
|
|
1281
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1282
|
+
let l = self
|
|
1283
|
+
.stack
|
|
1284
|
+
.pop()
|
|
1285
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1286
|
+
let op =
|
|
1287
|
+
u8_to_binop(op_u8).ok_or_else(|| format!("Unknown binop: {}", op_u8))?;
|
|
1288
|
+
let result = eval_binop(op, &l, &r)?;
|
|
1289
|
+
self.stack.push(result);
|
|
1290
|
+
}
|
|
1291
|
+
Opcode::UnaryOp => {
|
|
1292
|
+
let op_u8 = Self::read_u16(code, &mut ip) as u8;
|
|
1293
|
+
let o = self
|
|
1294
|
+
.stack
|
|
1295
|
+
.pop()
|
|
1296
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1297
|
+
let op = u8_to_unaryop(op_u8)
|
|
1298
|
+
.ok_or_else(|| format!("Unknown unary op: {}", op_u8))?;
|
|
1299
|
+
let result = eval_unary(op, &o)?;
|
|
1300
|
+
self.stack.push(result);
|
|
1301
|
+
}
|
|
1302
|
+
Opcode::GetMember => {
|
|
1303
|
+
let idx = Self::read_u16(code, &mut ip);
|
|
1304
|
+
let key = names
|
|
1305
|
+
.get(idx as usize)
|
|
1306
|
+
.ok_or_else(|| "Name index out of bounds".to_string())?;
|
|
1307
|
+
let obj = self
|
|
1308
|
+
.stack
|
|
1309
|
+
.pop()
|
|
1310
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1311
|
+
let v = get_member(&obj, key)?;
|
|
1312
|
+
self.stack.push(v);
|
|
1313
|
+
}
|
|
1314
|
+
Opcode::GetMemberOptional => {
|
|
1315
|
+
let idx = Self::read_u16(code, &mut ip);
|
|
1316
|
+
let key = names
|
|
1317
|
+
.get(idx as usize)
|
|
1318
|
+
.ok_or_else(|| "Name index out of bounds".to_string())?;
|
|
1319
|
+
let obj = self
|
|
1320
|
+
.stack
|
|
1321
|
+
.pop()
|
|
1322
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1323
|
+
let v = get_member(&obj, key).unwrap_or(Value::Null);
|
|
1324
|
+
self.stack.push(v);
|
|
1325
|
+
}
|
|
1326
|
+
Opcode::SetMember => {
|
|
1327
|
+
let idx = Self::read_u16(code, &mut ip);
|
|
1328
|
+
let key = names
|
|
1329
|
+
.get(idx as usize)
|
|
1330
|
+
.ok_or_else(|| "Name index out of bounds".to_string())?;
|
|
1331
|
+
let val = self
|
|
1332
|
+
.stack
|
|
1333
|
+
.pop()
|
|
1334
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1335
|
+
let obj = self
|
|
1336
|
+
.stack
|
|
1337
|
+
.pop()
|
|
1338
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1339
|
+
set_member(&obj, key, val.clone())?;
|
|
1340
|
+
self.stack.push(val); // assignment yields value
|
|
1341
|
+
}
|
|
1342
|
+
Opcode::GetIndex => {
|
|
1343
|
+
let idx_val = self
|
|
1344
|
+
.stack
|
|
1345
|
+
.pop()
|
|
1346
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1347
|
+
let obj = self
|
|
1348
|
+
.stack
|
|
1349
|
+
.pop()
|
|
1350
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1351
|
+
let v = get_index(&obj, &idx_val)?;
|
|
1352
|
+
self.stack.push(v);
|
|
1353
|
+
}
|
|
1354
|
+
Opcode::SetIndex => {
|
|
1355
|
+
// Stack: [obj, idx, val, val] (Dup of val for expression result).
|
|
1356
|
+
// Pop val (dup), val, idx, obj; use (obj, idx, val) for set_index; leave val on stack.
|
|
1357
|
+
let dup_val = self
|
|
1358
|
+
.stack
|
|
1359
|
+
.pop()
|
|
1360
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1361
|
+
let val = self
|
|
1362
|
+
.stack
|
|
1363
|
+
.pop()
|
|
1364
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1365
|
+
let idx_val = self
|
|
1366
|
+
.stack
|
|
1367
|
+
.pop()
|
|
1368
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1369
|
+
let obj = self
|
|
1370
|
+
.stack
|
|
1371
|
+
.pop()
|
|
1372
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1373
|
+
set_index(&obj, &idx_val, val.clone())?;
|
|
1374
|
+
self.stack.push(dup_val); // assignment yields the assigned value
|
|
1375
|
+
}
|
|
1376
|
+
Opcode::NewArray => {
|
|
1377
|
+
let n = Self::read_u16(code, &mut ip) as usize;
|
|
1378
|
+
let mut elems = Vec::with_capacity(n);
|
|
1379
|
+
for _ in 0..n {
|
|
1380
|
+
elems.push(
|
|
1381
|
+
self.stack
|
|
1382
|
+
.pop()
|
|
1383
|
+
.ok_or_else(|| "Stack underflow".to_string())?,
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
elems.reverse();
|
|
1387
|
+
self.stack.push(Value::Array(VmRef::new(elems)));
|
|
1388
|
+
}
|
|
1389
|
+
Opcode::NewObject => {
|
|
1390
|
+
let n = Self::read_u16(code, &mut ip) as usize;
|
|
1391
|
+
let mut map = ObjectMap::with_capacity(n.max(1));
|
|
1392
|
+
for _ in 0..n {
|
|
1393
|
+
let val = self
|
|
1394
|
+
.stack
|
|
1395
|
+
.pop()
|
|
1396
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1397
|
+
let key_val = self
|
|
1398
|
+
.stack
|
|
1399
|
+
.pop()
|
|
1400
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1401
|
+
let key = key_val.to_display_string().into();
|
|
1402
|
+
map.insert(key, val);
|
|
1403
|
+
}
|
|
1404
|
+
self.stack.push(value_object_from_map(map));
|
|
1405
|
+
}
|
|
1406
|
+
Opcode::EnterTry => {
|
|
1407
|
+
let offset = Self::read_u16(code, &mut ip) as usize;
|
|
1408
|
+
let catch_ip = ip + offset;
|
|
1409
|
+
try_handlers.push((catch_ip, self.stack.len()));
|
|
1410
|
+
}
|
|
1411
|
+
Opcode::ExitTry => {
|
|
1412
|
+
try_handlers.pop();
|
|
1413
|
+
}
|
|
1414
|
+
Opcode::ConcatArray => {
|
|
1415
|
+
let right = self
|
|
1416
|
+
.stack
|
|
1417
|
+
.pop()
|
|
1418
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1419
|
+
let left = self
|
|
1420
|
+
.stack
|
|
1421
|
+
.pop()
|
|
1422
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1423
|
+
let (mut a, b) = (
|
|
1424
|
+
match &left {
|
|
1425
|
+
Value::Array(arr) => arr.borrow().clone(),
|
|
1426
|
+
_ => {
|
|
1427
|
+
return Err(format!(
|
|
1428
|
+
"ConcatArray: left must be array, got {}",
|
|
1429
|
+
left.to_display_string()
|
|
1430
|
+
));
|
|
1431
|
+
}
|
|
1432
|
+
},
|
|
1433
|
+
match &right {
|
|
1434
|
+
Value::Array(arr) => arr.borrow().clone(),
|
|
1435
|
+
_ => {
|
|
1436
|
+
return Err(format!(
|
|
1437
|
+
"ConcatArray: right must be array, got {}",
|
|
1438
|
+
right.to_display_string()
|
|
1439
|
+
));
|
|
1440
|
+
}
|
|
1441
|
+
},
|
|
1442
|
+
);
|
|
1443
|
+
a.extend(b);
|
|
1444
|
+
self.stack.push(Value::Array(VmRef::new(a)));
|
|
1445
|
+
}
|
|
1446
|
+
Opcode::MergeObject => {
|
|
1447
|
+
let right = self
|
|
1448
|
+
.stack
|
|
1449
|
+
.pop()
|
|
1450
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1451
|
+
let left = self
|
|
1452
|
+
.stack
|
|
1453
|
+
.pop()
|
|
1454
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1455
|
+
match (&left, &right) {
|
|
1456
|
+
(Value::Object(l), Value::Object(r)) => {
|
|
1457
|
+
let merged = merge_object_data(l, r);
|
|
1458
|
+
self.stack.push(Value::Object(VmRef::new(merged)));
|
|
1459
|
+
}
|
|
1460
|
+
_ => {
|
|
1461
|
+
return Err(format!(
|
|
1462
|
+
"MergeObject: expected two objects, got {} and {}",
|
|
1463
|
+
left.to_display_string(),
|
|
1464
|
+
right.to_display_string()
|
|
1465
|
+
));
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
Opcode::ArraySortNumeric => {
|
|
1470
|
+
let operand = Self::read_u16(code, &mut ip);
|
|
1471
|
+
let asc = operand == 0;
|
|
1472
|
+
let arr = self
|
|
1473
|
+
.stack
|
|
1474
|
+
.pop()
|
|
1475
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1476
|
+
let result = if asc {
|
|
1477
|
+
arr_builtins::sort_numeric_asc(&arr)
|
|
1478
|
+
} else {
|
|
1479
|
+
arr_builtins::sort_numeric_desc(&arr)
|
|
1480
|
+
};
|
|
1481
|
+
self.stack.push(result);
|
|
1482
|
+
}
|
|
1483
|
+
Opcode::ArraySortByProperty => {
|
|
1484
|
+
let prop_idx = Self::read_u16(code, &mut ip);
|
|
1485
|
+
let asc = Self::read_u16(code, &mut ip) == 0;
|
|
1486
|
+
let arr = self
|
|
1487
|
+
.stack
|
|
1488
|
+
.pop()
|
|
1489
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1490
|
+
let prop = constants
|
|
1491
|
+
.get(prop_idx as usize)
|
|
1492
|
+
.and_then(|c| {
|
|
1493
|
+
if let Constant::String(s) = c {
|
|
1494
|
+
Some(s.as_ref())
|
|
1495
|
+
} else {
|
|
1496
|
+
None
|
|
1497
|
+
}
|
|
1498
|
+
})
|
|
1499
|
+
.unwrap_or("");
|
|
1500
|
+
let result = arr_builtins::sort_by_property_numeric(&arr, prop, asc);
|
|
1501
|
+
self.stack.push(result);
|
|
1502
|
+
}
|
|
1503
|
+
Opcode::ArrayMapIdentity => {
|
|
1504
|
+
let arr = self
|
|
1505
|
+
.stack
|
|
1506
|
+
.pop()
|
|
1507
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1508
|
+
let result = match &arr {
|
|
1509
|
+
Value::Array(a) => Value::Array(VmRef::new(a.borrow().clone())),
|
|
1510
|
+
_ => Value::Null,
|
|
1511
|
+
};
|
|
1512
|
+
self.stack.push(result);
|
|
1513
|
+
}
|
|
1514
|
+
Opcode::ArrayMapBinOp => {
|
|
1515
|
+
let binop_u8 = code[ip];
|
|
1516
|
+
ip += 1;
|
|
1517
|
+
let const_idx = Self::read_u16(code, &mut ip);
|
|
1518
|
+
let param_left = code[ip] == 0; // 0 = param on left (x op const), 1 = param on right (const op x)
|
|
1519
|
+
ip += 1;
|
|
1520
|
+
let binop = u8_to_binop(binop_u8)
|
|
1521
|
+
.ok_or_else(|| format!("Unknown binop in ArrayMapBinOp: {}", binop_u8))?;
|
|
1522
|
+
let arr = self
|
|
1523
|
+
.stack
|
|
1524
|
+
.pop()
|
|
1525
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1526
|
+
let const_val = constants
|
|
1527
|
+
.get(const_idx as usize)
|
|
1528
|
+
.map(|c| c.to_value())
|
|
1529
|
+
.unwrap_or(Value::Null);
|
|
1530
|
+
let result = if let Value::Array(a) = &arr {
|
|
1531
|
+
let arr_borrow = a.borrow();
|
|
1532
|
+
let mapped: Vec<Value> = arr_borrow
|
|
1533
|
+
.iter()
|
|
1534
|
+
.map(|v| {
|
|
1535
|
+
let l: Value = if param_left {
|
|
1536
|
+
(*v).clone()
|
|
1537
|
+
} else {
|
|
1538
|
+
const_val.clone()
|
|
1539
|
+
};
|
|
1540
|
+
let r: Value = if param_left {
|
|
1541
|
+
const_val.clone()
|
|
1542
|
+
} else {
|
|
1543
|
+
(*v).clone()
|
|
1544
|
+
};
|
|
1545
|
+
eval_binop(binop, &l, &r).unwrap_or(Value::Null)
|
|
1546
|
+
})
|
|
1547
|
+
.collect();
|
|
1548
|
+
Value::Array(VmRef::new(mapped))
|
|
1549
|
+
} else {
|
|
1550
|
+
Value::Null
|
|
1551
|
+
};
|
|
1552
|
+
self.stack.push(result);
|
|
1553
|
+
}
|
|
1554
|
+
Opcode::ArrayFilterBinOp => {
|
|
1555
|
+
let binop_u8 = code[ip];
|
|
1556
|
+
ip += 1;
|
|
1557
|
+
let const_idx = Self::read_u16(code, &mut ip);
|
|
1558
|
+
let param_left = code[ip] == 0; // 0 = param on left (x op const), 1 = param on right (const op x)
|
|
1559
|
+
ip += 1;
|
|
1560
|
+
let binop = u8_to_binop(binop_u8).ok_or_else(|| {
|
|
1561
|
+
format!("Unknown binop in ArrayFilterBinOp: {}", binop_u8)
|
|
1562
|
+
})?;
|
|
1563
|
+
let arr = self
|
|
1564
|
+
.stack
|
|
1565
|
+
.pop()
|
|
1566
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1567
|
+
let const_val = constants
|
|
1568
|
+
.get(const_idx as usize)
|
|
1569
|
+
.map(|c| c.to_value())
|
|
1570
|
+
.unwrap_or(Value::Null);
|
|
1571
|
+
let result = if let Value::Array(a) = &arr {
|
|
1572
|
+
let arr_borrow = a.borrow();
|
|
1573
|
+
let filtered: Vec<Value> = arr_borrow
|
|
1574
|
+
.iter()
|
|
1575
|
+
.filter(|v| {
|
|
1576
|
+
let l: Value = if param_left {
|
|
1577
|
+
(*v).clone()
|
|
1578
|
+
} else {
|
|
1579
|
+
const_val.clone()
|
|
1580
|
+
};
|
|
1581
|
+
let r: Value = if param_left {
|
|
1582
|
+
const_val.clone()
|
|
1583
|
+
} else {
|
|
1584
|
+
(*v).clone()
|
|
1585
|
+
};
|
|
1586
|
+
let b = eval_binop(binop, &l, &r).unwrap_or(Value::Null);
|
|
1587
|
+
b.is_truthy()
|
|
1588
|
+
})
|
|
1589
|
+
.cloned()
|
|
1590
|
+
.collect();
|
|
1591
|
+
Value::Array(VmRef::new(filtered))
|
|
1592
|
+
} else {
|
|
1593
|
+
Value::Null
|
|
1594
|
+
};
|
|
1595
|
+
self.stack.push(result);
|
|
1596
|
+
}
|
|
1597
|
+
Opcode::Throw => {
|
|
1598
|
+
let v = self
|
|
1599
|
+
.stack
|
|
1600
|
+
.pop()
|
|
1601
|
+
.ok_or_else(|| "Stack underflow".to_string())?;
|
|
1602
|
+
Self::unwind_throw(&mut try_handlers, &mut self.stack, &mut ip, v)?;
|
|
1603
|
+
}
|
|
1604
|
+
Opcode::AwaitPromise => {
|
|
1605
|
+
let v = self
|
|
1606
|
+
.stack
|
|
1607
|
+
.pop()
|
|
1608
|
+
.ok_or_else(|| "Stack underflow in AwaitPromise".to_string())?;
|
|
1609
|
+
#[cfg(any(feature = "http", feature = "promise"))]
|
|
1610
|
+
{
|
|
1611
|
+
use tishlang_core::Value as V;
|
|
1612
|
+
match v {
|
|
1613
|
+
V::Promise(p) => match p.block_until_settled() {
|
|
1614
|
+
Ok(val) => self.stack.push(val),
|
|
1615
|
+
Err(rej) => {
|
|
1616
|
+
Self::unwind_throw(
|
|
1617
|
+
&mut try_handlers,
|
|
1618
|
+
&mut self.stack,
|
|
1619
|
+
&mut ip,
|
|
1620
|
+
rej,
|
|
1621
|
+
)?;
|
|
1622
|
+
}
|
|
1623
|
+
},
|
|
1624
|
+
other => self.stack.push(tishlang_runtime::await_promise(other)),
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
#[cfg(not(any(feature = "http", feature = "promise")))]
|
|
1628
|
+
{
|
|
1629
|
+
self.stack.push(v);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
Opcode::LoadNativeExport => {
|
|
1633
|
+
let spec_idx = Self::read_u16(code, &mut ip);
|
|
1634
|
+
let export_idx = Self::read_u16(code, &mut ip);
|
|
1635
|
+
let spec = match constants.get(spec_idx as usize) {
|
|
1636
|
+
Some(Constant::String(s)) => s.as_ref(),
|
|
1637
|
+
_ => {
|
|
1638
|
+
return Err(
|
|
1639
|
+
"LoadNativeExport: spec constant out of bounds or not string"
|
|
1640
|
+
.to_string(),
|
|
1641
|
+
);
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1644
|
+
let export_name = match constants.get(export_idx as usize) {
|
|
1645
|
+
Some(Constant::String(s)) => s.as_ref(),
|
|
1646
|
+
_ => {
|
|
1647
|
+
return Err("LoadNativeExport: export_name constant out of bounds or not string".to_string());
|
|
1648
|
+
}
|
|
1649
|
+
};
|
|
1650
|
+
// Phase-2 item 11: consult externally registered native
|
|
1651
|
+
// modules (populated via `Vm::register_native_module`)
|
|
1652
|
+
// before falling through to the built-in lookup. Embedders
|
|
1653
|
+
// on the cranelift / llvm backends that want to expose
|
|
1654
|
+
// `cargo:…` Rust crates should register the module's
|
|
1655
|
+
// exports map before calling `vm.run(chunk)`.
|
|
1656
|
+
let from_registry: Option<Value> = if spec.starts_with("cargo:") {
|
|
1657
|
+
let regs = self.native_modules.borrow();
|
|
1658
|
+
regs.get(spec)
|
|
1659
|
+
.and_then(|m| m.borrow().get(&Arc::from(export_name)).cloned())
|
|
1660
|
+
} else {
|
|
1661
|
+
None
|
|
1662
|
+
};
|
|
1663
|
+
let v = from_registry
|
|
1664
|
+
.or_else(|| get_builtin_export(self.capabilities.as_ref(), spec, export_name))
|
|
1665
|
+
.ok_or_else(|| {
|
|
1666
|
+
if spec.starts_with("cargo:") {
|
|
1667
|
+
format!(
|
|
1668
|
+
"cargo:{} is not registered on the bytecode VM. Embedders must call Vm::register_native_module before run(). Spec: {} export: {}",
|
|
1669
|
+
spec.trim_start_matches("cargo:"),
|
|
1670
|
+
spec,
|
|
1671
|
+
export_name,
|
|
1672
|
+
)
|
|
1673
|
+
} else {
|
|
1674
|
+
format!(
|
|
1675
|
+
"Built-in module '{}' does not export '{}' or capability not enabled for this run. Use e.g. tish run --feature fs (or full). The tish binary must also be built with that capability linked in.",
|
|
1676
|
+
spec, export_name
|
|
1677
|
+
)
|
|
1678
|
+
}
|
|
1679
|
+
})?;
|
|
1680
|
+
self.stack.push(v);
|
|
1681
|
+
}
|
|
1682
|
+
Opcode::Closure | Opcode::LoadThis => {
|
|
1683
|
+
return Err(format!("Unhandled opcode: {:?}", opcode));
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
#[cfg(feature = "timers")]
|
|
1689
|
+
if cap_allows(self.capabilities.as_ref(), "timers") {
|
|
1690
|
+
tishlang_runtime::drain_timers();
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
Ok(self.stack.pop().unwrap_or(Value::Null))
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
impl Default for Vm {
|
|
1698
|
+
fn default() -> Self {
|
|
1699
|
+
Self::new()
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
/// Rough byte capacity for string coercion (matches hot paths like `"x" + n + "ms"`).
|
|
1704
|
+
fn estimate_string_concat_len(v: &Value) -> usize {
|
|
1705
|
+
match v {
|
|
1706
|
+
Value::String(s) => s.len(),
|
|
1707
|
+
Value::Number(_) => 24,
|
|
1708
|
+
Value::Bool(_) => 5,
|
|
1709
|
+
Value::Null => 4,
|
|
1710
|
+
_ => 32,
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
/// Append JS-style string conversion without an intermediate `String` per operand (unlike
|
|
1715
|
+
/// `format!("{}{}", a.to_display_string(), b.to_display_string())`, which triple-allocates).
|
|
1716
|
+
fn append_value_for_string_concat(out: &mut String, v: &Value) {
|
|
1717
|
+
use std::fmt::Write;
|
|
1718
|
+
match v {
|
|
1719
|
+
Value::Number(n) => {
|
|
1720
|
+
if n.is_nan() {
|
|
1721
|
+
out.push_str("NaN");
|
|
1722
|
+
} else if *n == f64::INFINITY {
|
|
1723
|
+
out.push_str("Infinity");
|
|
1724
|
+
} else if *n == f64::NEG_INFINITY {
|
|
1725
|
+
out.push_str("-Infinity");
|
|
1726
|
+
} else {
|
|
1727
|
+
let _ = write!(out, "{n}");
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
Value::String(s) => out.push_str(s.as_ref()),
|
|
1731
|
+
Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
|
|
1732
|
+
Value::Null => out.push_str("null"),
|
|
1733
|
+
_ => out.push_str(&v.to_display_string()),
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
fn eval_binop(op: BinOp, l: &Value, r: &Value) -> Result<Value, String> {
|
|
1738
|
+
use tishlang_ast::BinOp::*;
|
|
1739
|
+
use tishlang_core::Value::*;
|
|
1740
|
+
let ln = l.as_number().unwrap_or(f64::NAN);
|
|
1741
|
+
let rn = r.as_number().unwrap_or(f64::NAN);
|
|
1742
|
+
match op {
|
|
1743
|
+
Add => {
|
|
1744
|
+
if matches!(l, Value::String(_)) || matches!(r, Value::String(_)) {
|
|
1745
|
+
let cap = estimate_string_concat_len(l) + estimate_string_concat_len(r);
|
|
1746
|
+
let mut buf = std::string::String::with_capacity(cap);
|
|
1747
|
+
append_value_for_string_concat(&mut buf, l);
|
|
1748
|
+
append_value_for_string_concat(&mut buf, r);
|
|
1749
|
+
Ok(String(buf.into()))
|
|
1750
|
+
} else {
|
|
1751
|
+
Ok(Number(ln + rn))
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
Sub => Ok(Number(ln - rn)),
|
|
1755
|
+
Mul => Ok(Number(ln * rn)),
|
|
1756
|
+
Div => Ok(Number(if rn == 0.0 { f64::NAN } else { ln / rn })),
|
|
1757
|
+
Mod => Ok(Number(if rn == 0.0 { f64::NAN } else { ln % rn })),
|
|
1758
|
+
Pow => Ok(Number(ln.powf(rn))),
|
|
1759
|
+
Eq => Ok(Bool(l.strict_eq(r))),
|
|
1760
|
+
Ne => Ok(Bool(!l.strict_eq(r))),
|
|
1761
|
+
StrictEq => Ok(Bool(l.strict_eq(r))),
|
|
1762
|
+
StrictNe => Ok(Bool(!l.strict_eq(r))),
|
|
1763
|
+
Lt => Ok(Bool(ln < rn)),
|
|
1764
|
+
Le => Ok(Bool(ln <= rn)),
|
|
1765
|
+
Gt => Ok(Bool(ln > rn)),
|
|
1766
|
+
Ge => Ok(Bool(ln >= rn)),
|
|
1767
|
+
And => Ok(Bool(l.is_truthy() && r.is_truthy())),
|
|
1768
|
+
Or => Ok(Bool(l.is_truthy() || r.is_truthy())),
|
|
1769
|
+
BitAnd => Ok(Number((ln as i32 & rn as i32) as f64)),
|
|
1770
|
+
BitOr => Ok(Number((ln as i32 | rn as i32) as f64)),
|
|
1771
|
+
BitXor => Ok(Number((ln as i32 ^ rn as i32) as f64)),
|
|
1772
|
+
Shl => Ok(Number(((ln as i32) << (rn as i32)) as f64)),
|
|
1773
|
+
Shr => Ok(Number(((ln as i32) >> (rn as i32)) as f64)),
|
|
1774
|
+
In => Ok(Bool(match r {
|
|
1775
|
+
Value::Object(_) => object_has(r, l),
|
|
1776
|
+
Value::Array(a) => {
|
|
1777
|
+
let key_s: Arc<str> = match l {
|
|
1778
|
+
Value::String(s) => Arc::clone(s),
|
|
1779
|
+
Value::Number(n) => n.to_string().into(),
|
|
1780
|
+
_ => l.to_display_string().into(),
|
|
1781
|
+
};
|
|
1782
|
+
if key_s.as_ref() == "length" {
|
|
1783
|
+
true
|
|
1784
|
+
} else if let Ok(idx) = key_s.parse::<usize>() {
|
|
1785
|
+
idx < a.borrow().len()
|
|
1786
|
+
} else {
|
|
1787
|
+
false
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
_ => false,
|
|
1791
|
+
})),
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
fn eval_unary(op: UnaryOp, o: &Value) -> Result<Value, String> {
|
|
1796
|
+
use tishlang_ast::UnaryOp::*;
|
|
1797
|
+
use tishlang_core::Value::*;
|
|
1798
|
+
match op {
|
|
1799
|
+
Not => Ok(Bool(!o.is_truthy())),
|
|
1800
|
+
Neg => Ok(Number(-o.as_number().unwrap_or(f64::NAN))),
|
|
1801
|
+
Pos => Ok(Number(o.as_number().unwrap_or(f64::NAN))),
|
|
1802
|
+
BitNot => Ok(Number(!(o.as_number().unwrap_or(0.0) as i32) as f64)),
|
|
1803
|
+
Void => Ok(Null),
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
fn get_member(obj: &Value, key: &Arc<str>) -> Result<Value, String> {
|
|
1808
|
+
match obj {
|
|
1809
|
+
Value::Object(m) => {
|
|
1810
|
+
let map = m.borrow();
|
|
1811
|
+
map.strings
|
|
1812
|
+
.get(key.as_ref())
|
|
1813
|
+
.cloned()
|
|
1814
|
+
.ok_or_else(|| format!("Property '{}' not found", key))
|
|
1815
|
+
}
|
|
1816
|
+
Value::Array(a) => {
|
|
1817
|
+
let key_s = key.as_ref();
|
|
1818
|
+
if let Ok(idx) = key_s.parse::<usize>() {
|
|
1819
|
+
let arr = a.borrow();
|
|
1820
|
+
return arr
|
|
1821
|
+
.get(idx)
|
|
1822
|
+
.cloned()
|
|
1823
|
+
.ok_or_else(|| "Index out of bounds".to_string());
|
|
1824
|
+
}
|
|
1825
|
+
if key_s == "length" {
|
|
1826
|
+
return Ok(Value::Number(a.borrow().len() as f64));
|
|
1827
|
+
}
|
|
1828
|
+
let a_clone = a.clone();
|
|
1829
|
+
let method: ArrayMethodFn = match key_s {
|
|
1830
|
+
"push" => make_native_fn(move |args: &[Value]| {
|
|
1831
|
+
arr_builtins::push(&Value::Array(a_clone.clone()), args)
|
|
1832
|
+
}),
|
|
1833
|
+
"pop" => make_native_fn(move |_args: &[Value]| {
|
|
1834
|
+
arr_builtins::pop(&Value::Array(a_clone.clone()))
|
|
1835
|
+
}),
|
|
1836
|
+
"shift" => make_native_fn(move |_args: &[Value]| {
|
|
1837
|
+
arr_builtins::shift(&Value::Array(a_clone.clone()))
|
|
1838
|
+
}),
|
|
1839
|
+
"unshift" => make_native_fn(move |args: &[Value]| {
|
|
1840
|
+
arr_builtins::unshift(&Value::Array(a_clone.clone()), args)
|
|
1841
|
+
}),
|
|
1842
|
+
"reverse" => make_native_fn(move |_args: &[Value]| {
|
|
1843
|
+
arr_builtins::reverse(&Value::Array(a_clone.clone()))
|
|
1844
|
+
}),
|
|
1845
|
+
"shuffle" => make_native_fn(move |_args: &[Value]| {
|
|
1846
|
+
arr_builtins::shuffle(&Value::Array(a_clone.clone()))
|
|
1847
|
+
}),
|
|
1848
|
+
"slice" => make_native_fn(move |args: &[Value]| {
|
|
1849
|
+
let start = args.first().unwrap_or(&Value::Null);
|
|
1850
|
+
let end = args.get(1).unwrap_or(&Value::Null);
|
|
1851
|
+
arr_builtins::slice(&Value::Array(a_clone.clone()), start, end)
|
|
1852
|
+
}),
|
|
1853
|
+
"concat" => make_native_fn(move |args: &[Value]| {
|
|
1854
|
+
arr_builtins::concat(&Value::Array(a_clone.clone()), args)
|
|
1855
|
+
}),
|
|
1856
|
+
"join" => make_native_fn(move |args: &[Value]| {
|
|
1857
|
+
let sep = args.first().unwrap_or(&Value::Null);
|
|
1858
|
+
arr_builtins::join(&Value::Array(a_clone.clone()), sep)
|
|
1859
|
+
}),
|
|
1860
|
+
"indexOf" => make_native_fn(move |args: &[Value]| {
|
|
1861
|
+
let search = args.first().unwrap_or(&Value::Null);
|
|
1862
|
+
arr_builtins::index_of(&Value::Array(a_clone.clone()), search)
|
|
1863
|
+
}),
|
|
1864
|
+
"includes" => make_native_fn(move |args: &[Value]| {
|
|
1865
|
+
let search = args.first().unwrap_or(&Value::Null);
|
|
1866
|
+
let from = args.get(1);
|
|
1867
|
+
arr_builtins::includes(&Value::Array(a_clone.clone()), search, from)
|
|
1868
|
+
}),
|
|
1869
|
+
"map" => make_native_fn(move |args: &[Value]| {
|
|
1870
|
+
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
1871
|
+
arr_builtins::map(&Value::Array(a_clone.clone()), &cb)
|
|
1872
|
+
}),
|
|
1873
|
+
"filter" => make_native_fn(move |args: &[Value]| {
|
|
1874
|
+
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
1875
|
+
arr_builtins::filter(&Value::Array(a_clone.clone()), &cb)
|
|
1876
|
+
}),
|
|
1877
|
+
"reduce" => make_native_fn(move |args: &[Value]| {
|
|
1878
|
+
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
1879
|
+
let init = args.get(1).cloned().unwrap_or(Value::Null);
|
|
1880
|
+
arr_builtins::reduce(&Value::Array(a_clone.clone()), &cb, &init)
|
|
1881
|
+
}),
|
|
1882
|
+
"forEach" => make_native_fn(move |args: &[Value]| {
|
|
1883
|
+
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
1884
|
+
arr_builtins::for_each(&Value::Array(a_clone.clone()), &cb)
|
|
1885
|
+
}),
|
|
1886
|
+
"find" => make_native_fn(move |args: &[Value]| {
|
|
1887
|
+
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
1888
|
+
arr_builtins::find(&Value::Array(a_clone.clone()), &cb)
|
|
1889
|
+
}),
|
|
1890
|
+
"findIndex" => make_native_fn(move |args: &[Value]| {
|
|
1891
|
+
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
1892
|
+
arr_builtins::find_index(&Value::Array(a_clone.clone()), &cb)
|
|
1893
|
+
}),
|
|
1894
|
+
"some" => make_native_fn(move |args: &[Value]| {
|
|
1895
|
+
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
1896
|
+
arr_builtins::some(&Value::Array(a_clone.clone()), &cb)
|
|
1897
|
+
}),
|
|
1898
|
+
"every" => make_native_fn(move |args: &[Value]| {
|
|
1899
|
+
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
1900
|
+
arr_builtins::every(&Value::Array(a_clone.clone()), &cb)
|
|
1901
|
+
}),
|
|
1902
|
+
"flat" => make_native_fn(move |args: &[Value]| {
|
|
1903
|
+
let depth = args.first().unwrap_or(&Value::Number(1.0));
|
|
1904
|
+
arr_builtins::flat(&Value::Array(a_clone.clone()), depth)
|
|
1905
|
+
}),
|
|
1906
|
+
"flatMap" => make_native_fn(move |args: &[Value]| {
|
|
1907
|
+
let cb = args.first().cloned().unwrap_or(Value::Null);
|
|
1908
|
+
arr_builtins::flat_map(&Value::Array(a_clone.clone()), &cb)
|
|
1909
|
+
}),
|
|
1910
|
+
"sort" => make_native_fn(move |args: &[Value]| {
|
|
1911
|
+
let cmp = args.first();
|
|
1912
|
+
if let Some(Value::Function(_)) = cmp {
|
|
1913
|
+
arr_builtins::sort_with_comparator(
|
|
1914
|
+
&Value::Array(a_clone.clone()),
|
|
1915
|
+
cmp.unwrap(),
|
|
1916
|
+
)
|
|
1917
|
+
} else {
|
|
1918
|
+
arr_builtins::sort_default(&Value::Array(a_clone.clone()))
|
|
1919
|
+
}
|
|
1920
|
+
}),
|
|
1921
|
+
"splice" => make_native_fn(move |args: &[Value]| {
|
|
1922
|
+
let start = args.first().unwrap_or(&Value::Null);
|
|
1923
|
+
let delete_count = args.get(1).map(|v| v as &Value);
|
|
1924
|
+
let items: Vec<Value> = args.get(2..).unwrap_or(&[]).to_vec();
|
|
1925
|
+
arr_builtins::splice(
|
|
1926
|
+
&Value::Array(a_clone.clone()),
|
|
1927
|
+
start,
|
|
1928
|
+
delete_count,
|
|
1929
|
+
&items,
|
|
1930
|
+
)
|
|
1931
|
+
}),
|
|
1932
|
+
_ => return Err(format!("Property '{}' not found", key)),
|
|
1933
|
+
};
|
|
1934
|
+
Ok(Value::Function(method))
|
|
1935
|
+
}
|
|
1936
|
+
Value::String(s) => {
|
|
1937
|
+
let key_s = key.as_ref();
|
|
1938
|
+
if let Ok(idx) = key_s.parse::<usize>() {
|
|
1939
|
+
return match s.chars().nth(idx) {
|
|
1940
|
+
Some(c) => Ok(Value::String(Arc::from(c.to_string()))),
|
|
1941
|
+
None => Err("Index out of bounds".to_string()),
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
if key_s == "length" {
|
|
1945
|
+
return Ok(Value::Number(s.chars().count() as f64));
|
|
1946
|
+
}
|
|
1947
|
+
let s_clone: Arc<str> = Arc::clone(s);
|
|
1948
|
+
let method: ArrayMethodFn = match key_s {
|
|
1949
|
+
"indexOf" => make_native_fn(move |args: &[Value]| {
|
|
1950
|
+
let search = args.first().unwrap_or(&Value::Null);
|
|
1951
|
+
let from = args.get(1);
|
|
1952
|
+
str_builtins::index_of(&Value::String(Arc::clone(&s_clone)), search, from)
|
|
1953
|
+
}),
|
|
1954
|
+
"lastIndexOf" => make_native_fn(move |args: &[Value]| {
|
|
1955
|
+
let search = args.first().unwrap_or(&Value::Null);
|
|
1956
|
+
let position = args.get(1).cloned().unwrap_or(Value::Number(f64::INFINITY));
|
|
1957
|
+
str_builtins::last_index_of(
|
|
1958
|
+
&Value::String(Arc::clone(&s_clone)),
|
|
1959
|
+
search,
|
|
1960
|
+
&position,
|
|
1961
|
+
)
|
|
1962
|
+
}),
|
|
1963
|
+
"includes" => make_native_fn(move |args: &[Value]| {
|
|
1964
|
+
let search = args.first().unwrap_or(&Value::Null);
|
|
1965
|
+
let from = args.get(1);
|
|
1966
|
+
str_builtins::includes(&Value::String(Arc::clone(&s_clone)), search, from)
|
|
1967
|
+
}),
|
|
1968
|
+
"slice" => make_native_fn(move |args: &[Value]| {
|
|
1969
|
+
let start = args.first().unwrap_or(&Value::Null);
|
|
1970
|
+
let end = args.get(1).unwrap_or(&Value::Null);
|
|
1971
|
+
str_builtins::slice(&Value::String(Arc::clone(&s_clone)), start, end)
|
|
1972
|
+
}),
|
|
1973
|
+
"substring" => make_native_fn(move |args: &[Value]| {
|
|
1974
|
+
let start = args.first().unwrap_or(&Value::Null);
|
|
1975
|
+
let end = args.get(1).unwrap_or(&Value::Null);
|
|
1976
|
+
str_builtins::substring(&Value::String(Arc::clone(&s_clone)), start, end)
|
|
1977
|
+
}),
|
|
1978
|
+
"split" => make_native_fn(move |args: &[Value]| {
|
|
1979
|
+
let sep = args.first().unwrap_or(&Value::Null);
|
|
1980
|
+
str_builtins::split(&Value::String(Arc::clone(&s_clone)), sep)
|
|
1981
|
+
}),
|
|
1982
|
+
"trim" => make_native_fn(move |_args: &[Value]| {
|
|
1983
|
+
str_builtins::trim(&Value::String(Arc::clone(&s_clone)))
|
|
1984
|
+
}),
|
|
1985
|
+
"toUpperCase" => make_native_fn(move |_args: &[Value]| {
|
|
1986
|
+
str_builtins::to_upper_case(&Value::String(Arc::clone(&s_clone)))
|
|
1987
|
+
}),
|
|
1988
|
+
"toLowerCase" => make_native_fn(move |_args: &[Value]| {
|
|
1989
|
+
str_builtins::to_lower_case(&Value::String(Arc::clone(&s_clone)))
|
|
1990
|
+
}),
|
|
1991
|
+
"startsWith" => make_native_fn(move |args: &[Value]| {
|
|
1992
|
+
let search = args.first().unwrap_or(&Value::Null);
|
|
1993
|
+
str_builtins::starts_with(&Value::String(Arc::clone(&s_clone)), search)
|
|
1994
|
+
}),
|
|
1995
|
+
"endsWith" => make_native_fn(move |args: &[Value]| {
|
|
1996
|
+
let search = args.first().unwrap_or(&Value::Null);
|
|
1997
|
+
str_builtins::ends_with(&Value::String(Arc::clone(&s_clone)), search)
|
|
1998
|
+
}),
|
|
1999
|
+
"replace" => make_native_fn(move |args: &[Value]| {
|
|
2000
|
+
let search = args.first().unwrap_or(&Value::Null);
|
|
2001
|
+
let replacement = args.get(1).unwrap_or(&Value::Null);
|
|
2002
|
+
str_builtins::replace(&Value::String(Arc::clone(&s_clone)), search, replacement)
|
|
2003
|
+
}),
|
|
2004
|
+
"replaceAll" => make_native_fn(move |args: &[Value]| {
|
|
2005
|
+
let search = args.first().unwrap_or(&Value::Null);
|
|
2006
|
+
let replacement = args.get(1).unwrap_or(&Value::Null);
|
|
2007
|
+
str_builtins::replace_all(
|
|
2008
|
+
&Value::String(Arc::clone(&s_clone)),
|
|
2009
|
+
search,
|
|
2010
|
+
replacement,
|
|
2011
|
+
)
|
|
2012
|
+
}),
|
|
2013
|
+
"charAt" => make_native_fn(move |args: &[Value]| {
|
|
2014
|
+
let idx = args.first().unwrap_or(&Value::Null);
|
|
2015
|
+
str_builtins::char_at(&Value::String(Arc::clone(&s_clone)), idx)
|
|
2016
|
+
}),
|
|
2017
|
+
"charCodeAt" => make_native_fn(move |args: &[Value]| {
|
|
2018
|
+
let idx = args.first().unwrap_or(&Value::Null);
|
|
2019
|
+
str_builtins::char_code_at(&Value::String(Arc::clone(&s_clone)), idx)
|
|
2020
|
+
}),
|
|
2021
|
+
"repeat" => make_native_fn(move |args: &[Value]| {
|
|
2022
|
+
let count = args.first().unwrap_or(&Value::Null);
|
|
2023
|
+
str_builtins::repeat(&Value::String(Arc::clone(&s_clone)), count)
|
|
2024
|
+
}),
|
|
2025
|
+
"padStart" => make_native_fn(move |args: &[Value]| {
|
|
2026
|
+
let target_len = args.first().unwrap_or(&Value::Null);
|
|
2027
|
+
let pad = args.get(1).unwrap_or(&Value::Null);
|
|
2028
|
+
str_builtins::pad_start(&Value::String(Arc::clone(&s_clone)), target_len, pad)
|
|
2029
|
+
}),
|
|
2030
|
+
"padEnd" => make_native_fn(move |args: &[Value]| {
|
|
2031
|
+
let target_len = args.first().unwrap_or(&Value::Null);
|
|
2032
|
+
let pad = args.get(1).unwrap_or(&Value::Null);
|
|
2033
|
+
str_builtins::pad_end(&Value::String(Arc::clone(&s_clone)), target_len, pad)
|
|
2034
|
+
}),
|
|
2035
|
+
_ => return Err(format!("Property '{}' not found", key)),
|
|
2036
|
+
};
|
|
2037
|
+
Ok(Value::Function(method))
|
|
2038
|
+
}
|
|
2039
|
+
#[cfg(any(feature = "http", feature = "promise"))]
|
|
2040
|
+
Value::Promise(p) => match key.as_ref() {
|
|
2041
|
+
"then" => {
|
|
2042
|
+
let pc = Arc::clone(p);
|
|
2043
|
+
Ok(Value::native(move |args| {
|
|
2044
|
+
tishlang_runtime::promise_instance_then(&pc, args)
|
|
2045
|
+
}))
|
|
2046
|
+
}
|
|
2047
|
+
"catch" => {
|
|
2048
|
+
let pc = Arc::clone(p);
|
|
2049
|
+
Ok(Value::native(move |args| {
|
|
2050
|
+
tishlang_runtime::promise_instance_catch(&pc, args)
|
|
2051
|
+
}))
|
|
2052
|
+
}
|
|
2053
|
+
_ => Err(format!("Property '{}' not found", key)),
|
|
2054
|
+
},
|
|
2055
|
+
_ => Err(format!(
|
|
2056
|
+
"Cannot read property '{}' of {}",
|
|
2057
|
+
key,
|
|
2058
|
+
obj.type_name()
|
|
2059
|
+
)),
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
fn set_member(obj: &Value, key: &Arc<str>, val: Value) -> Result<(), String> {
|
|
2064
|
+
match obj {
|
|
2065
|
+
Value::Object(m) => {
|
|
2066
|
+
m.borrow_mut().strings.insert(Arc::clone(key), val);
|
|
2067
|
+
Ok(())
|
|
2068
|
+
}
|
|
2069
|
+
Value::Array(a) => {
|
|
2070
|
+
let idx: usize = key.as_ref().parse().unwrap_or(0);
|
|
2071
|
+
let mut arr = a.borrow_mut();
|
|
2072
|
+
if idx < arr.len() {
|
|
2073
|
+
arr[idx] = val;
|
|
2074
|
+
} else {
|
|
2075
|
+
arr.resize(idx + 1, Value::Null);
|
|
2076
|
+
arr[idx] = val;
|
|
2077
|
+
}
|
|
2078
|
+
Ok(())
|
|
2079
|
+
}
|
|
2080
|
+
_ => Err(format!("Cannot set property of {}", obj.type_name())),
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
fn get_index(obj: &Value, idx: &Value) -> Result<Value, String> {
|
|
2085
|
+
match obj {
|
|
2086
|
+
Value::Array(a) => {
|
|
2087
|
+
let i = match idx {
|
|
2088
|
+
Value::Number(n) => *n as usize,
|
|
2089
|
+
_ => {
|
|
2090
|
+
return Err(format!(
|
|
2091
|
+
"Array index must be number, got {}",
|
|
2092
|
+
idx.type_name()
|
|
2093
|
+
));
|
|
2094
|
+
}
|
|
2095
|
+
};
|
|
2096
|
+
Ok(a
|
|
2097
|
+
.borrow()
|
|
2098
|
+
.get(i)
|
|
2099
|
+
.cloned()
|
|
2100
|
+
.unwrap_or(Value::Null))
|
|
2101
|
+
}
|
|
2102
|
+
Value::String(s) => {
|
|
2103
|
+
let i = match idx {
|
|
2104
|
+
Value::Number(n) => {
|
|
2105
|
+
let n = *n;
|
|
2106
|
+
if n < 0.0 || n.fract() != 0.0 {
|
|
2107
|
+
return Err(format!(
|
|
2108
|
+
"String index must be non-negative integer, got {}",
|
|
2109
|
+
n
|
|
2110
|
+
));
|
|
2111
|
+
}
|
|
2112
|
+
let i = n as usize;
|
|
2113
|
+
let len = s.chars().count();
|
|
2114
|
+
if i >= len {
|
|
2115
|
+
return Err("Index out of bounds".to_string());
|
|
2116
|
+
}
|
|
2117
|
+
i
|
|
2118
|
+
}
|
|
2119
|
+
_ => {
|
|
2120
|
+
return Err(format!(
|
|
2121
|
+
"String index must be number, got {}",
|
|
2122
|
+
idx.type_name()
|
|
2123
|
+
));
|
|
2124
|
+
}
|
|
2125
|
+
};
|
|
2126
|
+
match s.chars().nth(i) {
|
|
2127
|
+
Some(c) => Ok(Value::String(Arc::from(c.to_string()))),
|
|
2128
|
+
None => Err("Index out of bounds".to_string()),
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
Value::Object(_) => object_get(obj, idx).ok_or_else(|| {
|
|
2132
|
+
format!(
|
|
2133
|
+
"Property '{}' not found",
|
|
2134
|
+
idx.to_display_string()
|
|
2135
|
+
)
|
|
2136
|
+
}),
|
|
2137
|
+
#[cfg(any(feature = "http", feature = "promise"))]
|
|
2138
|
+
Value::Promise(_) => {
|
|
2139
|
+
let key_arc: std::sync::Arc<str> = match idx {
|
|
2140
|
+
Value::String(s) => std::sync::Arc::clone(s),
|
|
2141
|
+
_ => {
|
|
2142
|
+
return Err(format!(
|
|
2143
|
+
"Promise bracket access requires a string key, got {}",
|
|
2144
|
+
idx.type_name()
|
|
2145
|
+
));
|
|
2146
|
+
}
|
|
2147
|
+
};
|
|
2148
|
+
get_member(obj, &key_arc)
|
|
2149
|
+
},
|
|
2150
|
+
_ => Err(format!(
|
|
2151
|
+
"Cannot read property '{}' of {}",
|
|
2152
|
+
idx.to_display_string(),
|
|
2153
|
+
obj.type_name()
|
|
2154
|
+
)),
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
fn set_index(obj: &Value, idx: &Value, val: Value) -> Result<(), String> {
|
|
2159
|
+
match obj {
|
|
2160
|
+
Value::Array(a) => {
|
|
2161
|
+
let i = match idx {
|
|
2162
|
+
Value::Number(n) => *n as usize,
|
|
2163
|
+
_ => {
|
|
2164
|
+
return Err(format!(
|
|
2165
|
+
"Array index must be number, got {}",
|
|
2166
|
+
idx.type_name()
|
|
2167
|
+
));
|
|
2168
|
+
}
|
|
2169
|
+
};
|
|
2170
|
+
let mut arr = a.borrow_mut();
|
|
2171
|
+
while arr.len() <= i {
|
|
2172
|
+
arr.push(Value::Null);
|
|
2173
|
+
}
|
|
2174
|
+
arr[i] = val;
|
|
2175
|
+
Ok(())
|
|
2176
|
+
}
|
|
2177
|
+
Value::Object(_) => object_set(obj, idx, val),
|
|
2178
|
+
_ => Err(format!("Cannot set property of {}", obj.type_name())),
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
/// Run a chunk with every capability linked into this `tishlang_vm` build (tests, embedders).
|
|
2183
|
+
pub fn run(chunk: &Chunk) -> Result<Value, String> {
|
|
2184
|
+
let mut vm = Vm::new();
|
|
2185
|
+
vm.run_with_options(chunk, false)
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
/// Run a chunk with options (e.g. REPL persistence for top-level declarations).
|
|
2189
|
+
pub fn run_with_options(chunk: &Chunk, opts: VmRunOptions) -> Result<Value, String> {
|
|
2190
|
+
let mut vm = Vm::with_capabilities(opts.capabilities);
|
|
2191
|
+
vm.run_with_options(chunk, opts.repl_mode)
|
|
2192
|
+
}
|