@tishlang/tish-format 1.0.12 → 2.0.1

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 (189) hide show
  1. package/Cargo.toml +51 -0
  2. package/LICENSE +13 -0
  3. package/bin/tish-format +0 -0
  4. package/crates/js_to_tish/Cargo.toml +11 -0
  5. package/crates/js_to_tish/README.md +18 -0
  6. package/crates/js_to_tish/src/error.rs +55 -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 +611 -0
  10. package/crates/js_to_tish/src/transform/stmt.rs +503 -0
  11. package/crates/js_to_tish/src/transform.rs +60 -0
  12. package/crates/tish/Cargo.toml +62 -0
  13. package/crates/tish/build.rs +21 -0
  14. package/crates/tish/src/cargo_native_registry.rs +32 -0
  15. package/crates/tish/src/cli_help.rs +576 -0
  16. package/crates/tish/src/main.rs +853 -0
  17. package/crates/tish/src/repl_completion.rs +199 -0
  18. package/crates/tish/tests/cargo_example_compile.rs +67 -0
  19. package/crates/tish/tests/error_source_location.rs +36 -0
  20. package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
  21. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
  22. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
  23. package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
  24. package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
  25. package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
  26. package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
  27. package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
  28. package/crates/tish/tests/integration_test.rs +1406 -0
  29. package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
  30. package/crates/tish/tests/shortcircuit.rs +65 -0
  31. package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
  32. package/crates/tish/tests/tty_capability.rs +43 -0
  33. package/crates/tish_ast/Cargo.toml +9 -0
  34. package/crates/tish_ast/src/ast.rs +649 -0
  35. package/crates/tish_ast/src/lib.rs +5 -0
  36. package/crates/tish_build_utils/Cargo.toml +11 -0
  37. package/crates/tish_build_utils/src/lib.rs +577 -0
  38. package/crates/tish_builtins/Cargo.toml +22 -0
  39. package/crates/tish_builtins/src/array.rs +803 -0
  40. package/crates/tish_builtins/src/collections.rs +481 -0
  41. package/crates/tish_builtins/src/construct.rs +199 -0
  42. package/crates/tish_builtins/src/date.rs +538 -0
  43. package/crates/tish_builtins/src/globals.rs +293 -0
  44. package/crates/tish_builtins/src/helpers.rs +35 -0
  45. package/crates/tish_builtins/src/iterator.rs +129 -0
  46. package/crates/tish_builtins/src/lib.rs +21 -0
  47. package/crates/tish_builtins/src/math.rs +89 -0
  48. package/crates/tish_builtins/src/number.rs +96 -0
  49. package/crates/tish_builtins/src/object.rs +36 -0
  50. package/crates/tish_builtins/src/string.rs +646 -0
  51. package/crates/tish_builtins/src/symbol.rs +83 -0
  52. package/crates/tish_builtins/src/typedarrays.rs +298 -0
  53. package/crates/tish_bytecode/Cargo.toml +17 -0
  54. package/crates/tish_bytecode/src/chunk.rs +164 -0
  55. package/crates/tish_bytecode/src/compiler.rs +2604 -0
  56. package/crates/tish_bytecode/src/encoding.rs +102 -0
  57. package/crates/tish_bytecode/src/lib.rs +20 -0
  58. package/crates/tish_bytecode/src/opcode.rs +185 -0
  59. package/crates/tish_bytecode/src/peephole.rs +189 -0
  60. package/crates/tish_bytecode/src/serialize.rs +193 -0
  61. package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
  62. package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
  63. package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
  64. package/crates/tish_compile/Cargo.toml +27 -0
  65. package/crates/tish_compile/src/check.rs +774 -0
  66. package/crates/tish_compile/src/codegen.rs +7317 -0
  67. package/crates/tish_compile/src/infer.rs +1681 -0
  68. package/crates/tish_compile/src/lib.rs +206 -0
  69. package/crates/tish_compile/src/resolve.rs +1951 -0
  70. package/crates/tish_compile/src/types.rs +605 -0
  71. package/crates/tish_compile_js/Cargo.toml +18 -0
  72. package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
  73. package/crates/tish_compile_js/src/codegen.rs +938 -0
  74. package/crates/tish_compile_js/src/error.rs +20 -0
  75. package/crates/tish_compile_js/src/lib.rs +26 -0
  76. package/crates/tish_compile_js/src/tests_jsx.rs +414 -0
  77. package/crates/tish_compiler_wasm/Cargo.toml +21 -0
  78. package/crates/tish_compiler_wasm/src/lib.rs +57 -0
  79. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
  80. package/crates/tish_core/Cargo.toml +32 -0
  81. package/crates/tish_core/src/console_style.rs +170 -0
  82. package/crates/tish_core/src/json.rs +430 -0
  83. package/crates/tish_core/src/lib.rs +20 -0
  84. package/crates/tish_core/src/macros.rs +36 -0
  85. package/crates/tish_core/src/shape.rs +85 -0
  86. package/crates/tish_core/src/uri.rs +118 -0
  87. package/crates/tish_core/src/value.rs +1350 -0
  88. package/crates/tish_core/src/vmref.rs +183 -0
  89. package/crates/tish_cranelift/Cargo.toml +19 -0
  90. package/crates/tish_cranelift/src/lib.rs +43 -0
  91. package/crates/tish_cranelift/src/link.rs +130 -0
  92. package/crates/tish_cranelift/src/lower.rs +85 -0
  93. package/crates/tish_cranelift_runtime/Cargo.toml +26 -0
  94. package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
  95. package/crates/tish_eval/Cargo.toml +51 -0
  96. package/crates/tish_eval/src/eval.rs +4265 -0
  97. package/crates/tish_eval/src/http.rs +191 -0
  98. package/crates/tish_eval/src/lib.rs +99 -0
  99. package/crates/tish_eval/src/natives.rs +551 -0
  100. package/crates/tish_eval/src/promise.rs +179 -0
  101. package/crates/tish_eval/src/regex.rs +299 -0
  102. package/crates/tish_eval/src/timers.rs +120 -0
  103. package/crates/tish_eval/src/value.rs +336 -0
  104. package/crates/tish_eval/src/value_convert.rs +117 -0
  105. package/crates/tish_ffi/Cargo.toml +26 -0
  106. package/crates/tish_ffi/src/lib.rs +518 -0
  107. package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
  108. package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
  109. package/crates/tish_ffi/tests/loader.rs +65 -0
  110. package/crates/tish_fmt/Cargo.toml +16 -0
  111. package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
  112. package/crates/tish_fmt/src/lib.rs +2157 -0
  113. package/crates/tish_jsx_web/Cargo.toml +9 -0
  114. package/crates/tish_jsx_web/README.md +5 -0
  115. package/crates/tish_jsx_web/src/lib.rs +2 -0
  116. package/crates/tish_lexer/Cargo.toml +9 -0
  117. package/crates/tish_lexer/src/lib.rs +1104 -0
  118. package/crates/tish_lexer/src/token.rs +170 -0
  119. package/crates/tish_lint/Cargo.toml +18 -0
  120. package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
  121. package/crates/tish_lint/src/lib.rs +281 -0
  122. package/crates/tish_llvm/Cargo.toml +13 -0
  123. package/crates/tish_llvm/src/lib.rs +115 -0
  124. package/crates/tish_lsp/Cargo.toml +25 -0
  125. package/crates/tish_lsp/README.md +26 -0
  126. package/crates/tish_lsp/src/builtin_goto.rs +362 -0
  127. package/crates/tish_lsp/src/import_goto.rs +564 -0
  128. package/crates/tish_lsp/src/main.rs +1459 -0
  129. package/crates/tish_native/Cargo.toml +16 -0
  130. package/crates/tish_native/src/build.rs +481 -0
  131. package/crates/tish_native/src/config.rs +48 -0
  132. package/crates/tish_native/src/lib.rs +416 -0
  133. package/crates/tish_opt/Cargo.toml +13 -0
  134. package/crates/tish_opt/src/lib.rs +1046 -0
  135. package/crates/tish_parser/Cargo.toml +11 -0
  136. package/crates/tish_parser/src/lib.rs +386 -0
  137. package/crates/tish_parser/src/parser.rs +2726 -0
  138. package/crates/tish_pg/Cargo.toml +34 -0
  139. package/crates/tish_pg/README.md +38 -0
  140. package/crates/tish_pg/src/error.rs +52 -0
  141. package/crates/tish_pg/src/lib.rs +955 -0
  142. package/crates/tish_resolve/Cargo.toml +13 -0
  143. package/crates/tish_resolve/src/lib.rs +3601 -0
  144. package/crates/tish_resolve/src/pos.rs +141 -0
  145. package/crates/tish_runtime/Cargo.toml +100 -0
  146. package/crates/tish_runtime/src/http.rs +1347 -0
  147. package/crates/tish_runtime/src/http_fetch.rs +492 -0
  148. package/crates/tish_runtime/src/http_hyper.rs +441 -0
  149. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  150. package/crates/tish_runtime/src/lib.rs +1447 -0
  151. package/crates/tish_runtime/src/native_promise.rs +15 -0
  152. package/crates/tish_runtime/src/promise.rs +558 -0
  153. package/crates/tish_runtime/src/promise_io.rs +38 -0
  154. package/crates/tish_runtime/src/timers.rs +172 -0
  155. package/crates/tish_runtime/src/tty.rs +226 -0
  156. package/crates/tish_runtime/src/ws.rs +778 -0
  157. package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
  158. package/crates/tish_ui/Cargo.toml +17 -0
  159. package/crates/tish_ui/src/jsx.rs +692 -0
  160. package/crates/tish_ui/src/lib.rs +20 -0
  161. package/crates/tish_ui/src/runtime/hooks.rs +573 -0
  162. package/crates/tish_ui/src/runtime/mod.rs +183 -0
  163. package/crates/tish_vm/Cargo.toml +60 -0
  164. package/crates/tish_vm/src/jit.rs +1050 -0
  165. package/crates/tish_vm/src/lib.rs +41 -0
  166. package/crates/tish_vm/src/vm.rs +3536 -0
  167. package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
  168. package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
  169. package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
  170. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
  171. package/crates/tish_wasm/Cargo.toml +15 -0
  172. package/crates/tish_wasm/src/lib.rs +428 -0
  173. package/crates/tish_wasm_runtime/Cargo.toml +37 -0
  174. package/crates/tish_wasm_runtime/src/gpu.rs +429 -0
  175. package/crates/tish_wasm_runtime/src/lib.rs +42 -0
  176. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  177. package/crates/tishlang_cargo_bindgen/src/classify.rs +261 -0
  178. package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
  179. package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
  180. package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
  181. package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
  182. package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
  183. package/justfile +276 -0
  184. package/package.json +2 -2
  185. package/platform/darwin-arm64/tish-fmt +0 -0
  186. package/platform/darwin-x64/tish-fmt +0 -0
  187. package/platform/linux-arm64/tish-fmt +0 -0
  188. package/platform/linux-x64/tish-fmt +0 -0
  189. package/platform/win32-x64/tish-fmt.exe +0 -0
@@ -0,0 +1,551 @@
1
+ //! Native function implementations for the interpreter.
2
+
3
+ use crate::value::Value;
4
+
5
+ fn get_num(v: &Value) -> f64 {
6
+ match v {
7
+ Value::Number(n) => *n,
8
+ _ => f64::NAN,
9
+ }
10
+ }
11
+
12
+ pub fn console_debug(args: &[Value]) -> Result<Value, String> {
13
+ if get_log_level() == 0 {
14
+ let parts: Vec<String> = args.iter().map(|v| v.to_string()).collect();
15
+ println!("{}", parts.join(" "));
16
+ }
17
+ Ok(Value::Null)
18
+ }
19
+
20
+ pub fn console_info(args: &[Value]) -> Result<Value, String> {
21
+ if get_log_level() <= 1 {
22
+ let parts: Vec<String> = args.iter().map(|v| v.to_string()).collect();
23
+ println!("{}", parts.join(" "));
24
+ }
25
+ Ok(Value::Null)
26
+ }
27
+
28
+ pub fn console_log(args: &[Value]) -> Result<Value, String> {
29
+ if get_log_level() <= 2 {
30
+ let parts: Vec<String> = args.iter().map(|v| v.to_string()).collect();
31
+ println!("{}", parts.join(" "));
32
+ }
33
+ Ok(Value::Null)
34
+ }
35
+
36
+ pub fn console_warn(args: &[Value]) -> Result<Value, String> {
37
+ if get_log_level() <= 3 {
38
+ let parts: Vec<String> = args.iter().map(|v| v.to_string()).collect();
39
+ eprintln!("{}", parts.join(" "));
40
+ }
41
+ Ok(Value::Null)
42
+ }
43
+
44
+ pub fn console_error(args: &[Value]) -> Result<Value, String> {
45
+ let parts: Vec<String> = args.iter().map(|v| v.to_string()).collect();
46
+ eprintln!("{}", parts.join(" "));
47
+ Ok(Value::Null)
48
+ }
49
+
50
+ fn get_log_level() -> u8 {
51
+ std::env::var("TISH_LOG_LEVEL")
52
+ .ok()
53
+ .and_then(|s| s.parse().ok())
54
+ .unwrap_or(2)
55
+ }
56
+
57
+ pub fn parse_int(args: &[Value]) -> Result<Value, String> {
58
+ let s = args.first().map(|v| v.to_string()).unwrap_or_default();
59
+ let s = s.trim();
60
+ let radix = args
61
+ .get(1)
62
+ .and_then(|v| match v {
63
+ Value::Number(n) => Some(*n as i32),
64
+ _ => None,
65
+ })
66
+ .unwrap_or(10);
67
+ let n = if (2..=36).contains(&radix) {
68
+ let prefix: String = s
69
+ .chars()
70
+ .take_while(|c| *c == '-' || *c == '+' || c.is_digit(radix as u32))
71
+ .collect();
72
+ i64::from_str_radix(&prefix, radix as u32)
73
+ .ok()
74
+ .map(|n| n as f64)
75
+ } else {
76
+ None
77
+ };
78
+ Ok(Value::Number(n.unwrap_or(f64::NAN)))
79
+ }
80
+
81
+ pub fn parse_float(args: &[Value]) -> Result<Value, String> {
82
+ let s = args.first().map(|v| v.to_string()).unwrap_or_default();
83
+ // JS parseFloat parses the longest leading numeric prefix (issue #36); shared with VM/native.
84
+ Ok(Value::Number(tishlang_builtins::globals::js_parse_float(&s)))
85
+ }
86
+
87
+ pub fn is_finite(args: &[Value]) -> Result<Value, String> {
88
+ let b = args
89
+ .first()
90
+ .is_some_and(|v| matches!(v, Value::Number(n) if n.is_finite()));
91
+ Ok(Value::Bool(b))
92
+ }
93
+
94
+ pub fn is_nan(args: &[Value]) -> Result<Value, String> {
95
+ let b = args.first().is_none_or(|v| {
96
+ matches!(v, Value::Number(n) if n.is_nan()) || !matches!(v, Value::Number(_))
97
+ });
98
+ Ok(Value::Bool(b))
99
+ }
100
+
101
+ pub fn boolean_native(args: &[Value]) -> Result<Value, String> {
102
+ let v = args.first().unwrap_or(&Value::Null);
103
+ Ok(Value::Bool(v.is_truthy()))
104
+ }
105
+
106
+ pub fn decode_uri(args: &[Value]) -> Result<Value, String> {
107
+ let s = args.first().map(|v| v.to_string()).unwrap_or_default();
108
+ Ok(Value::String(
109
+ tishlang_core::percent_decode(&s).unwrap_or(s).into(),
110
+ ))
111
+ }
112
+
113
+ pub fn encode_uri(args: &[Value]) -> Result<Value, String> {
114
+ let s = args.first().map(|v| v.to_string()).unwrap_or_default();
115
+ Ok(Value::String(tishlang_core::percent_encode(&s).into()))
116
+ }
117
+
118
+ pub fn html_escape(args: &[Value]) -> Result<Value, String> {
119
+ let input = match args.first() {
120
+ Some(Value::String(s)) => s.to_string(),
121
+ Some(v) => v.to_string(),
122
+ None => return Ok(Value::Null),
123
+ };
124
+ let bytes = input.as_bytes();
125
+ let mut extra = 0usize;
126
+ for b in bytes {
127
+ match b {
128
+ b'&' => extra += 4,
129
+ b'<' | b'>' => extra += 3,
130
+ b'"' => extra += 5,
131
+ b'\'' => extra += 4,
132
+ _ => {}
133
+ }
134
+ }
135
+ if extra == 0 {
136
+ return Ok(Value::String(input.into()));
137
+ }
138
+ let mut out = String::with_capacity(input.len() + extra);
139
+ let mut last = 0usize;
140
+ for (i, b) in bytes.iter().enumerate() {
141
+ let repl: Option<&'static str> = match b {
142
+ b'&' => Some("&amp;"),
143
+ b'<' => Some("&lt;"),
144
+ b'>' => Some("&gt;"),
145
+ b'"' => Some("&quot;"),
146
+ b'\'' => Some("&#39;"),
147
+ _ => None,
148
+ };
149
+ if let Some(r) = repl {
150
+ out.push_str(&input[last..i]);
151
+ out.push_str(r);
152
+ last = i + 1;
153
+ }
154
+ }
155
+ out.push_str(&input[last..]);
156
+ Ok(Value::String(out.into()))
157
+ }
158
+
159
+ pub fn math_abs(args: &[Value]) -> Result<Value, String> {
160
+ Ok(Value::Number(
161
+ get_num(args.first().unwrap_or(&Value::Null)).abs(),
162
+ ))
163
+ }
164
+
165
+ pub fn math_sqrt(args: &[Value]) -> Result<Value, String> {
166
+ Ok(Value::Number(
167
+ get_num(args.first().unwrap_or(&Value::Null)).sqrt(),
168
+ ))
169
+ }
170
+
171
+ pub fn math_min(args: &[Value]) -> Result<Value, String> {
172
+ let nums: Vec<f64> = args
173
+ .iter()
174
+ .filter_map(|v| match v {
175
+ Value::Number(n) => Some(*n),
176
+ _ => None,
177
+ })
178
+ .collect();
179
+ let n = nums.into_iter().fold(f64::INFINITY, f64::min);
180
+ Ok(Value::Number(if n == f64::INFINITY { f64::NAN } else { n }))
181
+ }
182
+
183
+ pub fn math_max(args: &[Value]) -> Result<Value, String> {
184
+ let nums: Vec<f64> = args
185
+ .iter()
186
+ .filter_map(|v| match v {
187
+ Value::Number(n) => Some(*n),
188
+ _ => None,
189
+ })
190
+ .collect();
191
+ let n = nums.into_iter().fold(f64::NEG_INFINITY, f64::max);
192
+ Ok(Value::Number(if n == f64::NEG_INFINITY {
193
+ f64::NAN
194
+ } else {
195
+ n
196
+ }))
197
+ }
198
+
199
+ pub fn math_floor(args: &[Value]) -> Result<Value, String> {
200
+ Ok(Value::Number(
201
+ get_num(args.first().unwrap_or(&Value::Null)).floor(),
202
+ ))
203
+ }
204
+
205
+ pub fn math_ceil(args: &[Value]) -> Result<Value, String> {
206
+ Ok(Value::Number(
207
+ get_num(args.first().unwrap_or(&Value::Null)).ceil(),
208
+ ))
209
+ }
210
+
211
+ pub fn math_round(args: &[Value]) -> Result<Value, String> {
212
+ Ok(Value::Number(
213
+ get_num(args.first().unwrap_or(&Value::Null)).round(),
214
+ ))
215
+ }
216
+
217
+ pub fn math_random(_args: &[Value]) -> Result<Value, String> {
218
+ // Match the VM / builtins (`rand::random::<f64>()`) — uniform [0,1). The old
219
+ // RandomState-hash was non-uniform and diverged from every other backend.
220
+ Ok(Value::Number(rand::random::<f64>()))
221
+ }
222
+
223
+ pub fn math_pow(args: &[Value]) -> Result<Value, String> {
224
+ let base = get_num(args.first().unwrap_or(&Value::Null));
225
+ let exp = get_num(args.get(1).unwrap_or(&Value::Null));
226
+ Ok(Value::Number(base.powf(exp)))
227
+ }
228
+
229
+ pub fn math_sin(args: &[Value]) -> Result<Value, String> {
230
+ Ok(Value::Number(
231
+ get_num(args.first().unwrap_or(&Value::Null)).sin(),
232
+ ))
233
+ }
234
+
235
+ pub fn math_cos(args: &[Value]) -> Result<Value, String> {
236
+ Ok(Value::Number(
237
+ get_num(args.first().unwrap_or(&Value::Null)).cos(),
238
+ ))
239
+ }
240
+
241
+ pub fn math_tan(args: &[Value]) -> Result<Value, String> {
242
+ Ok(Value::Number(
243
+ get_num(args.first().unwrap_or(&Value::Null)).tan(),
244
+ ))
245
+ }
246
+
247
+ pub fn math_log(args: &[Value]) -> Result<Value, String> {
248
+ Ok(Value::Number(
249
+ get_num(args.first().unwrap_or(&Value::Null)).ln(),
250
+ ))
251
+ }
252
+
253
+ pub fn math_exp(args: &[Value]) -> Result<Value, String> {
254
+ Ok(Value::Number(
255
+ get_num(args.first().unwrap_or(&Value::Null)).exp(),
256
+ ))
257
+ }
258
+
259
+ pub fn math_sign(args: &[Value]) -> Result<Value, String> {
260
+ let n = get_num(args.first().unwrap_or(&Value::Null));
261
+ let sign = if n.is_nan() {
262
+ f64::NAN
263
+ } else if n > 0.0 {
264
+ 1.0
265
+ } else if n < 0.0 {
266
+ -1.0
267
+ } else {
268
+ 0.0
269
+ };
270
+ Ok(Value::Number(sign))
271
+ }
272
+
273
+ pub fn math_trunc(args: &[Value]) -> Result<Value, String> {
274
+ Ok(Value::Number(
275
+ get_num(args.first().unwrap_or(&Value::Null)).trunc(),
276
+ ))
277
+ }
278
+
279
+ // Hyperbolic / inverse-hyperbolic / cbrt / base-2/10 logs — these were missing from the
280
+ // interpreter's `Math`, so they returned "Not a function" (issue #61, and an interp↔VM
281
+ // divergence under #67). One macro per unary `f64` method keeps them in lockstep with the VM.
282
+ macro_rules! math_unary {
283
+ ($name:ident, $method:ident) => {
284
+ pub fn $name(args: &[Value]) -> Result<Value, String> {
285
+ Ok(Value::Number(
286
+ get_num(args.first().unwrap_or(&Value::Null)).$method(),
287
+ ))
288
+ }
289
+ };
290
+ }
291
+ math_unary!(math_sinh, sinh);
292
+ math_unary!(math_cosh, cosh);
293
+ math_unary!(math_tanh, tanh);
294
+ math_unary!(math_asinh, asinh);
295
+ math_unary!(math_acosh, acosh);
296
+ math_unary!(math_atanh, atanh);
297
+ math_unary!(math_cbrt, cbrt);
298
+ math_unary!(math_log2, log2);
299
+ math_unary!(math_log10, log10);
300
+
301
+ pub fn array_is_array(args: &[Value]) -> Result<Value, String> {
302
+ Ok(Value::Bool(matches!(args.first(), Some(Value::Array(_)))))
303
+ }
304
+
305
+ /// Build a JS-style error object `{ name, message }` for the interpreter (issue #60).
306
+ fn make_error_obj(name: &str, args: &[Value]) -> Value {
307
+ let message = args.first().map(|v| v.to_string()).unwrap_or_default();
308
+ let mut m = crate::value::PropMap::with_capacity(2);
309
+ m.insert("name".into(), Value::String(name.into()));
310
+ m.insert("message".into(), Value::String(message.into()));
311
+ Value::object(m)
312
+ }
313
+
314
+ pub fn error_construct(args: &[Value]) -> Result<Value, String> {
315
+ Ok(make_error_obj("Error", args))
316
+ }
317
+ pub fn type_error_construct(args: &[Value]) -> Result<Value, String> {
318
+ Ok(make_error_obj("TypeError", args))
319
+ }
320
+ pub fn range_error_construct(args: &[Value]) -> Result<Value, String> {
321
+ Ok(make_error_obj("RangeError", args))
322
+ }
323
+ pub fn syntax_error_construct(args: &[Value]) -> Result<Value, String> {
324
+ Ok(make_error_obj("SyntaxError", args))
325
+ }
326
+
327
+ /// `Array(...)` / `new Array(...)` (issue #72) — mirrors `tishlang_builtins::construct::
328
+ /// array_construct` for the interpreter's `Value`. A single non-negative integer is a length
329
+ /// (null holes); other args become the array's elements.
330
+ pub fn array_construct(args: &[Value]) -> Result<Value, String> {
331
+ if let [Value::Number(n)] = args {
332
+ let n = *n;
333
+ if n >= 0.0 && n.fract() == 0.0 && n <= 4_294_967_295.0 {
334
+ return Ok(Value::array(vec![Value::Null; n as usize]));
335
+ }
336
+ }
337
+ Ok(Value::array(args.to_vec()))
338
+ }
339
+
340
+ /// `String(value)` as a function: JS `ToString` coercion (arrays comma-join recursively,
341
+ /// objects → "[object Object]", null → "null"), matching the VM/native `string_convert`.
342
+ pub fn string_convert(args: &[Value]) -> Result<Value, String> {
343
+ let v = args.first().unwrap_or(&Value::Null);
344
+ Ok(Value::String(v.to_js_string().into()))
345
+ }
346
+
347
+ /// `Number(value)` coercion (issue #36) — shares the string parser with the VM/native
348
+ /// backend so the result is byte-identical.
349
+ pub fn number_convert(args: &[Value]) -> Result<Value, String> {
350
+ let v = args.first().unwrap_or(&Value::Null);
351
+ let n = match v {
352
+ Value::Number(n) => *n,
353
+ Value::Bool(b) => {
354
+ if *b {
355
+ 1.0
356
+ } else {
357
+ 0.0
358
+ }
359
+ }
360
+ Value::Null => 0.0,
361
+ Value::String(s) => tishlang_builtins::globals::parse_numeric_string(s),
362
+ other => tishlang_builtins::globals::parse_numeric_string(&other.to_js_string()),
363
+ };
364
+ Ok(Value::Number(n))
365
+ }
366
+
367
+ pub fn string_from_char_code(args: &[Value]) -> Result<Value, String> {
368
+ let s: String = args
369
+ .iter()
370
+ .filter_map(|v| match v {
371
+ Value::Number(n) => Some(char::from_u32(*n as u32).unwrap_or('\u{FFFD}')),
372
+ _ => None,
373
+ })
374
+ .collect();
375
+ Ok(Value::String(s.into()))
376
+ }
377
+
378
+ #[cfg(feature = "process")]
379
+ pub fn process_exit(args: &[Value]) -> Result<Value, String> {
380
+ let code = args
381
+ .first()
382
+ .and_then(|v| match v {
383
+ Value::Number(n) => Some(*n as i32),
384
+ _ => None,
385
+ })
386
+ .unwrap_or(0);
387
+ std::process::exit(code);
388
+ }
389
+
390
+ #[cfg(feature = "process")]
391
+ pub fn process_cwd(_args: &[Value]) -> Result<Value, String> {
392
+ let cwd = std::env::current_dir()
393
+ .map(|p| p.to_string_lossy().into_owned())
394
+ .unwrap_or_default();
395
+ Ok(Value::String(cwd.into()))
396
+ }
397
+
398
+ #[cfg(feature = "process")]
399
+ pub fn process_exec(args: &[Value]) -> Result<Value, String> {
400
+ use std::process::Command;
401
+ let cmd = args.first().map(|v| v.to_string()).unwrap_or_default();
402
+ if cmd.is_empty() {
403
+ return Ok(Value::Number(0.0));
404
+ }
405
+ let output = Command::new("sh")
406
+ .arg("-c")
407
+ .arg(&cmd)
408
+ .output()
409
+ .map_err(|e| format!("exec failed: {}", e))?;
410
+ let code = output.status.code().unwrap_or(1);
411
+ Ok(Value::Number(code as f64))
412
+ }
413
+
414
+ #[cfg(feature = "fs")]
415
+ pub fn read_file(args: &[Value]) -> Result<Value, String> {
416
+ let path = args.first().map(|v| v.to_string()).unwrap_or_default();
417
+ match std::fs::read_to_string(&path) {
418
+ Ok(content) => Ok(Value::String(content.into())),
419
+ Err(e) => Ok(Value::String(format!("Error: {}", e).into())),
420
+ }
421
+ }
422
+
423
+ #[cfg(feature = "fs")]
424
+ pub fn write_file(args: &[Value]) -> Result<Value, String> {
425
+ let path = args.first().map(|v| v.to_string()).unwrap_or_default();
426
+ let content = args.get(1).map(|v| v.to_string()).unwrap_or_default();
427
+ match std::fs::write(&path, content) {
428
+ Ok(_) => Ok(Value::Bool(true)),
429
+ Err(_) => Ok(Value::Bool(false)),
430
+ }
431
+ }
432
+
433
+ #[cfg(feature = "fs")]
434
+ pub fn file_exists(args: &[Value]) -> Result<Value, String> {
435
+ let path = args.first().map(|v| v.to_string()).unwrap_or_default();
436
+ Ok(Value::Bool(std::path::Path::new(&path).exists()))
437
+ }
438
+
439
+ #[cfg(feature = "fs")]
440
+ pub fn is_dir(args: &[Value]) -> Result<Value, String> {
441
+ let path = args.first().map(|v| v.to_string()).unwrap_or_default();
442
+ Ok(Value::Bool(std::path::Path::new(&path).is_dir()))
443
+ }
444
+
445
+ #[cfg(feature = "fs")]
446
+ pub fn read_dir(args: &[Value]) -> Result<Value, String> {
447
+ use std::cell::RefCell;
448
+ use std::rc::Rc;
449
+
450
+ let path = args.first().map(|v| v.to_string()).unwrap_or_default();
451
+ match std::fs::read_dir(&path) {
452
+ Ok(entries) => {
453
+ let items: Vec<Value> = entries
454
+ .filter_map(|e| e.ok())
455
+ .map(|e| Value::String(e.file_name().to_string_lossy().into()))
456
+ .collect();
457
+ Ok(Value::Array(Rc::new(RefCell::new(items))))
458
+ }
459
+ Err(_) => Ok(Value::Array(Rc::new(RefCell::new(Vec::new())))),
460
+ }
461
+ }
462
+
463
+ #[cfg(feature = "fs")]
464
+ pub fn mkdir(args: &[Value]) -> Result<Value, String> {
465
+ let path = args.first().map(|v| v.to_string()).unwrap_or_default();
466
+ let recursive = args.get(1).map(|v| v.is_truthy()).unwrap_or(false);
467
+ let result = if recursive {
468
+ std::fs::create_dir_all(&path)
469
+ } else {
470
+ std::fs::create_dir(&path)
471
+ };
472
+ Ok(Value::Bool(result.is_ok()))
473
+ }
474
+
475
+ // ── Interactive terminal I/O (issue #101), behind the `tty` feature ──────────────────────
476
+ // Build the interpreter's `Value` from the shared, Value-agnostic core in
477
+ // `tishlang_runtime::tty`, so the interpreter, VM, and native backends behave identically.
478
+
479
+ #[cfg(feature = "tty")]
480
+ fn tty_obj(pairs: Vec<(&str, Value)>) -> Value {
481
+ let mut m = crate::value::PropMap::with_capacity(pairs.len());
482
+ for (k, v) in pairs {
483
+ m.insert(k.into(), v);
484
+ }
485
+ Value::object(m)
486
+ }
487
+
488
+ #[cfg(feature = "tty")]
489
+ pub fn tty_size(_args: &[Value]) -> Result<Value, String> {
490
+ Ok(match tishlang_runtime::tty::size() {
491
+ Some((cols, rows)) => tty_obj(vec![
492
+ ("cols", Value::Number(cols as f64)),
493
+ ("rows", Value::Number(rows as f64)),
494
+ ]),
495
+ None => Value::Null,
496
+ })
497
+ }
498
+
499
+ #[cfg(feature = "tty")]
500
+ pub fn tty_is_tty(_args: &[Value]) -> Result<Value, String> {
501
+ Ok(Value::Bool(tishlang_runtime::tty::is_tty()))
502
+ }
503
+
504
+ #[cfg(feature = "tty")]
505
+ pub fn tty_set_raw_mode(args: &[Value]) -> Result<Value, String> {
506
+ let on = args.first().map(|v| v.is_truthy()).unwrap_or(false);
507
+ Ok(Value::Bool(tishlang_runtime::tty::set_raw_mode(on)))
508
+ }
509
+
510
+ #[cfg(feature = "tty")]
511
+ pub fn tty_enter_alt_screen(_args: &[Value]) -> Result<Value, String> {
512
+ Ok(Value::Bool(tishlang_runtime::tty::enter_alt_screen()))
513
+ }
514
+
515
+ #[cfg(feature = "tty")]
516
+ pub fn tty_leave_alt_screen(_args: &[Value]) -> Result<Value, String> {
517
+ Ok(Value::Bool(tishlang_runtime::tty::leave_alt_screen()))
518
+ }
519
+
520
+ #[cfg(feature = "tty")]
521
+ pub fn tty_read_line(_args: &[Value]) -> Result<Value, String> {
522
+ Ok(match tishlang_runtime::tty::read_line() {
523
+ Some(s) => Value::String(s.into()),
524
+ None => Value::Null,
525
+ })
526
+ }
527
+
528
+ #[cfg(feature = "tty")]
529
+ pub fn tty_read(args: &[Value]) -> Result<Value, String> {
530
+ use tishlang_runtime::tty::TtyEvent;
531
+ let timeout = match args.first() {
532
+ Some(Value::Number(ms)) => Some(ms.max(0.0) as u64),
533
+ _ => None,
534
+ };
535
+ Ok(match tishlang_runtime::tty::read_event(timeout) {
536
+ Some(TtyEvent::Key { key, ctrl, alt, shift }) => tty_obj(vec![
537
+ ("type", Value::String("key".into())),
538
+ ("key", Value::String(key.into())),
539
+ ("ctrl", Value::Bool(ctrl)),
540
+ ("alt", Value::Bool(alt)),
541
+ ("shift", Value::Bool(shift)),
542
+ ]),
543
+ Some(TtyEvent::Resize { cols, rows }) => tty_obj(vec![
544
+ ("type", Value::String("resize".into())),
545
+ ("cols", Value::Number(cols as f64)),
546
+ ("rows", Value::Number(rows as f64)),
547
+ ]),
548
+ Some(TtyEvent::Other) => tty_obj(vec![("type", Value::String("other".into()))]),
549
+ None => Value::Null,
550
+ })
551
+ }