@tishlang/tish 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +2 -0
- package/README.md +2 -0
- package/bin/tish +0 -0
- package/crates/js_to_tish/src/error.rs +2 -8
- package/crates/js_to_tish/src/transform/expr.rs +128 -137
- package/crates/js_to_tish/src/transform/stmt.rs +62 -32
- package/crates/tish/Cargo.toml +15 -5
- package/crates/tish/src/cargo_native_registry.rs +29 -0
- package/crates/tish/src/cli_help.rs +92 -39
- package/crates/tish/src/main.rs +172 -86
- package/crates/tish/src/repl_completion.rs +3 -3
- package/crates/tish/tests/cargo_example_compile.rs +4 -2
- package/crates/tish/tests/integration_test.rs +216 -54
- package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
- package/crates/tish/tests/shortcircuit.rs +20 -5
- package/crates/tish_ast/src/ast.rs +92 -23
- package/crates/tish_build_utils/Cargo.toml +4 -0
- package/crates/tish_build_utils/src/lib.rs +136 -8
- package/crates/tish_builtins/Cargo.toml +5 -1
- package/crates/tish_builtins/src/array.rs +65 -33
- package/crates/tish_builtins/src/construct.rs +34 -39
- package/crates/tish_builtins/src/globals.rs +42 -26
- package/crates/tish_builtins/src/helpers.rs +2 -1
- package/crates/tish_builtins/src/lib.rs +5 -5
- package/crates/tish_builtins/src/math.rs +5 -3
- package/crates/tish_builtins/src/object.rs +3 -2
- package/crates/tish_builtins/src/string.rs +144 -22
- package/crates/tish_bytecode/src/chunk.rs +0 -1
- package/crates/tish_bytecode/src/compiler.rs +173 -71
- package/crates/tish_bytecode/src/opcode.rs +24 -6
- package/crates/tish_bytecode/src/peephole.rs +2 -2
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +1621 -453
- package/crates/tish_compile/src/infer.rs +75 -19
- package/crates/tish_compile/src/lib.rs +19 -8
- package/crates/tish_compile/src/resolve.rs +278 -137
- package/crates/tish_compile/src/types.rs +184 -24
- package/crates/tish_compile_js/Cargo.toml +1 -0
- package/crates/tish_compile_js/src/codegen.rs +181 -37
- package/crates/tish_compile_js/src/lib.rs +3 -1
- package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
- package/crates/tish_compiler_wasm/src/lib.rs +16 -13
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +69 -59
- package/crates/tish_core/Cargo.toml +8 -0
- package/crates/tish_core/src/json.rs +107 -56
- package/crates/tish_core/src/lib.rs +4 -2
- package/crates/tish_core/src/macros.rs +5 -5
- package/crates/tish_core/src/uri.rs +9 -6
- package/crates/tish_core/src/value.rs +145 -43
- package/crates/tish_core/src/vmref.rs +178 -0
- package/crates/tish_cranelift/src/link.rs +6 -9
- package/crates/tish_cranelift/src/lower.rs +14 -8
- package/crates/tish_eval/Cargo.toml +17 -2
- package/crates/tish_eval/src/eval.rs +474 -165
- package/crates/tish_eval/src/http.rs +61 -0
- package/crates/tish_eval/src/lib.rs +12 -8
- package/crates/tish_eval/src/natives.rs +136 -38
- package/crates/tish_eval/src/promise.rs +14 -8
- package/crates/tish_eval/src/timers.rs +28 -19
- package/crates/tish_eval/src/value.rs +17 -6
- package/crates/tish_eval/src/value_convert.rs +13 -5
- package/crates/tish_fmt/src/lib.rs +149 -43
- package/crates/tish_lexer/src/lib.rs +232 -63
- package/crates/tish_lexer/src/token.rs +10 -6
- package/crates/tish_llvm/src/lib.rs +17 -8
- package/crates/tish_lsp/Cargo.toml +4 -1
- package/crates/tish_lsp/README.md +1 -1
- package/crates/tish_lsp/src/builtin_goto.rs +261 -0
- package/crates/tish_lsp/src/import_goto.rs +549 -0
- package/crates/tish_lsp/src/main.rs +504 -106
- package/crates/tish_native/src/build.rs +4 -8
- package/crates/tish_native/src/lib.rs +54 -21
- package/crates/tish_opt/src/lib.rs +84 -52
- package/crates/tish_parser/src/lib.rs +45 -13
- package/crates/tish_parser/src/parser.rs +505 -130
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3436 -0
- package/crates/tish_resolve/src/pos.rs +133 -0
- package/crates/tish_runtime/Cargo.toml +68 -3
- package/crates/tish_runtime/src/http.rs +1136 -145
- package/crates/tish_runtime/src/http_fetch.rs +38 -27
- package/crates/tish_runtime/src/http_hyper.rs +418 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +375 -189
- package/crates/tish_runtime/src/promise.rs +199 -40
- package/crates/tish_runtime/src/promise_io.rs +2 -1
- package/crates/tish_runtime/src/timers.rs +37 -1
- package/crates/tish_runtime/src/ws.rs +65 -42
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
- package/crates/tish_ui/src/jsx.rs +317 -27
- package/crates/tish_ui/src/lib.rs +5 -2
- package/crates/tish_ui/src/runtime/hooks.rs +406 -45
- package/crates/tish_ui/src/runtime/mod.rs +36 -9
- package/crates/tish_vm/Cargo.toml +15 -5
- package/crates/tish_vm/src/vm.rs +725 -281
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +11 -4
- package/crates/tish_wasm/src/lib.rs +55 -42
- package/crates/tish_wasm_runtime/Cargo.toml +2 -1
- package/crates/tish_wasm_runtime/src/lib.rs +1 -1
- package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +120 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +350 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
- package/justfile +8 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//! HTTP server for the Tish interpreter. Client `fetch` uses `tishlang_runtime` from eval.
|
|
2
2
|
|
|
3
3
|
use crate::value::{PropMap, Value};
|
|
4
|
+
use std::fs::File;
|
|
4
5
|
use std::sync::Arc;
|
|
5
6
|
|
|
6
7
|
use tokio::runtime::Runtime;
|
|
@@ -101,6 +102,66 @@ pub fn value_to_response(value: &Value) -> (u16, Vec<(String, String)>, String)
|
|
|
101
102
|
(status, headers, body)
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
/// If the response value has a `file` key, stream that path (binary-safe). Matches `tishlang_runtime` HTTP behavior.
|
|
106
|
+
pub(crate) fn extract_file_from_response(value: &Value) -> Option<(u16, Vec<(String, String)>, String)> {
|
|
107
|
+
let Value::Object(obj) = value else {
|
|
108
|
+
return None;
|
|
109
|
+
};
|
|
110
|
+
let obj_ref = obj.borrow();
|
|
111
|
+
let file_val = obj_ref.get(&Arc::from("file"))?;
|
|
112
|
+
let Value::String(file_path) = file_val else {
|
|
113
|
+
return None;
|
|
114
|
+
};
|
|
115
|
+
let file_path = file_path.to_string();
|
|
116
|
+
let status = obj_ref
|
|
117
|
+
.get(&Arc::from("status"))
|
|
118
|
+
.and_then(|v| match v {
|
|
119
|
+
Value::Number(n) => Some(*n as u16),
|
|
120
|
+
_ => None,
|
|
121
|
+
})
|
|
122
|
+
.unwrap_or(200);
|
|
123
|
+
let headers = obj_ref
|
|
124
|
+
.get(&Arc::from("headers"))
|
|
125
|
+
.and_then(|v| match v {
|
|
126
|
+
Value::Object(h) => Some(
|
|
127
|
+
h.borrow()
|
|
128
|
+
.iter()
|
|
129
|
+
.map(|(k, v)| (k.to_string(), v.to_string()))
|
|
130
|
+
.collect(),
|
|
131
|
+
),
|
|
132
|
+
_ => None,
|
|
133
|
+
})
|
|
134
|
+
.unwrap_or_default();
|
|
135
|
+
Some((status, headers, file_path))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
pub(crate) fn send_file_response(
|
|
139
|
+
request: tiny_http::Request,
|
|
140
|
+
status: u16,
|
|
141
|
+
headers: Vec<(String, String)>,
|
|
142
|
+
file_path: String,
|
|
143
|
+
) {
|
|
144
|
+
let file = match File::open(&file_path) {
|
|
145
|
+
Ok(f) => f,
|
|
146
|
+
Err(e) => {
|
|
147
|
+
eprintln!("Failed to open file {}: {}", file_path, e);
|
|
148
|
+
let fallback =
|
|
149
|
+
tiny_http::Response::from_string(format!("File not found: {}", file_path))
|
|
150
|
+
.with_status_code(tiny_http::StatusCode(500));
|
|
151
|
+
let _ = request.respond(fallback);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
let status_code = tiny_http::StatusCode(status);
|
|
156
|
+
let mut response = tiny_http::Response::from_file(file).with_status_code(status_code);
|
|
157
|
+
for (key, value) in headers {
|
|
158
|
+
if let Ok(header) = tiny_http::Header::from_bytes(key.as_bytes(), value.as_bytes()) {
|
|
159
|
+
response = response.with_header(header);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
let _ = request.respond(response);
|
|
163
|
+
}
|
|
164
|
+
|
|
104
165
|
/// Send a response using tiny_http.
|
|
105
166
|
pub fn send_response(
|
|
106
167
|
request: tiny_http::Request,
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
mod eval;
|
|
4
4
|
#[cfg(feature = "http")]
|
|
5
5
|
mod http;
|
|
6
|
-
|
|
6
|
+
mod natives;
|
|
7
7
|
#[cfg(feature = "http")]
|
|
8
8
|
mod promise;
|
|
9
|
-
#[cfg(feature = "http")]
|
|
10
|
-
mod timers;
|
|
11
|
-
mod natives;
|
|
12
9
|
#[cfg(feature = "regex")]
|
|
13
10
|
pub mod regex;
|
|
11
|
+
#[cfg(feature = "timers")]
|
|
12
|
+
mod timers;
|
|
14
13
|
mod value;
|
|
14
|
+
pub mod value_convert;
|
|
15
15
|
|
|
16
16
|
pub use eval::Evaluator;
|
|
17
17
|
pub use value::PropMap;
|
|
@@ -36,7 +36,7 @@ pub fn run(source: &str) -> Result<Value, String> {
|
|
|
36
36
|
let program = tishlang_parser::parse(source)?;
|
|
37
37
|
let mut eval = Evaluator::new();
|
|
38
38
|
let result = eval.eval_program(&program)?;
|
|
39
|
-
#[cfg(feature = "
|
|
39
|
+
#[cfg(feature = "timers")]
|
|
40
40
|
eval.run_timer_phase()?;
|
|
41
41
|
Ok(result)
|
|
42
42
|
}
|
|
@@ -51,16 +51,20 @@ pub fn format_value_for_console(value: &Value, colors: bool) -> String {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/// Run a Tish file with import/export support. Resolves relative imports from the file's directory.
|
|
54
|
-
pub fn run_file(
|
|
54
|
+
pub fn run_file(
|
|
55
|
+
path: &std::path::Path,
|
|
56
|
+
project_root: Option<&std::path::Path>,
|
|
57
|
+
) -> Result<Value, String> {
|
|
55
58
|
let path = path
|
|
56
59
|
.canonicalize()
|
|
57
60
|
.map_err(|e| format!("Cannot canonicalize {}: {}", path.display(), e))?;
|
|
58
|
-
let source = std::fs::read_to_string(&path)
|
|
61
|
+
let source = std::fs::read_to_string(&path)
|
|
62
|
+
.map_err(|e| format!("Cannot read {}: {}", path.display(), e))?;
|
|
59
63
|
let program = tishlang_parser::parse(&source)?;
|
|
60
64
|
let mut eval = Evaluator::new();
|
|
61
65
|
eval.set_current_dir(project_root.or(path.parent()));
|
|
62
66
|
let result = eval.eval_program(&program)?;
|
|
63
|
-
#[cfg(feature = "
|
|
67
|
+
#[cfg(feature = "timers")]
|
|
64
68
|
eval.run_timer_phase()?;
|
|
65
69
|
Ok(result)
|
|
66
70
|
}
|
|
@@ -57,16 +57,21 @@ fn get_log_level() -> u8 {
|
|
|
57
57
|
pub fn parse_int(args: &[Value]) -> Result<Value, String> {
|
|
58
58
|
let s = args.first().map(|v| v.to_string()).unwrap_or_default();
|
|
59
59
|
let s = s.trim();
|
|
60
|
-
let radix = args
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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);
|
|
64
67
|
let n = if (2..=36).contains(&radix) {
|
|
65
68
|
let prefix: String = s
|
|
66
69
|
.chars()
|
|
67
70
|
.take_while(|c| *c == '-' || *c == '+' || c.is_digit(radix as u32))
|
|
68
71
|
.collect();
|
|
69
|
-
i64::from_str_radix(&prefix, radix as u32)
|
|
72
|
+
i64::from_str_radix(&prefix, radix as u32)
|
|
73
|
+
.ok()
|
|
74
|
+
.map(|n| n as f64)
|
|
70
75
|
} else {
|
|
71
76
|
None
|
|
72
77
|
};
|
|
@@ -80,12 +85,16 @@ pub fn parse_float(args: &[Value]) -> Result<Value, String> {
|
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
pub fn is_finite(args: &[Value]) -> Result<Value, String> {
|
|
83
|
-
let b = args
|
|
88
|
+
let b = args
|
|
89
|
+
.first()
|
|
90
|
+
.is_some_and(|v| matches!(v, Value::Number(n) if n.is_finite()));
|
|
84
91
|
Ok(Value::Bool(b))
|
|
85
92
|
}
|
|
86
93
|
|
|
87
94
|
pub fn is_nan(args: &[Value]) -> Result<Value, String> {
|
|
88
|
-
let b = args.first().is_none_or(|v|
|
|
95
|
+
let b = args.first().is_none_or(|v| {
|
|
96
|
+
matches!(v, Value::Number(n) if n.is_nan()) || !matches!(v, Value::Number(_))
|
|
97
|
+
});
|
|
89
98
|
Ok(Value::Bool(b))
|
|
90
99
|
}
|
|
91
100
|
|
|
@@ -96,7 +105,9 @@ pub fn boolean_native(args: &[Value]) -> Result<Value, String> {
|
|
|
96
105
|
|
|
97
106
|
pub fn decode_uri(args: &[Value]) -> Result<Value, String> {
|
|
98
107
|
let s = args.first().map(|v| v.to_string()).unwrap_or_default();
|
|
99
|
-
Ok(Value::String(
|
|
108
|
+
Ok(Value::String(
|
|
109
|
+
tishlang_core::percent_decode(&s).unwrap_or(s).into(),
|
|
110
|
+
))
|
|
100
111
|
}
|
|
101
112
|
|
|
102
113
|
pub fn encode_uri(args: &[Value]) -> Result<Value, String> {
|
|
@@ -104,42 +115,103 @@ pub fn encode_uri(args: &[Value]) -> Result<Value, String> {
|
|
|
104
115
|
Ok(Value::String(tishlang_core::percent_encode(&s).into()))
|
|
105
116
|
}
|
|
106
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
|
+
|
|
107
159
|
pub fn math_abs(args: &[Value]) -> Result<Value, String> {
|
|
108
|
-
Ok(Value::Number(
|
|
160
|
+
Ok(Value::Number(
|
|
161
|
+
get_num(args.first().unwrap_or(&Value::Null)).abs(),
|
|
162
|
+
))
|
|
109
163
|
}
|
|
110
164
|
|
|
111
165
|
pub fn math_sqrt(args: &[Value]) -> Result<Value, String> {
|
|
112
|
-
Ok(Value::Number(
|
|
166
|
+
Ok(Value::Number(
|
|
167
|
+
get_num(args.first().unwrap_or(&Value::Null)).sqrt(),
|
|
168
|
+
))
|
|
113
169
|
}
|
|
114
170
|
|
|
115
171
|
pub fn math_min(args: &[Value]) -> Result<Value, String> {
|
|
116
|
-
let nums: Vec<f64> = args
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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();
|
|
120
179
|
let n = nums.into_iter().fold(f64::INFINITY, f64::min);
|
|
121
180
|
Ok(Value::Number(if n == f64::INFINITY { f64::NAN } else { n }))
|
|
122
181
|
}
|
|
123
182
|
|
|
124
183
|
pub fn math_max(args: &[Value]) -> Result<Value, String> {
|
|
125
|
-
let nums: Vec<f64> = args
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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();
|
|
129
191
|
let n = nums.into_iter().fold(f64::NEG_INFINITY, f64::max);
|
|
130
|
-
Ok(Value::Number(if n == f64::NEG_INFINITY {
|
|
192
|
+
Ok(Value::Number(if n == f64::NEG_INFINITY {
|
|
193
|
+
f64::NAN
|
|
194
|
+
} else {
|
|
195
|
+
n
|
|
196
|
+
}))
|
|
131
197
|
}
|
|
132
198
|
|
|
133
199
|
pub fn math_floor(args: &[Value]) -> Result<Value, String> {
|
|
134
|
-
Ok(Value::Number(
|
|
200
|
+
Ok(Value::Number(
|
|
201
|
+
get_num(args.first().unwrap_or(&Value::Null)).floor(),
|
|
202
|
+
))
|
|
135
203
|
}
|
|
136
204
|
|
|
137
205
|
pub fn math_ceil(args: &[Value]) -> Result<Value, String> {
|
|
138
|
-
Ok(Value::Number(
|
|
206
|
+
Ok(Value::Number(
|
|
207
|
+
get_num(args.first().unwrap_or(&Value::Null)).ceil(),
|
|
208
|
+
))
|
|
139
209
|
}
|
|
140
210
|
|
|
141
211
|
pub fn math_round(args: &[Value]) -> Result<Value, String> {
|
|
142
|
-
Ok(Value::Number(
|
|
212
|
+
Ok(Value::Number(
|
|
213
|
+
get_num(args.first().unwrap_or(&Value::Null)).round(),
|
|
214
|
+
))
|
|
143
215
|
}
|
|
144
216
|
|
|
145
217
|
pub fn math_random(_args: &[Value]) -> Result<Value, String> {
|
|
@@ -156,33 +228,53 @@ pub fn math_pow(args: &[Value]) -> Result<Value, String> {
|
|
|
156
228
|
}
|
|
157
229
|
|
|
158
230
|
pub fn math_sin(args: &[Value]) -> Result<Value, String> {
|
|
159
|
-
Ok(Value::Number(
|
|
231
|
+
Ok(Value::Number(
|
|
232
|
+
get_num(args.first().unwrap_or(&Value::Null)).sin(),
|
|
233
|
+
))
|
|
160
234
|
}
|
|
161
235
|
|
|
162
236
|
pub fn math_cos(args: &[Value]) -> Result<Value, String> {
|
|
163
|
-
Ok(Value::Number(
|
|
237
|
+
Ok(Value::Number(
|
|
238
|
+
get_num(args.first().unwrap_or(&Value::Null)).cos(),
|
|
239
|
+
))
|
|
164
240
|
}
|
|
165
241
|
|
|
166
242
|
pub fn math_tan(args: &[Value]) -> Result<Value, String> {
|
|
167
|
-
Ok(Value::Number(
|
|
243
|
+
Ok(Value::Number(
|
|
244
|
+
get_num(args.first().unwrap_or(&Value::Null)).tan(),
|
|
245
|
+
))
|
|
168
246
|
}
|
|
169
247
|
|
|
170
248
|
pub fn math_log(args: &[Value]) -> Result<Value, String> {
|
|
171
|
-
Ok(Value::Number(
|
|
249
|
+
Ok(Value::Number(
|
|
250
|
+
get_num(args.first().unwrap_or(&Value::Null)).ln(),
|
|
251
|
+
))
|
|
172
252
|
}
|
|
173
253
|
|
|
174
254
|
pub fn math_exp(args: &[Value]) -> Result<Value, String> {
|
|
175
|
-
Ok(Value::Number(
|
|
255
|
+
Ok(Value::Number(
|
|
256
|
+
get_num(args.first().unwrap_or(&Value::Null)).exp(),
|
|
257
|
+
))
|
|
176
258
|
}
|
|
177
259
|
|
|
178
260
|
pub fn math_sign(args: &[Value]) -> Result<Value, String> {
|
|
179
261
|
let n = get_num(args.first().unwrap_or(&Value::Null));
|
|
180
|
-
let sign = if n.is_nan() {
|
|
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
|
+
};
|
|
181
271
|
Ok(Value::Number(sign))
|
|
182
272
|
}
|
|
183
273
|
|
|
184
274
|
pub fn math_trunc(args: &[Value]) -> Result<Value, String> {
|
|
185
|
-
Ok(Value::Number(
|
|
275
|
+
Ok(Value::Number(
|
|
276
|
+
get_num(args.first().unwrap_or(&Value::Null)).trunc(),
|
|
277
|
+
))
|
|
186
278
|
}
|
|
187
279
|
|
|
188
280
|
pub fn date_now(_args: &[Value]) -> Result<Value, String> {
|
|
@@ -199,19 +291,25 @@ pub fn array_is_array(args: &[Value]) -> Result<Value, String> {
|
|
|
199
291
|
}
|
|
200
292
|
|
|
201
293
|
pub fn string_from_char_code(args: &[Value]) -> Result<Value, String> {
|
|
202
|
-
let s: String = args
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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();
|
|
206
301
|
Ok(Value::String(s.into()))
|
|
207
302
|
}
|
|
208
303
|
|
|
209
304
|
#[cfg(feature = "process")]
|
|
210
305
|
pub fn process_exit(args: &[Value]) -> Result<Value, String> {
|
|
211
|
-
let code = args
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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);
|
|
215
313
|
std::process::exit(code);
|
|
216
314
|
}
|
|
217
315
|
|
|
@@ -274,7 +372,7 @@ pub fn is_dir(args: &[Value]) -> Result<Value, String> {
|
|
|
274
372
|
pub fn read_dir(args: &[Value]) -> Result<Value, String> {
|
|
275
373
|
use std::cell::RefCell;
|
|
276
374
|
use std::rc::Rc;
|
|
277
|
-
|
|
375
|
+
|
|
278
376
|
let path = args.first().map(|v| v.to_string()).unwrap_or_default();
|
|
279
377
|
match std::fs::read_dir(&path) {
|
|
280
378
|
Ok(entries) => {
|
|
@@ -116,7 +116,11 @@ pub fn settle_promise(
|
|
|
116
116
|
}
|
|
117
117
|
};
|
|
118
118
|
if let Some(tx) = tx {
|
|
119
|
-
let result = if is_resolve {
|
|
119
|
+
let result = if is_resolve {
|
|
120
|
+
Ok(value.clone())
|
|
121
|
+
} else {
|
|
122
|
+
Err(value.clone())
|
|
123
|
+
};
|
|
120
124
|
let _ = tx.send(result);
|
|
121
125
|
}
|
|
122
126
|
Ok((value, is_resolve, reactions))
|
|
@@ -134,7 +138,9 @@ pub fn add_reaction(state: &PromiseStateRef, reaction: Reaction) {
|
|
|
134
138
|
impl Clone for Reaction {
|
|
135
139
|
fn clone(&self) -> Self {
|
|
136
140
|
match self {
|
|
137
|
-
Reaction::Then(a, b, r1, r2) =>
|
|
141
|
+
Reaction::Then(a, b, r1, r2) => {
|
|
142
|
+
Reaction::Then(a.clone(), b.clone(), r1.clone(), r2.clone())
|
|
143
|
+
}
|
|
138
144
|
Reaction::Finally(f, r1, r2) => Reaction::Finally(f.clone(), r1.clone(), r2.clone()),
|
|
139
145
|
}
|
|
140
146
|
}
|
|
@@ -156,18 +162,18 @@ pub fn block_until_settled(promise_ref: &PromiseRef) -> PromiseAwaitResult {
|
|
|
156
162
|
match result {
|
|
157
163
|
Ok(Ok(v)) => PromiseAwaitResult::Fulfilled(v),
|
|
158
164
|
Ok(Err(v)) => PromiseAwaitResult::Rejected(v),
|
|
159
|
-
Err(_) =>
|
|
160
|
-
"Promise channel dropped before settlement".to_string()
|
|
161
|
-
|
|
165
|
+
Err(_) => {
|
|
166
|
+
PromiseAwaitResult::Error("Promise channel dropped before settlement".to_string())
|
|
167
|
+
}
|
|
162
168
|
}
|
|
163
169
|
} else {
|
|
164
170
|
let state = promise_ref.state.borrow();
|
|
165
171
|
match &*state {
|
|
166
172
|
PromiseState::Fulfilled(v) => PromiseAwaitResult::Fulfilled(v.clone()),
|
|
167
173
|
PromiseState::Rejected(v) => PromiseAwaitResult::Rejected(v.clone()),
|
|
168
|
-
PromiseState::Pending { .. } =>
|
|
169
|
-
"Promise receiver already consumed".to_string()
|
|
170
|
-
|
|
174
|
+
PromiseState::Pending { .. } => {
|
|
175
|
+
PromiseAwaitResult::Error("Promise receiver already consumed".to_string())
|
|
176
|
+
}
|
|
171
177
|
}
|
|
172
178
|
}
|
|
173
179
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
use std::cell::RefCell;
|
|
6
6
|
use std::collections::HashMap;
|
|
7
|
-
use std::time::{Duration, Instant};
|
|
8
7
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
8
|
+
use std::time::{Duration, Instant};
|
|
9
9
|
|
|
10
10
|
use crate::value::Value;
|
|
11
11
|
|
|
@@ -32,12 +32,15 @@ pub fn setTimeout(callback: Value, args: Vec<Value>, delay_ms: u64) -> u64 {
|
|
|
32
32
|
let id = next_id();
|
|
33
33
|
let due = Instant::now() + Duration::from_millis(delay_ms);
|
|
34
34
|
REGISTRY.with(|r| {
|
|
35
|
-
r.borrow_mut().insert(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
r.borrow_mut().insert(
|
|
36
|
+
id,
|
|
37
|
+
TimerEntry {
|
|
38
|
+
due,
|
|
39
|
+
callback,
|
|
40
|
+
args,
|
|
41
|
+
interval_ms: 0,
|
|
42
|
+
},
|
|
43
|
+
);
|
|
41
44
|
});
|
|
42
45
|
id
|
|
43
46
|
}
|
|
@@ -48,12 +51,15 @@ pub fn setInterval(callback: Value, args: Vec<Value>, delay_ms: u64) -> u64 {
|
|
|
48
51
|
let id = next_id();
|
|
49
52
|
let due = Instant::now() + Duration::from_millis(delay_ms);
|
|
50
53
|
REGISTRY.with(|r| {
|
|
51
|
-
r.borrow_mut().insert(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
r.borrow_mut().insert(
|
|
55
|
+
id,
|
|
56
|
+
TimerEntry {
|
|
57
|
+
due,
|
|
58
|
+
callback,
|
|
59
|
+
args,
|
|
60
|
+
interval_ms: delay_ms,
|
|
61
|
+
},
|
|
62
|
+
);
|
|
57
63
|
});
|
|
58
64
|
id
|
|
59
65
|
}
|
|
@@ -88,12 +94,15 @@ pub fn take_due_timers() -> Vec<(u64, Value, Vec<Value>, u64)> {
|
|
|
88
94
|
pub fn re_register_interval(id: u64, callback: Value, args: Vec<Value>, interval_ms: u64) {
|
|
89
95
|
let due = Instant::now() + Duration::from_millis(interval_ms);
|
|
90
96
|
REGISTRY.with(|r| {
|
|
91
|
-
r.borrow_mut().insert(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
r.borrow_mut().insert(
|
|
98
|
+
id,
|
|
99
|
+
TimerEntry {
|
|
100
|
+
due,
|
|
101
|
+
callback,
|
|
102
|
+
args,
|
|
103
|
+
interval_ms,
|
|
104
|
+
},
|
|
105
|
+
);
|
|
97
106
|
});
|
|
98
107
|
}
|
|
99
108
|
|
|
@@ -15,9 +15,9 @@ use tishlang_core::NativeFn as CoreNativeFn;
|
|
|
15
15
|
|
|
16
16
|
/// Property map for interpreter `Value::Object` (uses `eval::Value`, not `tishlang_core::Value`).
|
|
17
17
|
pub type PropMap = AHashMap<Arc<str>, Value>;
|
|
18
|
+
use tishlang_core::TishOpaque;
|
|
18
19
|
#[cfg(feature = "http")]
|
|
19
20
|
use tishlang_core::TishPromise;
|
|
20
|
-
use tishlang_core::TishOpaque;
|
|
21
21
|
|
|
22
22
|
#[cfg(feature = "http")]
|
|
23
23
|
pub use crate::promise::PromiseResolver;
|
|
@@ -61,7 +61,7 @@ pub enum Value {
|
|
|
61
61
|
#[cfg(feature = "http")]
|
|
62
62
|
BoundPromiseMethod(crate::promise::PromiseRef, std::sync::Arc<str>),
|
|
63
63
|
/// Timer builtins: setTimeout, setInterval. Need evaluator for callback.
|
|
64
|
-
#[cfg(feature = "
|
|
64
|
+
#[cfg(feature = "timers")]
|
|
65
65
|
TimerBuiltin(std::sync::Arc<str>),
|
|
66
66
|
/// Native `tishlang_core` Promise (fetch / reader.read / response.text).
|
|
67
67
|
#[cfg(feature = "http")]
|
|
@@ -88,7 +88,12 @@ impl std::fmt::Debug for Value {
|
|
|
88
88
|
#[cfg(feature = "http")]
|
|
89
89
|
Value::Serve => write!(f, "Serve"),
|
|
90
90
|
#[cfg(feature = "regex")]
|
|
91
|
-
Value::RegExp(re) => write!(
|
|
91
|
+
Value::RegExp(re) => write!(
|
|
92
|
+
f,
|
|
93
|
+
"RegExp(/{}/{})",
|
|
94
|
+
re.borrow().source,
|
|
95
|
+
re.borrow().flags_string()
|
|
96
|
+
),
|
|
92
97
|
#[cfg(feature = "http")]
|
|
93
98
|
Value::Promise(_) => write!(f, "Promise"),
|
|
94
99
|
#[cfg(feature = "http")]
|
|
@@ -96,7 +101,9 @@ impl std::fmt::Debug for Value {
|
|
|
96
101
|
#[cfg(feature = "http")]
|
|
97
102
|
Value::PromiseConstructor => write!(f, "[Function: Promise]"),
|
|
98
103
|
#[cfg(feature = "http")]
|
|
99
|
-
Value::BoundPromiseMethod(_, _)
|
|
104
|
+
Value::BoundPromiseMethod(_, _) => write!(f, "[Function]"),
|
|
105
|
+
#[cfg(feature = "timers")]
|
|
106
|
+
Value::TimerBuiltin(_) => write!(f, "[Function]"),
|
|
100
107
|
#[cfg(feature = "http")]
|
|
101
108
|
Value::CorePromise(_) => write!(f, "Promise"),
|
|
102
109
|
Value::CoreFn(_) => write!(f, "CoreFn"),
|
|
@@ -151,7 +158,9 @@ impl std::fmt::Display for Value {
|
|
|
151
158
|
#[cfg(feature = "http")]
|
|
152
159
|
Value::PromiseConstructor => write!(f, "function Promise() {{ [native code] }}"),
|
|
153
160
|
#[cfg(feature = "http")]
|
|
154
|
-
Value::BoundPromiseMethod(_, _)
|
|
161
|
+
Value::BoundPromiseMethod(_, _) => write!(f, "[Function]"),
|
|
162
|
+
#[cfg(feature = "timers")]
|
|
163
|
+
Value::TimerBuiltin(_) => write!(f, "[Function]"),
|
|
155
164
|
#[cfg(feature = "http")]
|
|
156
165
|
Value::CorePromise(_) => write!(f, "[Promise]"),
|
|
157
166
|
Value::CoreFn(_) => write!(f, "[Function]"),
|
|
@@ -187,7 +196,9 @@ impl Value {
|
|
|
187
196
|
(Value::Array(a), Value::Array(b)) => Rc::ptr_eq(a, b),
|
|
188
197
|
(Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b),
|
|
189
198
|
(Value::Opaque(a), Value::Opaque(b)) => Arc::ptr_eq(a, b),
|
|
190
|
-
(Value::OpaqueMethod(a, ak), Value::OpaqueMethod(b, bk)) =>
|
|
199
|
+
(Value::OpaqueMethod(a, ak), Value::OpaqueMethod(b, bk)) => {
|
|
200
|
+
Arc::ptr_eq(a, b) && ak == bk
|
|
201
|
+
}
|
|
191
202
|
_ => false,
|
|
192
203
|
}
|
|
193
204
|
}
|
|
@@ -4,7 +4,9 @@ use std::cell::RefCell;
|
|
|
4
4
|
use std::rc::Rc;
|
|
5
5
|
use std::sync::Arc;
|
|
6
6
|
|
|
7
|
-
use tishlang_core::{ObjectMap, Value as CoreValue};
|
|
7
|
+
use tishlang_core::{ObjectMap, Value as CoreValue, VmRef};
|
|
8
|
+
#[cfg(feature = "regex")]
|
|
9
|
+
use tishlang_core::TishRegExp;
|
|
8
10
|
|
|
9
11
|
use crate::value::{PropMap, Value};
|
|
10
12
|
|
|
@@ -20,14 +22,14 @@ pub fn eval_to_core(v: &Value) -> Result<CoreValue, String> {
|
|
|
20
22
|
for item in arr.borrow().iter() {
|
|
21
23
|
out.push(eval_to_core(item)?);
|
|
22
24
|
}
|
|
23
|
-
Ok(CoreValue::Array(
|
|
25
|
+
Ok(CoreValue::Array(VmRef::new(out)))
|
|
24
26
|
}
|
|
25
27
|
Value::Object(map) => {
|
|
26
28
|
let mut out = ObjectMap::default();
|
|
27
29
|
for (k, v) in map.borrow().iter() {
|
|
28
30
|
out.insert(Arc::clone(k), eval_to_core(v)?);
|
|
29
31
|
}
|
|
30
|
-
Ok(CoreValue::Object(
|
|
32
|
+
Ok(CoreValue::Object(VmRef::new(out)))
|
|
31
33
|
}
|
|
32
34
|
Value::Opaque(o) => Ok(CoreValue::Opaque(Arc::clone(o))),
|
|
33
35
|
_ => Err(format!(
|
|
@@ -63,13 +65,19 @@ pub fn core_to_eval(v: CoreValue) -> Value {
|
|
|
63
65
|
CoreValue::Promise(p) => Value::CorePromise(Arc::clone(&p)),
|
|
64
66
|
#[cfg(not(feature = "http"))]
|
|
65
67
|
CoreValue::Promise(_) => Value::Null,
|
|
66
|
-
|
|
68
|
+
// `CoreNativeFn` is feature-gated (Rc vs Arc), so use Clone::clone
|
|
69
|
+
// which works for either.
|
|
70
|
+
CoreValue::Function(f) => Value::CoreFn(f.clone()),
|
|
67
71
|
// tishlang_core gets regex from http or regex features; handle RegExp when it exists
|
|
68
72
|
#[cfg(any(feature = "http", feature = "regex"))]
|
|
69
73
|
CoreValue::RegExp(re) => {
|
|
70
74
|
#[cfg(feature = "regex")]
|
|
71
75
|
{
|
|
72
|
-
|
|
76
|
+
// Core uses `VmRef<TishRegExp>` (potentially `Arc<Mutex>`),
|
|
77
|
+
// interpreter uses `Rc<RefCell<TishRegExp>>`. Clone the
|
|
78
|
+
// inner state across so the two storage shapes can coexist.
|
|
79
|
+
let inner: TishRegExp = re.borrow().clone();
|
|
80
|
+
Value::RegExp(Rc::new(RefCell::new(inner)))
|
|
73
81
|
}
|
|
74
82
|
#[cfg(not(feature = "regex"))]
|
|
75
83
|
{
|