@tishlang/tish-lsp 2.38.0 → 2.43.8
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 +4 -1
- package/bin/tish-lsp +0 -0
- package/crates/tish/src/cli_help.rs +28 -0
- package/crates/tish/src/main.rs +41 -5
- package/crates/tish/tests/scope_merge.rs +79 -0
- package/crates/tish_build_utils/src/lib.rs +27 -1
- package/crates/tish_builtins/Cargo.toml +14 -6
- package/crates/tish_builtins/src/array.rs +130 -54
- package/crates/tish_builtins/src/collections.rs +9 -5
- package/crates/tish_builtins/src/construct.rs +7 -1
- package/crates/tish_builtins/src/date.rs +15 -1
- package/crates/tish_builtins/src/globals.rs +38 -3
- package/crates/tish_builtins/src/helpers.rs +7 -3
- package/crates/tish_builtins/src/iterator.rs +5 -1
- package/crates/tish_builtins/src/lib.rs +3 -0
- package/crates/tish_builtins/src/math.rs +14 -0
- package/crates/tish_builtins/src/number.rs +72 -6
- package/crates/tish_builtins/src/object.rs +6 -1
- package/crates/tish_builtins/src/string.rs +13 -2
- package/crates/tish_builtins/src/symbol.rs +17 -17
- package/crates/tish_builtins/src/typedarrays.rs +9 -2
- package/crates/tish_bytecode/src/compiler.rs +76 -2
- package/crates/tish_bytecode/src/lib.rs +1 -1
- package/crates/tish_bytecode/src/opcode.rs +77 -2
- package/crates/tish_compile/src/codegen.rs +812 -47
- package/crates/tish_compile/src/infer.rs +37 -41
- package/crates/tish_compile/src/lib.rs +13 -1
- package/crates/tish_compile/src/platform_resolve.rs +361 -0
- package/crates/tish_compile/src/resolve.rs +148 -10
- package/crates/tish_compile/src/schemes.rs +278 -0
- package/crates/tish_compile/src/types.rs +141 -2
- package/crates/tish_compile/tests/perf_codegen_320.rs +2 -2
- package/crates/tish_compile/tests/perf_codegen_module_const_forof.rs +3 -1
- package/crates/tish_compile/tests/platform_resolve_cli.rs +164 -0
- package/crates/tish_compile/tests/regr_gba_numerics_gated.rs +58 -0
- package/crates/tish_compiler_wasm/Cargo.toml +1 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +56 -6
- package/crates/tish_core/Cargo.toml +39 -4
- package/crates/tish_core/src/compat.rs +506 -0
- package/crates/tish_core/src/console_style.rs +19 -1
- package/crates/tish_core/src/json.rs +34 -10
- package/crates/tish_core/src/lib.rs +121 -25
- package/crates/tish_core/src/macros.rs +1 -1
- package/crates/tish_core/src/shape.rs +4 -2
- package/crates/tish_core/src/uri.rs +8 -1
- package/crates/tish_core/src/value.rs +182 -54
- package/crates/tish_core/src/vmref.rs +17 -5
- package/crates/tish_eval/Cargo.toml +2 -0
- package/crates/tish_eval/src/eval.rs +90 -3
- package/crates/tish_eval/src/value.rs +8 -0
- package/crates/tish_eval/src/value_convert.rs +316 -21
- package/crates/tish_lsp/Cargo.toml +3 -1
- package/crates/tish_lsp/src/import_goto.rs +41 -14
- package/crates/tish_lsp/src/main.rs +18 -0
- package/crates/tish_native/src/build.rs +246 -0
- package/crates/tish_native/src/config.rs +20 -0
- package/crates/tish_native/src/lib.rs +16 -6
- package/crates/tish_parser/src/lib.rs +28 -0
- package/crates/tish_parser/src/parser.rs +25 -4
- package/crates/tish_runtime/src/http_hyper.rs +70 -0
- package/crates/tish_runtime/src/lib.rs +55 -10
- package/crates/tish_runtime/src/ws.rs +94 -21
- package/crates/tish_runtime_gba/Cargo.lock +837 -0
- package/crates/tish_runtime_gba/Cargo.toml +16 -0
- package/crates/tish_runtime_gba/src/gba.rs +211 -0
- package/crates/tish_runtime_gba/src/lib.rs +620 -0
- package/crates/tish_vm/src/jit.rs +1446 -164
- package/crates/tish_vm/src/vm.rs +535 -173
- package/justfile +9 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-lsp +0 -0
- package/platform/darwin-x64/tish-lsp +0 -0
- package/platform/linux-arm64/tish-lsp +0 -0
- package/platform/linux-x64/tish-lsp +0 -0
- package/platform/win32-x64/tish-lsp.exe +0 -0
|
@@ -2,7 +2,34 @@
|
|
|
2
2
|
//!
|
|
3
3
|
//! This crate provides the unified Value type and utilities used by both
|
|
4
4
|
//! the interpreter (tishlang_eval) and compiled runtime (tishlang_runtime).
|
|
5
|
+
#![cfg_attr(feature = "portable", no_std)]
|
|
5
6
|
|
|
7
|
+
// `portable` (no_std, Arc→Rc) and `send-values` (parking_lot, std-only) are mutually
|
|
8
|
+
// exclusive; unified by Cargo's additive features they'd try to compile parking_lot for a
|
|
9
|
+
// no_std target with a confusing far-from-root-cause error. Fail loudly at the source instead.
|
|
10
|
+
#[cfg(all(feature = "portable", feature = "send-values"))]
|
|
11
|
+
compile_error!("tishlang_core features `portable` and `send-values` are mutually exclusive");
|
|
12
|
+
|
|
13
|
+
// `portable` implies `#![no_std]`, so `std` must be OFF (depend on this crate as
|
|
14
|
+
// `default-features = false, features = ["portable"]`). If both are on — the usual slip of
|
|
15
|
+
// requesting `portable` without disabling the default `std` feature — the `no_std` attribute
|
|
16
|
+
// above still applies while the `std`-only deps (ahash/arcstr) are pulled in, producing a
|
|
17
|
+
// confusing cascade. Catch it at the source with a message that names the fix.
|
|
18
|
+
#[cfg(all(feature = "portable", feature = "std"))]
|
|
19
|
+
compile_error!(
|
|
20
|
+
"tishlang_core feature `portable` requires `default-features = false` (the default `std` feature must be disabled)"
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
extern crate alloc;
|
|
24
|
+
|
|
25
|
+
#[cfg(feature = "portable")]
|
|
26
|
+
use alloc::{
|
|
27
|
+
format,
|
|
28
|
+
string::{String, ToString},
|
|
29
|
+
vec::Vec,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
mod compat;
|
|
6
33
|
mod console_style;
|
|
7
34
|
mod json;
|
|
8
35
|
mod macros;
|
|
@@ -11,13 +38,38 @@ mod uri;
|
|
|
11
38
|
mod value;
|
|
12
39
|
mod vmref;
|
|
13
40
|
|
|
41
|
+
/// Hidden re-export so the exported `tish_module!` macro can name the right
|
|
42
|
+
/// smart pointer (`std::sync::Arc` on host, `Rc` under `portable`) at its
|
|
43
|
+
/// expansion sites without leaking the private `compat` module.
|
|
44
|
+
#[doc(hidden)]
|
|
45
|
+
pub use compat::Arc as __TishArc;
|
|
46
|
+
|
|
47
|
+
/// Portable-runtime surface consumed by `tishlang_builtins` and the GBA facade
|
|
48
|
+
/// (`tishlang_runtime_gba`): the libm-backed float trait and the installable
|
|
49
|
+
/// clock / RNG hooks. Only present under `portable`.
|
|
50
|
+
#[cfg(feature = "portable")]
|
|
51
|
+
pub use compat::{
|
|
52
|
+
install_clock, install_rng, next_u64, now_ms, random_f64, seed_rng, FloatExt, SingleCore,
|
|
53
|
+
};
|
|
54
|
+
|
|
14
55
|
pub use console_style::{format_value_styled, format_values_for_console, use_console_colors};
|
|
15
56
|
pub use json::{
|
|
16
57
|
escape_json_string_into, json_parse, json_stringify, json_stringify_into, write_json_number,
|
|
17
58
|
};
|
|
18
59
|
pub use shape::{ShapeId, DICT_SHAPE, EMPTY_SHAPE};
|
|
19
60
|
pub use uri::{percent_decode, percent_decode_component, percent_encode, percent_encode_component};
|
|
20
|
-
pub use
|
|
61
|
+
pub use compat::ArcStr;
|
|
62
|
+
/// The core type vocabulary, re-exported so `tishlang_builtins`, generated code,
|
|
63
|
+
/// and the GBA facade share one source of truth: `Arc` (std `Arc` / `Rc` under
|
|
64
|
+
/// `portable`), and the object hasher (`ahash` / `FxHasher`). Names match the std
|
|
65
|
+
/// originals so hosted call sites are unchanged.
|
|
66
|
+
pub use compat::{AHashMap, Arc, RandomState};
|
|
67
|
+
/// Lock / once-cell vocabulary, namespaced to avoid polluting the crate root:
|
|
68
|
+
/// std `sync` types on the host, single-core shims under `portable`. Used by
|
|
69
|
+
/// `tishlang_builtins` for its symbol registry.
|
|
70
|
+
pub mod sync {
|
|
71
|
+
pub use crate::compat::{Mutex, OnceLock, RwLock};
|
|
72
|
+
}
|
|
21
73
|
pub use value::*;
|
|
22
74
|
pub use vmref::{VmReadGuard, VmRef, VmWriteGuard};
|
|
23
75
|
|
|
@@ -26,20 +78,34 @@ pub use vmref::{VmReadGuard, VmRef, VmWriteGuard};
|
|
|
26
78
|
/// `[tish-exe, <file>, args...]` so a script sees its own args — not the `run` subcommand. Compiled
|
|
27
79
|
/// native binaries don't touch this; they read `std::env::args()` directly (which is already their
|
|
28
80
|
/// own argv). #88
|
|
81
|
+
#[cfg(not(feature = "portable"))]
|
|
29
82
|
static PROCESS_ARGV: std::sync::OnceLock<Vec<String>> = std::sync::OnceLock::new();
|
|
30
83
|
|
|
31
84
|
/// Override `process.argv` for the interpreter/VM (set once by `tish run` before executing). A
|
|
32
85
|
/// later call is ignored (the first override wins), matching the single-program-per-process model.
|
|
86
|
+
/// No-op under `portable` (an embedded GBA ROM has no process argv).
|
|
87
|
+
#[cfg(not(feature = "portable"))]
|
|
33
88
|
pub fn set_process_argv(argv: Vec<String>) {
|
|
34
89
|
let _ = PROCESS_ARGV.set(argv);
|
|
35
90
|
}
|
|
36
91
|
|
|
92
|
+
/// See [`set_process_argv`]. No-op under `portable`.
|
|
93
|
+
#[cfg(feature = "portable")]
|
|
94
|
+
pub fn set_process_argv(_argv: Vec<String>) {}
|
|
95
|
+
|
|
37
96
|
/// The argv a script should see as `process.argv`: the [`set_process_argv`] override if present,
|
|
38
|
-
/// else the host process's `std::env::args()`.
|
|
97
|
+
/// else the host process's `std::env::args()`. Empty under `portable`.
|
|
39
98
|
pub fn process_argv() -> Vec<String> {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
99
|
+
#[cfg(not(feature = "portable"))]
|
|
100
|
+
{
|
|
101
|
+
match PROCESS_ARGV.get() {
|
|
102
|
+
Some(v) => v.clone(),
|
|
103
|
+
None => std::env::args().collect(),
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
#[cfg(feature = "portable")]
|
|
107
|
+
{
|
|
108
|
+
Vec::new()
|
|
43
109
|
}
|
|
44
110
|
}
|
|
45
111
|
|
|
@@ -54,9 +120,14 @@ pub fn process_argv() -> Vec<String> {
|
|
|
54
120
|
/// can poll it without a `builtins -> runtime` dependency cycle; `tishlang_runtime` and `tishlang_vm`
|
|
55
121
|
/// share this one slot. First-throw-wins; drained exactly once by [`take_pending_throw`] at the frame
|
|
56
122
|
/// that converts it back into a `Result`.
|
|
123
|
+
#[cfg(not(feature = "portable"))]
|
|
57
124
|
thread_local! {
|
|
58
|
-
static PENDING_THROW:
|
|
125
|
+
static PENDING_THROW: core::cell::RefCell<Option<Value>> = const { core::cell::RefCell::new(None) };
|
|
59
126
|
}
|
|
127
|
+
// Single-core: a plain static with the same `.with()` API. See compat::SingleCore.
|
|
128
|
+
#[cfg(feature = "portable")]
|
|
129
|
+
static PENDING_THROW: compat::SingleCore<core::cell::RefCell<Option<Value>>> =
|
|
130
|
+
compat::SingleCore::new(core::cell::RefCell::new(None));
|
|
60
131
|
|
|
61
132
|
/// Park a thrown value to propagate across a non-`Result` boundary. First-throw-wins: if one is
|
|
62
133
|
/// already pending (an erroneous continuation reached a second throw before the slot was drained),
|
|
@@ -80,13 +151,17 @@ pub fn take_pending_throw() -> Option<Value> {
|
|
|
80
151
|
PENDING_THROW.with(|c| c.borrow_mut().take())
|
|
81
152
|
}
|
|
82
153
|
|
|
154
|
+
// Current user-function call depth for the bytecode VM. The VM's recursive path builds a fresh
|
|
155
|
+
// `Vm` per call (so no shared struct field can accumulate) and its `Callable::call` signature is
|
|
156
|
+
// fixed — a thread-local is the VM's equivalent of the interpreter's shared `Rc<Cell>` counter.
|
|
157
|
+
// Lives here (not `tish_vm`) beside `PENDING_THROW` for the same layering reason. #381
|
|
158
|
+
#[cfg(not(feature = "portable"))]
|
|
83
159
|
thread_local! {
|
|
84
|
-
|
|
85
|
-
/// `Vm` per call (so no shared struct field can accumulate) and its `Callable::call` signature is
|
|
86
|
-
/// fixed — a thread-local is the VM's equivalent of the interpreter's shared `Rc<Cell>` counter.
|
|
87
|
-
/// Lives here (not `tish_vm`) beside `PENDING_THROW` for the same layering reason. #381
|
|
88
|
-
static CALL_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
|
|
160
|
+
static CALL_DEPTH: core::cell::Cell<usize> = const { core::cell::Cell::new(0) };
|
|
89
161
|
}
|
|
162
|
+
#[cfg(feature = "portable")]
|
|
163
|
+
static CALL_DEPTH: compat::SingleCore<core::cell::Cell<usize>> =
|
|
164
|
+
compat::SingleCore::new(core::cell::Cell::new(0));
|
|
90
165
|
|
|
91
166
|
/// Enter one VM call frame; returns the new depth. Pair every call with [`dec_call_depth`].
|
|
92
167
|
#[inline]
|
|
@@ -107,14 +182,32 @@ pub fn dec_call_depth() {
|
|
|
107
182
|
/// Default recursion ceiling shared by every backend that counts call depth (#381). Chosen with the
|
|
108
183
|
/// interpreter: comfortably deep for real programs, but reached long before counted recursion can
|
|
109
184
|
/// exhaust memory or the stack.
|
|
185
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
110
186
|
pub const DEFAULT_MAX_CALL_DEPTH: usize = 20_000;
|
|
111
187
|
|
|
188
|
+
/// wasm32 ceiling (#531). On wasm the embedded VM runs on the single guest stack — there is no host
|
|
189
|
+
/// worker thread to grow into (`Vm` can't spawn on wasm), and the host runtime (wasmtime) bounds
|
|
190
|
+
/// guest call depth by its own `max_wasm_stack`. That limit is NOT reachable from the `.wasm`: it
|
|
191
|
+
/// overflows into an UNCATCHABLE module trap after only ~300 `Vm::run_chunk` frames — the tish
|
|
192
|
+
/// function locals live in the VM operand stack, so the per-frame host cost is ~constant regardless
|
|
193
|
+
/// of the program, and the trap depth is a stable ~310 under default wasmtime. A far lower ceiling
|
|
194
|
+
/// keeps the frame-counting guard firing first (measured safe with margin below the trap), so deep
|
|
195
|
+
/// recursion raises a CATCHABLE `RangeError` on WASI too — matching interp/vm/native rather than
|
|
196
|
+
/// aborting the process. WASI programs are thus capped much shallower than native; that is inherent
|
|
197
|
+
/// to wasm's host-bounded call stack, not a tish choice.
|
|
198
|
+
#[cfg(target_arch = "wasm32")]
|
|
199
|
+
pub const DEFAULT_MAX_CALL_DEPTH: usize = 256;
|
|
200
|
+
|
|
201
|
+
// The recursion ceiling, lazily initialized from `TISH_MAX_CALL_DEPTH` (0 = uninitialized). A
|
|
202
|
+
// thread-local Cell (not OnceLock) so tests can override it per-thread without racing the
|
|
203
|
+
// process-wide env. Shared by the VM's guard and the native backend's boxed-call guard. #381
|
|
204
|
+
#[cfg(not(feature = "portable"))]
|
|
112
205
|
thread_local! {
|
|
113
|
-
|
|
114
|
-
// thread-local Cell (not OnceLock) so tests can override it per-thread without racing the
|
|
115
|
-
// process-wide env. Shared by the VM's guard and the native backend's boxed-call guard. #381
|
|
116
|
-
static MAX_CALL_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
|
|
206
|
+
static MAX_CALL_DEPTH: core::cell::Cell<usize> = const { core::cell::Cell::new(0) };
|
|
117
207
|
}
|
|
208
|
+
#[cfg(feature = "portable")]
|
|
209
|
+
static MAX_CALL_DEPTH: compat::SingleCore<core::cell::Cell<usize>> =
|
|
210
|
+
compat::SingleCore::new(core::cell::Cell::new(0));
|
|
118
211
|
|
|
119
212
|
/// The recursion ceiling for depth-counting guards: user-fn calls deeper than this raise a catchable
|
|
120
213
|
/// `RangeError` instead of overflowing the native stack. `TISH_MAX_CALL_DEPTH` overrides (default
|
|
@@ -125,11 +218,14 @@ pub fn max_call_depth() -> usize {
|
|
|
125
218
|
if v != 0 {
|
|
126
219
|
return v;
|
|
127
220
|
}
|
|
221
|
+
#[cfg(not(feature = "portable"))]
|
|
128
222
|
let init = std::env::var("TISH_MAX_CALL_DEPTH")
|
|
129
223
|
.ok()
|
|
130
224
|
.and_then(|s| s.parse::<usize>().ok())
|
|
131
225
|
.filter(|&n| n > 0)
|
|
132
226
|
.unwrap_or(DEFAULT_MAX_CALL_DEPTH);
|
|
227
|
+
#[cfg(feature = "portable")]
|
|
228
|
+
let init = DEFAULT_MAX_CALL_DEPTH;
|
|
133
229
|
c.set(init);
|
|
134
230
|
init
|
|
135
231
|
})
|
|
@@ -146,11 +242,11 @@ pub fn set_max_call_depth_for_test(n: usize) {
|
|
|
146
242
|
pub fn stack_overflow_error() -> Value {
|
|
147
243
|
let mut e = ObjectMap::default();
|
|
148
244
|
e.insert(
|
|
149
|
-
|
|
245
|
+
Arc::from("name"),
|
|
150
246
|
Value::String("RangeError".into()),
|
|
151
247
|
);
|
|
152
248
|
e.insert(
|
|
153
|
-
|
|
249
|
+
Arc::from("message"),
|
|
154
250
|
Value::String("Maximum call stack size exceeded".into()),
|
|
155
251
|
);
|
|
156
252
|
Value::object(e)
|
|
@@ -163,11 +259,11 @@ pub fn stack_overflow_error() -> Value {
|
|
|
163
259
|
pub fn not_a_function_error(message: impl Into<String>) -> Value {
|
|
164
260
|
let mut e = ObjectMap::default();
|
|
165
261
|
e.insert(
|
|
166
|
-
|
|
262
|
+
Arc::from("name"),
|
|
167
263
|
Value::String("TypeError".into()),
|
|
168
264
|
);
|
|
169
265
|
e.insert(
|
|
170
|
-
|
|
266
|
+
Arc::from("message"),
|
|
171
267
|
Value::String(message.into().into()),
|
|
172
268
|
);
|
|
173
269
|
Value::object(e)
|
|
@@ -179,11 +275,11 @@ pub fn not_a_function_error(message: impl Into<String>) -> Value {
|
|
|
179
275
|
pub fn type_error(message: impl Into<String>) -> Value {
|
|
180
276
|
let mut e = ObjectMap::default();
|
|
181
277
|
e.insert(
|
|
182
|
-
|
|
278
|
+
Arc::from("name"),
|
|
183
279
|
Value::String("TypeError".into()),
|
|
184
280
|
);
|
|
185
281
|
e.insert(
|
|
186
|
-
|
|
282
|
+
Arc::from("message"),
|
|
187
283
|
Value::String(message.into().into()),
|
|
188
284
|
);
|
|
189
285
|
Value::object(e)
|
|
@@ -207,11 +303,11 @@ pub fn str_to_array_index(s: &str) -> Option<usize> {
|
|
|
207
303
|
pub fn range_error(message: impl Into<String>) -> Value {
|
|
208
304
|
let mut e = ObjectMap::default();
|
|
209
305
|
e.insert(
|
|
210
|
-
|
|
306
|
+
Arc::from("name"),
|
|
211
307
|
Value::String("RangeError".into()),
|
|
212
308
|
);
|
|
213
309
|
e.insert(
|
|
214
|
-
|
|
310
|
+
Arc::from("message"),
|
|
215
311
|
Value::String(message.into().into()),
|
|
216
312
|
);
|
|
217
313
|
Value::object(e)
|
|
@@ -224,11 +320,11 @@ pub fn range_error(message: impl Into<String>) -> Value {
|
|
|
224
320
|
pub fn cannot_read_property_error(prop: &str) -> Value {
|
|
225
321
|
let mut e = ObjectMap::default();
|
|
226
322
|
e.insert(
|
|
227
|
-
|
|
323
|
+
Arc::from("name"),
|
|
228
324
|
Value::String("TypeError".into()),
|
|
229
325
|
);
|
|
230
326
|
e.insert(
|
|
231
|
-
|
|
327
|
+
Arc::from("message"),
|
|
232
328
|
Value::String(format!("Cannot read property '{}' of null", prop).into()),
|
|
233
329
|
);
|
|
234
330
|
Value::object(e)
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
//! `PropMap` on a cache miss). Phase 1b will attach the ordered key list to each shape so objects can
|
|
13
13
|
//! drop per-object key storage entirely (the butterfly representation).
|
|
14
14
|
|
|
15
|
-
use
|
|
15
|
+
use crate::compat::{AHashMap, Arc, OnceLock, RwLock};
|
|
16
|
+
#[cfg(feature = "portable")]
|
|
17
|
+
use alloc::{vec, vec::Vec};
|
|
16
18
|
|
|
17
19
|
/// Identity of an object's ordered key-set.
|
|
18
20
|
pub type ShapeId = u32;
|
|
@@ -31,7 +33,7 @@ pub const DICT_SHAPE: ShapeId = u32::MAX;
|
|
|
31
33
|
/// ahash is ~2–3× faster on short string keys. Random-seeded per process, so no HashDoS regression.
|
|
32
34
|
#[derive(Default)]
|
|
33
35
|
struct ShapeNode {
|
|
34
|
-
transitions:
|
|
36
|
+
transitions: AHashMap<Arc<str>, ShapeId>,
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
struct Registry {
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
//! URI encoding/decoding utilities.
|
|
2
2
|
|
|
3
|
+
#[cfg(feature = "portable")]
|
|
4
|
+
use alloc::{
|
|
5
|
+
format,
|
|
6
|
+
string::{String, ToString},
|
|
7
|
+
vec::Vec,
|
|
8
|
+
};
|
|
9
|
+
|
|
3
10
|
/// Percent-decode a string (for decodeURI).
|
|
4
11
|
/// Does NOT decode reserved URI characters: ; / ? : @ & = + $ , #
|
|
5
12
|
/// These are characters that encodeURI does not encode, so decodeURI won't decode them.
|
|
@@ -98,7 +105,7 @@ pub fn percent_decode_component(input: &str) -> Result<String, String> {
|
|
|
98
105
|
while i < bytes.len() {
|
|
99
106
|
if bytes[i] == b'%' {
|
|
100
107
|
if i + 2 < bytes.len() {
|
|
101
|
-
if let Some(b) =
|
|
108
|
+
if let Some(b) = core::str::from_utf8(&bytes[i + 1..i + 3])
|
|
102
109
|
.ok()
|
|
103
110
|
.and_then(|s| u8::from_str_radix(s, 16).ok())
|
|
104
111
|
{
|