@tishlang/tish 1.6.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 +2 -0
- package/README.md +2 -0
- package/bin/tish +0 -0
- package/crates/js_to_tish/src/error.rs +2 -8
- package/crates/js_to_tish/src/transform/expr.rs +128 -137
- package/crates/js_to_tish/src/transform/stmt.rs +62 -32
- package/crates/tish/Cargo.toml +15 -5
- package/crates/tish/src/cargo_native_registry.rs +29 -0
- package/crates/tish/src/cli_help.rs +92 -39
- package/crates/tish/src/main.rs +172 -86
- package/crates/tish/src/repl_completion.rs +3 -3
- package/crates/tish/tests/cargo_example_compile.rs +4 -2
- package/crates/tish/tests/integration_test.rs +216 -54
- package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
- package/crates/tish/tests/shortcircuit.rs +20 -5
- package/crates/tish_ast/src/ast.rs +92 -23
- package/crates/tish_build_utils/Cargo.toml +4 -0
- package/crates/tish_build_utils/src/lib.rs +136 -8
- package/crates/tish_builtins/Cargo.toml +5 -1
- package/crates/tish_builtins/src/array.rs +65 -33
- package/crates/tish_builtins/src/construct.rs +34 -39
- package/crates/tish_builtins/src/globals.rs +42 -26
- package/crates/tish_builtins/src/helpers.rs +2 -1
- package/crates/tish_builtins/src/lib.rs +5 -5
- package/crates/tish_builtins/src/math.rs +5 -3
- package/crates/tish_builtins/src/object.rs +3 -2
- package/crates/tish_builtins/src/string.rs +144 -22
- package/crates/tish_bytecode/src/chunk.rs +0 -1
- package/crates/tish_bytecode/src/compiler.rs +173 -71
- package/crates/tish_bytecode/src/opcode.rs +24 -6
- package/crates/tish_bytecode/src/peephole.rs +2 -2
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +1621 -453
- package/crates/tish_compile/src/infer.rs +75 -19
- package/crates/tish_compile/src/lib.rs +19 -8
- package/crates/tish_compile/src/resolve.rs +278 -137
- package/crates/tish_compile/src/types.rs +184 -24
- package/crates/tish_compile_js/Cargo.toml +1 -0
- package/crates/tish_compile_js/src/codegen.rs +181 -37
- package/crates/tish_compile_js/src/lib.rs +3 -1
- package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
- package/crates/tish_compiler_wasm/src/lib.rs +16 -13
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +69 -59
- package/crates/tish_core/Cargo.toml +8 -0
- package/crates/tish_core/src/json.rs +107 -56
- package/crates/tish_core/src/lib.rs +4 -2
- package/crates/tish_core/src/macros.rs +5 -5
- package/crates/tish_core/src/uri.rs +9 -6
- package/crates/tish_core/src/value.rs +145 -43
- package/crates/tish_core/src/vmref.rs +178 -0
- package/crates/tish_cranelift/src/link.rs +6 -9
- package/crates/tish_cranelift/src/lower.rs +14 -8
- package/crates/tish_eval/Cargo.toml +17 -2
- package/crates/tish_eval/src/eval.rs +474 -165
- package/crates/tish_eval/src/http.rs +61 -0
- package/crates/tish_eval/src/lib.rs +12 -8
- package/crates/tish_eval/src/natives.rs +136 -38
- package/crates/tish_eval/src/promise.rs +14 -8
- package/crates/tish_eval/src/timers.rs +28 -19
- package/crates/tish_eval/src/value.rs +17 -6
- package/crates/tish_eval/src/value_convert.rs +13 -5
- package/crates/tish_fmt/src/lib.rs +149 -43
- package/crates/tish_lexer/src/lib.rs +232 -63
- package/crates/tish_lexer/src/token.rs +10 -6
- package/crates/tish_llvm/src/lib.rs +17 -8
- 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 +504 -106
- package/crates/tish_native/src/build.rs +4 -8
- package/crates/tish_native/src/lib.rs +54 -21
- package/crates/tish_opt/src/lib.rs +84 -52
- package/crates/tish_parser/src/lib.rs +45 -13
- package/crates/tish_parser/src/parser.rs +505 -130
- 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 +1136 -145
- package/crates/tish_runtime/src/http_fetch.rs +38 -27
- 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 +375 -189
- package/crates/tish_runtime/src/promise.rs +199 -40
- 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 +65 -42
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
- package/crates/tish_ui/src/jsx.rs +317 -27
- 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 +725 -281
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +11 -4
- package/crates/tish_wasm/src/lib.rs +55 -42
- 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 +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +120 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +350 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
- 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,13 +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(
|
|
86
|
-
|
|
87
|
-
Value::Function(Rc::new(|_| Value::Null)),
|
|
88
|
-
);
|
|
89
|
-
Value::Object(Rc::new(RefCell::new(m)))
|
|
86
|
+
m.insert(Arc::from("stop"), Value::native(|_| Value::Null));
|
|
87
|
+
Value::Object(VmRef::new(m))
|
|
90
88
|
}
|
|
91
89
|
|
|
92
90
|
fn oscillator_stub() -> Value {
|
|
@@ -96,13 +94,10 @@ fn oscillator_stub() -> Value {
|
|
|
96
94
|
m.insert(Arc::from("connect"), connect_fn());
|
|
97
95
|
m.insert(
|
|
98
96
|
Arc::from("start"),
|
|
99
|
-
Value::
|
|
100
|
-
);
|
|
101
|
-
m.insert(
|
|
102
|
-
Arc::from("stop"),
|
|
103
|
-
Value::Function(Rc::new(|_| Value::Null)),
|
|
97
|
+
Value::native(|_| Value::Null),
|
|
104
98
|
);
|
|
105
|
-
|
|
99
|
+
m.insert(Arc::from("stop"), Value::native(|_| Value::Null));
|
|
100
|
+
Value::Object(VmRef::new(m))
|
|
106
101
|
}
|
|
107
102
|
|
|
108
103
|
fn audio_context_instance() -> Value {
|
|
@@ -112,66 +107,66 @@ fn audio_context_instance() -> Value {
|
|
|
112
107
|
|
|
113
108
|
ctx.insert(
|
|
114
109
|
Arc::from("createGain"),
|
|
115
|
-
Value::
|
|
110
|
+
Value::native(|_| audio_node_stub()),
|
|
116
111
|
);
|
|
117
112
|
ctx.insert(
|
|
118
113
|
Arc::from("createBiquadFilter"),
|
|
119
|
-
Value::
|
|
114
|
+
Value::native(|_| audio_node_stub()),
|
|
120
115
|
);
|
|
121
116
|
ctx.insert(
|
|
122
117
|
Arc::from("createStereoPanner"),
|
|
123
|
-
Value::
|
|
118
|
+
Value::native(|_| stereo_panner_stub()),
|
|
124
119
|
);
|
|
125
120
|
ctx.insert(
|
|
126
121
|
Arc::from("createAnalyser"),
|
|
127
|
-
Value::
|
|
122
|
+
Value::native(|_| analyser_stub()),
|
|
128
123
|
);
|
|
129
124
|
ctx.insert(
|
|
130
125
|
Arc::from("createBuffer"),
|
|
131
|
-
Value::
|
|
126
|
+
Value::native(|args: &[Value]| {
|
|
132
127
|
let len = args
|
|
133
128
|
.get(1)
|
|
134
129
|
.and_then(Value::as_number)
|
|
135
130
|
.unwrap_or(0.0)
|
|
136
131
|
.clamp(0.0, 1_000_000_000.0) as usize;
|
|
137
132
|
audio_buffer_stub(len)
|
|
138
|
-
})
|
|
133
|
+
}),
|
|
139
134
|
);
|
|
140
135
|
ctx.insert(
|
|
141
136
|
Arc::from("createBufferSource"),
|
|
142
|
-
Value::
|
|
137
|
+
Value::native(|_| buffer_source_stub()),
|
|
143
138
|
);
|
|
144
139
|
ctx.insert(
|
|
145
140
|
Arc::from("createOscillator"),
|
|
146
|
-
Value::
|
|
141
|
+
Value::native(|_| oscillator_stub()),
|
|
147
142
|
);
|
|
148
143
|
ctx.insert(
|
|
149
144
|
Arc::from("decodeAudioData"),
|
|
150
|
-
Value::
|
|
145
|
+
Value::native(|_| Value::Null),
|
|
151
146
|
);
|
|
152
147
|
|
|
153
|
-
Value::Object(
|
|
148
|
+
Value::Object(VmRef::new(ctx))
|
|
154
149
|
}
|
|
155
150
|
|
|
156
151
|
/// Global `Uint8Array` for native/VM: `new Uint8Array(n)` β numeric array of zeros (not real bytes).
|
|
157
152
|
pub fn uint8_array_constructor_value() -> Value {
|
|
158
|
-
let ctor =
|
|
153
|
+
let ctor = Value::native(|args: &[Value]| {
|
|
159
154
|
let len = args
|
|
160
155
|
.first()
|
|
161
156
|
.and_then(Value::as_number)
|
|
162
157
|
.unwrap_or(0.0)
|
|
163
158
|
.clamp(0.0, 1_000_000_000.0) as usize;
|
|
164
|
-
Value::Array(
|
|
159
|
+
Value::Array(VmRef::new(vec![Value::Number(0.0); len]))
|
|
165
160
|
});
|
|
166
161
|
let mut m = ObjectMap::default();
|
|
167
|
-
m.insert(Arc::from(CONSTRUCT),
|
|
168
|
-
Value::Object(
|
|
162
|
+
m.insert(Arc::from(CONSTRUCT), ctor);
|
|
163
|
+
Value::Object(VmRef::new(m))
|
|
169
164
|
}
|
|
170
165
|
|
|
171
166
|
/// Global `AudioContext` for native/VM: stub graph (no real audio).
|
|
172
167
|
pub fn audio_context_constructor_value() -> Value {
|
|
173
|
-
let ctor =
|
|
168
|
+
let ctor = Value::native(|_args: &[Value]| audio_context_instance());
|
|
174
169
|
let mut m = ObjectMap::default();
|
|
175
|
-
m.insert(Arc::from(CONSTRUCT),
|
|
176
|
-
Value::Object(
|
|
170
|
+
m.insert(Arc::from(CONSTRUCT), ctor);
|
|
171
|
+
Value::Object(VmRef::new(m))
|
|
177
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};
|
|
@@ -16,29 +17,35 @@ pub fn boolean(args: &[Value]) -> Value {
|
|
|
16
17
|
|
|
17
18
|
/// decodeURI(str)
|
|
18
19
|
pub fn decode_uri(args: &[Value]) -> Value {
|
|
19
|
-
let s = args
|
|
20
|
+
let s = args
|
|
21
|
+
.first()
|
|
22
|
+
.map(Value::to_display_string)
|
|
23
|
+
.unwrap_or_default();
|
|
20
24
|
Value::String(percent_decode(&s).unwrap_or(s).into())
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
/// encodeURI(str)
|
|
24
28
|
pub fn encode_uri(args: &[Value]) -> Value {
|
|
25
|
-
let s = args
|
|
29
|
+
let s = args
|
|
30
|
+
.first()
|
|
31
|
+
.map(Value::to_display_string)
|
|
32
|
+
.unwrap_or_default();
|
|
26
33
|
Value::String(percent_encode(&s).into())
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
/// isFinite(value)
|
|
30
37
|
pub fn is_finite(args: &[Value]) -> Value {
|
|
31
|
-
Value::Bool(
|
|
38
|
+
Value::Bool(
|
|
39
|
+
args.first()
|
|
40
|
+
.is_some_and(|v| matches!(v, Value::Number(n) if n.is_finite())),
|
|
41
|
+
)
|
|
32
42
|
}
|
|
33
43
|
|
|
34
44
|
/// isNaN(value)
|
|
35
45
|
pub fn is_nan(args: &[Value]) -> Value {
|
|
36
|
-
Value::Bool(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|| !matches!(v, Value::Number(_))
|
|
40
|
-
}),
|
|
41
|
-
)
|
|
46
|
+
Value::Bool(args.first().is_none_or(|v| {
|
|
47
|
+
matches!(v, Value::Number(n) if n.is_nan()) || !matches!(v, Value::Number(_))
|
|
48
|
+
}))
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
/// Array.isArray(value)
|
|
@@ -72,9 +79,9 @@ pub fn object_keys(args: &[Value]) -> Value {
|
|
|
72
79
|
.keys()
|
|
73
80
|
.map(|k| Value::String(Arc::clone(k)))
|
|
74
81
|
.collect();
|
|
75
|
-
Value::Array(
|
|
82
|
+
Value::Array(VmRef::new(keys))
|
|
76
83
|
} else {
|
|
77
|
-
Value::Array(
|
|
84
|
+
Value::Array(VmRef::new(Vec::new()))
|
|
78
85
|
}
|
|
79
86
|
}
|
|
80
87
|
|
|
@@ -83,9 +90,9 @@ pub fn object_values(args: &[Value]) -> Value {
|
|
|
83
90
|
if let Some(Value::Object(obj)) = args.first() {
|
|
84
91
|
let obj_borrow = obj.borrow();
|
|
85
92
|
let values: Vec<Value> = obj_borrow.values().cloned().collect();
|
|
86
|
-
Value::Array(
|
|
93
|
+
Value::Array(VmRef::new(values))
|
|
87
94
|
} else {
|
|
88
|
-
Value::Array(
|
|
95
|
+
Value::Array(VmRef::new(Vec::new()))
|
|
89
96
|
}
|
|
90
97
|
}
|
|
91
98
|
|
|
@@ -96,15 +103,15 @@ pub fn object_entries(args: &[Value]) -> Value {
|
|
|
96
103
|
let entries: Vec<Value> = obj_borrow
|
|
97
104
|
.iter()
|
|
98
105
|
.map(|(k, v)| {
|
|
99
|
-
Value::Array(
|
|
106
|
+
Value::Array(VmRef::new(vec![
|
|
100
107
|
Value::String(Arc::clone(k)),
|
|
101
108
|
v.clone(),
|
|
102
|
-
]))
|
|
109
|
+
]))
|
|
103
110
|
})
|
|
104
111
|
.collect();
|
|
105
|
-
Value::Array(
|
|
112
|
+
Value::Array(VmRef::new(entries))
|
|
106
113
|
} else {
|
|
107
|
-
Value::Array(
|
|
114
|
+
Value::Array(VmRef::new(Vec::new()))
|
|
108
115
|
}
|
|
109
116
|
}
|
|
110
117
|
|
|
@@ -139,17 +146,23 @@ pub fn object_assign(args: &[Value]) -> Value {
|
|
|
139
146
|
}
|
|
140
147
|
}
|
|
141
148
|
drop(target_mut);
|
|
142
|
-
Value::Object(
|
|
149
|
+
Value::Object(target.clone())
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
/// parseInt(string, radix?)
|
|
146
153
|
pub fn parse_int(args: &[Value]) -> Value {
|
|
147
|
-
let s = args
|
|
154
|
+
let s = args
|
|
155
|
+
.first()
|
|
156
|
+
.map(Value::to_display_string)
|
|
157
|
+
.unwrap_or_default();
|
|
148
158
|
let s = s.trim();
|
|
149
|
-
let radix = args
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
159
|
+
let radix = args
|
|
160
|
+
.get(1)
|
|
161
|
+
.and_then(|v| match v {
|
|
162
|
+
Value::Number(n) => Some(*n as i32),
|
|
163
|
+
_ => None,
|
|
164
|
+
})
|
|
165
|
+
.unwrap_or(10);
|
|
153
166
|
|
|
154
167
|
if (2..=36).contains(&radix) {
|
|
155
168
|
let prefix: String = s
|
|
@@ -165,7 +178,10 @@ pub fn parse_int(args: &[Value]) -> Value {
|
|
|
165
178
|
|
|
166
179
|
/// parseFloat(string)
|
|
167
180
|
pub fn parse_float(args: &[Value]) -> Value {
|
|
168
|
-
let s = args
|
|
181
|
+
let s = args
|
|
182
|
+
.first()
|
|
183
|
+
.map(Value::to_display_string)
|
|
184
|
+
.unwrap_or_default();
|
|
169
185
|
Value::Number(s.trim().parse().unwrap_or(f64::NAN))
|
|
170
186
|
}
|
|
171
187
|
|
|
@@ -188,8 +204,8 @@ pub fn object_from_entries(args: &[Value]) -> Value {
|
|
|
188
204
|
}
|
|
189
205
|
}
|
|
190
206
|
|
|
191
|
-
Value::Object(
|
|
207
|
+
Value::Object(VmRef::new(obj))
|
|
192
208
|
} else {
|
|
193
|
-
Value::Object(
|
|
209
|
+
Value::Object(VmRef::new(ObjectMap::default()))
|
|
194
210
|
}
|
|
195
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.
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
//! and native signatures.
|
|
6
6
|
|
|
7
7
|
pub mod array;
|
|
8
|
-
pub mod string;
|
|
9
|
-
pub mod object;
|
|
10
|
-
pub mod math;
|
|
11
|
-
pub mod helpers;
|
|
12
|
-
pub mod globals;
|
|
13
8
|
pub mod construct;
|
|
9
|
+
pub mod globals;
|
|
10
|
+
pub mod helpers;
|
|
11
|
+
pub mod math;
|
|
12
|
+
pub mod object;
|
|
13
|
+
pub mod string;
|
|
14
14
|
|
|
15
15
|
pub use tishlang_core::Value;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//! Math builtin functions.
|
|
2
2
|
|
|
3
|
-
use tishlang_core::Value;
|
|
4
3
|
use crate::helpers::extract_num;
|
|
4
|
+
use tishlang_core::Value;
|
|
5
5
|
|
|
6
6
|
macro_rules! math_unary {
|
|
7
7
|
($name:ident, $op:ident) => {
|
|
@@ -31,14 +31,16 @@ math_unary!(trunc, trunc);
|
|
|
31
31
|
math_unary!(cbrt, cbrt);
|
|
32
32
|
|
|
33
33
|
pub fn min(args: &[Value]) -> Value {
|
|
34
|
-
let n = args
|
|
34
|
+
let n = args
|
|
35
|
+
.iter()
|
|
35
36
|
.filter_map(|v| extract_num(Some(v)))
|
|
36
37
|
.fold(f64::INFINITY, f64::min);
|
|
37
38
|
Value::Number(if n == f64::INFINITY { f64::NAN } else { n })
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
pub fn max(args: &[Value]) -> Value {
|
|
41
|
-
let n = args
|
|
42
|
+
let n = args
|
|
43
|
+
.iter()
|
|
42
44
|
.filter_map(|v| extract_num(Some(v)))
|
|
43
45
|
.fold(f64::NEG_INFINITY, f64::max);
|
|
44
46
|
Value::Number(if n == f64::NEG_INFINITY { f64::NAN } else { n })
|
|
@@ -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.
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
//! All indices use character (Unicode scalar) positions for consistency with
|
|
4
4
|
//! JavaScript, matching .length and .charAt(). Byte offsets are never exposed.
|
|
5
5
|
|
|
6
|
+
use crate::helpers::normalize_index;
|
|
7
|
+
use tishlang_core::VmRef;
|
|
6
8
|
use std::cell::RefCell;
|
|
7
9
|
use std::rc::Rc;
|
|
8
10
|
use std::sync::Arc;
|
|
9
11
|
use tishlang_core::Value;
|
|
10
|
-
use crate::helpers::normalize_index;
|
|
11
12
|
|
|
12
13
|
/// Byte offset -> character index.
|
|
13
14
|
fn byte_to_char_index(s: &str, byte_offset: usize) -> usize {
|
|
@@ -152,7 +153,10 @@ pub fn slice(s: &Value, start: &Value, end: &Value) -> Value {
|
|
|
152
153
|
if let Value::String(s) = s {
|
|
153
154
|
let chars: Vec<char> = s.chars().collect();
|
|
154
155
|
let len = chars.len() as i64;
|
|
155
|
-
let (si, ei) = (
|
|
156
|
+
let (si, ei) = (
|
|
157
|
+
normalize_index(start, len, 0),
|
|
158
|
+
normalize_index(end, len, len as usize),
|
|
159
|
+
);
|
|
156
160
|
let result: String = if si < ei {
|
|
157
161
|
chars[si..ei].iter().collect()
|
|
158
162
|
} else {
|
|
@@ -191,12 +195,13 @@ pub fn split(s: &Value, sep: &Value) -> Value {
|
|
|
191
195
|
if let Value::String(s) = s {
|
|
192
196
|
let separator = match sep {
|
|
193
197
|
Value::String(ss) => ss.as_ref(),
|
|
194
|
-
_ => return Value::Array(
|
|
198
|
+
_ => return Value::Array(VmRef::new(vec![Value::String(Arc::clone(s))])),
|
|
195
199
|
};
|
|
196
|
-
let parts: Vec<Value> = s
|
|
200
|
+
let parts: Vec<Value> = s
|
|
201
|
+
.split(separator)
|
|
197
202
|
.map(|p| Value::String(p.into()))
|
|
198
203
|
.collect();
|
|
199
|
-
Value::Array(
|
|
204
|
+
Value::Array(VmRef::new(parts))
|
|
200
205
|
} else {
|
|
201
206
|
Value::Null
|
|
202
207
|
}
|
|
@@ -244,9 +249,19 @@ pub fn ends_with(s: &Value, search: &Value) -> Value {
|
|
|
244
249
|
|
|
245
250
|
fn replace_impl(s: &Value, search: &Value, replacement: &Value, all: bool) -> Value {
|
|
246
251
|
if let Value::String(s) = s {
|
|
247
|
-
let search_str = match search {
|
|
248
|
-
|
|
249
|
-
|
|
252
|
+
let search_str = match search {
|
|
253
|
+
Value::String(ss) => ss.as_ref(),
|
|
254
|
+
_ => return Value::String(Arc::clone(s)),
|
|
255
|
+
};
|
|
256
|
+
let repl_str = match replacement {
|
|
257
|
+
Value::String(ss) => ss.as_ref(),
|
|
258
|
+
_ => "",
|
|
259
|
+
};
|
|
260
|
+
let result = if all {
|
|
261
|
+
s.replace(search_str, repl_str)
|
|
262
|
+
} else {
|
|
263
|
+
s.replacen(search_str, repl_str, 1)
|
|
264
|
+
};
|
|
250
265
|
Value::String(result.into())
|
|
251
266
|
} else {
|
|
252
267
|
Value::Null
|
|
@@ -261,14 +276,66 @@ pub fn replace_all(s: &Value, search: &Value, replacement: &Value) -> Value {
|
|
|
261
276
|
replace_impl(s, search, replacement, true)
|
|
262
277
|
}
|
|
263
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
|
+
|
|
264
326
|
fn char_at_idx(s: &str, idx: usize) -> Option<char> {
|
|
265
327
|
s.chars().nth(idx)
|
|
266
328
|
}
|
|
267
329
|
|
|
268
330
|
pub fn char_at(s: &Value, idx: &Value) -> Value {
|
|
269
331
|
if let Value::String(s) = s {
|
|
270
|
-
let idx = match idx {
|
|
271
|
-
|
|
332
|
+
let idx = match idx {
|
|
333
|
+
Value::Number(n) => *n as usize,
|
|
334
|
+
_ => 0,
|
|
335
|
+
};
|
|
336
|
+
char_at_idx(s, idx)
|
|
337
|
+
.map(|c| Value::String(c.to_string().into()))
|
|
338
|
+
.unwrap_or(Value::String("".into()))
|
|
272
339
|
} else {
|
|
273
340
|
Value::Null
|
|
274
341
|
}
|
|
@@ -276,8 +343,13 @@ pub fn char_at(s: &Value, idx: &Value) -> Value {
|
|
|
276
343
|
|
|
277
344
|
pub fn char_code_at(s: &Value, idx: &Value) -> Value {
|
|
278
345
|
if let Value::String(s) = s {
|
|
279
|
-
let idx = match idx {
|
|
280
|
-
|
|
346
|
+
let idx = match idx {
|
|
347
|
+
Value::Number(n) => *n as usize,
|
|
348
|
+
_ => 0,
|
|
349
|
+
};
|
|
350
|
+
char_at_idx(s, idx)
|
|
351
|
+
.map(|c| Value::Number(c as u32 as f64))
|
|
352
|
+
.unwrap_or(Value::Number(f64::NAN))
|
|
281
353
|
} else {
|
|
282
354
|
Value::Null
|
|
283
355
|
}
|
|
@@ -311,7 +383,11 @@ fn pad_impl(s: &Value, target_len: &Value, pad: &Value, at_start: bool) -> Value
|
|
|
311
383
|
}
|
|
312
384
|
let needed = target_len - char_count;
|
|
313
385
|
let padding: String = pad_str.chars().cycle().take(needed).collect();
|
|
314
|
-
let result = if at_start {
|
|
386
|
+
let result = if at_start {
|
|
387
|
+
format!("{}{}", padding, s)
|
|
388
|
+
} else {
|
|
389
|
+
format!("{}{}", s, padding)
|
|
390
|
+
};
|
|
315
391
|
Value::String(result.into())
|
|
316
392
|
} else {
|
|
317
393
|
Value::Null
|
|
@@ -382,16 +458,31 @@ mod tests {
|
|
|
382
458
|
fn includes_basic() {
|
|
383
459
|
assert_same!(includes(&s("hello"), &s("ll"), None), Value::Bool(true));
|
|
384
460
|
assert_same!(includes(&s("hello"), &s("x"), None), Value::Bool(false));
|
|
385
|
-
assert_same!(
|
|
386
|
-
|
|
461
|
+
assert_same!(
|
|
462
|
+
includes(&s("hello"), &s("l"), Some(&n(3.0))),
|
|
463
|
+
Value::Bool(true)
|
|
464
|
+
);
|
|
465
|
+
assert_same!(
|
|
466
|
+
includes(&s("hello"), &s("l"), Some(&n(4.0))),
|
|
467
|
+
Value::Bool(false)
|
|
468
|
+
);
|
|
387
469
|
}
|
|
388
470
|
|
|
389
471
|
#[test]
|
|
390
472
|
fn includes_negative_from() {
|
|
391
|
-
assert_same!(
|
|
392
|
-
|
|
473
|
+
assert_same!(
|
|
474
|
+
includes(&s("hello"), &s("o"), Some(&n(-1.0))),
|
|
475
|
+
Value::Bool(true)
|
|
476
|
+
);
|
|
477
|
+
assert_same!(
|
|
478
|
+
includes(&s("hello"), &s("h"), Some(&n(-5.0))),
|
|
479
|
+
Value::Bool(true)
|
|
480
|
+
);
|
|
393
481
|
// fromIndex -1 β start at len-1 = 1 ("i" only), "h" not found
|
|
394
|
-
assert_same!(
|
|
482
|
+
assert_same!(
|
|
483
|
+
includes(&s("hi"), &s("h"), Some(&n(-1.0))),
|
|
484
|
+
Value::Bool(false)
|
|
485
|
+
);
|
|
395
486
|
}
|
|
396
487
|
|
|
397
488
|
#[test]
|
|
@@ -420,7 +511,7 @@ mod tests {
|
|
|
420
511
|
assert_eq!(a.borrow().len(), 2);
|
|
421
512
|
assert_same!(
|
|
422
513
|
split(&s("x"), &n(1.0)),
|
|
423
|
-
Value::Array(
|
|
514
|
+
Value::Array(VmRef::new(vec![s("x")]))
|
|
424
515
|
);
|
|
425
516
|
assert_same!(split(&n(1.0), &s(",")), Value::Null);
|
|
426
517
|
assert_same!(trim(&s(" x ")), s("x"));
|
|
@@ -466,7 +557,10 @@ mod tests {
|
|
|
466
557
|
|
|
467
558
|
#[test]
|
|
468
559
|
fn last_index_of_basic() {
|
|
469
|
-
assert_same!(
|
|
560
|
+
assert_same!(
|
|
561
|
+
last_index_of(&s("abcabc"), &s("a"), &n(f64::INFINITY)),
|
|
562
|
+
n(3.0)
|
|
563
|
+
);
|
|
470
564
|
assert_same!(last_index_of(&s("abcabc"), &s("a"), &n(2.0)), n(0.0));
|
|
471
565
|
assert_same!(last_index_of(&s("hello"), &s("l"), &n(3.0)), n(3.0));
|
|
472
566
|
assert_same!(last_index_of(&s("hello"), &s("l"), &n(1.0)), n(-1.0));
|
|
@@ -490,12 +584,40 @@ mod tests {
|
|
|
490
584
|
|
|
491
585
|
#[test]
|
|
492
586
|
fn last_index_of_unicode() {
|
|
493
|
-
assert_same!(
|
|
494
|
-
|
|
587
|
+
assert_same!(
|
|
588
|
+
last_index_of(&s("πaπ"), &s("a"), &n(f64::INFINITY)),
|
|
589
|
+
n(1.0)
|
|
590
|
+
);
|
|
591
|
+
assert_same!(
|
|
592
|
+
last_index_of(&s("πaπ"), &s("π"), &n(f64::INFINITY)),
|
|
593
|
+
n(2.0)
|
|
594
|
+
);
|
|
495
595
|
}
|
|
496
596
|
|
|
497
597
|
#[test]
|
|
498
598
|
fn last_index_of_non_string() {
|
|
499
599
|
assert_same!(last_index_of(&n(1.0), &s("a"), &n(0.0)), n(-1.0));
|
|
500
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
|
+
}
|
|
501
623
|
}
|