@tishlang/tish-format 1.0.12 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +51 -0
- package/LICENSE +13 -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 +611 -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 +62 -0
- package/crates/tish/build.rs +21 -0
- package/crates/tish/src/cargo_native_registry.rs +32 -0
- package/crates/tish/src/cli_help.rs +576 -0
- package/crates/tish/src/main.rs +853 -0
- package/crates/tish/src/repl_completion.rs +199 -0
- package/crates/tish/tests/cargo_example_compile.rs +67 -0
- package/crates/tish/tests/error_source_location.rs +36 -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/fixtures/runtime_error_location.tish +5 -0
- package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
- package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
- package/crates/tish/tests/integration_test.rs +1406 -0
- package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
- package/crates/tish/tests/shortcircuit.rs +65 -0
- package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
- package/crates/tish/tests/tty_capability.rs +43 -0
- package/crates/tish_ast/Cargo.toml +9 -0
- package/crates/tish_ast/src/ast.rs +649 -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 +22 -0
- package/crates/tish_builtins/src/array.rs +803 -0
- package/crates/tish_builtins/src/collections.rs +481 -0
- package/crates/tish_builtins/src/construct.rs +199 -0
- package/crates/tish_builtins/src/date.rs +538 -0
- package/crates/tish_builtins/src/globals.rs +293 -0
- package/crates/tish_builtins/src/helpers.rs +35 -0
- package/crates/tish_builtins/src/iterator.rs +129 -0
- package/crates/tish_builtins/src/lib.rs +21 -0
- package/crates/tish_builtins/src/math.rs +89 -0
- package/crates/tish_builtins/src/number.rs +96 -0
- package/crates/tish_builtins/src/object.rs +36 -0
- package/crates/tish_builtins/src/string.rs +646 -0
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_builtins/src/typedarrays.rs +298 -0
- package/crates/tish_bytecode/Cargo.toml +17 -0
- package/crates/tish_bytecode/src/chunk.rs +164 -0
- package/crates/tish_bytecode/src/compiler.rs +2604 -0
- package/crates/tish_bytecode/src/encoding.rs +102 -0
- package/crates/tish_bytecode/src/lib.rs +20 -0
- package/crates/tish_bytecode/src/opcode.rs +185 -0
- package/crates/tish_bytecode/src/peephole.rs +189 -0
- package/crates/tish_bytecode/src/serialize.rs +193 -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 +27 -0
- package/crates/tish_compile/src/check.rs +774 -0
- package/crates/tish_compile/src/codegen.rs +7317 -0
- package/crates/tish_compile/src/infer.rs +1681 -0
- package/crates/tish_compile/src/lib.rs +206 -0
- package/crates/tish_compile/src/resolve.rs +1951 -0
- package/crates/tish_compile/src/types.rs +605 -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 +938 -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 +414 -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 +32 -0
- package/crates/tish_core/src/console_style.rs +170 -0
- package/crates/tish_core/src/json.rs +430 -0
- package/crates/tish_core/src/lib.rs +20 -0
- package/crates/tish_core/src/macros.rs +36 -0
- package/crates/tish_core/src/shape.rs +85 -0
- package/crates/tish_core/src/uri.rs +118 -0
- package/crates/tish_core/src/value.rs +1350 -0
- package/crates/tish_core/src/vmref.rs +183 -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 +130 -0
- package/crates/tish_cranelift/src/lower.rs +85 -0
- package/crates/tish_cranelift_runtime/Cargo.toml +26 -0
- package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
- package/crates/tish_eval/Cargo.toml +51 -0
- package/crates/tish_eval/src/eval.rs +4265 -0
- package/crates/tish_eval/src/http.rs +191 -0
- package/crates/tish_eval/src/lib.rs +99 -0
- package/crates/tish_eval/src/natives.rs +551 -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 +336 -0
- package/crates/tish_eval/src/value_convert.rs +117 -0
- package/crates/tish_ffi/Cargo.toml +26 -0
- package/crates/tish_ffi/src/lib.rs +518 -0
- package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
- package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
- package/crates/tish_ffi/tests/loader.rs +65 -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 +2157 -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 +1104 -0
- package/crates/tish_lexer/src/token.rs +170 -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 +281 -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 +564 -0
- package/crates/tish_lsp/src/main.rs +1459 -0
- package/crates/tish_native/Cargo.toml +16 -0
- package/crates/tish_native/src/build.rs +481 -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 +1046 -0
- package/crates/tish_parser/Cargo.toml +11 -0
- package/crates/tish_parser/src/lib.rs +386 -0
- package/crates/tish_parser/src/parser.rs +2726 -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 +3601 -0
- package/crates/tish_resolve/src/pos.rs +141 -0
- package/crates/tish_runtime/Cargo.toml +100 -0
- package/crates/tish_runtime/src/http.rs +1347 -0
- package/crates/tish_runtime/src/http_fetch.rs +492 -0
- package/crates/tish_runtime/src/http_hyper.rs +441 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +1447 -0
- package/crates/tish_runtime/src/native_promise.rs +15 -0
- package/crates/tish_runtime/src/promise.rs +558 -0
- package/crates/tish_runtime/src/promise_io.rs +38 -0
- package/crates/tish_runtime/src/timers.rs +172 -0
- package/crates/tish_runtime/src/tty.rs +226 -0
- package/crates/tish_runtime/src/ws.rs +778 -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 +692 -0
- package/crates/tish_ui/src/lib.rs +20 -0
- package/crates/tish_ui/src/runtime/hooks.rs +573 -0
- package/crates/tish_ui/src/runtime/mod.rs +183 -0
- package/crates/tish_vm/Cargo.toml +60 -0
- package/crates/tish_vm/src/jit.rs +1050 -0
- package/crates/tish_vm/src/lib.rs +41 -0
- package/crates/tish_vm/src/vm.rs +3536 -0
- package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -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 +428 -0
- package/crates/tish_wasm_runtime/Cargo.toml +37 -0
- package/crates/tish_wasm_runtime/src/gpu.rs +429 -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 +261 -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 +276 -0
- package/package.json +2 -2
- package/platform/darwin-arm64/tish-fmt +0 -0
- package/platform/darwin-x64/tish-fmt +0 -0
- package/platform/linux-arm64/tish-fmt +0 -0
- package/platform/linux-x64/tish-fmt +0 -0
- package/platform/win32-x64/tish-fmt.exe +0 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
//! Global builtin functions with signature (args: &[Value]) -> Value.
|
|
2
|
+
//!
|
|
3
|
+
//! Used by both tishlang_vm (bytecode) and tishlang_runtime (compiled). Keeps tishlang_vm
|
|
4
|
+
//! independent of tishlang_runtime.
|
|
5
|
+
|
|
6
|
+
use std::sync::Arc;
|
|
7
|
+
use tishlang_core::VmRef;
|
|
8
|
+
use tishlang_core::{percent_decode, percent_encode, ObjectMap, Value};
|
|
9
|
+
|
|
10
|
+
/// Boolean(value) - coerce to bool
|
|
11
|
+
pub fn boolean(args: &[Value]) -> Value {
|
|
12
|
+
let v = args.first().unwrap_or(&Value::Null);
|
|
13
|
+
Value::Bool(v.is_truthy())
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// decodeURI(str)
|
|
17
|
+
pub fn decode_uri(args: &[Value]) -> Value {
|
|
18
|
+
let s = args
|
|
19
|
+
.first()
|
|
20
|
+
.map(Value::to_display_string)
|
|
21
|
+
.unwrap_or_default();
|
|
22
|
+
Value::String(percent_decode(&s).unwrap_or(s).into())
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// encodeURI(str)
|
|
26
|
+
pub fn encode_uri(args: &[Value]) -> Value {
|
|
27
|
+
let s = args
|
|
28
|
+
.first()
|
|
29
|
+
.map(Value::to_display_string)
|
|
30
|
+
.unwrap_or_default();
|
|
31
|
+
Value::String(percent_encode(&s).into())
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// isFinite(value)
|
|
35
|
+
pub fn is_finite(args: &[Value]) -> Value {
|
|
36
|
+
Value::Bool(
|
|
37
|
+
args.first()
|
|
38
|
+
.is_some_and(|v| matches!(v, Value::Number(n) if n.is_finite())),
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// isNaN(value)
|
|
43
|
+
pub fn is_nan(args: &[Value]) -> Value {
|
|
44
|
+
Value::Bool(args.first().is_none_or(|v| {
|
|
45
|
+
matches!(v, Value::Number(n) if n.is_nan()) || !matches!(v, Value::Number(_))
|
|
46
|
+
}))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Array.isArray(value)
|
|
50
|
+
pub fn array_is_array(args: &[Value]) -> Value {
|
|
51
|
+
Value::Bool(matches!(args.first(), Some(Value::Array(_)) | Some(Value::NumberArray(_))))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// String(value) — convert value to string (JS String constructor as function).
|
|
55
|
+
/// Uses JS `ToString` (arrays comma-join recursively, objects → "[object Object]"),
|
|
56
|
+
/// not the inspect/display form.
|
|
57
|
+
pub fn string_convert(args: &[Value]) -> Value {
|
|
58
|
+
let v = args.first().unwrap_or(&Value::Null);
|
|
59
|
+
Value::String(v.to_js_string().into())
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// JS `Number(value)` coercion (ToNumber), issue #36. Numbers pass through; booleans →
|
|
63
|
+
/// 1/0; null → 0; strings parse (trimmed, with `0x`/`0b`/`0o` and `Infinity`, `""` → 0,
|
|
64
|
+
/// otherwise NaN); arrays/objects go via their string form (so `Number([5])` → 5,
|
|
65
|
+
/// `Number([])` → 0, objects → NaN).
|
|
66
|
+
pub fn number_convert(args: &[Value]) -> Value {
|
|
67
|
+
let v = args.first().unwrap_or(&Value::Null);
|
|
68
|
+
let n = match v {
|
|
69
|
+
Value::Number(n) => *n,
|
|
70
|
+
Value::Bool(b) => {
|
|
71
|
+
if *b {
|
|
72
|
+
1.0
|
|
73
|
+
} else {
|
|
74
|
+
0.0
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
Value::Null => 0.0,
|
|
78
|
+
Value::String(s) => parse_numeric_string(s),
|
|
79
|
+
other => parse_numeric_string(&other.to_js_string()),
|
|
80
|
+
};
|
|
81
|
+
Value::Number(n)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Parse a string as JS `Number` does: trimmed; `""` → 0; `0x`/`0o`/`0b` radix prefixes;
|
|
85
|
+
/// `Infinity`/`-Infinity`; plain decimal/float; anything else → NaN. Public so the
|
|
86
|
+
/// tree-walk interpreter (distinct `Value` type) shares the exact coercion.
|
|
87
|
+
pub fn parse_numeric_string(s: &str) -> f64 {
|
|
88
|
+
let t = s.trim();
|
|
89
|
+
if t.is_empty() {
|
|
90
|
+
return 0.0;
|
|
91
|
+
}
|
|
92
|
+
let radix = |rest: &str, r: u32| i64::from_str_radix(rest, r).map(|x| x as f64).unwrap_or(f64::NAN);
|
|
93
|
+
if let Some(rest) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) {
|
|
94
|
+
return radix(rest, 16);
|
|
95
|
+
}
|
|
96
|
+
if let Some(rest) = t.strip_prefix("0o").or_else(|| t.strip_prefix("0O")) {
|
|
97
|
+
return radix(rest, 8);
|
|
98
|
+
}
|
|
99
|
+
if let Some(rest) = t.strip_prefix("0b").or_else(|| t.strip_prefix("0B")) {
|
|
100
|
+
return radix(rest, 2);
|
|
101
|
+
}
|
|
102
|
+
match t {
|
|
103
|
+
"Infinity" | "+Infinity" => f64::INFINITY,
|
|
104
|
+
"-Infinity" => f64::NEG_INFINITY,
|
|
105
|
+
_ => t.parse::<f64>().unwrap_or(f64::NAN),
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// String.fromCharCode(...codes)
|
|
110
|
+
pub fn string_from_char_code(args: &[Value]) -> Value {
|
|
111
|
+
let s: String = args
|
|
112
|
+
.iter()
|
|
113
|
+
.filter_map(|v| match v {
|
|
114
|
+
Value::Number(n) => char::from_u32(*n as u32),
|
|
115
|
+
_ => None,
|
|
116
|
+
})
|
|
117
|
+
.collect();
|
|
118
|
+
Value::String(s.into())
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// Object.keys(obj)
|
|
122
|
+
pub fn object_keys(args: &[Value]) -> Value {
|
|
123
|
+
if let Some(Value::Object(obj)) = args.first() {
|
|
124
|
+
let obj_borrow = obj.borrow();
|
|
125
|
+
let keys: Vec<Value> = obj_borrow
|
|
126
|
+
.strings
|
|
127
|
+
.keys()
|
|
128
|
+
.map(|k| Value::String(tishlang_core::ArcStr::from(k.as_ref())))
|
|
129
|
+
.collect();
|
|
130
|
+
Value::Array(VmRef::new(keys))
|
|
131
|
+
} else {
|
|
132
|
+
Value::Array(VmRef::new(Vec::new()))
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// Object.values(obj)
|
|
137
|
+
pub fn object_values(args: &[Value]) -> Value {
|
|
138
|
+
if let Some(Value::Object(obj)) = args.first() {
|
|
139
|
+
let obj_borrow = obj.borrow();
|
|
140
|
+
let values: Vec<Value> = obj_borrow.strings.values().cloned().collect();
|
|
141
|
+
Value::Array(VmRef::new(values))
|
|
142
|
+
} else {
|
|
143
|
+
Value::Array(VmRef::new(Vec::new()))
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// Object.entries(obj)
|
|
148
|
+
pub fn object_entries(args: &[Value]) -> Value {
|
|
149
|
+
if let Some(Value::Object(obj)) = args.first() {
|
|
150
|
+
let obj_borrow = obj.borrow();
|
|
151
|
+
let entries: Vec<Value> = obj_borrow
|
|
152
|
+
.strings
|
|
153
|
+
.iter()
|
|
154
|
+
.map(|(k, v)| Value::Array(VmRef::new(vec![Value::String(tishlang_core::ArcStr::from(k.as_ref())), v.clone()])))
|
|
155
|
+
.collect();
|
|
156
|
+
Value::Array(VmRef::new(entries))
|
|
157
|
+
} else {
|
|
158
|
+
Value::Array(VmRef::new(Vec::new()))
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// Object.assign(target, ...sources)
|
|
163
|
+
pub fn object_assign(args: &[Value]) -> Value {
|
|
164
|
+
let target = match args.first() {
|
|
165
|
+
Some(Value::Object(obj)) => obj,
|
|
166
|
+
_ => return Value::Null,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
let additional_capacity: usize = args
|
|
170
|
+
.iter()
|
|
171
|
+
.skip(1)
|
|
172
|
+
.map(|source| {
|
|
173
|
+
if let Value::Object(src) = source {
|
|
174
|
+
src.borrow().len_entries()
|
|
175
|
+
} else {
|
|
176
|
+
0
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
.sum();
|
|
180
|
+
|
|
181
|
+
let mut target_mut = target.borrow_mut();
|
|
182
|
+
target_mut.strings.reserve(additional_capacity);
|
|
183
|
+
|
|
184
|
+
for source in args.iter().skip(1) {
|
|
185
|
+
if let Value::Object(src) = source {
|
|
186
|
+
let src_borrow = src.borrow();
|
|
187
|
+
for (k, v) in src_borrow.strings.iter() {
|
|
188
|
+
target_mut.strings.insert(Arc::clone(k), v.clone());
|
|
189
|
+
}
|
|
190
|
+
if let Some(ss) = &src_borrow.symbols {
|
|
191
|
+
if target_mut.symbols.is_none() {
|
|
192
|
+
target_mut.symbols = Some(Default::default());
|
|
193
|
+
}
|
|
194
|
+
let dst = target_mut.symbols.as_mut().unwrap();
|
|
195
|
+
dst.extend(ss.iter().map(|(id, v)| (*id, v.clone())));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
drop(target_mut);
|
|
200
|
+
Value::Object(target.clone())
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/// parseInt(string, radix?)
|
|
204
|
+
pub fn parse_int(args: &[Value]) -> Value {
|
|
205
|
+
let s = args
|
|
206
|
+
.first()
|
|
207
|
+
.map(Value::to_display_string)
|
|
208
|
+
.unwrap_or_default();
|
|
209
|
+
let s = s.trim();
|
|
210
|
+
let radix = args
|
|
211
|
+
.get(1)
|
|
212
|
+
.and_then(|v| match v {
|
|
213
|
+
Value::Number(n) => Some(*n as i32),
|
|
214
|
+
_ => None,
|
|
215
|
+
})
|
|
216
|
+
.unwrap_or(10);
|
|
217
|
+
|
|
218
|
+
if (2..=36).contains(&radix) {
|
|
219
|
+
let prefix: String = s
|
|
220
|
+
.chars()
|
|
221
|
+
.take_while(|c| *c == '-' || *c == '+' || c.is_digit(radix as u32))
|
|
222
|
+
.collect();
|
|
223
|
+
if let Ok(n) = i64::from_str_radix(&prefix, radix as u32) {
|
|
224
|
+
return Value::Number(n as f64);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
Value::Number(f64::NAN)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/// parseFloat(string)
|
|
231
|
+
pub fn parse_float(args: &[Value]) -> Value {
|
|
232
|
+
let s = args
|
|
233
|
+
.first()
|
|
234
|
+
.map(Value::to_display_string)
|
|
235
|
+
.unwrap_or_default();
|
|
236
|
+
Value::Number(js_parse_float(&s))
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// JS `parseFloat`: skips leading whitespace, then parses the **longest leading prefix**
|
|
240
|
+
/// that's a valid float (so `parseFloat("3.14abc")` → 3.14, `parseFloat("12.3.4")` → 12.3).
|
|
241
|
+
/// Handles `Infinity`/`-Infinity`; returns NaN when no numeric prefix is present. Issue #36.
|
|
242
|
+
/// Public so the tree-walk interpreter (distinct `Value`) shares the exact behavior.
|
|
243
|
+
pub fn js_parse_float(s: &str) -> f64 {
|
|
244
|
+
let t = s.trim_start();
|
|
245
|
+
if t.starts_with("Infinity") || t.starts_with("+Infinity") {
|
|
246
|
+
return f64::INFINITY;
|
|
247
|
+
}
|
|
248
|
+
if t.starts_with("-Infinity") {
|
|
249
|
+
return f64::NEG_INFINITY;
|
|
250
|
+
}
|
|
251
|
+
// Take a generous run of float-shaped chars, then shrink from the right until it parses.
|
|
252
|
+
let mut end = 0;
|
|
253
|
+
for (i, c) in t.char_indices() {
|
|
254
|
+
if c.is_ascii_digit() || matches!(c, '.' | '+' | '-' | 'e' | 'E') {
|
|
255
|
+
end = i + c.len_utf8();
|
|
256
|
+
} else {
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
let mut slice = &t[..end];
|
|
261
|
+
while !slice.is_empty() {
|
|
262
|
+
if let Ok(n) = slice.parse::<f64>() {
|
|
263
|
+
return n;
|
|
264
|
+
}
|
|
265
|
+
slice = &slice[..slice.len() - 1];
|
|
266
|
+
}
|
|
267
|
+
f64::NAN
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/// Object.fromEntries(entries)
|
|
271
|
+
pub fn object_from_entries(args: &[Value]) -> Value {
|
|
272
|
+
if let Some(Value::Array(entries)) = args.first() {
|
|
273
|
+
let entries_borrow = entries.borrow();
|
|
274
|
+
let mut obj: ObjectMap = ObjectMap::with_capacity(entries_borrow.len());
|
|
275
|
+
|
|
276
|
+
for entry in entries_borrow.iter() {
|
|
277
|
+
if let Value::Array(pair) = entry {
|
|
278
|
+
let pair_borrow = pair.borrow();
|
|
279
|
+
if pair_borrow.len() >= 2 {
|
|
280
|
+
let key: Arc<str> = match &pair_borrow[0] {
|
|
281
|
+
Value::String(s) => Arc::from(s.as_str()),
|
|
282
|
+
v => v.to_display_string().into(),
|
|
283
|
+
};
|
|
284
|
+
obj.insert(key, pair_borrow[1].clone());
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
Value::object(obj)
|
|
290
|
+
} else {
|
|
291
|
+
Value::empty_object()
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//! Common helper functions used across builtin implementations.
|
|
2
|
+
|
|
3
|
+
use std::sync::Arc;
|
|
4
|
+
use tishlang_core::{ObjectMap, Value};
|
|
5
|
+
|
|
6
|
+
/// Normalize an array index, handling negative indices.
|
|
7
|
+
/// Returns a valid index within bounds or the default value.
|
|
8
|
+
pub fn normalize_index(idx: &Value, len: i64, default: usize) -> usize {
|
|
9
|
+
match idx {
|
|
10
|
+
Value::Number(n) => {
|
|
11
|
+
let n = *n as i64;
|
|
12
|
+
if n < 0 {
|
|
13
|
+
(len + n).max(0) as usize
|
|
14
|
+
} else {
|
|
15
|
+
n.min(len) as usize
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
_ => default,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Create an error object with a single "error" field.
|
|
23
|
+
pub fn make_error_value(e: impl std::fmt::Display) -> Value {
|
|
24
|
+
let mut obj = ObjectMap::with_capacity(1);
|
|
25
|
+
obj.insert(Arc::from("error"), Value::String(e.to_string().into()));
|
|
26
|
+
Value::object(obj)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Extract a number from a Value, returning None for non-numbers.
|
|
30
|
+
pub fn extract_num(v: Option<&Value>) -> Option<f64> {
|
|
31
|
+
v.and_then(|val| match val {
|
|
32
|
+
Value::Number(n) => Some(*n),
|
|
33
|
+
_ => None,
|
|
34
|
+
})
|
|
35
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
//! JS-style iterator objects for `Map`/`Set` (`.values()` / `.keys()` / `.entries()`).
|
|
2
|
+
//!
|
|
3
|
+
//! Like [`crate::collections`] and [`crate::date`], an iterator is a plain `Value::Object`
|
|
4
|
+
//! whose `next` method is a per-instance `Value::native` closure capturing a snapshot of the
|
|
5
|
+
//! items plus a shared position cell — so the one implementation works on every backend with
|
|
6
|
+
//! no new `Value` variant. `next()` returns `{ value, done }`; once exhausted it keeps
|
|
7
|
+
//! returning `{ value: null, done: true }`.
|
|
8
|
+
//!
|
|
9
|
+
//! The runtimes drive iteration through [`tishlang_core::drain_iterator`], which calls `next()`
|
|
10
|
+
//! until `done` — that's how these objects work in `for…of`, spread, and `Array.from`. The
|
|
11
|
+
//! snapshot is taken when the iterator is built (`.values()` is called), matching how the old
|
|
12
|
+
//! array-returning version behaved; mid-iteration mutation of the source is not reflected (a
|
|
13
|
+
//! live iterator is a follow-up).
|
|
14
|
+
|
|
15
|
+
use std::sync::Arc;
|
|
16
|
+
|
|
17
|
+
use tishlang_core::{ObjectMap, Value, VmRef};
|
|
18
|
+
|
|
19
|
+
/// Build a single-use iterator object over `items`: `{ next() -> { value, done } }`.
|
|
20
|
+
pub fn array_iterator(items: Vec<Value>) -> Value {
|
|
21
|
+
let items: VmRef<Vec<Value>> = VmRef::new(items);
|
|
22
|
+
let pos: VmRef<usize> = VmRef::new(0);
|
|
23
|
+
|
|
24
|
+
let mut m = ObjectMap::default();
|
|
25
|
+
{
|
|
26
|
+
// Bulk-drain fast path for `for…of` / spread: return all REMAINING items (from the current
|
|
27
|
+
// position) as one array and exhaust the iterator — equivalent to calling `next()` until
|
|
28
|
+
// `done`, but with no per-element `{ value, done }` allocation. `drain_iterator` prefers this
|
|
29
|
+
// when present; manual `.next()` and partial consumption still work (it respects `pos`).
|
|
30
|
+
let items = items.clone();
|
|
31
|
+
let pos = pos.clone();
|
|
32
|
+
m.insert(
|
|
33
|
+
Arc::from("__drain__"),
|
|
34
|
+
Value::native(move |_args: &[Value]| {
|
|
35
|
+
let i = *pos.borrow();
|
|
36
|
+
let b = items.borrow();
|
|
37
|
+
let rest: Vec<Value> = if i < b.len() { b[i..].to_vec() } else { Vec::new() };
|
|
38
|
+
let len = b.len();
|
|
39
|
+
drop(b);
|
|
40
|
+
*pos.borrow_mut() = len;
|
|
41
|
+
Value::Array(VmRef::new(rest))
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
{
|
|
46
|
+
let items = items.clone();
|
|
47
|
+
let pos = pos.clone();
|
|
48
|
+
m.insert(
|
|
49
|
+
Arc::from("next"),
|
|
50
|
+
Value::native(move |_args: &[Value]| {
|
|
51
|
+
let i = *pos.borrow();
|
|
52
|
+
// Read the element (or note exhaustion) without holding the items borrow
|
|
53
|
+
// across the position write — two different `VmRef`s, but keep it tidy.
|
|
54
|
+
let (value, done) = {
|
|
55
|
+
let b = items.borrow();
|
|
56
|
+
if i < b.len() {
|
|
57
|
+
(b[i].clone(), false)
|
|
58
|
+
} else {
|
|
59
|
+
(Value::Null, true)
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
if !done {
|
|
63
|
+
*pos.borrow_mut() = i + 1;
|
|
64
|
+
}
|
|
65
|
+
let mut r = ObjectMap::default();
|
|
66
|
+
r.insert(Arc::from("value"), value);
|
|
67
|
+
r.insert(Arc::from("done"), Value::Bool(done));
|
|
68
|
+
Value::object(r)
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
Value::object(m)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#[cfg(test)]
|
|
76
|
+
mod tests {
|
|
77
|
+
use super::*;
|
|
78
|
+
|
|
79
|
+
fn call(obj: &Value, name: &str) -> Value {
|
|
80
|
+
let Value::Object(o) = obj else { panic!("not an object") };
|
|
81
|
+
let m = o.borrow().strings.get(name).cloned().expect("method missing");
|
|
82
|
+
let Value::Function(f) = m else { panic!("{name} is not callable") };
|
|
83
|
+
f.call(&[])
|
|
84
|
+
}
|
|
85
|
+
fn get(obj: &Value, key: &str) -> Value {
|
|
86
|
+
let Value::Object(o) = obj else { return Value::Null };
|
|
87
|
+
o.borrow().strings.get(key).cloned().unwrap_or(Value::Null)
|
|
88
|
+
}
|
|
89
|
+
fn num(v: &Value) -> f64 {
|
|
90
|
+
match v {
|
|
91
|
+
Value::Number(n) => *n,
|
|
92
|
+
_ => f64::NAN,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
fn nums(v: &Value) -> Vec<f64> {
|
|
96
|
+
match v {
|
|
97
|
+
Value::Array(a) => a.borrow().iter().map(num).collect(),
|
|
98
|
+
_ => vec![],
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#[test]
|
|
103
|
+
fn next_yields_each_then_done() {
|
|
104
|
+
let it = array_iterator(vec![Value::Number(1.0), Value::Number(2.0)]);
|
|
105
|
+
let r1 = call(&it, "next");
|
|
106
|
+
assert_eq!(num(&get(&r1, "value")), 1.0);
|
|
107
|
+
assert!(!get(&r1, "done").is_truthy());
|
|
108
|
+
assert_eq!(num(&get(&call(&it, "next"), "value")), 2.0);
|
|
109
|
+
// exhausted — and it keeps reporting done.
|
|
110
|
+
assert!(get(&call(&it, "next"), "done").is_truthy());
|
|
111
|
+
assert!(get(&call(&it, "next"), "done").is_truthy());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
#[test]
|
|
115
|
+
fn drain_returns_all_and_exhausts() {
|
|
116
|
+
let it = array_iterator(vec![Value::Number(1.0), Value::Number(2.0), Value::Number(3.0)]);
|
|
117
|
+
assert_eq!(nums(&call(&it, "__drain__")), vec![1.0, 2.0, 3.0]);
|
|
118
|
+
// draining exhausts the iterator (matches calling next() until done).
|
|
119
|
+
assert!(get(&call(&it, "next"), "done").is_truthy());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#[test]
|
|
123
|
+
fn drain_respects_current_position() {
|
|
124
|
+
let it = array_iterator(vec![Value::Number(1.0), Value::Number(2.0), Value::Number(3.0)]);
|
|
125
|
+
call(&it, "next"); // consume the first
|
|
126
|
+
assert_eq!(nums(&call(&it, "__drain__")), vec![2.0, 3.0]);
|
|
127
|
+
assert_eq!(nums(&call(&it, "__drain__")), Vec::<f64>::new()); // already drained
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//! Shared builtin implementations for Tish.
|
|
2
|
+
//!
|
|
3
|
+
//! Used by the compiled runtime (tishlang_runtime) and bytecode VM (tishlang_vm). The
|
|
4
|
+
//! interpreter (tishlang_eval) implements builtins inline due to different Value
|
|
5
|
+
//! and native signatures.
|
|
6
|
+
|
|
7
|
+
pub mod array;
|
|
8
|
+
pub mod collections;
|
|
9
|
+
pub mod construct;
|
|
10
|
+
pub mod date;
|
|
11
|
+
pub mod globals;
|
|
12
|
+
pub mod helpers;
|
|
13
|
+
pub mod iterator;
|
|
14
|
+
pub mod math;
|
|
15
|
+
pub mod number;
|
|
16
|
+
pub mod object;
|
|
17
|
+
pub mod string;
|
|
18
|
+
pub mod symbol;
|
|
19
|
+
pub mod typedarrays;
|
|
20
|
+
|
|
21
|
+
pub use tishlang_core::Value;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
//! Math builtin functions.
|
|
2
|
+
|
|
3
|
+
use crate::helpers::extract_num;
|
|
4
|
+
use tishlang_core::Value;
|
|
5
|
+
|
|
6
|
+
macro_rules! math_unary {
|
|
7
|
+
($name:ident, $op:ident) => {
|
|
8
|
+
pub fn $name(args: &[Value]) -> Value {
|
|
9
|
+
let n = extract_num(args.first()).unwrap_or(f64::NAN);
|
|
10
|
+
Value::Number(n.$op())
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
math_unary!(abs, abs);
|
|
16
|
+
math_unary!(sqrt, sqrt);
|
|
17
|
+
math_unary!(floor, floor);
|
|
18
|
+
math_unary!(ceil, ceil);
|
|
19
|
+
math_unary!(round, round);
|
|
20
|
+
math_unary!(sin, sin);
|
|
21
|
+
math_unary!(cos, cos);
|
|
22
|
+
math_unary!(tan, tan);
|
|
23
|
+
math_unary!(asin, asin);
|
|
24
|
+
math_unary!(acos, acos);
|
|
25
|
+
math_unary!(atan, atan);
|
|
26
|
+
math_unary!(log, ln);
|
|
27
|
+
math_unary!(log10, log10);
|
|
28
|
+
math_unary!(log2, log2);
|
|
29
|
+
math_unary!(exp, exp);
|
|
30
|
+
math_unary!(trunc, trunc);
|
|
31
|
+
math_unary!(cbrt, cbrt);
|
|
32
|
+
|
|
33
|
+
pub fn min(args: &[Value]) -> Value {
|
|
34
|
+
let n = args
|
|
35
|
+
.iter()
|
|
36
|
+
.filter_map(|v| extract_num(Some(v)))
|
|
37
|
+
.fold(f64::INFINITY, f64::min);
|
|
38
|
+
Value::Number(if n == f64::INFINITY { f64::NAN } else { n })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
pub fn max(args: &[Value]) -> Value {
|
|
42
|
+
let n = args
|
|
43
|
+
.iter()
|
|
44
|
+
.filter_map(|v| extract_num(Some(v)))
|
|
45
|
+
.fold(f64::NEG_INFINITY, f64::max);
|
|
46
|
+
Value::Number(if n == f64::NEG_INFINITY { f64::NAN } else { n })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pub fn pow(args: &[Value]) -> Value {
|
|
50
|
+
let base = extract_num(args.first()).unwrap_or(f64::NAN);
|
|
51
|
+
let exp = extract_num(args.get(1)).unwrap_or(f64::NAN);
|
|
52
|
+
Value::Number(base.powf(exp))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
pub fn random(_args: &[Value]) -> Value {
|
|
56
|
+
Value::Number(rand::random::<f64>())
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
pub fn sign(args: &[Value]) -> Value {
|
|
60
|
+
let n = extract_num(args.first()).unwrap_or(f64::NAN);
|
|
61
|
+
Value::Number(if n.is_nan() {
|
|
62
|
+
f64::NAN
|
|
63
|
+
} else if n > 0.0 {
|
|
64
|
+
1.0
|
|
65
|
+
} else if n < 0.0 {
|
|
66
|
+
-1.0
|
|
67
|
+
} else {
|
|
68
|
+
0.0
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pub fn atan2(args: &[Value]) -> Value {
|
|
73
|
+
let y = extract_num(args.first()).unwrap_or(f64::NAN);
|
|
74
|
+
let x = extract_num(args.get(1)).unwrap_or(f64::NAN);
|
|
75
|
+
Value::Number(y.atan2(x))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
pub fn hypot(args: &[Value]) -> Value {
|
|
79
|
+
let x = extract_num(args.first()).unwrap_or(0.0);
|
|
80
|
+
let y = extract_num(args.get(1)).unwrap_or(0.0);
|
|
81
|
+
Value::Number(x.hypot(y))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// ES6 `Math.imul`: 32-bit integer multiply (used by xmur3 PRNG in juke-cards).
|
|
85
|
+
pub fn imul(args: &[Value]) -> Value {
|
|
86
|
+
let a = extract_num(args.first()).unwrap_or(0.0) as i32;
|
|
87
|
+
let b = extract_num(args.get(1)).unwrap_or(0.0) as i32;
|
|
88
|
+
Value::Number(a.wrapping_mul(b) as f64)
|
|
89
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
//! Number builtin methods.
|
|
2
|
+
//!
|
|
3
|
+
//! Canonical, backend-agnostic implementations of `Number.prototype` methods.
|
|
4
|
+
//! The VM (`get_member`), the Rust runtime (`tishlang_runtime::number_to_fixed`),
|
|
5
|
+
//! and the tree-walk interpreter all route through here so every backend produces
|
|
6
|
+
//! byte-identical output — see `tish/docs/full-backend-parity-plan.md` (Workstream A).
|
|
7
|
+
|
|
8
|
+
use tishlang_core::Value;
|
|
9
|
+
|
|
10
|
+
/// `Number.prototype.toFixed(digits)` — ECMA-262 §21.1.3.3.
|
|
11
|
+
///
|
|
12
|
+
/// Formats the number using fixed-point notation with `digits` fraction digits.
|
|
13
|
+
/// `digits` is clamped to 0–20 (ECMA range) and defaults to 0 when absent/non-numeric,
|
|
14
|
+
/// matching `(1.5).toFixed() === "2"`. A non-number receiver yields `"NaN"`.
|
|
15
|
+
pub fn to_fixed(n: &Value, digits: &Value) -> Value {
|
|
16
|
+
let num = match n {
|
|
17
|
+
Value::Number(x) => *x,
|
|
18
|
+
_ => f64::NAN,
|
|
19
|
+
};
|
|
20
|
+
let d = match digits {
|
|
21
|
+
Value::Number(x) => (*x as i32).clamp(0, 20),
|
|
22
|
+
_ => 0,
|
|
23
|
+
} as usize;
|
|
24
|
+
Value::String(format!("{:.*}", d, num).into())
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// `Number.prototype.toString([radix])` — ECMA-262 §21.1.3.6.
|
|
28
|
+
///
|
|
29
|
+
/// Radix defaults to 10 (canonical JS number formatting). For radix 2–36 the value is
|
|
30
|
+
/// rendered in that base: sign, integer part via repeated division, and a fractional part
|
|
31
|
+
/// (bounded to 52 digits, like V8). NaN / ±Infinity stringify as in base 10 regardless of
|
|
32
|
+
/// radix. An out-of-range radix yields `"RadixError"` so the caller can surface a RangeError.
|
|
33
|
+
pub fn to_string(n: &Value, radix: &Value) -> Value {
|
|
34
|
+
let num = match n {
|
|
35
|
+
Value::Number(x) => *x,
|
|
36
|
+
_ => f64::NAN,
|
|
37
|
+
};
|
|
38
|
+
let r = match radix {
|
|
39
|
+
Value::Number(x) => *x as i64,
|
|
40
|
+
_ => 10,
|
|
41
|
+
};
|
|
42
|
+
match number_to_string_radix(num, r) {
|
|
43
|
+
Some(s) => Value::String(s.into()),
|
|
44
|
+
None => Value::String("RadixError".into()),
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Backend-agnostic core of `Number.prototype.toString`: works on a plain `f64` so the
|
|
49
|
+
/// tree-walk interpreter (whose `Value` is a distinct type) can share the exact same
|
|
50
|
+
/// formatting. Returns `None` for an out-of-range radix (caller surfaces a RangeError).
|
|
51
|
+
pub fn number_to_string_radix(num: f64, radix: i64) -> Option<String> {
|
|
52
|
+
if !(2..=36).contains(&radix) {
|
|
53
|
+
return None;
|
|
54
|
+
}
|
|
55
|
+
if radix == 10 || num.is_nan() || num.is_infinite() {
|
|
56
|
+
return Some(tishlang_core::js_number_to_string(num));
|
|
57
|
+
}
|
|
58
|
+
let radix = radix as u32;
|
|
59
|
+
const DIGITS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";
|
|
60
|
+
let negative = num < 0.0;
|
|
61
|
+
let value = num.abs();
|
|
62
|
+
let int_part = value.trunc();
|
|
63
|
+
let mut frac = value - int_part;
|
|
64
|
+
|
|
65
|
+
// Integer part: collect base-`radix` digits least-significant first, then reverse.
|
|
66
|
+
let mut int_digits = Vec::new();
|
|
67
|
+
let mut i = int_part;
|
|
68
|
+
if i == 0.0 {
|
|
69
|
+
int_digits.push(b'0');
|
|
70
|
+
}
|
|
71
|
+
while i >= 1.0 {
|
|
72
|
+
let d = (i % radix as f64) as usize;
|
|
73
|
+
int_digits.push(DIGITS[d]);
|
|
74
|
+
i = (i / radix as f64).trunc();
|
|
75
|
+
}
|
|
76
|
+
int_digits.reverse();
|
|
77
|
+
let mut out = String::with_capacity(int_digits.len() + 2);
|
|
78
|
+
if negative {
|
|
79
|
+
out.push('-');
|
|
80
|
+
}
|
|
81
|
+
out.push_str(std::str::from_utf8(&int_digits).unwrap());
|
|
82
|
+
|
|
83
|
+
// Fractional part: multiply-by-radix, emitting the integer overflow each step.
|
|
84
|
+
if frac > 0.0 {
|
|
85
|
+
out.push('.');
|
|
86
|
+
let mut count = 0;
|
|
87
|
+
while frac > 0.0 && count < 52 {
|
|
88
|
+
frac *= radix as f64;
|
|
89
|
+
let d = frac.trunc() as usize;
|
|
90
|
+
out.push(DIGITS[d] as char);
|
|
91
|
+
frac -= frac.trunc();
|
|
92
|
+
count += 1;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
Some(out)
|
|
96
|
+
}
|