@tishlang/tish-format 1.0.12 → 1.0.13
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 +49 -0
- package/LICENSE +13 -0
- package/README.md +138 -0
- package/bin/tish-format +0 -0
- package/crates/js_to_tish/Cargo.toml +11 -0
- package/crates/js_to_tish/README.md +18 -0
- package/crates/js_to_tish/src/error.rs +55 -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 +610 -0
- package/crates/js_to_tish/src/transform/stmt.rs +503 -0
- package/crates/js_to_tish/src/transform.rs +60 -0
- package/crates/tish/Cargo.toml +54 -0
- package/crates/tish/src/cargo_native_registry.rs +32 -0
- package/crates/tish/src/cli_help.rs +565 -0
- package/crates/tish/src/main.rs +781 -0
- package/crates/tish/src/repl_completion.rs +200 -0
- package/crates/tish/tests/cargo_example_compile.rs +67 -0
- package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
- package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
- package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
- package/crates/tish/tests/integration_test.rs +1095 -0
- package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
- package/crates/tish/tests/shortcircuit.rs +65 -0
- package/crates/tish_ast/Cargo.toml +9 -0
- package/crates/tish_ast/src/ast.rs +620 -0
- package/crates/tish_ast/src/lib.rs +5 -0
- package/crates/tish_build_utils/Cargo.toml +11 -0
- package/crates/tish_build_utils/src/lib.rs +577 -0
- package/crates/tish_builtins/Cargo.toml +20 -0
- package/crates/tish_builtins/src/array.rs +441 -0
- package/crates/tish_builtins/src/construct.rs +159 -0
- package/crates/tish_builtins/src/globals.rs +213 -0
- package/crates/tish_builtins/src/helpers.rs +35 -0
- package/crates/tish_builtins/src/lib.rs +16 -0
- package/crates/tish_builtins/src/math.rs +89 -0
- package/crates/tish_builtins/src/object.rs +36 -0
- package/crates/tish_builtins/src/string.rs +647 -0
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_bytecode/Cargo.toml +17 -0
- package/crates/tish_bytecode/src/chunk.rs +96 -0
- package/crates/tish_bytecode/src/compiler.rs +1760 -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 +142 -0
- package/crates/tish_bytecode/src/peephole.rs +189 -0
- package/crates/tish_bytecode/src/serialize.rs +163 -0
- package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
- package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
- package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
- package/crates/tish_compile/Cargo.toml +26 -0
- package/crates/tish_compile/src/codegen.rs +5332 -0
- package/crates/tish_compile/src/infer.rs +292 -0
- package/crates/tish_compile/src/lib.rs +164 -0
- package/crates/tish_compile/src/resolve.rs +1388 -0
- package/crates/tish_compile/src/types.rs +501 -0
- package/crates/tish_compile_js/Cargo.toml +18 -0
- package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
- package/crates/tish_compile_js/src/codegen.rs +871 -0
- package/crates/tish_compile_js/src/error.rs +20 -0
- package/crates/tish_compile_js/src/lib.rs +26 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +350 -0
- package/crates/tish_compiler_wasm/Cargo.toml +21 -0
- package/crates/tish_compiler_wasm/src/lib.rs +57 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
- package/crates/tish_core/Cargo.toml +26 -0
- package/crates/tish_core/src/console_style.rs +160 -0
- package/crates/tish_core/src/json.rs +387 -0
- package/crates/tish_core/src/lib.rs +17 -0
- package/crates/tish_core/src/macros.rs +36 -0
- package/crates/tish_core/src/uri.rs +118 -0
- package/crates/tish_core/src/value.rs +696 -0
- package/crates/tish_core/src/vmref.rs +178 -0
- package/crates/tish_cranelift/Cargo.toml +19 -0
- package/crates/tish_cranelift/src/lib.rs +43 -0
- package/crates/tish_cranelift/src/link.rs +117 -0
- package/crates/tish_cranelift/src/lower.rs +85 -0
- package/crates/tish_cranelift_runtime/Cargo.toml +25 -0
- package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
- package/crates/tish_eval/Cargo.toml +45 -0
- package/crates/tish_eval/src/eval.rs +3717 -0
- package/crates/tish_eval/src/http.rs +188 -0
- package/crates/tish_eval/src/lib.rs +99 -0
- package/crates/tish_eval/src/natives.rs +399 -0
- package/crates/tish_eval/src/promise.rs +179 -0
- package/crates/tish_eval/src/regex.rs +299 -0
- package/crates/tish_eval/src/timers.rs +120 -0
- package/crates/tish_eval/src/value.rs +318 -0
- package/crates/tish_eval/src/value_convert.rs +111 -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 +2101 -0
- package/crates/tish_jsx_web/Cargo.toml +9 -0
- package/crates/tish_jsx_web/README.md +5 -0
- package/crates/tish_jsx_web/src/lib.rs +2 -0
- package/crates/tish_lexer/Cargo.toml +9 -0
- package/crates/tish_lexer/src/lib.rs +716 -0
- package/crates/tish_lexer/src/token.rs +163 -0
- package/crates/tish_lint/Cargo.toml +18 -0
- package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
- package/crates/tish_lint/src/lib.rs +289 -0
- package/crates/tish_llvm/Cargo.toml +13 -0
- package/crates/tish_llvm/src/lib.rs +115 -0
- package/crates/tish_lsp/Cargo.toml +25 -0
- package/crates/tish_lsp/README.md +26 -0
- package/crates/tish_lsp/src/builtin_goto.rs +362 -0
- package/crates/tish_lsp/src/import_goto.rs +562 -0
- package/crates/tish_lsp/src/main.rs +1046 -0
- package/crates/tish_native/Cargo.toml +16 -0
- package/crates/tish_native/src/build.rs +427 -0
- package/crates/tish_native/src/config.rs +48 -0
- package/crates/tish_native/src/lib.rs +416 -0
- package/crates/tish_opt/Cargo.toml +13 -0
- package/crates/tish_opt/src/lib.rs +943 -0
- package/crates/tish_parser/Cargo.toml +11 -0
- package/crates/tish_parser/src/lib.rs +332 -0
- package/crates/tish_parser/src/parser.rs +2304 -0
- package/crates/tish_pg/Cargo.toml +34 -0
- package/crates/tish_pg/README.md +38 -0
- package/crates/tish_pg/src/error.rs +52 -0
- package/crates/tish_pg/src/lib.rs +955 -0
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3561 -0
- package/crates/tish_resolve/src/pos.rs +141 -0
- package/crates/tish_runtime/Cargo.toml +96 -0
- package/crates/tish_runtime/src/http.rs +1298 -0
- package/crates/tish_runtime/src/http_fetch.rs +471 -0
- 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 +1192 -0
- package/crates/tish_runtime/src/native_promise.rs +15 -0
- package/crates/tish_runtime/src/promise.rs +248 -0
- package/crates/tish_runtime/src/promise_io.rs +38 -0
- package/crates/tish_runtime/src/timers.rs +166 -0
- package/crates/tish_runtime/src/ws.rs +761 -0
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
- package/crates/tish_ui/Cargo.toml +17 -0
- package/crates/tish_ui/src/jsx.rs +682 -0
- package/crates/tish_ui/src/lib.rs +20 -0
- package/crates/tish_ui/src/runtime/hooks.rs +569 -0
- package/crates/tish_ui/src/runtime/mod.rs +180 -0
- package/crates/tish_vm/Cargo.toml +47 -0
- package/crates/tish_vm/src/lib.rs +39 -0
- package/crates/tish_vm/src/vm.rs +2192 -0
- package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
- package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
- package/crates/tish_wasm/Cargo.toml +15 -0
- package/crates/tish_wasm/src/lib.rs +424 -0
- package/crates/tish_wasm_runtime/Cargo.toml +37 -0
- package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
- package/crates/tish_wasm_runtime/src/lib.rs +42 -0
- package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +263 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
- package/justfile +268 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-fmt +0 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
//! HTTP server for the Tish interpreter. Client `fetch` uses `tishlang_runtime` from eval.
|
|
2
|
+
|
|
3
|
+
use crate::value::{PropMap, Value};
|
|
4
|
+
use std::fs::File;
|
|
5
|
+
use std::sync::Arc;
|
|
6
|
+
|
|
7
|
+
use tokio::runtime::Runtime;
|
|
8
|
+
|
|
9
|
+
thread_local! {
|
|
10
|
+
pub(crate) static RUNTIME: Runtime = tokio::runtime::Builder::new_multi_thread()
|
|
11
|
+
.worker_threads(4)
|
|
12
|
+
.enable_all()
|
|
13
|
+
.build()
|
|
14
|
+
.expect("Failed to create tokio runtime");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/// Create an HTTP server that listens on the given port.
|
|
18
|
+
pub fn create_server(port: u16) -> Result<tiny_http::Server, String> {
|
|
19
|
+
let addr = format!("0.0.0.0:{}", port);
|
|
20
|
+
tiny_http::Server::http(&addr).map_err(|e| format!("Failed to start server: {}", e))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Convert a tiny_http::Request into a Tish Value object.
|
|
24
|
+
pub fn request_to_value(request: &mut tiny_http::Request) -> Value {
|
|
25
|
+
let mut obj: PropMap = PropMap::with_capacity(6);
|
|
26
|
+
|
|
27
|
+
obj.insert(
|
|
28
|
+
Arc::from("method"),
|
|
29
|
+
Value::String(request.method().to_string().into()),
|
|
30
|
+
);
|
|
31
|
+
obj.insert(
|
|
32
|
+
Arc::from("url"),
|
|
33
|
+
Value::String(request.url().to_string().into()),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
let path = request.url().split('?').next().unwrap_or("/");
|
|
37
|
+
obj.insert(Arc::from("path"), Value::String(path.into()));
|
|
38
|
+
|
|
39
|
+
let query_string = request.url().split('?').nth(1).unwrap_or("");
|
|
40
|
+
obj.insert(Arc::from("query"), Value::String(query_string.into()));
|
|
41
|
+
|
|
42
|
+
let mut headers_obj: PropMap = PropMap::with_capacity(request.headers().len());
|
|
43
|
+
for header in request.headers() {
|
|
44
|
+
headers_obj.insert(
|
|
45
|
+
Arc::from(header.field.as_str().as_str()),
|
|
46
|
+
Value::String(header.value.as_str().into()),
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
obj.insert(Arc::from("headers"), Value::object(headers_obj));
|
|
50
|
+
|
|
51
|
+
let mut body = String::new();
|
|
52
|
+
let _ = request.as_reader().read_to_string(&mut body);
|
|
53
|
+
obj.insert(Arc::from("body"), Value::String(body.into()));
|
|
54
|
+
|
|
55
|
+
Value::object(obj)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Extract response data from a Tish Value object.
|
|
59
|
+
pub fn value_to_response(value: &Value) -> (u16, Vec<(String, String)>, String) {
|
|
60
|
+
let default_status = 200u16;
|
|
61
|
+
let default_body = String::new();
|
|
62
|
+
|
|
63
|
+
let (status, headers, body) = match value {
|
|
64
|
+
Value::Object(obj) => {
|
|
65
|
+
let obj_ref = obj.borrow();
|
|
66
|
+
|
|
67
|
+
let status = obj_ref
|
|
68
|
+
.strings
|
|
69
|
+
.get("status")
|
|
70
|
+
.and_then(|v| match v {
|
|
71
|
+
Value::Number(n) => Some(*n as u16),
|
|
72
|
+
_ => None,
|
|
73
|
+
})
|
|
74
|
+
.unwrap_or(default_status);
|
|
75
|
+
|
|
76
|
+
let body = obj_ref
|
|
77
|
+
.strings
|
|
78
|
+
.get("body")
|
|
79
|
+
.map(|v| v.to_string())
|
|
80
|
+
.unwrap_or_default();
|
|
81
|
+
|
|
82
|
+
let headers = obj_ref
|
|
83
|
+
.strings
|
|
84
|
+
.get("headers")
|
|
85
|
+
.and_then(|v| match v {
|
|
86
|
+
Value::Object(h) => Some(
|
|
87
|
+
h.borrow()
|
|
88
|
+
.strings
|
|
89
|
+
.iter()
|
|
90
|
+
.map(|(k, v)| (k.to_string(), v.to_string()))
|
|
91
|
+
.collect(),
|
|
92
|
+
),
|
|
93
|
+
_ => None,
|
|
94
|
+
})
|
|
95
|
+
.unwrap_or_default();
|
|
96
|
+
|
|
97
|
+
(status, headers, body)
|
|
98
|
+
}
|
|
99
|
+
Value::String(s) => (default_status, vec![], s.to_string()),
|
|
100
|
+
_ => (default_status, vec![], default_body),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
(status, headers, body)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// If the response value has a `file` key, stream that path (binary-safe). Matches `tishlang_runtime` HTTP behavior.
|
|
107
|
+
pub(crate) fn extract_file_from_response(
|
|
108
|
+
value: &Value,
|
|
109
|
+
) -> Option<(u16, Vec<(String, String)>, String)> {
|
|
110
|
+
let Value::Object(obj) = value else {
|
|
111
|
+
return None;
|
|
112
|
+
};
|
|
113
|
+
let obj_ref = obj.borrow();
|
|
114
|
+
let file_val = obj_ref.strings.get("file")?;
|
|
115
|
+
let Value::String(file_path) = file_val else {
|
|
116
|
+
return None;
|
|
117
|
+
};
|
|
118
|
+
let file_path = file_path.to_string();
|
|
119
|
+
let status = obj_ref
|
|
120
|
+
.strings
|
|
121
|
+
.get("status")
|
|
122
|
+
.and_then(|v| match v {
|
|
123
|
+
Value::Number(n) => Some(*n as u16),
|
|
124
|
+
_ => None,
|
|
125
|
+
})
|
|
126
|
+
.unwrap_or(200);
|
|
127
|
+
let headers = obj_ref
|
|
128
|
+
.strings
|
|
129
|
+
.get("headers")
|
|
130
|
+
.and_then(|v| match v {
|
|
131
|
+
Value::Object(h) => Some(
|
|
132
|
+
h.borrow()
|
|
133
|
+
.strings
|
|
134
|
+
.iter()
|
|
135
|
+
.map(|(k, v)| (k.to_string(), v.to_string()))
|
|
136
|
+
.collect(),
|
|
137
|
+
),
|
|
138
|
+
_ => None,
|
|
139
|
+
})
|
|
140
|
+
.unwrap_or_default();
|
|
141
|
+
Some((status, headers, file_path))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
pub(crate) fn send_file_response(
|
|
145
|
+
request: tiny_http::Request,
|
|
146
|
+
status: u16,
|
|
147
|
+
headers: Vec<(String, String)>,
|
|
148
|
+
file_path: String,
|
|
149
|
+
) {
|
|
150
|
+
let file = match File::open(&file_path) {
|
|
151
|
+
Ok(f) => f,
|
|
152
|
+
Err(e) => {
|
|
153
|
+
eprintln!("Failed to open file {}: {}", file_path, e);
|
|
154
|
+
let fallback =
|
|
155
|
+
tiny_http::Response::from_string(format!("File not found: {}", file_path))
|
|
156
|
+
.with_status_code(tiny_http::StatusCode(500));
|
|
157
|
+
let _ = request.respond(fallback);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
let status_code = tiny_http::StatusCode(status);
|
|
162
|
+
let mut response = tiny_http::Response::from_file(file).with_status_code(status_code);
|
|
163
|
+
for (key, value) in headers {
|
|
164
|
+
if let Ok(header) = tiny_http::Header::from_bytes(key.as_bytes(), value.as_bytes()) {
|
|
165
|
+
response = response.with_header(header);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
let _ = request.respond(response);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/// Send a response using tiny_http.
|
|
172
|
+
pub fn send_response(
|
|
173
|
+
request: tiny_http::Request,
|
|
174
|
+
status: u16,
|
|
175
|
+
headers: Vec<(String, String)>,
|
|
176
|
+
body: String,
|
|
177
|
+
) {
|
|
178
|
+
let status_code = tiny_http::StatusCode(status);
|
|
179
|
+
let mut response = tiny_http::Response::from_string(body).with_status_code(status_code);
|
|
180
|
+
|
|
181
|
+
for (key, value) in headers {
|
|
182
|
+
if let Ok(header) = tiny_http::Header::from_bytes(key.as_bytes(), value.as_bytes()) {
|
|
183
|
+
response = response.with_header(header);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let _ = request.respond(response);
|
|
188
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
//! Tish tree-walk interpreter.
|
|
2
|
+
|
|
3
|
+
mod eval;
|
|
4
|
+
#[cfg(feature = "http")]
|
|
5
|
+
mod http;
|
|
6
|
+
mod natives;
|
|
7
|
+
#[cfg(feature = "http")]
|
|
8
|
+
mod promise;
|
|
9
|
+
#[cfg(feature = "regex")]
|
|
10
|
+
pub mod regex;
|
|
11
|
+
#[cfg(feature = "timers")]
|
|
12
|
+
mod timers;
|
|
13
|
+
mod value;
|
|
14
|
+
pub mod value_convert;
|
|
15
|
+
|
|
16
|
+
pub use eval::Evaluator;
|
|
17
|
+
pub use value::PropMap;
|
|
18
|
+
pub use value::Value;
|
|
19
|
+
|
|
20
|
+
/// Trait for pluggable native modules (e.g. Polars). Implement to register
|
|
21
|
+
/// globals with the interpreter. Return a map of (global_name, Value).
|
|
22
|
+
pub trait TishNativeModule: Send + Sync {
|
|
23
|
+
fn name(&self) -> &'static str;
|
|
24
|
+
fn register(&self) -> std::collections::HashMap<std::sync::Arc<str>, Value>;
|
|
25
|
+
|
|
26
|
+
/// Virtual `tish:*` modules for `import { x } from 'tish:…'` (e.g. `tish:polars`).
|
|
27
|
+
/// Return `(specifier, exports_object)` pairs. Default: none.
|
|
28
|
+
fn virtual_builtin_modules(&self) -> Vec<(&'static str, Value)> {
|
|
29
|
+
vec![]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
#[cfg(feature = "regex")]
|
|
33
|
+
pub use regex::TishRegExp;
|
|
34
|
+
|
|
35
|
+
pub fn run(source: &str) -> Result<Value, String> {
|
|
36
|
+
let program = tishlang_parser::parse(source)?;
|
|
37
|
+
let mut eval = Evaluator::new();
|
|
38
|
+
let result = eval.eval_program(&program)?;
|
|
39
|
+
#[cfg(feature = "timers")]
|
|
40
|
+
eval.run_timer_phase()?;
|
|
41
|
+
Ok(result)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Run a Tish file with import/export support. Resolves relative imports from the file's directory.
|
|
45
|
+
/// Format an interpreter value for console output (Node/Bun-style colors when `colors` is true).
|
|
46
|
+
pub fn format_value_for_console(value: &Value, colors: bool) -> String {
|
|
47
|
+
match value_convert::eval_to_core(value) {
|
|
48
|
+
Ok(core_val) => tishlang_core::format_value_styled(&core_val, colors),
|
|
49
|
+
Err(_) => value.to_string(),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Run a Tish file with import/export support. Resolves relative imports from the file's directory.
|
|
54
|
+
pub fn run_file(
|
|
55
|
+
path: &std::path::Path,
|
|
56
|
+
project_root: Option<&std::path::Path>,
|
|
57
|
+
) -> Result<Value, String> {
|
|
58
|
+
let path = path
|
|
59
|
+
.canonicalize()
|
|
60
|
+
.map_err(|e| format!("Cannot canonicalize {}: {}", path.display(), e))?;
|
|
61
|
+
let source = std::fs::read_to_string(&path)
|
|
62
|
+
.map_err(|e| format!("Cannot read {}: {}", path.display(), e))?;
|
|
63
|
+
let program = tishlang_parser::parse(&source)?;
|
|
64
|
+
let mut eval = Evaluator::new();
|
|
65
|
+
eval.set_current_dir(project_root.or(path.parent()));
|
|
66
|
+
let result = eval.eval_program(&program)?;
|
|
67
|
+
#[cfg(feature = "timers")]
|
|
68
|
+
eval.run_timer_phase()?;
|
|
69
|
+
Ok(result)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#[cfg(test)]
|
|
73
|
+
mod global_scope_tests {
|
|
74
|
+
use super::*;
|
|
75
|
+
|
|
76
|
+
#[test]
|
|
77
|
+
fn symbol_global_loads() {
|
|
78
|
+
let mut e = Evaluator::new();
|
|
79
|
+
let program = tishlang_parser::parse("Symbol").expect("parse");
|
|
80
|
+
let r = e.eval_program(&program);
|
|
81
|
+
assert!(
|
|
82
|
+
r.is_ok(),
|
|
83
|
+
"expected Symbol global, got {:?}",
|
|
84
|
+
r.as_ref().err()
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#[test]
|
|
89
|
+
fn symbol_call_under_typeof_loads() {
|
|
90
|
+
let mut e = Evaluator::new();
|
|
91
|
+
let program = tishlang_parser::parse("typeof Symbol(\"z\")").expect("parse");
|
|
92
|
+
let r = e.eval_program(&program);
|
|
93
|
+
assert!(
|
|
94
|
+
r.is_ok(),
|
|
95
|
+
"expected Symbol global for typeof Symbol(\"z\"), got {:?}",
|
|
96
|
+
r.as_ref().err()
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,399 @@
|
|
|
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
|
+
let n: f64 = s.trim().parse().unwrap_or(f64::NAN);
|
|
84
|
+
Ok(Value::Number(n))
|
|
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("&"),
|
|
143
|
+
b'<' => Some("<"),
|
|
144
|
+
b'>' => Some(">"),
|
|
145
|
+
b'"' => Some("""),
|
|
146
|
+
b'\'' => Some("'"),
|
|
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
|
+
use std::collections::hash_map::RandomState;
|
|
219
|
+
use std::hash::{BuildHasher, Hasher};
|
|
220
|
+
let random = RandomState::new().build_hasher().finish() as f64 / u64::MAX as f64;
|
|
221
|
+
Ok(Value::Number(random))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
pub fn math_pow(args: &[Value]) -> Result<Value, String> {
|
|
225
|
+
let base = get_num(args.first().unwrap_or(&Value::Null));
|
|
226
|
+
let exp = get_num(args.get(1).unwrap_or(&Value::Null));
|
|
227
|
+
Ok(Value::Number(base.powf(exp)))
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
pub fn math_sin(args: &[Value]) -> Result<Value, String> {
|
|
231
|
+
Ok(Value::Number(
|
|
232
|
+
get_num(args.first().unwrap_or(&Value::Null)).sin(),
|
|
233
|
+
))
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
pub fn math_cos(args: &[Value]) -> Result<Value, String> {
|
|
237
|
+
Ok(Value::Number(
|
|
238
|
+
get_num(args.first().unwrap_or(&Value::Null)).cos(),
|
|
239
|
+
))
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
pub fn math_tan(args: &[Value]) -> Result<Value, String> {
|
|
243
|
+
Ok(Value::Number(
|
|
244
|
+
get_num(args.first().unwrap_or(&Value::Null)).tan(),
|
|
245
|
+
))
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
pub fn math_log(args: &[Value]) -> Result<Value, String> {
|
|
249
|
+
Ok(Value::Number(
|
|
250
|
+
get_num(args.first().unwrap_or(&Value::Null)).ln(),
|
|
251
|
+
))
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
pub fn math_exp(args: &[Value]) -> Result<Value, String> {
|
|
255
|
+
Ok(Value::Number(
|
|
256
|
+
get_num(args.first().unwrap_or(&Value::Null)).exp(),
|
|
257
|
+
))
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
pub fn math_sign(args: &[Value]) -> Result<Value, String> {
|
|
261
|
+
let n = get_num(args.first().unwrap_or(&Value::Null));
|
|
262
|
+
let sign = if n.is_nan() {
|
|
263
|
+
f64::NAN
|
|
264
|
+
} else if n > 0.0 {
|
|
265
|
+
1.0
|
|
266
|
+
} else if n < 0.0 {
|
|
267
|
+
-1.0
|
|
268
|
+
} else {
|
|
269
|
+
0.0
|
|
270
|
+
};
|
|
271
|
+
Ok(Value::Number(sign))
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
pub fn math_trunc(args: &[Value]) -> Result<Value, String> {
|
|
275
|
+
Ok(Value::Number(
|
|
276
|
+
get_num(args.first().unwrap_or(&Value::Null)).trunc(),
|
|
277
|
+
))
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
pub fn date_now(_args: &[Value]) -> Result<Value, String> {
|
|
281
|
+
use std::time::{SystemTime, UNIX_EPOCH};
|
|
282
|
+
let ms = SystemTime::now()
|
|
283
|
+
.duration_since(UNIX_EPOCH)
|
|
284
|
+
.map(|d| d.as_millis() as f64)
|
|
285
|
+
.unwrap_or(0.0);
|
|
286
|
+
Ok(Value::Number(ms))
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
pub fn array_is_array(args: &[Value]) -> Result<Value, String> {
|
|
290
|
+
Ok(Value::Bool(matches!(args.first(), Some(Value::Array(_)))))
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
pub fn string_from_char_code(args: &[Value]) -> Result<Value, String> {
|
|
294
|
+
let s: String = args
|
|
295
|
+
.iter()
|
|
296
|
+
.filter_map(|v| match v {
|
|
297
|
+
Value::Number(n) => Some(char::from_u32(*n as u32).unwrap_or('\u{FFFD}')),
|
|
298
|
+
_ => None,
|
|
299
|
+
})
|
|
300
|
+
.collect();
|
|
301
|
+
Ok(Value::String(s.into()))
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
#[cfg(feature = "process")]
|
|
305
|
+
pub fn process_exit(args: &[Value]) -> Result<Value, String> {
|
|
306
|
+
let code = args
|
|
307
|
+
.first()
|
|
308
|
+
.and_then(|v| match v {
|
|
309
|
+
Value::Number(n) => Some(*n as i32),
|
|
310
|
+
_ => None,
|
|
311
|
+
})
|
|
312
|
+
.unwrap_or(0);
|
|
313
|
+
std::process::exit(code);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
#[cfg(feature = "process")]
|
|
317
|
+
pub fn process_cwd(_args: &[Value]) -> Result<Value, String> {
|
|
318
|
+
let cwd = std::env::current_dir()
|
|
319
|
+
.map(|p| p.to_string_lossy().into_owned())
|
|
320
|
+
.unwrap_or_default();
|
|
321
|
+
Ok(Value::String(cwd.into()))
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
#[cfg(feature = "process")]
|
|
325
|
+
pub fn process_exec(args: &[Value]) -> Result<Value, String> {
|
|
326
|
+
use std::process::Command;
|
|
327
|
+
let cmd = args.first().map(|v| v.to_string()).unwrap_or_default();
|
|
328
|
+
if cmd.is_empty() {
|
|
329
|
+
return Ok(Value::Number(0.0));
|
|
330
|
+
}
|
|
331
|
+
let output = Command::new("sh")
|
|
332
|
+
.arg("-c")
|
|
333
|
+
.arg(&cmd)
|
|
334
|
+
.output()
|
|
335
|
+
.map_err(|e| format!("exec failed: {}", e))?;
|
|
336
|
+
let code = output.status.code().unwrap_or(1);
|
|
337
|
+
Ok(Value::Number(code as f64))
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
#[cfg(feature = "fs")]
|
|
341
|
+
pub fn read_file(args: &[Value]) -> Result<Value, String> {
|
|
342
|
+
let path = args.first().map(|v| v.to_string()).unwrap_or_default();
|
|
343
|
+
match std::fs::read_to_string(&path) {
|
|
344
|
+
Ok(content) => Ok(Value::String(content.into())),
|
|
345
|
+
Err(e) => Ok(Value::String(format!("Error: {}", e).into())),
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
#[cfg(feature = "fs")]
|
|
350
|
+
pub fn write_file(args: &[Value]) -> Result<Value, String> {
|
|
351
|
+
let path = args.first().map(|v| v.to_string()).unwrap_or_default();
|
|
352
|
+
let content = args.get(1).map(|v| v.to_string()).unwrap_or_default();
|
|
353
|
+
match std::fs::write(&path, content) {
|
|
354
|
+
Ok(_) => Ok(Value::Bool(true)),
|
|
355
|
+
Err(_) => Ok(Value::Bool(false)),
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
#[cfg(feature = "fs")]
|
|
360
|
+
pub fn file_exists(args: &[Value]) -> Result<Value, String> {
|
|
361
|
+
let path = args.first().map(|v| v.to_string()).unwrap_or_default();
|
|
362
|
+
Ok(Value::Bool(std::path::Path::new(&path).exists()))
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
#[cfg(feature = "fs")]
|
|
366
|
+
pub fn is_dir(args: &[Value]) -> Result<Value, String> {
|
|
367
|
+
let path = args.first().map(|v| v.to_string()).unwrap_or_default();
|
|
368
|
+
Ok(Value::Bool(std::path::Path::new(&path).is_dir()))
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
#[cfg(feature = "fs")]
|
|
372
|
+
pub fn read_dir(args: &[Value]) -> Result<Value, String> {
|
|
373
|
+
use std::cell::RefCell;
|
|
374
|
+
use std::rc::Rc;
|
|
375
|
+
|
|
376
|
+
let path = args.first().map(|v| v.to_string()).unwrap_or_default();
|
|
377
|
+
match std::fs::read_dir(&path) {
|
|
378
|
+
Ok(entries) => {
|
|
379
|
+
let items: Vec<Value> = entries
|
|
380
|
+
.filter_map(|e| e.ok())
|
|
381
|
+
.map(|e| Value::String(e.file_name().to_string_lossy().into()))
|
|
382
|
+
.collect();
|
|
383
|
+
Ok(Value::Array(Rc::new(RefCell::new(items))))
|
|
384
|
+
}
|
|
385
|
+
Err(_) => Ok(Value::Array(Rc::new(RefCell::new(Vec::new())))),
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
#[cfg(feature = "fs")]
|
|
390
|
+
pub fn mkdir(args: &[Value]) -> Result<Value, String> {
|
|
391
|
+
let path = args.first().map(|v| v.to_string()).unwrap_or_default();
|
|
392
|
+
let recursive = args.get(1).map(|v| v.is_truthy()).unwrap_or(false);
|
|
393
|
+
let result = if recursive {
|
|
394
|
+
std::fs::create_dir_all(&path)
|
|
395
|
+
} else {
|
|
396
|
+
std::fs::create_dir(&path)
|
|
397
|
+
};
|
|
398
|
+
Ok(Value::Bool(result.is_ok()))
|
|
399
|
+
}
|