@tishlang/tish 1.0.7 → 1.0.11
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 +43 -0
- package/LICENSE +13 -0
- package/README.md +66 -0
- package/crates/js_to_tish/Cargo.toml +9 -0
- package/crates/js_to_tish/README.md +18 -0
- package/crates/js_to_tish/src/error.rs +61 -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 +608 -0
- package/crates/js_to_tish/src/transform/stmt.rs +474 -0
- package/crates/js_to_tish/src/transform.rs +60 -0
- package/crates/tish/Cargo.toml +44 -0
- package/crates/tish/src/main.rs +585 -0
- package/crates/tish/src/repl_completion.rs +200 -0
- package/crates/tish/tests/integration_test.rs +726 -0
- package/crates/tish_ast/Cargo.toml +7 -0
- package/crates/tish_ast/src/ast.rs +494 -0
- package/crates/tish_ast/src/lib.rs +5 -0
- package/crates/tish_build_utils/Cargo.toml +5 -0
- package/crates/tish_build_utils/src/lib.rs +175 -0
- package/crates/tish_builtins/Cargo.toml +12 -0
- package/crates/tish_builtins/src/array.rs +410 -0
- package/crates/tish_builtins/src/globals.rs +197 -0
- package/crates/tish_builtins/src/helpers.rs +38 -0
- package/crates/tish_builtins/src/lib.rs +14 -0
- package/crates/tish_builtins/src/math.rs +80 -0
- package/crates/tish_builtins/src/object.rs +36 -0
- package/crates/tish_builtins/src/string.rs +253 -0
- package/crates/tish_bytecode/Cargo.toml +15 -0
- package/crates/tish_bytecode/src/chunk.rs +97 -0
- package/crates/tish_bytecode/src/compiler.rs +1361 -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 +110 -0
- package/crates/tish_bytecode/src/peephole.rs +159 -0
- package/crates/tish_bytecode/src/serialize.rs +163 -0
- package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
- package/crates/tish_bytecode/tests/shortcircuit.rs +49 -0
- package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
- package/crates/tish_compile/Cargo.toml +21 -0
- package/crates/tish_compile/src/codegen.rs +3316 -0
- package/crates/tish_compile/src/lib.rs +71 -0
- package/crates/tish_compile/src/resolve.rs +631 -0
- package/crates/tish_compile/src/types.rs +304 -0
- package/crates/tish_compile_js/Cargo.toml +16 -0
- package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
- package/crates/tish_compile_js/src/codegen.rs +794 -0
- package/crates/tish_compile_js/src/error.rs +20 -0
- package/crates/tish_compile_js/src/js_intrinsics.rs +82 -0
- package/crates/tish_compile_js/src/lib.rs +27 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +32 -0
- package/crates/tish_compiler_wasm/Cargo.toml +19 -0
- package/crates/tish_compiler_wasm/src/lib.rs +55 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +462 -0
- package/crates/tish_core/Cargo.toml +11 -0
- package/crates/tish_core/src/console_style.rs +128 -0
- package/crates/tish_core/src/json.rs +327 -0
- package/crates/tish_core/src/lib.rs +15 -0
- package/crates/tish_core/src/macros.rs +37 -0
- package/crates/tish_core/src/uri.rs +115 -0
- package/crates/tish_core/src/value.rs +376 -0
- package/crates/tish_cranelift/Cargo.toml +17 -0
- package/crates/tish_cranelift/src/lib.rs +41 -0
- package/crates/tish_cranelift/src/link.rs +120 -0
- package/crates/tish_cranelift/src/lower.rs +77 -0
- package/crates/tish_cranelift_runtime/Cargo.toml +19 -0
- package/crates/tish_cranelift_runtime/src/lib.rs +43 -0
- package/crates/tish_eval/Cargo.toml +26 -0
- package/crates/tish_eval/src/eval.rs +3205 -0
- package/crates/tish_eval/src/http.rs +122 -0
- package/crates/tish_eval/src/lib.rs +59 -0
- package/crates/tish_eval/src/natives.rs +301 -0
- package/crates/tish_eval/src/promise.rs +173 -0
- package/crates/tish_eval/src/regex.rs +298 -0
- package/crates/tish_eval/src/timers.rs +111 -0
- package/crates/tish_eval/src/value.rs +224 -0
- package/crates/tish_eval/src/value_convert.rs +85 -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 +884 -0
- package/crates/tish_jsx_web/Cargo.toml +7 -0
- package/crates/tish_jsx_web/README.md +18 -0
- package/crates/tish_jsx_web/src/lib.rs +157 -0
- package/crates/tish_jsx_web/vendor/Lattish.tish +347 -0
- package/crates/tish_lexer/Cargo.toml +7 -0
- package/crates/tish_lexer/src/lib.rs +430 -0
- package/crates/tish_lexer/src/token.rs +155 -0
- package/crates/tish_lint/Cargo.toml +17 -0
- package/crates/tish_lint/src/bin/tish-lint.rs +77 -0
- package/crates/tish_lint/src/lib.rs +278 -0
- package/crates/tish_llvm/Cargo.toml +11 -0
- package/crates/tish_llvm/src/lib.rs +106 -0
- package/crates/tish_lsp/Cargo.toml +22 -0
- package/crates/tish_lsp/README.md +26 -0
- package/crates/tish_lsp/src/main.rs +615 -0
- package/crates/tish_native/Cargo.toml +14 -0
- package/crates/tish_native/src/build.rs +102 -0
- package/crates/tish_native/src/lib.rs +237 -0
- package/crates/tish_opt/Cargo.toml +11 -0
- package/crates/tish_opt/src/lib.rs +896 -0
- package/crates/tish_parser/Cargo.toml +9 -0
- package/crates/tish_parser/src/lib.rs +123 -0
- package/crates/tish_parser/src/parser.rs +1714 -0
- package/crates/tish_runtime/Cargo.toml +26 -0
- package/crates/tish_runtime/src/http.rs +308 -0
- package/crates/tish_runtime/src/http_fetch.rs +453 -0
- package/crates/tish_runtime/src/lib.rs +1004 -0
- package/crates/tish_runtime/src/native_promise.rs +26 -0
- package/crates/tish_runtime/src/promise.rs +77 -0
- package/crates/tish_runtime/src/promise_io.rs +41 -0
- package/crates/tish_runtime/src/timers.rs +125 -0
- package/crates/tish_runtime/src/ws.rs +725 -0
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +99 -0
- package/crates/tish_vm/Cargo.toml +31 -0
- package/crates/tish_vm/src/lib.rs +39 -0
- package/crates/tish_vm/src/vm.rs +1399 -0
- package/crates/tish_wasm/Cargo.toml +13 -0
- package/crates/tish_wasm/src/lib.rs +358 -0
- package/crates/tish_wasm_runtime/Cargo.toml +25 -0
- package/crates/tish_wasm_runtime/src/lib.rs +36 -0
- package/justfile +260 -0
- package/package.json +8 -3
- 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
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
//! Unified Value type for Tish runtime values.
|
|
2
|
+
|
|
3
|
+
use std::cell::RefCell;
|
|
4
|
+
use std::collections::HashMap;
|
|
5
|
+
use std::rc::Rc;
|
|
6
|
+
use std::sync::Arc;
|
|
7
|
+
|
|
8
|
+
#[cfg(feature = "regex")]
|
|
9
|
+
use fancy_regex::Regex;
|
|
10
|
+
|
|
11
|
+
/// Native function signature.
|
|
12
|
+
/// Returns Value directly (not Result) for simplicity and backward compatibility.
|
|
13
|
+
pub type NativeFn = Rc<dyn Fn(&[Value]) -> Value>;
|
|
14
|
+
|
|
15
|
+
/// Trait for opaque Rust types exposed to Tish (e.g. Polars DataFrame).
|
|
16
|
+
/// Implementors provide method dispatch so Tish can call methods on the value.
|
|
17
|
+
pub trait TishOpaque: Send + Sync {
|
|
18
|
+
/// Display name for the type (e.g. "DataFrame").
|
|
19
|
+
fn type_name(&self) -> &'static str;
|
|
20
|
+
|
|
21
|
+
/// Get a method by name. Returns a native function if the method exists.
|
|
22
|
+
fn get_method(&self, name: &str) -> Option<NativeFn>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// Trait for Promise-like values that can be awaited (block until settled).
|
|
26
|
+
/// Implemented by the runtime for native compile; interpreter uses its own Promise.
|
|
27
|
+
pub trait TishPromise: Send + Sync {
|
|
28
|
+
fn block_until_settled(&self) -> std::result::Result<Value, Value>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// JavaScript RegExp flags
|
|
32
|
+
#[cfg(feature = "regex")]
|
|
33
|
+
#[derive(Debug, Clone, Default)]
|
|
34
|
+
pub struct RegExpFlags {
|
|
35
|
+
pub global: bool,
|
|
36
|
+
pub ignore_case: bool,
|
|
37
|
+
pub multiline: bool,
|
|
38
|
+
pub dot_all: bool,
|
|
39
|
+
pub unicode: bool,
|
|
40
|
+
pub sticky: bool,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#[cfg(feature = "regex")]
|
|
44
|
+
impl RegExpFlags {
|
|
45
|
+
pub fn from_string(flags: &str) -> Result<Self, String> {
|
|
46
|
+
let mut result = Self::default();
|
|
47
|
+
for c in flags.chars() {
|
|
48
|
+
match c {
|
|
49
|
+
'g' => { if result.global { return Err(format!("duplicate flag '{}'", c)); } result.global = true; }
|
|
50
|
+
'i' => { if result.ignore_case { return Err(format!("duplicate flag '{}'", c)); } result.ignore_case = true; }
|
|
51
|
+
'm' => { if result.multiline { return Err(format!("duplicate flag '{}'", c)); } result.multiline = true; }
|
|
52
|
+
's' => { if result.dot_all { return Err(format!("duplicate flag '{}'", c)); } result.dot_all = true; }
|
|
53
|
+
'u' => { if result.unicode { return Err(format!("duplicate flag '{}'", c)); } result.unicode = true; }
|
|
54
|
+
'y' => { if result.sticky { return Err(format!("duplicate flag '{}'", c)); } result.sticky = true; }
|
|
55
|
+
_ => return Err(format!("unknown flag '{}'", c)),
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
Ok(result)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#[cfg(feature = "regex")]
|
|
64
|
+
impl std::fmt::Display for RegExpFlags {
|
|
65
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
66
|
+
if self.global { f.write_str("g")?; }
|
|
67
|
+
if self.ignore_case { f.write_str("i")?; }
|
|
68
|
+
if self.multiline { f.write_str("m")?; }
|
|
69
|
+
if self.dot_all { f.write_str("s")?; }
|
|
70
|
+
if self.unicode { f.write_str("u")?; }
|
|
71
|
+
if self.sticky { f.write_str("y")?; }
|
|
72
|
+
Ok(())
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Tish RegExp object
|
|
77
|
+
#[cfg(feature = "regex")]
|
|
78
|
+
#[derive(Debug, Clone)]
|
|
79
|
+
pub struct TishRegExp {
|
|
80
|
+
pub source: String,
|
|
81
|
+
pub flags: RegExpFlags,
|
|
82
|
+
pub regex: Arc<Regex>,
|
|
83
|
+
pub last_index: usize,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#[cfg(feature = "regex")]
|
|
87
|
+
impl TishRegExp {
|
|
88
|
+
pub fn new(pattern: &str, flags_str: &str) -> Result<Self, String> {
|
|
89
|
+
let flags = RegExpFlags::from_string(flags_str)?;
|
|
90
|
+
let mut regex_pattern = pattern.to_string();
|
|
91
|
+
|
|
92
|
+
if flags.ignore_case || flags.multiline || flags.dot_all {
|
|
93
|
+
let mut flag_prefix = String::from("(?");
|
|
94
|
+
if flags.ignore_case { flag_prefix.push('i'); }
|
|
95
|
+
if flags.multiline { flag_prefix.push('m'); }
|
|
96
|
+
if flags.dot_all { flag_prefix.push('s'); }
|
|
97
|
+
flag_prefix.push(')');
|
|
98
|
+
regex_pattern = format!("{}{}", flag_prefix, regex_pattern);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let regex = Regex::new(®ex_pattern)
|
|
102
|
+
.map_err(|e| format!("Invalid regular expression: {}", e))?;
|
|
103
|
+
|
|
104
|
+
Ok(Self { source: pattern.to_string(), flags, regex: Arc::new(regex), last_index: 0 })
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
pub fn flags_string(&self) -> String { self.flags.to_string() }
|
|
108
|
+
|
|
109
|
+
pub fn test(&mut self, input: &str) -> bool {
|
|
110
|
+
if self.flags.global || self.flags.sticky {
|
|
111
|
+
let start = self.last_index;
|
|
112
|
+
if start > input.chars().count() {
|
|
113
|
+
self.last_index = 0;
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let byte_start: usize = input.chars().take(start).map(|c| c.len_utf8()).sum();
|
|
118
|
+
let search_str = &input[byte_start..];
|
|
119
|
+
|
|
120
|
+
match self.regex.find(search_str) {
|
|
121
|
+
Ok(Some(m)) => {
|
|
122
|
+
if self.flags.sticky && m.start() != 0 {
|
|
123
|
+
self.last_index = 0;
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
let match_end_chars = input[byte_start..byte_start + m.end()].chars().count();
|
|
127
|
+
self.last_index = start + match_end_chars;
|
|
128
|
+
true
|
|
129
|
+
}
|
|
130
|
+
_ => {
|
|
131
|
+
self.last_index = 0;
|
|
132
|
+
false
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
self.regex.is_match(input).unwrap_or(false)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// Runtime value for Tish programs.
|
|
142
|
+
/// Used by both interpreter and compiled code.
|
|
143
|
+
#[derive(Clone)]
|
|
144
|
+
pub enum Value {
|
|
145
|
+
Number(f64),
|
|
146
|
+
String(Arc<str>),
|
|
147
|
+
Bool(bool),
|
|
148
|
+
Null,
|
|
149
|
+
Array(Rc<RefCell<Vec<Value>>>),
|
|
150
|
+
Object(Rc<RefCell<HashMap<Arc<str>, Value>>>),
|
|
151
|
+
Function(NativeFn),
|
|
152
|
+
#[cfg(feature = "regex")]
|
|
153
|
+
RegExp(Rc<RefCell<TishRegExp>>),
|
|
154
|
+
/// Promise (for native compile). Interpreter uses tish_eval::Value::Promise.
|
|
155
|
+
Promise(Arc<dyn TishPromise>),
|
|
156
|
+
/// Opaque handle to a native Rust type (e.g. Polars DataFrame).
|
|
157
|
+
Opaque(Arc<dyn TishOpaque>),
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
impl std::fmt::Debug for Value {
|
|
161
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
162
|
+
match self {
|
|
163
|
+
Value::Number(n) => write!(f, "Number({})", n),
|
|
164
|
+
Value::String(s) => write!(f, "String({:?})", s.as_ref()),
|
|
165
|
+
Value::Bool(b) => write!(f, "Bool({})", b),
|
|
166
|
+
Value::Null => write!(f, "Null"),
|
|
167
|
+
Value::Array(arr) => write!(f, "Array({:?})", arr.borrow()),
|
|
168
|
+
Value::Object(obj) => write!(f, "Object({:?})", obj.borrow()),
|
|
169
|
+
Value::Function(_) => write!(f, "Function"),
|
|
170
|
+
#[cfg(feature = "regex")]
|
|
171
|
+
Value::RegExp(re) => write!(f, "RegExp(/{}/{})", re.borrow().source, re.borrow().flags_string()),
|
|
172
|
+
Value::Promise(_) => write!(f, "Promise"),
|
|
173
|
+
Value::Opaque(o) => write!(f, "{}(opaque)", o.type_name()),
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
impl Value {
|
|
179
|
+
/// Convert value to display string (for console output).
|
|
180
|
+
pub fn to_display_string(&self) -> String {
|
|
181
|
+
match self {
|
|
182
|
+
Value::Number(n) => {
|
|
183
|
+
if n.is_nan() {
|
|
184
|
+
"NaN".to_string()
|
|
185
|
+
} else if *n == f64::INFINITY {
|
|
186
|
+
"Infinity".to_string()
|
|
187
|
+
} else if *n == f64::NEG_INFINITY {
|
|
188
|
+
"-Infinity".to_string()
|
|
189
|
+
} else {
|
|
190
|
+
n.to_string()
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
Value::String(s) => s.to_string(),
|
|
194
|
+
Value::Bool(b) => b.to_string(),
|
|
195
|
+
Value::Null => "null".to_string(),
|
|
196
|
+
Value::Array(arr) => {
|
|
197
|
+
let inner: Vec<String> = arr.borrow().iter().map(|v| v.to_display_string()).collect();
|
|
198
|
+
format!("[{}]", inner.join(", "))
|
|
199
|
+
}
|
|
200
|
+
Value::Object(obj) => {
|
|
201
|
+
let inner: Vec<String> = obj
|
|
202
|
+
.borrow()
|
|
203
|
+
.iter()
|
|
204
|
+
.map(|(k, v)| format!("{}: {}", k.as_ref(), v.to_display_string()))
|
|
205
|
+
.collect();
|
|
206
|
+
format!("{{{}}}", inner.join(", "))
|
|
207
|
+
}
|
|
208
|
+
Value::Function(_) => "[Function]".to_string(),
|
|
209
|
+
Value::Promise(_) => "[object Promise]".to_string(),
|
|
210
|
+
Value::Opaque(o) => format!("[object {}]", o.type_name()),
|
|
211
|
+
#[cfg(feature = "regex")]
|
|
212
|
+
Value::RegExp(re) => {
|
|
213
|
+
let re = re.borrow();
|
|
214
|
+
format!("/{}/{}", re.source, re.flags_string())
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// Check if value is truthy (for conditionals).
|
|
220
|
+
pub fn is_truthy(&self) -> bool {
|
|
221
|
+
match self {
|
|
222
|
+
Value::Null => false,
|
|
223
|
+
Value::Bool(b) => *b,
|
|
224
|
+
Value::Number(n) => *n != 0.0 && !n.is_nan(),
|
|
225
|
+
Value::String(s) => !s.is_empty(),
|
|
226
|
+
_ => true,
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/// Strict equality (===).
|
|
231
|
+
pub fn strict_eq(&self, other: &Value) -> bool {
|
|
232
|
+
match (self, other) {
|
|
233
|
+
(Value::Number(a), Value::Number(b)) => {
|
|
234
|
+
if a.is_nan() || b.is_nan() {
|
|
235
|
+
false
|
|
236
|
+
} else {
|
|
237
|
+
a == b
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
(Value::String(a), Value::String(b)) => a == b,
|
|
241
|
+
(Value::Bool(a), Value::Bool(b)) => a == b,
|
|
242
|
+
(Value::Null, Value::Null) => true,
|
|
243
|
+
(Value::Array(a), Value::Array(b)) => Rc::ptr_eq(a, b),
|
|
244
|
+
(Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b),
|
|
245
|
+
(Value::Function(a), Value::Function(b)) => Rc::ptr_eq(a, b),
|
|
246
|
+
#[cfg(feature = "regex")]
|
|
247
|
+
(Value::RegExp(a), Value::RegExp(b)) => Rc::ptr_eq(a, b),
|
|
248
|
+
(Value::Promise(a), Value::Promise(b)) => Arc::ptr_eq(a, b),
|
|
249
|
+
(Value::Opaque(a), Value::Opaque(b)) => Arc::ptr_eq(a, b),
|
|
250
|
+
_ => false,
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/// Create a new array Value from a Vec.
|
|
255
|
+
pub fn array(items: Vec<Value>) -> Self {
|
|
256
|
+
Value::Array(Rc::new(RefCell::new(items)))
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/// Create a new object Value from a HashMap.
|
|
260
|
+
pub fn object(map: HashMap<Arc<str>, Value>) -> Self {
|
|
261
|
+
Value::Object(Rc::new(RefCell::new(map)))
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/// Create an empty array Value.
|
|
265
|
+
pub fn empty_array() -> Self {
|
|
266
|
+
Value::Array(Rc::new(RefCell::new(Vec::new())))
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/// Create an empty object Value.
|
|
270
|
+
pub fn empty_object() -> Self {
|
|
271
|
+
Value::Object(Rc::new(RefCell::new(HashMap::new())))
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/// Extract the number value, if this is a Number.
|
|
275
|
+
pub fn as_number(&self) -> Option<f64> {
|
|
276
|
+
match self {
|
|
277
|
+
Value::Number(n) => Some(*n),
|
|
278
|
+
_ => None,
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/// JavaScript-style typeof string for this value.
|
|
283
|
+
pub fn type_name(&self) -> &'static str {
|
|
284
|
+
match self {
|
|
285
|
+
Value::Number(_) => "number",
|
|
286
|
+
Value::String(_) => "string",
|
|
287
|
+
Value::Bool(_) => "boolean",
|
|
288
|
+
Value::Null => "null",
|
|
289
|
+
Value::Array(_) => "object",
|
|
290
|
+
Value::Object(_) => "object",
|
|
291
|
+
Value::Function(_) => "function",
|
|
292
|
+
#[cfg(feature = "regex")]
|
|
293
|
+
Value::RegExp(_) => "object",
|
|
294
|
+
Value::Promise(_) => "object",
|
|
295
|
+
Value::Opaque(o) => o.type_name(),
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/// Property/method names for REPL tab completion (e.g. after `obj.`).
|
|
300
|
+
pub fn completion_keys(&self) -> Vec<String> {
|
|
301
|
+
match self {
|
|
302
|
+
Value::Object(m) => {
|
|
303
|
+
let mut keys: Vec<String> = m.borrow().keys().map(|k| k.to_string()).collect();
|
|
304
|
+
keys.sort();
|
|
305
|
+
keys
|
|
306
|
+
}
|
|
307
|
+
Value::Array(_) => {
|
|
308
|
+
vec![
|
|
309
|
+
"length".into(),
|
|
310
|
+
"at".into(),
|
|
311
|
+
"concat".into(),
|
|
312
|
+
"copyWithin".into(),
|
|
313
|
+
"entries".into(),
|
|
314
|
+
"every".into(),
|
|
315
|
+
"fill".into(),
|
|
316
|
+
"filter".into(),
|
|
317
|
+
"find".into(),
|
|
318
|
+
"findIndex".into(),
|
|
319
|
+
"findLast".into(),
|
|
320
|
+
"findLastIndex".into(),
|
|
321
|
+
"flat".into(),
|
|
322
|
+
"flatMap".into(),
|
|
323
|
+
"forEach".into(),
|
|
324
|
+
"includes".into(),
|
|
325
|
+
"indexOf".into(),
|
|
326
|
+
"join".into(),
|
|
327
|
+
"keys".into(),
|
|
328
|
+
"lastIndexOf".into(),
|
|
329
|
+
"map".into(),
|
|
330
|
+
"pop".into(),
|
|
331
|
+
"push".into(),
|
|
332
|
+
"reduce".into(),
|
|
333
|
+
"reduceRight".into(),
|
|
334
|
+
"reverse".into(),
|
|
335
|
+
"shift".into(),
|
|
336
|
+
"slice".into(),
|
|
337
|
+
"some".into(),
|
|
338
|
+
"sort".into(),
|
|
339
|
+
"splice".into(),
|
|
340
|
+
"toLocaleString".into(),
|
|
341
|
+
"toReversed".into(),
|
|
342
|
+
"toSorted".into(),
|
|
343
|
+
"toSpliced".into(),
|
|
344
|
+
"toString".into(),
|
|
345
|
+
"unshift".into(),
|
|
346
|
+
"values".into(),
|
|
347
|
+
"shuffle".into(),
|
|
348
|
+
]
|
|
349
|
+
}
|
|
350
|
+
Value::String(_) => {
|
|
351
|
+
vec![
|
|
352
|
+
"length".into(),
|
|
353
|
+
"charAt".into(),
|
|
354
|
+
"charCodeAt".into(),
|
|
355
|
+
"endsWith".into(),
|
|
356
|
+
"includes".into(),
|
|
357
|
+
"indexOf".into(),
|
|
358
|
+
"padEnd".into(),
|
|
359
|
+
"padStart".into(),
|
|
360
|
+
"repeat".into(),
|
|
361
|
+
"replace".into(),
|
|
362
|
+
"replaceAll".into(),
|
|
363
|
+
"slice".into(),
|
|
364
|
+
"split".into(),
|
|
365
|
+
"startsWith".into(),
|
|
366
|
+
"substring".into(),
|
|
367
|
+
"toLowerCase".into(),
|
|
368
|
+
"toUpperCase".into(),
|
|
369
|
+
"trim".into(),
|
|
370
|
+
]
|
|
371
|
+
}
|
|
372
|
+
Value::Number(_) => vec!["toFixed".into(), "toExponential".into(), "toPrecision".into()],
|
|
373
|
+
_ => vec![],
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "tish_cranelift"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
description = "Bytecode to native via Cranelift"
|
|
6
|
+
|
|
7
|
+
[dependencies]
|
|
8
|
+
tish_build_utils = { path = "../tish_build_utils" }
|
|
9
|
+
cranelift = "0.130"
|
|
10
|
+
cranelift-codegen = "0.130"
|
|
11
|
+
cranelift-frontend = "0.130"
|
|
12
|
+
cranelift-module = "0.130"
|
|
13
|
+
cranelift-native = "0.130"
|
|
14
|
+
cranelift-object = "0.130"
|
|
15
|
+
target-lexicon = "0.13"
|
|
16
|
+
tish_bytecode = { path = "../tish_bytecode" }
|
|
17
|
+
tish_core = { path = "../tish_core" }
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//! Bytecode to native via Cranelift.
|
|
2
|
+
//!
|
|
3
|
+
//! Compiles Tish bytecode to native object files and links with a minimal runtime.
|
|
4
|
+
|
|
5
|
+
mod link;
|
|
6
|
+
mod lower;
|
|
7
|
+
|
|
8
|
+
pub use link::link_to_binary;
|
|
9
|
+
|
|
10
|
+
use std::path::Path;
|
|
11
|
+
|
|
12
|
+
use tish_bytecode::Chunk;
|
|
13
|
+
|
|
14
|
+
/// Error from Cranelift compilation.
|
|
15
|
+
#[derive(Debug)]
|
|
16
|
+
pub struct CraneliftError {
|
|
17
|
+
pub message: String,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
impl std::fmt::Display for CraneliftError {
|
|
21
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
22
|
+
write!(f, "{}", self.message)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
impl std::error::Error for CraneliftError {}
|
|
27
|
+
|
|
28
|
+
/// Compile a bytecode chunk to a native binary.
|
|
29
|
+
/// `features` are passed to tish_cranelift_runtime (e.g. fs, process, http for built-in modules).
|
|
30
|
+
pub fn compile_chunk_to_native(
|
|
31
|
+
chunk: &Chunk,
|
|
32
|
+
output_path: &Path,
|
|
33
|
+
features: &[String],
|
|
34
|
+
) -> Result<(), CraneliftError> {
|
|
35
|
+
let object_path = output_path.with_extension("o");
|
|
36
|
+
lower::lower_and_emit(chunk, &object_path)?;
|
|
37
|
+
link::link_to_binary(&object_path, output_path, features)?;
|
|
38
|
+
// Clean up .o file
|
|
39
|
+
let _ = std::fs::remove_file(&object_path);
|
|
40
|
+
Ok(())
|
|
41
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
//! Link object file with runtime to produce final binary.
|
|
2
|
+
//!
|
|
3
|
+
//! Uses Cargo to build a small binary that links our .o and runs the chunk.
|
|
4
|
+
|
|
5
|
+
use std::fs;
|
|
6
|
+
use std::path::Path;
|
|
7
|
+
|
|
8
|
+
use crate::CraneliftError;
|
|
9
|
+
|
|
10
|
+
pub fn link_to_binary(
|
|
11
|
+
object_path: &Path,
|
|
12
|
+
output_path: &Path,
|
|
13
|
+
features: &[String],
|
|
14
|
+
) -> Result<(), CraneliftError> {
|
|
15
|
+
let workspace_root = tish_build_utils::find_workspace_root().map_err(|e| CraneliftError {
|
|
16
|
+
message: e,
|
|
17
|
+
})?;
|
|
18
|
+
let out_name = output_path
|
|
19
|
+
.file_stem()
|
|
20
|
+
.and_then(|s| s.to_str())
|
|
21
|
+
.unwrap_or("tish_out");
|
|
22
|
+
let build_dir = tish_build_utils::create_build_dir("tish_cranelift_build", out_name)
|
|
23
|
+
.map_err(|e| CraneliftError { message: e })?;
|
|
24
|
+
|
|
25
|
+
let object_path_str = object_path
|
|
26
|
+
.canonicalize()
|
|
27
|
+
.map_err(|e| CraneliftError {
|
|
28
|
+
message: format!("Cannot canonicalize object path: {}", e),
|
|
29
|
+
})?
|
|
30
|
+
.display()
|
|
31
|
+
.to_string()
|
|
32
|
+
.replace('\\', "/");
|
|
33
|
+
|
|
34
|
+
// tish_cranelift_runtime path (workspace/crates/tish_cranelift_runtime)
|
|
35
|
+
let runtime_path = workspace_root
|
|
36
|
+
.join("crates")
|
|
37
|
+
.join("tish_cranelift_runtime")
|
|
38
|
+
.canonicalize()
|
|
39
|
+
.map_err(|e| CraneliftError {
|
|
40
|
+
message: format!("Cannot find tish_cranelift_runtime: {}", e),
|
|
41
|
+
})?
|
|
42
|
+
.display()
|
|
43
|
+
.to_string()
|
|
44
|
+
.replace('\\', "/");
|
|
45
|
+
|
|
46
|
+
let features_str = if features.is_empty() {
|
|
47
|
+
String::new()
|
|
48
|
+
} else {
|
|
49
|
+
format!(
|
|
50
|
+
", features = [{}]",
|
|
51
|
+
features
|
|
52
|
+
.iter()
|
|
53
|
+
.map(|f| format!("{:?}", f))
|
|
54
|
+
.collect::<Vec<_>>()
|
|
55
|
+
.join(", ")
|
|
56
|
+
)
|
|
57
|
+
};
|
|
58
|
+
let cargo_toml_fixed = format!(
|
|
59
|
+
r#"[package]
|
|
60
|
+
name = "tish_cranelift_out"
|
|
61
|
+
version = "0.1.0"
|
|
62
|
+
edition = "2021"
|
|
63
|
+
|
|
64
|
+
[[bin]]
|
|
65
|
+
name = "{}"
|
|
66
|
+
path = "src/main.rs"
|
|
67
|
+
|
|
68
|
+
[dependencies]
|
|
69
|
+
tish_cranelift_runtime = {{ path = {:?}{} }}
|
|
70
|
+
"#,
|
|
71
|
+
out_name, runtime_path, features_str
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
let main_rs = r#"
|
|
75
|
+
extern "C" {
|
|
76
|
+
static tish_chunk_data: [u8; 1];
|
|
77
|
+
static tish_chunk_len: u64;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn main() {
|
|
81
|
+
let len = unsafe { tish_chunk_len } as usize;
|
|
82
|
+
let ptr = unsafe { tish_chunk_data.as_ptr() };
|
|
83
|
+
let exit_code = tish_cranelift_runtime::tish_run_chunk(ptr, len);
|
|
84
|
+
std::process::exit(exit_code);
|
|
85
|
+
}
|
|
86
|
+
"#;
|
|
87
|
+
|
|
88
|
+
let build_rs = format!(
|
|
89
|
+
r#"
|
|
90
|
+
fn main() {{
|
|
91
|
+
println!("cargo:rustc-link-arg={}");
|
|
92
|
+
}}
|
|
93
|
+
"#,
|
|
94
|
+
object_path_str
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
fs::write(build_dir.join("Cargo.toml"), cargo_toml_fixed).map_err(|e| CraneliftError {
|
|
98
|
+
message: format!("Cannot write Cargo.toml: {}", e),
|
|
99
|
+
})?;
|
|
100
|
+
fs::write(build_dir.join("src/main.rs"), main_rs).map_err(|e| CraneliftError {
|
|
101
|
+
message: format!("Cannot write main.rs: {}", e),
|
|
102
|
+
})?;
|
|
103
|
+
fs::write(build_dir.join("build.rs"), build_rs).map_err(|e| CraneliftError {
|
|
104
|
+
message: format!("Cannot write build.rs: {}", e),
|
|
105
|
+
})?;
|
|
106
|
+
|
|
107
|
+
tish_build_utils::run_cargo_build(&build_dir, None).map_err(|e| CraneliftError { message: e })?;
|
|
108
|
+
|
|
109
|
+
let binary_dir = build_dir.join("target").join("release");
|
|
110
|
+
let binary =
|
|
111
|
+
tish_build_utils::find_release_binary(&binary_dir, out_name)
|
|
112
|
+
.map_err(|e| CraneliftError { message: e })?;
|
|
113
|
+
let target = tish_build_utils::resolve_output_path(output_path, out_name);
|
|
114
|
+
tish_build_utils::copy_binary_to_output(&binary, &target)
|
|
115
|
+
.map_err(|e| CraneliftError { message: e })?;
|
|
116
|
+
|
|
117
|
+
Ok(())
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
//! Bytecode to Cranelift IR lowering.
|
|
2
|
+
//!
|
|
3
|
+
//! Emits object file with tish_chunk_data and tish_chunk_len symbols.
|
|
4
|
+
//! The link step builds a Rust binary that reads these and runs via tish_vm.
|
|
5
|
+
|
|
6
|
+
use std::path::Path;
|
|
7
|
+
|
|
8
|
+
use cranelift::codegen::settings::Configurable;
|
|
9
|
+
use cranelift::codegen::settings;
|
|
10
|
+
use cranelift_module::{DataDescription, Linkage, Module};
|
|
11
|
+
use cranelift_object::{ObjectBuilder, ObjectModule};
|
|
12
|
+
|
|
13
|
+
use tish_bytecode::{serialize, Chunk};
|
|
14
|
+
|
|
15
|
+
use crate::CraneliftError;
|
|
16
|
+
|
|
17
|
+
pub fn lower_and_emit(chunk: &Chunk, object_path: &Path) -> Result<(), CraneliftError> {
|
|
18
|
+
let mut settings_builder = settings::builder();
|
|
19
|
+
settings_builder.set("opt_level", "speed").map_err(|_| CraneliftError {
|
|
20
|
+
message: "Failed to set opt_level".to_string(),
|
|
21
|
+
})?;
|
|
22
|
+
let flags = settings::Flags::new(settings_builder);
|
|
23
|
+
|
|
24
|
+
let isa_builder = cranelift_native::builder().map_err(|e| CraneliftError {
|
|
25
|
+
message: format!("Failed to build ISA: {}", e),
|
|
26
|
+
})?;
|
|
27
|
+
let isa = isa_builder.finish(flags).map_err(|e| CraneliftError {
|
|
28
|
+
message: format!("Failed to finish ISA: {}", e),
|
|
29
|
+
})?;
|
|
30
|
+
|
|
31
|
+
let object_builder = ObjectBuilder::new(isa, "tish_cranelift", cranelift_module::default_libcall_names())
|
|
32
|
+
.map_err(|e| CraneliftError {
|
|
33
|
+
message: format!("Failed to create ObjectBuilder: {}", e),
|
|
34
|
+
})?;
|
|
35
|
+
let mut module = ObjectModule::new(object_builder);
|
|
36
|
+
|
|
37
|
+
// Serialize chunk and emit as data - link step will build a Rust binary that reads it
|
|
38
|
+
let chunk_data = serialize(chunk);
|
|
39
|
+
let chunk_len = chunk_data.len() as u64;
|
|
40
|
+
let data_id = module
|
|
41
|
+
.declare_data("tish_chunk_data", Linkage::Export, false, false)
|
|
42
|
+
.map_err(|e| CraneliftError {
|
|
43
|
+
message: format!("Failed to declare chunk data: {}", e),
|
|
44
|
+
})?;
|
|
45
|
+
let mut data_desc = DataDescription::new();
|
|
46
|
+
data_desc.define(chunk_data.into_boxed_slice());
|
|
47
|
+
module
|
|
48
|
+
.define_data(data_id, &data_desc)
|
|
49
|
+
.map_err(|e| CraneliftError {
|
|
50
|
+
message: format!("Failed to define chunk data: {}", e),
|
|
51
|
+
})?;
|
|
52
|
+
|
|
53
|
+
let len_data = chunk_len.to_le_bytes();
|
|
54
|
+
let len_id = module
|
|
55
|
+
.declare_data("tish_chunk_len", Linkage::Export, false, false)
|
|
56
|
+
.map_err(|e| CraneliftError {
|
|
57
|
+
message: format!("Failed to declare chunk len: {}", e),
|
|
58
|
+
})?;
|
|
59
|
+
let mut len_desc = DataDescription::new();
|
|
60
|
+
len_desc.define(len_data.to_vec().into_boxed_slice());
|
|
61
|
+
module
|
|
62
|
+
.define_data(len_id, &len_desc)
|
|
63
|
+
.map_err(|e| CraneliftError {
|
|
64
|
+
message: format!("Failed to define chunk len: {}", e),
|
|
65
|
+
})?;
|
|
66
|
+
|
|
67
|
+
let object_product = module.finish();
|
|
68
|
+
let bytes = object_product.emit().map_err(|e| CraneliftError {
|
|
69
|
+
message: format!("Failed to emit object: {}", e),
|
|
70
|
+
})?;
|
|
71
|
+
|
|
72
|
+
std::fs::write(object_path, bytes).map_err(|e| CraneliftError {
|
|
73
|
+
message: format!("Failed to write object file: {}", e),
|
|
74
|
+
})?;
|
|
75
|
+
|
|
76
|
+
Ok(())
|
|
77
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "tish_cranelift_runtime"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
description = "Runtime for Cranelift-compiled Tish bytecode"
|
|
6
|
+
|
|
7
|
+
[features]
|
|
8
|
+
default = []
|
|
9
|
+
fs = ["tish_vm/fs"]
|
|
10
|
+
process = ["tish_vm/process"]
|
|
11
|
+
http = ["tish_vm/http"]
|
|
12
|
+
|
|
13
|
+
[lib]
|
|
14
|
+
crate-type = ["staticlib", "rlib"]
|
|
15
|
+
|
|
16
|
+
[dependencies]
|
|
17
|
+
tish_bytecode = { path = "../tish_bytecode" }
|
|
18
|
+
tish_vm = { path = "../tish_vm" }
|
|
19
|
+
tish_core = { path = "../tish_core" }
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//! Runtime for Cranelift-compiled Tish programs.
|
|
2
|
+
//!
|
|
3
|
+
//! Provides tish_run_chunk(ptr, len) which deserializes and runs bytecode.
|
|
4
|
+
|
|
5
|
+
use tish_bytecode::deserialize;
|
|
6
|
+
use tish_vm::Vm;
|
|
7
|
+
|
|
8
|
+
/// Serialization format:
|
|
9
|
+
/// - u64: code len
|
|
10
|
+
/// - bytes: code
|
|
11
|
+
/// - u64: constants count
|
|
12
|
+
/// - for each constant: u8 tag + payload
|
|
13
|
+
/// - u64: names count
|
|
14
|
+
/// - for each name: u64 len + bytes
|
|
15
|
+
///
|
|
16
|
+
/// Rust-callable wrapper. Run serialized chunk data. Returns exit code (0 on success).
|
|
17
|
+
pub fn tish_run_chunk(ptr: *const u8, len: usize) -> i32 {
|
|
18
|
+
tish_run_chunk_impl(ptr, len)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
#[no_mangle]
|
|
22
|
+
extern "C" fn tish_run_chunk_impl(ptr: *const u8, len: usize) -> i32 {
|
|
23
|
+
if ptr.is_null() || len < 8 {
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
|
|
27
|
+
match deserialize(slice) {
|
|
28
|
+
Ok(chunk) => {
|
|
29
|
+
let mut vm = Vm::new();
|
|
30
|
+
match vm.run(&chunk) {
|
|
31
|
+
Ok(_) => 0,
|
|
32
|
+
Err(e) => {
|
|
33
|
+
eprintln!("Runtime error: {}", e);
|
|
34
|
+
1
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
Err(e) => {
|
|
39
|
+
eprintln!("Deserialization error: {}", e);
|
|
40
|
+
1
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|