@tishlang/tish 1.9.1 → 1.10.0
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/bin/tish +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +8 -6
- package/crates/js_to_tish/src/transform/stmt.rs +12 -13
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish/src/cargo_native_registry.rs +4 -1
- package/crates/tish/src/main.rs +11 -8
- package/crates/tish/tests/integration_test.rs +145 -7
- package/crates/tish_ast/src/ast.rs +3 -9
- package/crates/tish_build_utils/src/lib.rs +43 -15
- package/crates/tish_builtins/src/array.rs +2 -3
- package/crates/tish_builtins/src/construct.rs +15 -28
- package/crates/tish_builtins/src/globals.rs +18 -16
- package/crates/tish_builtins/src/helpers.rs +1 -4
- package/crates/tish_builtins/src/lib.rs +1 -0
- package/crates/tish_builtins/src/object.rs +10 -10
- package/crates/tish_builtins/src/string.rs +1 -3
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_compile/src/codegen.rs +123 -138
- package/crates/tish_compile/src/lib.rs +25 -3
- package/crates/tish_compile/src/resolve.rs +6 -3
- package/crates/tish_compile/src/types.rs +6 -6
- package/crates/tish_compile_js/src/codegen.rs +50 -29
- package/crates/tish_compile_js/src/tests_jsx.rs +44 -0
- package/crates/tish_core/src/console_style.rs +9 -0
- package/crates/tish_core/src/json.rs +17 -7
- package/crates/tish_core/src/macros.rs +2 -2
- package/crates/tish_core/src/value.rs +192 -4
- package/crates/tish_cranelift_runtime/Cargo.toml +4 -0
- package/crates/tish_eval/src/eval.rs +135 -73
- package/crates/tish_eval/src/http.rs +18 -12
- package/crates/tish_eval/src/lib.rs +29 -0
- package/crates/tish_eval/src/regex.rs +1 -1
- package/crates/tish_eval/src/value.rs +89 -4
- package/crates/tish_eval/src/value_convert.rs +30 -8
- package/crates/tish_fmt/src/lib.rs +4 -1
- package/crates/tish_lexer/src/lib.rs +7 -2
- package/crates/tish_llvm/src/lib.rs +2 -2
- package/crates/tish_lsp/src/builtin_goto.rs +111 -10
- package/crates/tish_lsp/src/import_goto.rs +35 -22
- package/crates/tish_lsp/src/main.rs +118 -85
- package/crates/tish_native/src/build.rs +187 -10
- package/crates/tish_native/src/lib.rs +92 -8
- package/crates/tish_parser/src/lib.rs +77 -0
- package/crates/tish_parser/src/parser.rs +71 -74
- package/crates/tish_pg/src/error.rs +1 -1
- package/crates/tish_pg/src/lib.rs +61 -73
- package/crates/tish_resolve/src/lib.rs +283 -158
- package/crates/tish_resolve/src/pos.rs +10 -2
- package/crates/tish_runtime/Cargo.toml +3 -0
- package/crates/tish_runtime/src/http.rs +39 -39
- package/crates/tish_runtime/src/http_fetch.rs +12 -12
- package/crates/tish_runtime/src/lib.rs +26 -43
- package/crates/tish_runtime/src/native_promise.rs +0 -11
- package/crates/tish_runtime/src/promise.rs +14 -1
- package/crates/tish_runtime/src/promise_io.rs +1 -4
- package/crates/tish_runtime/src/ws.rs +40 -27
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +10 -8
- package/crates/tish_ui/src/jsx.rs +6 -4
- package/crates/tish_ui/src/lib.rs +2 -2
- package/crates/tish_ui/src/runtime/hooks.rs +5 -15
- package/crates/tish_ui/src/runtime/mod.rs +16 -17
- package/crates/tish_vm/Cargo.toml +2 -0
- package/crates/tish_vm/src/vm.rs +218 -153
- package/crates/tish_wasm/src/lib.rs +33 -7
- package/crates/tish_wasm_runtime/Cargo.toml +4 -1
- package/crates/tish_wasm_runtime/src/lib.rs +2 -1
- package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
- package/crates/tishlang_cargo_bindgen/src/discover.rs +10 -5
- package/crates/tishlang_cargo_bindgen/src/infer.rs +18 -8
- package/crates/tishlang_cargo_bindgen/src/lib.rs +25 -26
- package/crates/tishlang_cargo_bindgen/src/main.rs +41 -38
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +4 -1
- package/justfile +3 -3
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
|
@@ -34,6 +34,29 @@ impl Codegen {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/// ECMAScript does not allow `if (c) const x = 1` / `while (c) let y = 2` without a block.
|
|
38
|
+
fn stmt_needs_braces_in_js_control_head(stmt: &Statement) -> bool {
|
|
39
|
+
matches!(
|
|
40
|
+
stmt,
|
|
41
|
+
Statement::VarDecl { .. } | Statement::VarDeclDestructure { .. }
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fn emit_js_control_body(&mut self, body: &Statement) -> Result<(), CompileError> {
|
|
46
|
+
if Self::stmt_needs_braces_in_js_control_head(body) {
|
|
47
|
+
self.writeln("{");
|
|
48
|
+
self.indent += 1;
|
|
49
|
+
self.emit_statement(body)?;
|
|
50
|
+
self.indent -= 1;
|
|
51
|
+
self.writeln("}");
|
|
52
|
+
} else {
|
|
53
|
+
self.indent += 1;
|
|
54
|
+
self.emit_statement(body)?;
|
|
55
|
+
self.indent -= 1;
|
|
56
|
+
}
|
|
57
|
+
Ok(())
|
|
58
|
+
}
|
|
59
|
+
|
|
37
60
|
fn indent_str(&self) -> String {
|
|
38
61
|
" ".repeat(self.indent)
|
|
39
62
|
}
|
|
@@ -49,7 +72,11 @@ impl Codegen {
|
|
|
49
72
|
}
|
|
50
73
|
|
|
51
74
|
fn output_line(&self) -> u32 {
|
|
52
|
-
self.output
|
|
75
|
+
self.output
|
|
76
|
+
.as_bytes()
|
|
77
|
+
.iter()
|
|
78
|
+
.filter(|&&b| b == b'\n')
|
|
79
|
+
.count() as u32
|
|
53
80
|
}
|
|
54
81
|
|
|
55
82
|
fn escape_ident(s: &str) -> String {
|
|
@@ -155,22 +182,16 @@ impl Codegen {
|
|
|
155
182
|
} => {
|
|
156
183
|
let c = self.emit_expr(cond)?;
|
|
157
184
|
self.writeln(&format!("if ({})", c));
|
|
158
|
-
self.
|
|
159
|
-
self.emit_statement(then_branch)?;
|
|
160
|
-
self.indent -= 1;
|
|
185
|
+
self.emit_js_control_body(then_branch)?;
|
|
161
186
|
if let Some(eb) = else_branch {
|
|
162
187
|
self.writeln("else");
|
|
163
|
-
self.
|
|
164
|
-
self.emit_statement(eb)?;
|
|
165
|
-
self.indent -= 1;
|
|
188
|
+
self.emit_js_control_body(eb)?;
|
|
166
189
|
}
|
|
167
190
|
}
|
|
168
191
|
Statement::While { cond, body, .. } => {
|
|
169
192
|
let c = self.emit_expr(cond)?;
|
|
170
193
|
self.writeln(&format!("while ({})", c));
|
|
171
|
-
self.
|
|
172
|
-
self.emit_statement(body)?;
|
|
173
|
-
self.indent -= 1;
|
|
194
|
+
self.emit_js_control_body(body)?;
|
|
174
195
|
}
|
|
175
196
|
Statement::For {
|
|
176
197
|
init,
|
|
@@ -179,7 +200,10 @@ impl Codegen {
|
|
|
179
200
|
body,
|
|
180
201
|
..
|
|
181
202
|
} => {
|
|
182
|
-
|
|
203
|
+
// Keep the whole `for (...)` on one line with normal statement indentation (do not
|
|
204
|
+
// mix bare `write("for (")` with `writeln(")")`, which indents `)` on a new line).
|
|
205
|
+
let mut header = self.indent_str();
|
|
206
|
+
header.push_str("for (");
|
|
183
207
|
if let Some(i) = init {
|
|
184
208
|
match i.as_ref() {
|
|
185
209
|
Statement::VarDecl {
|
|
@@ -192,32 +216,32 @@ impl Codegen {
|
|
|
192
216
|
let escaped = Self::escape_ident(name.as_ref());
|
|
193
217
|
if let Some(e) = opt_init {
|
|
194
218
|
let ex = self.emit_expr(e)?;
|
|
195
|
-
|
|
219
|
+
header.push_str(&format!("{} {} = {}", decl, escaped, ex));
|
|
196
220
|
} else {
|
|
197
|
-
|
|
221
|
+
header.push_str(&format!("{} {}", decl, escaped));
|
|
198
222
|
}
|
|
199
223
|
}
|
|
200
224
|
Statement::ExprStmt { expr, .. } => {
|
|
201
225
|
let ex = self.emit_expr(expr)?;
|
|
202
|
-
|
|
226
|
+
header.push_str(&ex);
|
|
203
227
|
}
|
|
204
228
|
_ => return Err(CompileError::new("Unsupported for init")),
|
|
205
229
|
}
|
|
206
230
|
}
|
|
207
|
-
|
|
231
|
+
header.push_str("; ");
|
|
208
232
|
if let Some(c) = cond {
|
|
209
233
|
let ce = self.emit_expr(c)?;
|
|
210
|
-
|
|
234
|
+
header.push_str(&ce);
|
|
211
235
|
}
|
|
212
|
-
|
|
236
|
+
header.push_str("; ");
|
|
213
237
|
if let Some(u) = update {
|
|
214
238
|
let ue = self.emit_expr(u)?;
|
|
215
|
-
|
|
239
|
+
header.push_str(&ue);
|
|
216
240
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
self.
|
|
220
|
-
self.
|
|
241
|
+
header.push(')');
|
|
242
|
+
header.push('\n');
|
|
243
|
+
self.output.push_str(&header);
|
|
244
|
+
self.emit_js_control_body(body)?;
|
|
221
245
|
}
|
|
222
246
|
Statement::ForOf {
|
|
223
247
|
name,
|
|
@@ -228,9 +252,7 @@ impl Codegen {
|
|
|
228
252
|
let escaped = Self::escape_ident(name.as_ref());
|
|
229
253
|
let it = self.emit_expr(iterable)?;
|
|
230
254
|
self.writeln(&format!("for (const {} of {})", escaped, it));
|
|
231
|
-
self.
|
|
232
|
-
self.emit_statement(body)?;
|
|
233
|
-
self.indent -= 1;
|
|
255
|
+
self.emit_js_control_body(body)?;
|
|
234
256
|
}
|
|
235
257
|
Statement::Return { value, .. } => {
|
|
236
258
|
if let Some(v) = value {
|
|
@@ -761,7 +783,8 @@ fn compile_project_js_inner(
|
|
|
761
783
|
let modules = tishlang_compile::resolve_project(entry_path, project_root)
|
|
762
784
|
.map_err(|e| CompileError { message: e })?;
|
|
763
785
|
tishlang_compile::detect_cycles(&modules).map_err(|e| CompileError { message: e })?;
|
|
764
|
-
let merged =
|
|
786
|
+
let merged =
|
|
787
|
+
tishlang_compile::merge_modules(modules).map_err(|e| CompileError { message: e })?;
|
|
765
788
|
let program = if optimize {
|
|
766
789
|
tishlang_opt::optimize(&merged.program)
|
|
767
790
|
} else {
|
|
@@ -844,7 +867,5 @@ pub fn compile_project_with_jsx(
|
|
|
844
867
|
} else {
|
|
845
868
|
format!("{stem}.js")
|
|
846
869
|
};
|
|
847
|
-
Ok(
|
|
848
|
-
compile_project_js_inner(entry_path, project_root, optimize, false, &out_name)?.js,
|
|
849
|
-
)
|
|
870
|
+
Ok(compile_project_js_inner(entry_path, project_root, optimize, false, &out_name)?.js)
|
|
850
871
|
}
|
|
@@ -303,4 +303,48 @@ fn factory() {
|
|
|
303
303
|
&js[..800.min(js.len())]
|
|
304
304
|
);
|
|
305
305
|
}
|
|
306
|
+
|
|
307
|
+
#[test]
|
|
308
|
+
fn fn_body_two_lets_not_split_by_closing_brace() {
|
|
309
|
+
let src = "fn h() {\n let a = 1\n let b = 2\n}\n";
|
|
310
|
+
let program = parse(src).expect("parse");
|
|
311
|
+
let js = compile_with_jsx(&program, false).expect("compile");
|
|
312
|
+
let i = js.find("let a = 1").expect("let a");
|
|
313
|
+
let j = js.find("let b = 2").expect("let b");
|
|
314
|
+
assert!(
|
|
315
|
+
!js[i..j].contains('}'),
|
|
316
|
+
"first let must not end in an inner block before second let (regression #43): {:?}",
|
|
317
|
+
&js[i..j]
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
#[test]
|
|
322
|
+
fn control_flow_wraps_lexical_decl_body_in_block_for_valid_js() {
|
|
323
|
+
let src = r#"fn f() {
|
|
324
|
+
if (true)
|
|
325
|
+
const x = 1
|
|
326
|
+
while (false)
|
|
327
|
+
let y = 2
|
|
328
|
+
for (;;)
|
|
329
|
+
const z = 3
|
|
330
|
+
for (const v of [])
|
|
331
|
+
let w = 4
|
|
332
|
+
}"#;
|
|
333
|
+
let program = parse(src).expect("parse");
|
|
334
|
+
let js = compile_with_jsx(&program, false).expect("compile");
|
|
335
|
+
for (label, key, decl) in [
|
|
336
|
+
("if", "if (true)", "const x = 1"),
|
|
337
|
+
("while", "while (false)", "let y = 2"),
|
|
338
|
+
("for", "for (; ; )", "const z = 3"),
|
|
339
|
+
("for-of", "for (const v of [])", "let w = 4"),
|
|
340
|
+
] {
|
|
341
|
+
let i = js.find(key).expect(label);
|
|
342
|
+
let j = js.find(decl).expect(label);
|
|
343
|
+
assert!(
|
|
344
|
+
i < j && js[i..j].contains('{'),
|
|
345
|
+
"{label}: expected '{{' between {key:?} and {decl:?}, got {:?}",
|
|
346
|
+
&js[i..j]
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
306
350
|
}
|
|
@@ -84,6 +84,7 @@ fn format_value_styled_inner(value: &Value, colors: bool, quote_strings: bool) -
|
|
|
84
84
|
Value::Object(obj) => {
|
|
85
85
|
let inner: Vec<String> = obj
|
|
86
86
|
.borrow()
|
|
87
|
+
.strings
|
|
87
88
|
.iter()
|
|
88
89
|
.map(|(k, v)| {
|
|
89
90
|
format!(
|
|
@@ -96,6 +97,14 @@ fn format_value_styled_inner(value: &Value, colors: bool, quote_strings: bool) -
|
|
|
96
97
|
let sep = format!("{PUNCT}, {RESET}");
|
|
97
98
|
format!("{PUNCT}{{{RESET} {} {PUNCT}}}{RESET}", inner.join(&sep))
|
|
98
99
|
}
|
|
100
|
+
Value::Symbol(s) => {
|
|
101
|
+
let body = s
|
|
102
|
+
.description
|
|
103
|
+
.as_ref()
|
|
104
|
+
.map(|d| d.as_ref())
|
|
105
|
+
.unwrap_or("");
|
|
106
|
+
format!("{SPECIAL}Symbol({body}){RESET}")
|
|
107
|
+
}
|
|
99
108
|
Value::Function(_) => format!("{SPECIAL}[Function]{RESET}"),
|
|
100
109
|
Value::Promise(_) => format!("{SPECIAL}[object Promise]{RESET}"),
|
|
101
110
|
Value::Opaque(o) => format!("{SPECIAL}[object {}]{RESET}", o.type_name()),
|
|
@@ -70,8 +70,8 @@ pub fn json_stringify_into(buf: &mut String, value: &Value) {
|
|
|
70
70
|
let borrowed = obj.borrow();
|
|
71
71
|
// Sort keys for deterministic output. Pre-allocate to avoid
|
|
72
72
|
// a fresh `Vec` realloc inside `keys().collect()`.
|
|
73
|
-
let mut keys: Vec<&Arc<str>> = Vec::with_capacity(borrowed.len());
|
|
74
|
-
keys.extend(borrowed.keys());
|
|
73
|
+
let mut keys: Vec<&Arc<str>> = Vec::with_capacity(borrowed.strings.len());
|
|
74
|
+
keys.extend(borrowed.strings.keys());
|
|
75
75
|
keys.sort_unstable_by(|a, b| a.as_ref().cmp(b.as_ref()));
|
|
76
76
|
buf.push('{');
|
|
77
77
|
for (i, key) in keys.into_iter().enumerate() {
|
|
@@ -81,11 +81,13 @@ pub fn json_stringify_into(buf: &mut String, value: &Value) {
|
|
|
81
81
|
buf.push('"');
|
|
82
82
|
escape_json_string_into(buf, key);
|
|
83
83
|
buf.push_str("\":");
|
|
84
|
-
json_stringify_into(buf, borrowed.get(key).unwrap());
|
|
84
|
+
json_stringify_into(buf, borrowed.strings.get(key).unwrap());
|
|
85
85
|
}
|
|
86
86
|
buf.push('}');
|
|
87
87
|
}
|
|
88
|
-
Value::Function(_) | Value::Promise(_) | Value::Opaque(_)
|
|
88
|
+
Value::Function(_) | Value::Promise(_) | Value::Opaque(_) | Value::Symbol(_) => {
|
|
89
|
+
buf.push_str("null");
|
|
90
|
+
}
|
|
89
91
|
#[cfg(feature = "regex")]
|
|
90
92
|
Value::RegExp(_) => buf.push_str("null"),
|
|
91
93
|
}
|
|
@@ -312,7 +314,10 @@ fn parse_object(input: &str) -> Result<(Value, &str), String> {
|
|
|
312
314
|
|
|
313
315
|
input = input.trim_start();
|
|
314
316
|
if let Some(rest) = input.strip_prefix('}') {
|
|
315
|
-
return Ok((
|
|
317
|
+
return Ok((
|
|
318
|
+
Value::Object(VmRef::new(crate::ObjectData::from_strings(map))),
|
|
319
|
+
rest,
|
|
320
|
+
));
|
|
316
321
|
}
|
|
317
322
|
|
|
318
323
|
loop {
|
|
@@ -339,7 +344,12 @@ fn parse_object(input: &str) -> Result<(Value, &str), String> {
|
|
|
339
344
|
|
|
340
345
|
match input.chars().next() {
|
|
341
346
|
Some(',') => input = &input[1..],
|
|
342
|
-
Some('}') =>
|
|
347
|
+
Some('}') => {
|
|
348
|
+
return Ok((
|
|
349
|
+
Value::Object(VmRef::new(crate::ObjectData::from_strings(map))),
|
|
350
|
+
&input[1..],
|
|
351
|
+
));
|
|
352
|
+
}
|
|
343
353
|
_ => return Err("Expected ',' or '}' in object".to_string()),
|
|
344
354
|
}
|
|
345
355
|
}
|
|
@@ -369,7 +379,7 @@ mod tests {
|
|
|
369
379
|
|
|
370
380
|
match (&value, &reparsed) {
|
|
371
381
|
(Value::Object(a), Value::Object(b)) => {
|
|
372
|
-
assert_eq!(a.borrow().
|
|
382
|
+
assert_eq!(a.borrow().len_entries(), b.borrow().len_entries());
|
|
373
383
|
}
|
|
374
384
|
_ => panic!("Expected objects"),
|
|
375
385
|
}
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
macro_rules! tish_module {
|
|
25
25
|
($($name:expr => $fn:expr),* $(,)?) => {{
|
|
26
26
|
use std::sync::Arc;
|
|
27
|
-
use $crate::{ObjectMap, Value
|
|
27
|
+
use $crate::{ObjectMap, Value};
|
|
28
28
|
let mut map = ObjectMap::default();
|
|
29
29
|
$(
|
|
30
30
|
// `Value::native` picks the right Rc / Arc wrapper depending on
|
|
31
31
|
// whether the `send-values` feature is enabled upstream.
|
|
32
32
|
map.insert(Arc::from($name), Value::native($fn));
|
|
33
33
|
)*
|
|
34
|
-
Value::
|
|
34
|
+
Value::object(map)
|
|
35
35
|
}};
|
|
36
36
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
//! Unified Value type for Tish runtime values.
|
|
2
2
|
|
|
3
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
3
4
|
use std::sync::Arc;
|
|
4
5
|
|
|
5
6
|
use ahash::AHashMap;
|
|
@@ -10,6 +11,47 @@ use crate::vmref::VmRef;
|
|
|
10
11
|
/// Uses a faster hasher than `std::collections::HashMap` for string-heavy workloads.
|
|
11
12
|
pub type ObjectMap = AHashMap<Arc<str>, Value>;
|
|
12
13
|
|
|
14
|
+
static NEXT_SYMBOL_ID: AtomicU64 = AtomicU64::new(1);
|
|
15
|
+
|
|
16
|
+
fn next_symbol_id() -> u64 {
|
|
17
|
+
NEXT_SYMBOL_ID.fetch_add(1, Ordering::Relaxed)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Allocate a unique symbol id (for `Symbol()` and first-time `Symbol.for` entries).
|
|
21
|
+
#[inline]
|
|
22
|
+
pub fn alloc_symbol_id() -> u64 {
|
|
23
|
+
next_symbol_id()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Primitive Symbol (ECMAScript-style): identity is `Arc` pointer equality.
|
|
27
|
+
#[derive(Debug)]
|
|
28
|
+
pub struct TishSymbol {
|
|
29
|
+
pub id: u64,
|
|
30
|
+
pub description: Option<Arc<str>>,
|
|
31
|
+
/// Set when created via `Symbol.for(key)` (global registry).
|
|
32
|
+
pub registry_key: Option<Arc<str>>,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl TishSymbol {
|
|
36
|
+
/// Unique symbol (`Symbol("desc")`).
|
|
37
|
+
pub fn new_unique(description: Option<Arc<str>>) -> Arc<Self> {
|
|
38
|
+
Arc::new(Self {
|
|
39
|
+
id: next_symbol_id(),
|
|
40
|
+
description,
|
|
41
|
+
registry_key: None,
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Registry symbol (`Symbol.for`): stable `id` for this registry key.
|
|
46
|
+
pub fn new_registry(id: u64, registry_key: Arc<str>, description: Option<Arc<str>>) -> Arc<Self> {
|
|
47
|
+
Arc::new(Self {
|
|
48
|
+
id,
|
|
49
|
+
description,
|
|
50
|
+
registry_key: Some(registry_key),
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
13
55
|
#[cfg(feature = "regex")]
|
|
14
56
|
use fancy_regex::Regex;
|
|
15
57
|
|
|
@@ -222,7 +264,9 @@ pub enum Value {
|
|
|
222
264
|
Bool(bool),
|
|
223
265
|
Null,
|
|
224
266
|
Array(VmRef<Vec<Value>>),
|
|
225
|
-
Object(VmRef<
|
|
267
|
+
Object(VmRef<ObjectData>),
|
|
268
|
+
/// ECMAScript-style primitive symbol (identity by `Arc`).
|
|
269
|
+
Symbol(Arc<TishSymbol>),
|
|
226
270
|
Function(NativeFn),
|
|
227
271
|
#[cfg(feature = "regex")]
|
|
228
272
|
RegExp(VmRef<TishRegExp>),
|
|
@@ -232,6 +276,134 @@ pub enum Value {
|
|
|
232
276
|
Opaque(Arc<dyn TishOpaque>),
|
|
233
277
|
}
|
|
234
278
|
|
|
279
|
+
/// Ordinary object: string-keyed properties plus optional symbol-keyed side map.
|
|
280
|
+
#[derive(Clone, Debug, Default)]
|
|
281
|
+
pub struct ObjectData {
|
|
282
|
+
pub strings: ObjectMap,
|
|
283
|
+
pub symbols: Option<AHashMap<u64, Value>>,
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
impl ObjectData {
|
|
287
|
+
#[inline]
|
|
288
|
+
pub fn from_strings(strings: ObjectMap) -> Self {
|
|
289
|
+
Self {
|
|
290
|
+
strings,
|
|
291
|
+
symbols: None,
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
#[inline]
|
|
296
|
+
pub fn len_entries(&self) -> usize {
|
|
297
|
+
self.strings.len() + self.symbols.as_ref().map(|s| s.len()).unwrap_or(0)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/// Read a property from an object value.
|
|
302
|
+
pub fn object_get(obj: &Value, key: &Value) -> Option<Value> {
|
|
303
|
+
let Value::Object(od) = obj else {
|
|
304
|
+
return None;
|
|
305
|
+
};
|
|
306
|
+
let b = od.borrow();
|
|
307
|
+
match key {
|
|
308
|
+
Value::Symbol(s) => b.symbols.as_ref()?.get(&s.id).cloned(),
|
|
309
|
+
Value::Number(n) => {
|
|
310
|
+
let k: Arc<str> = n.to_string().into();
|
|
311
|
+
b.strings.get(&k).cloned()
|
|
312
|
+
}
|
|
313
|
+
Value::String(k) => b.strings.get(k.as_ref()).cloned(),
|
|
314
|
+
_ => None,
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/// Set a property on an object.
|
|
319
|
+
pub fn object_set(obj: &Value, key: &Value, val: Value) -> Result<(), String> {
|
|
320
|
+
let Value::Object(od) = obj else {
|
|
321
|
+
return Err(format!("Cannot set property on {}", obj.type_name()));
|
|
322
|
+
};
|
|
323
|
+
let mut b = od.borrow_mut();
|
|
324
|
+
match key {
|
|
325
|
+
Value::Symbol(s) => {
|
|
326
|
+
if b.symbols.is_none() {
|
|
327
|
+
b.symbols = Some(AHashMap::default());
|
|
328
|
+
}
|
|
329
|
+
b.symbols.as_mut().unwrap().insert(s.id, val);
|
|
330
|
+
Ok(())
|
|
331
|
+
}
|
|
332
|
+
Value::Number(n) => {
|
|
333
|
+
b.strings.insert(n.to_string().into(), val);
|
|
334
|
+
Ok(())
|
|
335
|
+
}
|
|
336
|
+
Value::String(k) => {
|
|
337
|
+
b.strings.insert(Arc::clone(k), val);
|
|
338
|
+
Ok(())
|
|
339
|
+
}
|
|
340
|
+
_ => Err(format!(
|
|
341
|
+
"Object key must be string, number, or symbol, got {}",
|
|
342
|
+
key.type_name()
|
|
343
|
+
)),
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/// `key in obj` for objects.
|
|
348
|
+
pub fn object_has(obj: &Value, key: &Value) -> bool {
|
|
349
|
+
let Value::Object(od) = obj else {
|
|
350
|
+
return false;
|
|
351
|
+
};
|
|
352
|
+
let b = od.borrow();
|
|
353
|
+
match key {
|
|
354
|
+
Value::Symbol(s) => b.symbols.as_ref().is_some_and(|m| m.contains_key(&s.id)),
|
|
355
|
+
Value::Number(n) => {
|
|
356
|
+
let k: Arc<str> = n.to_string().into();
|
|
357
|
+
b.strings.contains_key(&k)
|
|
358
|
+
}
|
|
359
|
+
Value::String(k) => b.strings.contains_key(k.as_ref()),
|
|
360
|
+
_ => false,
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/// Invoke a callable [`Value`]: [`Value::Function`], or an object exposing `__call` (e.g. `Symbol`).
|
|
365
|
+
pub fn value_call(callee: &Value, args: &[Value]) -> Value {
|
|
366
|
+
match callee {
|
|
367
|
+
Value::Function(f) => f(args),
|
|
368
|
+
Value::Object(o) => {
|
|
369
|
+
let inner = o.borrow().strings.get("__call").cloned();
|
|
370
|
+
if let Some(inner) = inner {
|
|
371
|
+
return value_call(&inner, args);
|
|
372
|
+
}
|
|
373
|
+
panic!(
|
|
374
|
+
"Not a function: tried to call {:?} as a function (e.g. method on Null when read failed)",
|
|
375
|
+
callee
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
_ => panic!(
|
|
379
|
+
"Not a function: tried to call {:?} as a function (e.g. method on Null when read failed)",
|
|
380
|
+
callee
|
|
381
|
+
),
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/// Merge two object payloads (spread / VM MergeObject).
|
|
386
|
+
pub fn merge_object_data(left: &VmRef<ObjectData>, right: &VmRef<ObjectData>) -> ObjectData {
|
|
387
|
+
let l = left.borrow();
|
|
388
|
+
let r = right.borrow();
|
|
389
|
+
let mut strings = ObjectMap::with_capacity(l.strings.len() + r.strings.len());
|
|
390
|
+
strings.extend(l.strings.iter().map(|(k, v)| (Arc::clone(k), v.clone())));
|
|
391
|
+
strings.extend(r.strings.iter().map(|(k, v)| (Arc::clone(k), v.clone())));
|
|
392
|
+
let mut symbols: Option<AHashMap<u64, Value>> = None;
|
|
393
|
+
if let Some(ls) = &l.symbols {
|
|
394
|
+
symbols = Some(ls.clone());
|
|
395
|
+
}
|
|
396
|
+
if let Some(rs) = &r.symbols {
|
|
397
|
+
match &mut symbols {
|
|
398
|
+
Some(m) => {
|
|
399
|
+
m.extend(rs.iter().map(|(k, v)| (*k, v.clone())));
|
|
400
|
+
}
|
|
401
|
+
None => symbols = Some(rs.clone()),
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
ObjectData { strings, symbols }
|
|
405
|
+
}
|
|
406
|
+
|
|
235
407
|
impl std::fmt::Debug for Value {
|
|
236
408
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
237
409
|
match self {
|
|
@@ -241,6 +413,7 @@ impl std::fmt::Debug for Value {
|
|
|
241
413
|
Value::Null => write!(f, "Null"),
|
|
242
414
|
Value::Array(arr) => write!(f, "Array({:?})", arr.borrow()),
|
|
243
415
|
Value::Object(obj) => write!(f, "Object({:?})", obj.borrow()),
|
|
416
|
+
Value::Symbol(s) => write!(f, "Symbol({})", s.id),
|
|
244
417
|
Value::Function(_) => write!(f, "Function"),
|
|
245
418
|
#[cfg(feature = "regex")]
|
|
246
419
|
Value::RegExp(re) => write!(
|
|
@@ -281,11 +454,19 @@ impl Value {
|
|
|
281
454
|
Value::Object(obj) => {
|
|
282
455
|
let inner: Vec<String> = obj
|
|
283
456
|
.borrow()
|
|
457
|
+
.strings
|
|
284
458
|
.iter()
|
|
285
459
|
.map(|(k, v)| format!("{}: {}", k.as_ref(), v.to_display_string()))
|
|
286
460
|
.collect();
|
|
287
461
|
format!("{{{}}}", inner.join(", "))
|
|
288
462
|
}
|
|
463
|
+
Value::Symbol(s) => {
|
|
464
|
+
if let Some(d) = &s.description {
|
|
465
|
+
format!("Symbol({})", d)
|
|
466
|
+
} else {
|
|
467
|
+
"Symbol()".to_string()
|
|
468
|
+
}
|
|
469
|
+
}
|
|
289
470
|
Value::Function(_) => "[Function]".to_string(),
|
|
290
471
|
Value::Promise(_) => "[object Promise]".to_string(),
|
|
291
472
|
Value::Opaque(o) => format!("[object {}]", o.type_name()),
|
|
@@ -331,6 +512,7 @@ impl Value {
|
|
|
331
512
|
(Value::RegExp(a), Value::RegExp(b)) => VmRef::ptr_eq(a, b),
|
|
332
513
|
(Value::Promise(a), Value::Promise(b)) => Arc::ptr_eq(a, b),
|
|
333
514
|
(Value::Opaque(a), Value::Opaque(b)) => Arc::ptr_eq(a, b),
|
|
515
|
+
(Value::Symbol(a), Value::Symbol(b)) => Arc::ptr_eq(a, b),
|
|
334
516
|
_ => false,
|
|
335
517
|
}
|
|
336
518
|
}
|
|
@@ -364,7 +546,7 @@ impl Value {
|
|
|
364
546
|
|
|
365
547
|
/// Create a new object Value from a property map.
|
|
366
548
|
pub fn object(map: ObjectMap) -> Self {
|
|
367
|
-
Value::Object(VmRef::new(map))
|
|
549
|
+
Value::Object(VmRef::new(ObjectData::from_strings(map)))
|
|
368
550
|
}
|
|
369
551
|
|
|
370
552
|
/// Create an empty array Value.
|
|
@@ -374,7 +556,7 @@ impl Value {
|
|
|
374
556
|
|
|
375
557
|
/// Create an empty object Value.
|
|
376
558
|
pub fn empty_object() -> Self {
|
|
377
|
-
Value::Object(VmRef::new(
|
|
559
|
+
Value::Object(VmRef::new(ObjectData::default()))
|
|
378
560
|
}
|
|
379
561
|
|
|
380
562
|
/// Extract the number value, if this is a Number.
|
|
@@ -399,6 +581,7 @@ impl Value {
|
|
|
399
581
|
Value::RegExp(_) => "object",
|
|
400
582
|
Value::Promise(_) => "object",
|
|
401
583
|
Value::Opaque(o) => o.type_name(),
|
|
584
|
+
Value::Symbol(_) => "symbol",
|
|
402
585
|
}
|
|
403
586
|
}
|
|
404
587
|
|
|
@@ -406,7 +589,12 @@ impl Value {
|
|
|
406
589
|
pub fn completion_keys(&self) -> Vec<String> {
|
|
407
590
|
match self {
|
|
408
591
|
Value::Object(m) => {
|
|
409
|
-
let mut keys: Vec<String> = m
|
|
592
|
+
let mut keys: Vec<String> = m
|
|
593
|
+
.borrow()
|
|
594
|
+
.strings
|
|
595
|
+
.keys()
|
|
596
|
+
.map(|k| k.to_string())
|
|
597
|
+
.collect();
|
|
410
598
|
keys.sort();
|
|
411
599
|
keys
|
|
412
600
|
}
|
|
@@ -11,6 +11,10 @@ default = []
|
|
|
11
11
|
fs = ["tishlang_vm/fs"]
|
|
12
12
|
process = ["tishlang_vm/process"]
|
|
13
13
|
http = ["tishlang_vm/http"]
|
|
14
|
+
promise = ["tishlang_vm/promise"]
|
|
15
|
+
timers = ["tishlang_vm/timers"]
|
|
16
|
+
ws = ["tishlang_vm/ws"]
|
|
17
|
+
regex = ["tishlang_vm/regex"]
|
|
14
18
|
|
|
15
19
|
[lib]
|
|
16
20
|
crate-type = ["staticlib", "rlib"]
|