@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,179 @@
|
|
|
1
|
+
//! ECMA-262 §27.2 Promise implementation for Tish interpreter.
|
|
2
|
+
//! Requires tokio (http feature) for block_on and microtask scheduling.
|
|
3
|
+
|
|
4
|
+
use std::cell::RefCell;
|
|
5
|
+
use std::collections::VecDeque;
|
|
6
|
+
use std::rc::Rc;
|
|
7
|
+
|
|
8
|
+
use tokio::sync::oneshot;
|
|
9
|
+
|
|
10
|
+
use crate::value::Value;
|
|
11
|
+
|
|
12
|
+
/// A reaction from .then or .finally.
|
|
13
|
+
pub enum Reaction {
|
|
14
|
+
Then(
|
|
15
|
+
Option<Value>, // onFulfilled
|
|
16
|
+
Option<Value>, // onRejected
|
|
17
|
+
PromiseResolver, // resolve
|
|
18
|
+
PromiseResolver, // reject
|
|
19
|
+
),
|
|
20
|
+
Finally(Value, PromiseResolver, PromiseResolver), // onFinally, resolve, reject
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Promise state: Pending, Fulfilled, or Rejected.
|
|
24
|
+
pub enum PromiseState {
|
|
25
|
+
Pending {
|
|
26
|
+
tx: Option<oneshot::Sender<Result<Value, Value>>>,
|
|
27
|
+
reactions: VecDeque<Reaction>,
|
|
28
|
+
},
|
|
29
|
+
Fulfilled(Value),
|
|
30
|
+
Rejected(Value),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Internal state for a Promise, shared via Rc<RefCell<>>.
|
|
34
|
+
pub type PromiseStateRef = Rc<RefCell<PromiseState>>;
|
|
35
|
+
|
|
36
|
+
/// Receiver for blocking until a promise settles.
|
|
37
|
+
pub type PromiseRx = Rc<RefCell<Option<oneshot::Receiver<Result<Value, Value>>>>>;
|
|
38
|
+
|
|
39
|
+
/// A promise value holds state and the receiver for blocking until settled.
|
|
40
|
+
#[derive(Clone)]
|
|
41
|
+
pub struct PromiseRef {
|
|
42
|
+
pub state: PromiseStateRef,
|
|
43
|
+
pub rx: PromiseRx,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Data for resolve/reject callables passed to executor.
|
|
47
|
+
#[derive(Clone)]
|
|
48
|
+
pub struct PromiseResolver {
|
|
49
|
+
pub state: PromiseStateRef,
|
|
50
|
+
pub is_resolve: bool,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Extract PromiseResolver from Value::PromiseResolver. Panics if not.
|
|
54
|
+
pub fn extract_resolvers(resolve: &Value, reject: &Value) -> (PromiseResolver, PromiseResolver) {
|
|
55
|
+
let r = match resolve {
|
|
56
|
+
Value::PromiseResolver(x) => x.clone(),
|
|
57
|
+
_ => panic!("expected PromiseResolver"),
|
|
58
|
+
};
|
|
59
|
+
let j = match reject {
|
|
60
|
+
Value::PromiseResolver(x) => x.clone(),
|
|
61
|
+
_ => panic!("expected PromiseResolver"),
|
|
62
|
+
};
|
|
63
|
+
(r, j)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Create a new pending Promise. Returns (promise_value, resolve_value, reject_value).
|
|
67
|
+
/// The executor will be called with (resolve, reject). Resolve/reject can only be called once.
|
|
68
|
+
pub fn create_promise() -> (Value, Value, Value) {
|
|
69
|
+
let (tx, rx) = oneshot::channel();
|
|
70
|
+
let state = Rc::new(RefCell::new(PromiseState::Pending {
|
|
71
|
+
tx: Some(tx),
|
|
72
|
+
reactions: VecDeque::new(),
|
|
73
|
+
}));
|
|
74
|
+
let rx_cell = Rc::new(RefCell::new(Some(rx)));
|
|
75
|
+
let resolve = Value::PromiseResolver(PromiseResolver {
|
|
76
|
+
state: Rc::clone(&state),
|
|
77
|
+
is_resolve: true,
|
|
78
|
+
});
|
|
79
|
+
let reject = Value::PromiseResolver(PromiseResolver {
|
|
80
|
+
state: Rc::clone(&state),
|
|
81
|
+
is_resolve: false,
|
|
82
|
+
});
|
|
83
|
+
let promise = Value::Promise(PromiseRef {
|
|
84
|
+
state: Rc::clone(&state),
|
|
85
|
+
rx: rx_cell,
|
|
86
|
+
});
|
|
87
|
+
(promise, resolve, reject)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Result of settling: reactions to run (caller must run them with evaluator).
|
|
91
|
+
pub type SettleResult = (Value, bool, Vec<Reaction>);
|
|
92
|
+
|
|
93
|
+
/// Settle the promise (resolve or reject). Called when PromiseResolver is invoked.
|
|
94
|
+
/// Returns Ok((value, is_fulfilled, reactions)) if settled; reactions should be run by caller.
|
|
95
|
+
/// Returns Err(msg) if already settled.
|
|
96
|
+
pub fn settle_promise(
|
|
97
|
+
resolver: &PromiseResolver,
|
|
98
|
+
value: Value,
|
|
99
|
+
is_resolve: bool,
|
|
100
|
+
) -> Result<SettleResult, String> {
|
|
101
|
+
let (tx, reactions) = {
|
|
102
|
+
let mut state = resolver.state.borrow_mut();
|
|
103
|
+
match std::mem::replace(&mut *state, PromiseState::Fulfilled(Value::Null)) {
|
|
104
|
+
PromiseState::Pending { tx, reactions } => {
|
|
105
|
+
*state = if is_resolve {
|
|
106
|
+
PromiseState::Fulfilled(value.clone())
|
|
107
|
+
} else {
|
|
108
|
+
PromiseState::Rejected(value.clone())
|
|
109
|
+
};
|
|
110
|
+
(tx, reactions.into_iter().collect())
|
|
111
|
+
}
|
|
112
|
+
s @ PromiseState::Fulfilled(_) | s @ PromiseState::Rejected(_) => {
|
|
113
|
+
*state = s;
|
|
114
|
+
return Err("Promise already settled".to_string());
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
if let Some(tx) = tx {
|
|
119
|
+
let result = if is_resolve {
|
|
120
|
+
Ok(value.clone())
|
|
121
|
+
} else {
|
|
122
|
+
Err(value.clone())
|
|
123
|
+
};
|
|
124
|
+
let _ = tx.send(result);
|
|
125
|
+
}
|
|
126
|
+
Ok((value, is_resolve, reactions))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// Add a reaction to a pending promise. Caller must ensure promise is Pending.
|
|
130
|
+
pub fn add_reaction(state: &PromiseStateRef, reaction: Reaction) {
|
|
131
|
+
let mut s = state.borrow_mut();
|
|
132
|
+
if let PromiseState::Pending { reactions, .. } = &mut *s {
|
|
133
|
+
reactions.push_back(reaction);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Clone for Reaction
|
|
138
|
+
impl Clone for Reaction {
|
|
139
|
+
fn clone(&self) -> Self {
|
|
140
|
+
match self {
|
|
141
|
+
Reaction::Then(a, b, r1, r2) => {
|
|
142
|
+
Reaction::Then(a.clone(), b.clone(), r1.clone(), r2.clone())
|
|
143
|
+
}
|
|
144
|
+
Reaction::Finally(f, r1, r2) => Reaction::Finally(f.clone(), r1.clone(), r2.clone()),
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// Result of awaiting a promise: fulfilled value, or rejection/error.
|
|
150
|
+
#[derive(Debug)]
|
|
151
|
+
pub enum PromiseAwaitResult {
|
|
152
|
+
Fulfilled(Value),
|
|
153
|
+
Rejected(Value),
|
|
154
|
+
Error(String),
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// Block until the promise settles.
|
|
158
|
+
pub fn block_until_settled(promise_ref: &PromiseRef) -> PromiseAwaitResult {
|
|
159
|
+
let maybe_rx = promise_ref.rx.borrow_mut().take();
|
|
160
|
+
if let Some(rx) = maybe_rx {
|
|
161
|
+
let result = crate::http::RUNTIME.with(|rt| rt.block_on(rx));
|
|
162
|
+
match result {
|
|
163
|
+
Ok(Ok(v)) => PromiseAwaitResult::Fulfilled(v),
|
|
164
|
+
Ok(Err(v)) => PromiseAwaitResult::Rejected(v),
|
|
165
|
+
Err(_) => {
|
|
166
|
+
PromiseAwaitResult::Error("Promise channel dropped before settlement".to_string())
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
let state = promise_ref.state.borrow();
|
|
171
|
+
match &*state {
|
|
172
|
+
PromiseState::Fulfilled(v) => PromiseAwaitResult::Fulfilled(v.clone()),
|
|
173
|
+
PromiseState::Rejected(v) => PromiseAwaitResult::Rejected(v.clone()),
|
|
174
|
+
PromiseState::Pending { .. } => {
|
|
175
|
+
PromiseAwaitResult::Error("Promise receiver already consumed".to_string())
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
//! JavaScript-compatible regular expression support for Tish.
|
|
2
|
+
//!
|
|
3
|
+
//! Re-exports core types from tishlang_core and provides interpreter-specific functionality.
|
|
4
|
+
|
|
5
|
+
use std::cell::RefCell;
|
|
6
|
+
use std::rc::Rc;
|
|
7
|
+
use std::sync::Arc;
|
|
8
|
+
|
|
9
|
+
pub use tishlang_core::{RegExpFlags, TishRegExp};
|
|
10
|
+
|
|
11
|
+
use crate::value::PropMap;
|
|
12
|
+
|
|
13
|
+
use crate::value::Value;
|
|
14
|
+
|
|
15
|
+
/// RegExp.prototype.exec(string) - returns match object (array-like with index) or null
|
|
16
|
+
pub fn regexp_exec(re: &mut TishRegExp, input: &str) -> Value {
|
|
17
|
+
let start = if re.flags.global || re.flags.sticky {
|
|
18
|
+
re.last_index
|
|
19
|
+
} else {
|
|
20
|
+
0
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
let char_count = input.chars().count();
|
|
24
|
+
if start > char_count {
|
|
25
|
+
if re.flags.global || re.flags.sticky {
|
|
26
|
+
re.last_index = 0;
|
|
27
|
+
}
|
|
28
|
+
return Value::Null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let byte_start: usize = input.chars().take(start).map(|c| c.len_utf8()).sum();
|
|
32
|
+
let search_str = &input[byte_start..];
|
|
33
|
+
|
|
34
|
+
match re.regex.captures(search_str) {
|
|
35
|
+
Ok(Some(caps)) => {
|
|
36
|
+
let full_match = caps.get(0).unwrap();
|
|
37
|
+
|
|
38
|
+
if re.flags.sticky && full_match.start() != 0 {
|
|
39
|
+
re.last_index = 0;
|
|
40
|
+
return Value::Null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let match_byte_start = byte_start + full_match.start();
|
|
44
|
+
let match_char_index = input[..match_byte_start].chars().count();
|
|
45
|
+
|
|
46
|
+
let mut obj: PropMap = PropMap::default();
|
|
47
|
+
obj.insert(Arc::from("0"), Value::String(full_match.as_str().into()));
|
|
48
|
+
for i in 1..caps.len() {
|
|
49
|
+
let val = match caps.get(i) {
|
|
50
|
+
Some(m) => Value::String(m.as_str().into()),
|
|
51
|
+
None => Value::Null,
|
|
52
|
+
};
|
|
53
|
+
obj.insert(Arc::from(i.to_string().as_str()), val);
|
|
54
|
+
}
|
|
55
|
+
obj.insert(Arc::from("index"), Value::Number(match_char_index as f64));
|
|
56
|
+
|
|
57
|
+
if re.flags.global || re.flags.sticky {
|
|
58
|
+
let match_end_chars = input[..byte_start + full_match.end()].chars().count();
|
|
59
|
+
re.last_index = if full_match.start() == full_match.end() {
|
|
60
|
+
match_end_chars + 1
|
|
61
|
+
} else {
|
|
62
|
+
match_end_chars
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
Value::object(obj)
|
|
67
|
+
}
|
|
68
|
+
Ok(None) | Err(_) => {
|
|
69
|
+
if re.flags.global || re.flags.sticky {
|
|
70
|
+
re.last_index = 0;
|
|
71
|
+
}
|
|
72
|
+
Value::Null
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Create a RegExp Value from pattern and flags
|
|
78
|
+
pub fn create_regexp(pattern: &str, flags: &str) -> Result<Value, String> {
|
|
79
|
+
let re = TishRegExp::new(pattern, flags)?;
|
|
80
|
+
Ok(Value::RegExp(Rc::new(RefCell::new(re))))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// RegExp constructor function - handles `new RegExp(pattern, flags)` or `RegExp(pattern, flags)`
|
|
84
|
+
pub fn regexp_constructor(args: &[Value]) -> Result<Value, String> {
|
|
85
|
+
let pattern = match args.first() {
|
|
86
|
+
Some(Value::String(s)) => s.to_string(),
|
|
87
|
+
Some(Value::RegExp(re)) => {
|
|
88
|
+
if args.get(1).is_none() {
|
|
89
|
+
let re = re.borrow();
|
|
90
|
+
return create_regexp(&re.source, &re.flags_string());
|
|
91
|
+
}
|
|
92
|
+
re.borrow().source.clone()
|
|
93
|
+
}
|
|
94
|
+
Some(v) => v.to_string(),
|
|
95
|
+
None => String::new(),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
let flags = match args.get(1) {
|
|
99
|
+
Some(Value::String(s)) => s.to_string(),
|
|
100
|
+
Some(Value::Null) | None => String::new(),
|
|
101
|
+
Some(v) => v.to_string(),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
create_regexp(&pattern, &flags)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ============== String methods with regex support ==============
|
|
108
|
+
|
|
109
|
+
/// String.prototype.match(regexp) - returns array of matches or null
|
|
110
|
+
pub fn string_match(input: &str, regexp: &Value) -> Value {
|
|
111
|
+
match regexp {
|
|
112
|
+
Value::RegExp(re) => {
|
|
113
|
+
let mut re = re.borrow_mut();
|
|
114
|
+
|
|
115
|
+
if re.flags.global {
|
|
116
|
+
let mut matches = Vec::new();
|
|
117
|
+
re.last_index = 0;
|
|
118
|
+
|
|
119
|
+
while let Ok(Some(m)) = re.regex.find_from_pos(input, re.last_index) {
|
|
120
|
+
matches.push(Value::String(m.as_str().into()));
|
|
121
|
+
if m.start() == m.end() {
|
|
122
|
+
re.last_index = m.end() + 1;
|
|
123
|
+
} else {
|
|
124
|
+
re.last_index = m.end();
|
|
125
|
+
}
|
|
126
|
+
if re.last_index > input.len() {
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
re.last_index = 0;
|
|
132
|
+
|
|
133
|
+
if matches.is_empty() {
|
|
134
|
+
Value::Null
|
|
135
|
+
} else {
|
|
136
|
+
Value::Array(Rc::new(RefCell::new(matches)))
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
regexp_exec(&mut re, input)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
Value::String(pattern) => match TishRegExp::new(pattern, "") {
|
|
143
|
+
Ok(mut re) => regexp_exec(&mut re, input),
|
|
144
|
+
Err(_) => Value::Null,
|
|
145
|
+
},
|
|
146
|
+
_ => Value::Null,
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// String.prototype.replace(searchValue, replaceValue)
|
|
151
|
+
pub fn string_replace(input: &str, search: &Value, replace: &Value) -> Value {
|
|
152
|
+
let replacement = match replace {
|
|
153
|
+
Value::String(s) => s.to_string(),
|
|
154
|
+
v => v.to_string(),
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
match search {
|
|
158
|
+
Value::RegExp(re) => {
|
|
159
|
+
let re = re.borrow();
|
|
160
|
+
if re.flags.global {
|
|
161
|
+
match re.regex.replace_all(input, replacement.as_str()) {
|
|
162
|
+
std::borrow::Cow::Borrowed(s) => Value::String(s.into()),
|
|
163
|
+
std::borrow::Cow::Owned(s) => Value::String(s.into()),
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
match re.regex.replace(input, replacement.as_str()) {
|
|
167
|
+
std::borrow::Cow::Borrowed(s) => Value::String(s.into()),
|
|
168
|
+
std::borrow::Cow::Owned(s) => Value::String(s.into()),
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
Value::String(pattern) => {
|
|
173
|
+
Value::String(input.replacen(pattern.as_ref(), &replacement, 1).into())
|
|
174
|
+
}
|
|
175
|
+
_ => Value::String(input.into()),
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// Replace regex matches using a callback. Callback receives (match, g1, g2, ..., index, fullString).
|
|
180
|
+
pub fn string_replace_regex_with_fn<F>(
|
|
181
|
+
input: &str,
|
|
182
|
+
re: &TishRegExp,
|
|
183
|
+
invoke: &mut F,
|
|
184
|
+
) -> Result<Value, String>
|
|
185
|
+
where
|
|
186
|
+
F: FnMut(&[Value]) -> Result<String, String>,
|
|
187
|
+
{
|
|
188
|
+
let limit = if re.flags.global { usize::MAX } else { 1 };
|
|
189
|
+
let mut result = String::new();
|
|
190
|
+
let mut last_end: usize = 0;
|
|
191
|
+
for (count, cap_result) in re.regex.captures_iter(input).enumerate() {
|
|
192
|
+
if count >= limit {
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
let caps = cap_result.map_err(|e| format!("Regex error: {}", e))?;
|
|
196
|
+
let full = caps.get(0).unwrap();
|
|
197
|
+
let match_str = full.as_str();
|
|
198
|
+
let byte_start = full.start();
|
|
199
|
+
let char_index = input[..byte_start].chars().count();
|
|
200
|
+
|
|
201
|
+
let mut args = vec![Value::String(match_str.into())];
|
|
202
|
+
for i in 1..caps.len() {
|
|
203
|
+
let val = match caps.get(i) {
|
|
204
|
+
Some(m) => Value::String(m.as_str().into()),
|
|
205
|
+
None => Value::Null,
|
|
206
|
+
};
|
|
207
|
+
args.push(val);
|
|
208
|
+
}
|
|
209
|
+
args.push(Value::Number(char_index as f64));
|
|
210
|
+
args.push(Value::String(input.into()));
|
|
211
|
+
|
|
212
|
+
let repl = invoke(&args)?;
|
|
213
|
+
result.push_str(&input[last_end..byte_start]);
|
|
214
|
+
result.push_str(&repl);
|
|
215
|
+
last_end = full.end();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
result.push_str(&input[last_end..]);
|
|
219
|
+
Ok(Value::String(result.into()))
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/// String.prototype.search(regexp) - returns index of first match or -1
|
|
223
|
+
pub fn string_search(input: &str, regexp: &Value) -> Value {
|
|
224
|
+
match regexp {
|
|
225
|
+
Value::RegExp(re) => {
|
|
226
|
+
let re = re.borrow();
|
|
227
|
+
match re.regex.find(input) {
|
|
228
|
+
Ok(Some(m)) => {
|
|
229
|
+
let char_index = input[..m.start()].chars().count();
|
|
230
|
+
Value::Number(char_index as f64)
|
|
231
|
+
}
|
|
232
|
+
_ => Value::Number(-1.0),
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
Value::String(pattern) => match TishRegExp::new(pattern, "") {
|
|
236
|
+
Ok(re) => match re.regex.find(input) {
|
|
237
|
+
Ok(Some(m)) => {
|
|
238
|
+
let char_index = input[..m.start()].chars().count();
|
|
239
|
+
Value::Number(char_index as f64)
|
|
240
|
+
}
|
|
241
|
+
_ => Value::Number(-1.0),
|
|
242
|
+
},
|
|
243
|
+
Err(_) => Value::Number(-1.0),
|
|
244
|
+
},
|
|
245
|
+
_ => Value::Number(-1.0),
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/// String.prototype.split(separator, limit) - split string by regex or string
|
|
250
|
+
pub fn string_split(input: &str, separator: &Value, limit: Option<usize>) -> Value {
|
|
251
|
+
let max = limit.unwrap_or(usize::MAX);
|
|
252
|
+
|
|
253
|
+
if max == 0 {
|
|
254
|
+
return Value::Array(Rc::new(RefCell::new(Vec::new())));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
match separator {
|
|
258
|
+
Value::RegExp(re) => {
|
|
259
|
+
let re = re.borrow();
|
|
260
|
+
let mut result = Vec::new();
|
|
261
|
+
let mut last_end = 0;
|
|
262
|
+
|
|
263
|
+
for mat in re.regex.find_iter(input) {
|
|
264
|
+
match mat {
|
|
265
|
+
Ok(m) => {
|
|
266
|
+
if result.len() >= max - 1 {
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
result.push(Value::String(input[last_end..m.start()].into()));
|
|
270
|
+
last_end = m.end();
|
|
271
|
+
}
|
|
272
|
+
Err(_) => break,
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if result.len() < max {
|
|
277
|
+
result.push(Value::String(input[last_end..].into()));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
Value::Array(Rc::new(RefCell::new(result)))
|
|
281
|
+
}
|
|
282
|
+
Value::String(sep) => {
|
|
283
|
+
let parts: Vec<Value> = input
|
|
284
|
+
.splitn(max, sep.as_ref())
|
|
285
|
+
.map(|s| Value::String(s.into()))
|
|
286
|
+
.collect();
|
|
287
|
+
Value::Array(Rc::new(RefCell::new(parts)))
|
|
288
|
+
}
|
|
289
|
+
Value::Null => Value::Array(Rc::new(RefCell::new(vec![Value::String(input.into())]))),
|
|
290
|
+
_ => {
|
|
291
|
+
let sep_str = separator.to_string();
|
|
292
|
+
let parts: Vec<Value> = input
|
|
293
|
+
.splitn(max, &sep_str)
|
|
294
|
+
.map(|s| Value::String(s.into()))
|
|
295
|
+
.collect();
|
|
296
|
+
Value::Array(Rc::new(RefCell::new(parts)))
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
//! setTimeout, setInterval, clearTimeout, clearInterval.
|
|
2
|
+
//! Non-blocking: setTimeout returns immediately; callbacks run in a drain phase
|
|
3
|
+
//! after the script yields (when run() finishes the synchronous program).
|
|
4
|
+
|
|
5
|
+
use std::cell::RefCell;
|
|
6
|
+
use std::collections::HashMap;
|
|
7
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
8
|
+
use std::time::{Duration, Instant};
|
|
9
|
+
|
|
10
|
+
use crate::value::Value;
|
|
11
|
+
|
|
12
|
+
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
|
|
13
|
+
|
|
14
|
+
struct TimerEntry {
|
|
15
|
+
due: Instant,
|
|
16
|
+
callback: Value,
|
|
17
|
+
args: Vec<Value>,
|
|
18
|
+
interval_ms: u64,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
thread_local! {
|
|
22
|
+
static REGISTRY: RefCell<HashMap<u64, TimerEntry>> = RefCell::new(HashMap::new());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fn next_id() -> u64 {
|
|
26
|
+
NEXT_ID.fetch_add(1, Ordering::SeqCst)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Register a one-shot timer. Returns immediately with timer id.
|
|
30
|
+
#[allow(non_snake_case)]
|
|
31
|
+
pub fn setTimeout(callback: Value, args: Vec<Value>, delay_ms: u64) -> u64 {
|
|
32
|
+
let id = next_id();
|
|
33
|
+
let due = Instant::now() + Duration::from_millis(delay_ms);
|
|
34
|
+
REGISTRY.with(|r| {
|
|
35
|
+
r.borrow_mut().insert(
|
|
36
|
+
id,
|
|
37
|
+
TimerEntry {
|
|
38
|
+
due,
|
|
39
|
+
callback,
|
|
40
|
+
args,
|
|
41
|
+
interval_ms: 0,
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
id
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Register a repeating timer. Returns immediately with timer id.
|
|
49
|
+
#[allow(non_snake_case)]
|
|
50
|
+
pub fn setInterval(callback: Value, args: Vec<Value>, delay_ms: u64) -> u64 {
|
|
51
|
+
let id = next_id();
|
|
52
|
+
let due = Instant::now() + Duration::from_millis(delay_ms);
|
|
53
|
+
REGISTRY.with(|r| {
|
|
54
|
+
r.borrow_mut().insert(
|
|
55
|
+
id,
|
|
56
|
+
TimerEntry {
|
|
57
|
+
due,
|
|
58
|
+
callback,
|
|
59
|
+
args,
|
|
60
|
+
interval_ms: delay_ms,
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
id
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Remove a timer. No-op if already fired or invalid.
|
|
68
|
+
#[allow(non_snake_case)]
|
|
69
|
+
pub fn clearTimer(id: u64) {
|
|
70
|
+
REGISTRY.with(|r| {
|
|
71
|
+
r.borrow_mut().remove(&id);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Take all due timers and return (id, callback, args, interval_ms). Removes them from registry.
|
|
76
|
+
/// Caller should run callbacks; for interval_ms > 0, caller should re-register.
|
|
77
|
+
pub fn take_due_timers() -> Vec<(u64, Value, Vec<Value>, u64)> {
|
|
78
|
+
let now = Instant::now();
|
|
79
|
+
REGISTRY.with(|r| {
|
|
80
|
+
let mut reg = r.borrow_mut();
|
|
81
|
+
let due: Vec<_> = reg
|
|
82
|
+
.iter()
|
|
83
|
+
.filter(|(_, e)| e.due <= now)
|
|
84
|
+
.map(|(id, e)| (*id, e.callback.clone(), e.args.clone(), e.interval_ms))
|
|
85
|
+
.collect();
|
|
86
|
+
for (id, _, _, _) in &due {
|
|
87
|
+
reg.remove(id);
|
|
88
|
+
}
|
|
89
|
+
due
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Re-register an interval timer (called after running its callback).
|
|
94
|
+
pub fn re_register_interval(id: u64, callback: Value, args: Vec<Value>, interval_ms: u64) {
|
|
95
|
+
let due = Instant::now() + Duration::from_millis(interval_ms);
|
|
96
|
+
REGISTRY.with(|r| {
|
|
97
|
+
r.borrow_mut().insert(
|
|
98
|
+
id,
|
|
99
|
+
TimerEntry {
|
|
100
|
+
due,
|
|
101
|
+
callback,
|
|
102
|
+
args,
|
|
103
|
+
interval_ms,
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Check if any timers are still pending.
|
|
110
|
+
pub fn has_pending_timers() -> bool {
|
|
111
|
+
REGISTRY.with(|r| !r.borrow().is_empty())
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// Return the instant when the next timer is due, or None if registry is empty.
|
|
115
|
+
pub fn next_due_instant() -> Option<Instant> {
|
|
116
|
+
REGISTRY.with(|r| {
|
|
117
|
+
let reg = r.borrow();
|
|
118
|
+
reg.values().map(|e| e.due).min()
|
|
119
|
+
})
|
|
120
|
+
}
|