@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.
Files changed (127) hide show
  1. package/Cargo.toml +43 -0
  2. package/LICENSE +13 -0
  3. package/README.md +66 -0
  4. package/crates/js_to_tish/Cargo.toml +9 -0
  5. package/crates/js_to_tish/README.md +18 -0
  6. package/crates/js_to_tish/src/error.rs +61 -0
  7. package/crates/js_to_tish/src/lib.rs +11 -0
  8. package/crates/js_to_tish/src/span_util.rs +35 -0
  9. package/crates/js_to_tish/src/transform/expr.rs +608 -0
  10. package/crates/js_to_tish/src/transform/stmt.rs +474 -0
  11. package/crates/js_to_tish/src/transform.rs +60 -0
  12. package/crates/tish/Cargo.toml +44 -0
  13. package/crates/tish/src/main.rs +585 -0
  14. package/crates/tish/src/repl_completion.rs +200 -0
  15. package/crates/tish/tests/integration_test.rs +726 -0
  16. package/crates/tish_ast/Cargo.toml +7 -0
  17. package/crates/tish_ast/src/ast.rs +494 -0
  18. package/crates/tish_ast/src/lib.rs +5 -0
  19. package/crates/tish_build_utils/Cargo.toml +5 -0
  20. package/crates/tish_build_utils/src/lib.rs +175 -0
  21. package/crates/tish_builtins/Cargo.toml +12 -0
  22. package/crates/tish_builtins/src/array.rs +410 -0
  23. package/crates/tish_builtins/src/globals.rs +197 -0
  24. package/crates/tish_builtins/src/helpers.rs +38 -0
  25. package/crates/tish_builtins/src/lib.rs +14 -0
  26. package/crates/tish_builtins/src/math.rs +80 -0
  27. package/crates/tish_builtins/src/object.rs +36 -0
  28. package/crates/tish_builtins/src/string.rs +253 -0
  29. package/crates/tish_bytecode/Cargo.toml +15 -0
  30. package/crates/tish_bytecode/src/chunk.rs +97 -0
  31. package/crates/tish_bytecode/src/compiler.rs +1361 -0
  32. package/crates/tish_bytecode/src/encoding.rs +100 -0
  33. package/crates/tish_bytecode/src/lib.rs +19 -0
  34. package/crates/tish_bytecode/src/opcode.rs +110 -0
  35. package/crates/tish_bytecode/src/peephole.rs +159 -0
  36. package/crates/tish_bytecode/src/serialize.rs +163 -0
  37. package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
  38. package/crates/tish_bytecode/tests/shortcircuit.rs +49 -0
  39. package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
  40. package/crates/tish_compile/Cargo.toml +21 -0
  41. package/crates/tish_compile/src/codegen.rs +3316 -0
  42. package/crates/tish_compile/src/lib.rs +71 -0
  43. package/crates/tish_compile/src/resolve.rs +631 -0
  44. package/crates/tish_compile/src/types.rs +304 -0
  45. package/crates/tish_compile_js/Cargo.toml +16 -0
  46. package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
  47. package/crates/tish_compile_js/src/codegen.rs +794 -0
  48. package/crates/tish_compile_js/src/error.rs +20 -0
  49. package/crates/tish_compile_js/src/js_intrinsics.rs +82 -0
  50. package/crates/tish_compile_js/src/lib.rs +27 -0
  51. package/crates/tish_compile_js/src/tests_jsx.rs +32 -0
  52. package/crates/tish_compiler_wasm/Cargo.toml +19 -0
  53. package/crates/tish_compiler_wasm/src/lib.rs +55 -0
  54. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +462 -0
  55. package/crates/tish_core/Cargo.toml +11 -0
  56. package/crates/tish_core/src/console_style.rs +128 -0
  57. package/crates/tish_core/src/json.rs +327 -0
  58. package/crates/tish_core/src/lib.rs +15 -0
  59. package/crates/tish_core/src/macros.rs +37 -0
  60. package/crates/tish_core/src/uri.rs +115 -0
  61. package/crates/tish_core/src/value.rs +376 -0
  62. package/crates/tish_cranelift/Cargo.toml +17 -0
  63. package/crates/tish_cranelift/src/lib.rs +41 -0
  64. package/crates/tish_cranelift/src/link.rs +120 -0
  65. package/crates/tish_cranelift/src/lower.rs +77 -0
  66. package/crates/tish_cranelift_runtime/Cargo.toml +19 -0
  67. package/crates/tish_cranelift_runtime/src/lib.rs +43 -0
  68. package/crates/tish_eval/Cargo.toml +26 -0
  69. package/crates/tish_eval/src/eval.rs +3205 -0
  70. package/crates/tish_eval/src/http.rs +122 -0
  71. package/crates/tish_eval/src/lib.rs +59 -0
  72. package/crates/tish_eval/src/natives.rs +301 -0
  73. package/crates/tish_eval/src/promise.rs +173 -0
  74. package/crates/tish_eval/src/regex.rs +298 -0
  75. package/crates/tish_eval/src/timers.rs +111 -0
  76. package/crates/tish_eval/src/value.rs +224 -0
  77. package/crates/tish_eval/src/value_convert.rs +85 -0
  78. package/crates/tish_fmt/Cargo.toml +16 -0
  79. package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
  80. package/crates/tish_fmt/src/lib.rs +884 -0
  81. package/crates/tish_jsx_web/Cargo.toml +7 -0
  82. package/crates/tish_jsx_web/README.md +18 -0
  83. package/crates/tish_jsx_web/src/lib.rs +157 -0
  84. package/crates/tish_jsx_web/vendor/Lattish.tish +347 -0
  85. package/crates/tish_lexer/Cargo.toml +7 -0
  86. package/crates/tish_lexer/src/lib.rs +430 -0
  87. package/crates/tish_lexer/src/token.rs +155 -0
  88. package/crates/tish_lint/Cargo.toml +17 -0
  89. package/crates/tish_lint/src/bin/tish-lint.rs +77 -0
  90. package/crates/tish_lint/src/lib.rs +278 -0
  91. package/crates/tish_llvm/Cargo.toml +11 -0
  92. package/crates/tish_llvm/src/lib.rs +106 -0
  93. package/crates/tish_lsp/Cargo.toml +22 -0
  94. package/crates/tish_lsp/README.md +26 -0
  95. package/crates/tish_lsp/src/main.rs +615 -0
  96. package/crates/tish_native/Cargo.toml +14 -0
  97. package/crates/tish_native/src/build.rs +102 -0
  98. package/crates/tish_native/src/lib.rs +237 -0
  99. package/crates/tish_opt/Cargo.toml +11 -0
  100. package/crates/tish_opt/src/lib.rs +896 -0
  101. package/crates/tish_parser/Cargo.toml +9 -0
  102. package/crates/tish_parser/src/lib.rs +123 -0
  103. package/crates/tish_parser/src/parser.rs +1714 -0
  104. package/crates/tish_runtime/Cargo.toml +26 -0
  105. package/crates/tish_runtime/src/http.rs +308 -0
  106. package/crates/tish_runtime/src/http_fetch.rs +453 -0
  107. package/crates/tish_runtime/src/lib.rs +1004 -0
  108. package/crates/tish_runtime/src/native_promise.rs +26 -0
  109. package/crates/tish_runtime/src/promise.rs +77 -0
  110. package/crates/tish_runtime/src/promise_io.rs +41 -0
  111. package/crates/tish_runtime/src/timers.rs +125 -0
  112. package/crates/tish_runtime/src/ws.rs +725 -0
  113. package/crates/tish_runtime/tests/fetch_readable_stream.rs +99 -0
  114. package/crates/tish_vm/Cargo.toml +31 -0
  115. package/crates/tish_vm/src/lib.rs +39 -0
  116. package/crates/tish_vm/src/vm.rs +1399 -0
  117. package/crates/tish_wasm/Cargo.toml +13 -0
  118. package/crates/tish_wasm/src/lib.rs +358 -0
  119. package/crates/tish_wasm_runtime/Cargo.toml +25 -0
  120. package/crates/tish_wasm_runtime/src/lib.rs +36 -0
  121. package/justfile +260 -0
  122. package/package.json +8 -3
  123. package/platform/darwin-arm64/tish +0 -0
  124. package/platform/darwin-x64/tish +0 -0
  125. package/platform/linux-arm64/tish +0 -0
  126. package/platform/linux-x64/tish +0 -0
  127. 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(&regex_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
+ }