@tishlang/tish 1.7.0 → 1.8.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/Cargo.toml +1 -0
- package/README.md +2 -0
- package/bin/tish +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +28 -8
- package/crates/js_to_tish/src/transform/stmt.rs +49 -22
- package/crates/tish/Cargo.toml +15 -5
- package/crates/tish/src/cargo_native_registry.rs +29 -0
- package/crates/tish/src/cli_help.rs +16 -10
- package/crates/tish/src/main.rs +87 -32
- package/crates/tish/src/repl_completion.rs +3 -3
- package/crates/tish/tests/cargo_example_compile.rs +1 -1
- package/crates/tish/tests/integration_test.rs +19 -7
- package/crates/tish/tests/shortcircuit.rs +1 -1
- package/crates/tish_ast/src/ast.rs +80 -9
- package/crates/tish_build_utils/Cargo.toml +4 -0
- package/crates/tish_build_utils/src/lib.rs +105 -2
- package/crates/tish_builtins/Cargo.toml +5 -1
- package/crates/tish_builtins/src/array.rs +13 -12
- package/crates/tish_builtins/src/construct.rs +34 -33
- package/crates/tish_builtins/src/globals.rs +12 -11
- package/crates/tish_builtins/src/helpers.rs +2 -1
- package/crates/tish_builtins/src/object.rs +3 -2
- package/crates/tish_builtins/src/string.rs +73 -3
- package/crates/tish_bytecode/src/compiler.rs +12 -14
- package/crates/tish_bytecode/src/opcode.rs +12 -3
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +745 -199
- package/crates/tish_compile/src/infer.rs +6 -0
- package/crates/tish_compile/src/lib.rs +4 -3
- package/crates/tish_compile/src/resolve.rs +180 -82
- package/crates/tish_compile/src/types.rs +175 -11
- package/crates/tish_compile_js/Cargo.toml +1 -0
- package/crates/tish_compile_js/src/codegen.rs +152 -29
- package/crates/tish_compile_js/src/lib.rs +3 -1
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +31 -12
- package/crates/tish_core/Cargo.toml +8 -0
- package/crates/tish_core/src/json.rs +102 -53
- package/crates/tish_core/src/lib.rs +3 -1
- package/crates/tish_core/src/macros.rs +5 -5
- package/crates/tish_core/src/value.rs +53 -15
- package/crates/tish_core/src/vmref.rs +178 -0
- package/crates/tish_eval/Cargo.toml +17 -2
- package/crates/tish_eval/src/eval.rs +90 -28
- package/crates/tish_eval/src/http.rs +61 -0
- package/crates/tish_eval/src/lib.rs +3 -3
- package/crates/tish_eval/src/natives.rs +41 -0
- package/crates/tish_eval/src/value.rs +7 -3
- package/crates/tish_eval/src/value_convert.rs +13 -5
- package/crates/tish_fmt/src/lib.rs +120 -30
- package/crates/tish_lexer/src/lib.rs +20 -5
- package/crates/tish_lexer/src/token.rs +4 -0
- package/crates/tish_llvm/src/lib.rs +3 -1
- package/crates/tish_lsp/Cargo.toml +4 -1
- package/crates/tish_lsp/README.md +1 -1
- package/crates/tish_lsp/src/builtin_goto.rs +261 -0
- package/crates/tish_lsp/src/import_goto.rs +549 -0
- package/crates/tish_lsp/src/main.rs +502 -102
- package/crates/tish_native/src/build.rs +3 -2
- package/crates/tish_native/src/lib.rs +6 -2
- package/crates/tish_opt/src/lib.rs +17 -2
- package/crates/tish_parser/src/lib.rs +10 -3
- package/crates/tish_parser/src/parser.rs +346 -56
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3436 -0
- package/crates/tish_resolve/src/pos.rs +133 -0
- package/crates/tish_runtime/Cargo.toml +68 -3
- package/crates/tish_runtime/src/http.rs +1123 -141
- package/crates/tish_runtime/src/http_fetch.rs +15 -14
- 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 +159 -29
- package/crates/tish_runtime/src/promise.rs +199 -36
- package/crates/tish_runtime/src/promise_io.rs +2 -1
- package/crates/tish_runtime/src/timers.rs +37 -1
- package/crates/tish_runtime/src/ws.rs +26 -28
- package/crates/tish_ui/src/jsx.rs +279 -8
- package/crates/tish_ui/src/lib.rs +5 -2
- package/crates/tish_ui/src/runtime/hooks.rs +406 -45
- package/crates/tish_ui/src/runtime/mod.rs +36 -9
- package/crates/tish_vm/Cargo.toml +15 -5
- package/crates/tish_vm/src/vm.rs +506 -259
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +3 -1
- package/crates/tish_wasm/src/lib.rs +17 -14
- package/crates/tish_wasm_runtime/Cargo.toml +2 -1
- package/crates/tish_wasm_runtime/src/lib.rs +1 -1
- package/crates/tishlang_cargo_bindgen/Cargo.toml +1 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +68 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +5 -4
- package/justfile +8 -0
- 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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
//! Browser-exact behavior remains on `tish build --target js`.
|
|
3
3
|
|
|
4
4
|
use std::cell::RefCell;
|
|
5
|
+
use tishlang_core::VmRef;
|
|
5
6
|
use std::rc::Rc;
|
|
6
7
|
use std::sync::Arc;
|
|
7
8
|
|
|
@@ -16,7 +17,7 @@ pub fn construct(callee: &Value, args: &[Value]) -> Value {
|
|
|
16
17
|
Value::Object(o) => {
|
|
17
18
|
let b = o.borrow();
|
|
18
19
|
if let Some(Value::Function(ctor)) = b.get(&Arc::from(CONSTRUCT)) {
|
|
19
|
-
let c =
|
|
20
|
+
let c = ctor.clone();
|
|
20
21
|
drop(b);
|
|
21
22
|
return c(args);
|
|
22
23
|
}
|
|
@@ -29,11 +30,11 @@ pub fn construct(callee: &Value, args: &[Value]) -> Value {
|
|
|
29
30
|
fn param(initial: f64) -> Value {
|
|
30
31
|
let mut m = ObjectMap::default();
|
|
31
32
|
m.insert(Arc::from("value"), Value::Number(initial));
|
|
32
|
-
Value::Object(
|
|
33
|
+
Value::Object(VmRef::new(m))
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
fn connect_fn() -> Value {
|
|
36
|
-
Value::
|
|
37
|
+
Value::native(|_| Value::Null)
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/// Shared audio-node shape: connect, gain, optional filter fields.
|
|
@@ -44,33 +45,33 @@ fn audio_node_stub() -> Value {
|
|
|
44
45
|
m.insert(Arc::from("frequency"), param(440.0));
|
|
45
46
|
m.insert(Arc::from("Q"), param(1.0));
|
|
46
47
|
m.insert(Arc::from("type"), Value::String("peaking".into()));
|
|
47
|
-
Value::Object(
|
|
48
|
+
Value::Object(VmRef::new(m))
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
fn analyser_stub() -> Value {
|
|
51
52
|
let mut m = ObjectMap::default();
|
|
52
53
|
m.insert(Arc::from("connect"), connect_fn());
|
|
53
54
|
m.insert(Arc::from("fftSize"), Value::Number(2048.0));
|
|
54
|
-
Value::Object(
|
|
55
|
+
Value::Object(VmRef::new(m))
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
fn stereo_panner_stub() -> Value {
|
|
58
59
|
let mut m = ObjectMap::default();
|
|
59
60
|
m.insert(Arc::from("connect"), connect_fn());
|
|
60
61
|
m.insert(Arc::from("pan"), param(0.0));
|
|
61
|
-
Value::Object(
|
|
62
|
+
Value::Object(VmRef::new(m))
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
fn audio_buffer_stub(len: usize) -> Value {
|
|
65
66
|
let n = len.max(1);
|
|
66
|
-
let data =
|
|
67
|
-
let data2 =
|
|
67
|
+
let data = VmRef::new(vec![Value::Number(0.0); n]);
|
|
68
|
+
let data2 = data.clone();
|
|
68
69
|
let mut m = ObjectMap::default();
|
|
69
70
|
m.insert(
|
|
70
71
|
Arc::from("getChannelData"),
|
|
71
|
-
Value::
|
|
72
|
+
Value::native(move |_args| Value::Array(data2.clone())),
|
|
72
73
|
);
|
|
73
|
-
Value::Object(
|
|
74
|
+
Value::Object(VmRef::new(m))
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
fn buffer_source_stub() -> Value {
|
|
@@ -80,10 +81,10 @@ fn buffer_source_stub() -> Value {
|
|
|
80
81
|
m.insert(Arc::from("connect"), connect_fn());
|
|
81
82
|
m.insert(
|
|
82
83
|
Arc::from("start"),
|
|
83
|
-
Value::
|
|
84
|
+
Value::native(|_| Value::Null),
|
|
84
85
|
);
|
|
85
|
-
m.insert(Arc::from("stop"), Value::
|
|
86
|
-
Value::Object(
|
|
86
|
+
m.insert(Arc::from("stop"), Value::native(|_| Value::Null));
|
|
87
|
+
Value::Object(VmRef::new(m))
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
fn oscillator_stub() -> Value {
|
|
@@ -93,10 +94,10 @@ fn oscillator_stub() -> Value {
|
|
|
93
94
|
m.insert(Arc::from("connect"), connect_fn());
|
|
94
95
|
m.insert(
|
|
95
96
|
Arc::from("start"),
|
|
96
|
-
Value::
|
|
97
|
+
Value::native(|_| Value::Null),
|
|
97
98
|
);
|
|
98
|
-
m.insert(Arc::from("stop"), Value::
|
|
99
|
-
Value::Object(
|
|
99
|
+
m.insert(Arc::from("stop"), Value::native(|_| Value::Null));
|
|
100
|
+
Value::Object(VmRef::new(m))
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
fn audio_context_instance() -> Value {
|
|
@@ -106,66 +107,66 @@ fn audio_context_instance() -> Value {
|
|
|
106
107
|
|
|
107
108
|
ctx.insert(
|
|
108
109
|
Arc::from("createGain"),
|
|
109
|
-
Value::
|
|
110
|
+
Value::native(|_| audio_node_stub()),
|
|
110
111
|
);
|
|
111
112
|
ctx.insert(
|
|
112
113
|
Arc::from("createBiquadFilter"),
|
|
113
|
-
Value::
|
|
114
|
+
Value::native(|_| audio_node_stub()),
|
|
114
115
|
);
|
|
115
116
|
ctx.insert(
|
|
116
117
|
Arc::from("createStereoPanner"),
|
|
117
|
-
Value::
|
|
118
|
+
Value::native(|_| stereo_panner_stub()),
|
|
118
119
|
);
|
|
119
120
|
ctx.insert(
|
|
120
121
|
Arc::from("createAnalyser"),
|
|
121
|
-
Value::
|
|
122
|
+
Value::native(|_| analyser_stub()),
|
|
122
123
|
);
|
|
123
124
|
ctx.insert(
|
|
124
125
|
Arc::from("createBuffer"),
|
|
125
|
-
Value::
|
|
126
|
+
Value::native(|args: &[Value]| {
|
|
126
127
|
let len = args
|
|
127
128
|
.get(1)
|
|
128
129
|
.and_then(Value::as_number)
|
|
129
130
|
.unwrap_or(0.0)
|
|
130
131
|
.clamp(0.0, 1_000_000_000.0) as usize;
|
|
131
132
|
audio_buffer_stub(len)
|
|
132
|
-
})
|
|
133
|
+
}),
|
|
133
134
|
);
|
|
134
135
|
ctx.insert(
|
|
135
136
|
Arc::from("createBufferSource"),
|
|
136
|
-
Value::
|
|
137
|
+
Value::native(|_| buffer_source_stub()),
|
|
137
138
|
);
|
|
138
139
|
ctx.insert(
|
|
139
140
|
Arc::from("createOscillator"),
|
|
140
|
-
Value::
|
|
141
|
+
Value::native(|_| oscillator_stub()),
|
|
141
142
|
);
|
|
142
143
|
ctx.insert(
|
|
143
144
|
Arc::from("decodeAudioData"),
|
|
144
|
-
Value::
|
|
145
|
+
Value::native(|_| Value::Null),
|
|
145
146
|
);
|
|
146
147
|
|
|
147
|
-
Value::Object(
|
|
148
|
+
Value::Object(VmRef::new(ctx))
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
/// Global `Uint8Array` for native/VM: `new Uint8Array(n)` → numeric array of zeros (not real bytes).
|
|
151
152
|
pub fn uint8_array_constructor_value() -> Value {
|
|
152
|
-
let ctor =
|
|
153
|
+
let ctor = Value::native(|args: &[Value]| {
|
|
153
154
|
let len = args
|
|
154
155
|
.first()
|
|
155
156
|
.and_then(Value::as_number)
|
|
156
157
|
.unwrap_or(0.0)
|
|
157
158
|
.clamp(0.0, 1_000_000_000.0) as usize;
|
|
158
|
-
Value::Array(
|
|
159
|
+
Value::Array(VmRef::new(vec![Value::Number(0.0); len]))
|
|
159
160
|
});
|
|
160
161
|
let mut m = ObjectMap::default();
|
|
161
|
-
m.insert(Arc::from(CONSTRUCT),
|
|
162
|
-
Value::Object(
|
|
162
|
+
m.insert(Arc::from(CONSTRUCT), ctor);
|
|
163
|
+
Value::Object(VmRef::new(m))
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
/// Global `AudioContext` for native/VM: stub graph (no real audio).
|
|
166
167
|
pub fn audio_context_constructor_value() -> Value {
|
|
167
|
-
let ctor =
|
|
168
|
+
let ctor = Value::native(|_args: &[Value]| audio_context_instance());
|
|
168
169
|
let mut m = ObjectMap::default();
|
|
169
|
-
m.insert(Arc::from(CONSTRUCT),
|
|
170
|
-
Value::Object(
|
|
170
|
+
m.insert(Arc::from(CONSTRUCT), ctor);
|
|
171
|
+
Value::Object(VmRef::new(m))
|
|
171
172
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
//! independent of tishlang_runtime.
|
|
5
5
|
|
|
6
6
|
use std::cell::RefCell;
|
|
7
|
+
use tishlang_core::VmRef;
|
|
7
8
|
use std::rc::Rc;
|
|
8
9
|
use std::sync::Arc;
|
|
9
10
|
use tishlang_core::{percent_decode, percent_encode, ObjectMap, Value};
|
|
@@ -78,9 +79,9 @@ pub fn object_keys(args: &[Value]) -> Value {
|
|
|
78
79
|
.keys()
|
|
79
80
|
.map(|k| Value::String(Arc::clone(k)))
|
|
80
81
|
.collect();
|
|
81
|
-
Value::Array(
|
|
82
|
+
Value::Array(VmRef::new(keys))
|
|
82
83
|
} else {
|
|
83
|
-
Value::Array(
|
|
84
|
+
Value::Array(VmRef::new(Vec::new()))
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
|
|
@@ -89,9 +90,9 @@ pub fn object_values(args: &[Value]) -> Value {
|
|
|
89
90
|
if let Some(Value::Object(obj)) = args.first() {
|
|
90
91
|
let obj_borrow = obj.borrow();
|
|
91
92
|
let values: Vec<Value> = obj_borrow.values().cloned().collect();
|
|
92
|
-
Value::Array(
|
|
93
|
+
Value::Array(VmRef::new(values))
|
|
93
94
|
} else {
|
|
94
|
-
Value::Array(
|
|
95
|
+
Value::Array(VmRef::new(Vec::new()))
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
98
|
|
|
@@ -102,15 +103,15 @@ pub fn object_entries(args: &[Value]) -> Value {
|
|
|
102
103
|
let entries: Vec<Value> = obj_borrow
|
|
103
104
|
.iter()
|
|
104
105
|
.map(|(k, v)| {
|
|
105
|
-
Value::Array(
|
|
106
|
+
Value::Array(VmRef::new(vec![
|
|
106
107
|
Value::String(Arc::clone(k)),
|
|
107
108
|
v.clone(),
|
|
108
|
-
]))
|
|
109
|
+
]))
|
|
109
110
|
})
|
|
110
111
|
.collect();
|
|
111
|
-
Value::Array(
|
|
112
|
+
Value::Array(VmRef::new(entries))
|
|
112
113
|
} else {
|
|
113
|
-
Value::Array(
|
|
114
|
+
Value::Array(VmRef::new(Vec::new()))
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
|
|
@@ -145,7 +146,7 @@ pub fn object_assign(args: &[Value]) -> Value {
|
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
148
|
drop(target_mut);
|
|
148
|
-
Value::Object(
|
|
149
|
+
Value::Object(target.clone())
|
|
149
150
|
}
|
|
150
151
|
|
|
151
152
|
/// parseInt(string, radix?)
|
|
@@ -203,8 +204,8 @@ pub fn object_from_entries(args: &[Value]) -> Value {
|
|
|
203
204
|
}
|
|
204
205
|
}
|
|
205
206
|
|
|
206
|
-
Value::Object(
|
|
207
|
+
Value::Object(VmRef::new(obj))
|
|
207
208
|
} else {
|
|
208
|
-
Value::Object(
|
|
209
|
+
Value::Object(VmRef::new(ObjectMap::default()))
|
|
209
210
|
}
|
|
210
211
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//! Common helper functions used across builtin implementations.
|
|
2
2
|
|
|
3
3
|
use std::cell::RefCell;
|
|
4
|
+
use tishlang_core::VmRef;
|
|
4
5
|
use std::rc::Rc;
|
|
5
6
|
use std::sync::Arc;
|
|
6
7
|
use tishlang_core::{ObjectMap, Value};
|
|
@@ -25,7 +26,7 @@ pub fn normalize_index(idx: &Value, len: i64, default: usize) -> usize {
|
|
|
25
26
|
pub fn make_error_value(e: impl std::fmt::Display) -> Value {
|
|
26
27
|
let mut obj = ObjectMap::with_capacity(1);
|
|
27
28
|
obj.insert(Arc::from("error"), Value::String(e.to_string().into()));
|
|
28
|
-
Value::Object(
|
|
29
|
+
Value::Object(VmRef::new(obj))
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
/// Extract a number from a Value, returning None for non-numbers.
|
|
@@ -4,18 +4,19 @@
|
|
|
4
4
|
//! Functions will be migrated here from tishlang_runtime and tishlang_eval.
|
|
5
5
|
|
|
6
6
|
use std::cell::RefCell;
|
|
7
|
+
use tishlang_core::VmRef;
|
|
7
8
|
use std::rc::Rc;
|
|
8
9
|
use std::sync::Arc;
|
|
9
10
|
use tishlang_core::{ObjectMap, Value};
|
|
10
11
|
|
|
11
12
|
/// Create a new empty object Value.
|
|
12
13
|
pub fn new() -> Value {
|
|
13
|
-
Value::Object(
|
|
14
|
+
Value::Object(VmRef::new(ObjectMap::default()))
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
/// Create a new object Value with a given capacity.
|
|
17
18
|
pub fn with_capacity(capacity: usize) -> Value {
|
|
18
|
-
Value::Object(
|
|
19
|
+
Value::Object(VmRef::new(ObjectMap::with_capacity(capacity)))
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
/// Get the keys of an object.
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
//! JavaScript, matching .length and .charAt(). Byte offsets are never exposed.
|
|
5
5
|
|
|
6
6
|
use crate::helpers::normalize_index;
|
|
7
|
+
use tishlang_core::VmRef;
|
|
7
8
|
use std::cell::RefCell;
|
|
8
9
|
use std::rc::Rc;
|
|
9
10
|
use std::sync::Arc;
|
|
@@ -194,13 +195,13 @@ pub fn split(s: &Value, sep: &Value) -> Value {
|
|
|
194
195
|
if let Value::String(s) = s {
|
|
195
196
|
let separator = match sep {
|
|
196
197
|
Value::String(ss) => ss.as_ref(),
|
|
197
|
-
_ => return Value::Array(
|
|
198
|
+
_ => return Value::Array(VmRef::new(vec![Value::String(Arc::clone(s))])),
|
|
198
199
|
};
|
|
199
200
|
let parts: Vec<Value> = s
|
|
200
201
|
.split(separator)
|
|
201
202
|
.map(|p| Value::String(p.into()))
|
|
202
203
|
.collect();
|
|
203
|
-
Value::Array(
|
|
204
|
+
Value::Array(VmRef::new(parts))
|
|
204
205
|
} else {
|
|
205
206
|
Value::Null
|
|
206
207
|
}
|
|
@@ -275,6 +276,53 @@ pub fn replace_all(s: &Value, search: &Value, replacement: &Value) -> Value {
|
|
|
275
276
|
replace_impl(s, search, replacement, true)
|
|
276
277
|
}
|
|
277
278
|
|
|
279
|
+
/// HTML entity escape for the five canonical characters (`& < > " '`).
|
|
280
|
+
/// Single linear pass over the input; takes a zero-copy fast path when no
|
|
281
|
+
/// character needs escaping. Matches TFB's fortunes verifier byte-for-byte.
|
|
282
|
+
pub fn escape_html(s: &Value) -> Value {
|
|
283
|
+
let input = match s {
|
|
284
|
+
Value::String(s) => s.as_ref(),
|
|
285
|
+
Value::Null => return Value::String(Arc::from("")),
|
|
286
|
+
_ => return Value::Null,
|
|
287
|
+
};
|
|
288
|
+
let bytes = input.as_bytes();
|
|
289
|
+
let mut extra = 0usize;
|
|
290
|
+
for b in bytes {
|
|
291
|
+
match b {
|
|
292
|
+
b'&' => extra += 4,
|
|
293
|
+
b'<' | b'>' => extra += 3,
|
|
294
|
+
b'"' => extra += 5,
|
|
295
|
+
b'\'' => extra += 4,
|
|
296
|
+
_ => {}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if extra == 0 {
|
|
300
|
+
return Value::String(Arc::clone(match s {
|
|
301
|
+
Value::String(s) => s,
|
|
302
|
+
_ => unreachable!(),
|
|
303
|
+
}));
|
|
304
|
+
}
|
|
305
|
+
let mut out = String::with_capacity(input.len() + extra);
|
|
306
|
+
let mut last = 0usize;
|
|
307
|
+
for (i, b) in bytes.iter().enumerate() {
|
|
308
|
+
let repl: Option<&'static str> = match b {
|
|
309
|
+
b'&' => Some("&"),
|
|
310
|
+
b'<' => Some("<"),
|
|
311
|
+
b'>' => Some(">"),
|
|
312
|
+
b'"' => Some("""),
|
|
313
|
+
b'\'' => Some("'"),
|
|
314
|
+
_ => None,
|
|
315
|
+
};
|
|
316
|
+
if let Some(r) = repl {
|
|
317
|
+
out.push_str(&input[last..i]);
|
|
318
|
+
out.push_str(r);
|
|
319
|
+
last = i + 1;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
out.push_str(&input[last..]);
|
|
323
|
+
Value::String(Arc::from(out))
|
|
324
|
+
}
|
|
325
|
+
|
|
278
326
|
fn char_at_idx(s: &str, idx: usize) -> Option<char> {
|
|
279
327
|
s.chars().nth(idx)
|
|
280
328
|
}
|
|
@@ -463,7 +511,7 @@ mod tests {
|
|
|
463
511
|
assert_eq!(a.borrow().len(), 2);
|
|
464
512
|
assert_same!(
|
|
465
513
|
split(&s("x"), &n(1.0)),
|
|
466
|
-
Value::Array(
|
|
514
|
+
Value::Array(VmRef::new(vec![s("x")]))
|
|
467
515
|
);
|
|
468
516
|
assert_same!(split(&n(1.0), &s(",")), Value::Null);
|
|
469
517
|
assert_same!(trim(&s(" x ")), s("x"));
|
|
@@ -550,4 +598,26 @@ mod tests {
|
|
|
550
598
|
fn last_index_of_non_string() {
|
|
551
599
|
assert_same!(last_index_of(&n(1.0), &s("a"), &n(0.0)), n(-1.0));
|
|
552
600
|
}
|
|
601
|
+
|
|
602
|
+
#[test]
|
|
603
|
+
fn escape_html_basic() {
|
|
604
|
+
assert_same!(escape_html(&s("plain text")), s("plain text"));
|
|
605
|
+
assert_same!(
|
|
606
|
+
escape_html(&s("<script>alert(\"xss\")</script>")),
|
|
607
|
+
s("<script>alert("xss")</script>")
|
|
608
|
+
);
|
|
609
|
+
assert_same!(escape_html(&s("tom & jerry")), s("tom & jerry"));
|
|
610
|
+
assert_same!(escape_html(&s("it's")), s("it's"));
|
|
611
|
+
assert_same!(
|
|
612
|
+
escape_html(&s("<script>alert('x' & \"y\");</script>")),
|
|
613
|
+
s("<script>alert('x' & "y");</script>")
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
#[test]
|
|
618
|
+
fn escape_html_unicode_preserved() {
|
|
619
|
+
// Astral symbols / non-ASCII must round-trip unchanged.
|
|
620
|
+
assert_same!(escape_html(&s("フレーム")), s("フレーム"));
|
|
621
|
+
assert_same!(escape_html(&s("🎉 & 💥")), s("🎉 & 💥"));
|
|
622
|
+
}
|
|
553
623
|
}
|
|
@@ -275,12 +275,12 @@ impl<'a> Compiler<'a> {
|
|
|
275
275
|
if let (
|
|
276
276
|
Expr::Member {
|
|
277
277
|
object: lo,
|
|
278
|
-
prop: MemberProp::Name
|
|
278
|
+
prop: MemberProp::Name { name: p, .. },
|
|
279
279
|
..
|
|
280
280
|
},
|
|
281
281
|
Expr::Member {
|
|
282
282
|
object: ro,
|
|
283
|
-
prop: MemberProp::Name
|
|
283
|
+
prop: MemberProp::Name { name: pr, .. },
|
|
284
284
|
..
|
|
285
285
|
},
|
|
286
286
|
) = (left.as_ref(), right.as_ref())
|
|
@@ -982,6 +982,9 @@ impl<'a> Compiler<'a> {
|
|
|
982
982
|
});
|
|
983
983
|
}
|
|
984
984
|
},
|
|
985
|
+
Statement::TypeAlias { .. }
|
|
986
|
+
| Statement::DeclareVar { .. }
|
|
987
|
+
| Statement::DeclareFun { .. } => {}
|
|
985
988
|
}
|
|
986
989
|
Ok(())
|
|
987
990
|
}
|
|
@@ -1001,7 +1004,7 @@ impl<'a> Compiler<'a> {
|
|
|
1001
1004
|
DestructPattern::Array(elements) => {
|
|
1002
1005
|
for (i, elem) in elements.iter().enumerate() {
|
|
1003
1006
|
match elem {
|
|
1004
|
-
Some(DestructElement::Ident(name)) => {
|
|
1007
|
+
Some(DestructElement::Ident(name, _)) => {
|
|
1005
1008
|
self.emit(Opcode::Dup);
|
|
1006
1009
|
let idx = self.constant_idx(Constant::Number(i as f64));
|
|
1007
1010
|
self.emit(Opcode::LoadConst);
|
|
@@ -1031,7 +1034,7 @@ impl<'a> Compiler<'a> {
|
|
|
1031
1034
|
self.chunk.write_u16(key_idx);
|
|
1032
1035
|
self.emit(Opcode::GetIndex); // GetIndex pops obj, index and uses get_member
|
|
1033
1036
|
match &prop.value {
|
|
1034
|
-
DestructElement::Ident(name) => {
|
|
1037
|
+
DestructElement::Ident(name, _) => {
|
|
1035
1038
|
let idx = self.name_idx(name);
|
|
1036
1039
|
self.emit_u16(decl_op, idx);
|
|
1037
1040
|
if mutable {
|
|
@@ -1118,7 +1121,7 @@ impl<'a> Compiler<'a> {
|
|
|
1118
1121
|
if let (
|
|
1119
1122
|
Expr::Member {
|
|
1120
1123
|
object,
|
|
1121
|
-
prop: MemberProp::Name
|
|
1124
|
+
prop: MemberProp::Name { name: key, .. },
|
|
1122
1125
|
optional: false,
|
|
1123
1126
|
..
|
|
1124
1127
|
},
|
|
@@ -1231,7 +1234,7 @@ impl<'a> Compiler<'a> {
|
|
|
1231
1234
|
let jump_end = self.emit_jump(Opcode::Jump);
|
|
1232
1235
|
self.patch_jump(jump_to_get, self.chunk.code.len());
|
|
1233
1236
|
match prop {
|
|
1234
|
-
MemberProp::Name
|
|
1237
|
+
MemberProp::Name { name: key, .. } => {
|
|
1235
1238
|
let idx = self.name_idx(key);
|
|
1236
1239
|
self.emit_u16(Opcode::GetMemberOptional, idx);
|
|
1237
1240
|
}
|
|
@@ -1243,7 +1246,7 @@ impl<'a> Compiler<'a> {
|
|
|
1243
1246
|
self.patch_jump(jump_end, self.chunk.code.len());
|
|
1244
1247
|
} else {
|
|
1245
1248
|
match prop {
|
|
1246
|
-
MemberProp::Name
|
|
1249
|
+
MemberProp::Name { name: key, .. } => {
|
|
1247
1250
|
let idx = self.name_idx(key);
|
|
1248
1251
|
self.emit_u16(Opcode::GetMember, idx);
|
|
1249
1252
|
}
|
|
@@ -1511,14 +1514,9 @@ impl<'a> Compiler<'a> {
|
|
|
1511
1514
|
self.compile_jsx_fragment(children)?;
|
|
1512
1515
|
}
|
|
1513
1516
|
Expr::Await { operand, .. } => {
|
|
1514
|
-
// await expr =>
|
|
1515
|
-
let spec_idx = self.constant_idx(Constant::String(Arc::from("tish:http")));
|
|
1516
|
-
let await_idx = self.constant_idx(Constant::String(Arc::from("await")));
|
|
1517
|
-
self.emit(Opcode::LoadNativeExport);
|
|
1518
|
-
self.chunk.write_u16(spec_idx);
|
|
1519
|
-
self.chunk.write_u16(await_idx);
|
|
1517
|
+
// await expr => evaluate operand, then VM Opcode::AwaitPromise (throw on reject).
|
|
1520
1518
|
self.compile_expr(operand)?;
|
|
1521
|
-
self.
|
|
1519
|
+
self.emit(Opcode::AwaitPromise);
|
|
1522
1520
|
}
|
|
1523
1521
|
Expr::LogicalAssign {
|
|
1524
1522
|
name, op, value, ..
|
|
@@ -93,13 +93,16 @@ pub enum Opcode {
|
|
|
93
93
|
ExitBlock = 41,
|
|
94
94
|
/// Like [`DeclareVar`] but does not record block-scope undo (for `for`/`for-of` header bindings).
|
|
95
95
|
DeclareVarPlain = 42,
|
|
96
|
+
/// Pop the `await` operand value; if it is a `Promise`, block until settled, push the result,
|
|
97
|
+
/// or unwind to `catch` like `Throw` on rejection.
|
|
98
|
+
AwaitPromise = 43,
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
impl Opcode {
|
|
99
|
-
/// Decode byte to opcode. Safe for b in 0..=
|
|
102
|
+
/// Decode byte to opcode. Safe for b in 0..=43 (matches #[repr(u8)] discriminants).
|
|
100
103
|
#[inline]
|
|
101
104
|
pub fn from_u8(b: u8) -> Option<Opcode> {
|
|
102
|
-
if b <=
|
|
105
|
+
if b <= 43 {
|
|
103
106
|
Some(unsafe { std::mem::transmute(b) })
|
|
104
107
|
} else {
|
|
105
108
|
None
|
|
@@ -114,11 +117,17 @@ impl Opcode {
|
|
|
114
117
|
| Opcode::Dup
|
|
115
118
|
| Opcode::Return
|
|
116
119
|
| Opcode::ExitTry
|
|
120
|
+
| Opcode::ConcatArray
|
|
121
|
+
| Opcode::MergeObject
|
|
122
|
+
| Opcode::GetIndex
|
|
123
|
+
| Opcode::SetIndex
|
|
124
|
+
| Opcode::Throw
|
|
117
125
|
| Opcode::ArrayMapIdentity
|
|
118
126
|
| Opcode::CallSpread
|
|
119
127
|
| Opcode::ConstructSpread
|
|
120
128
|
| Opcode::EnterBlock
|
|
121
|
-
| Opcode::ExitBlock
|
|
129
|
+
| Opcode::ExitBlock
|
|
130
|
+
| Opcode::AwaitPromise => 1,
|
|
122
131
|
Opcode::ArraySortByProperty
|
|
123
132
|
| Opcode::ArrayMapBinOp
|
|
124
133
|
| Opcode::ArrayFilterBinOp
|