@tishlang/tish 1.4.2 → 1.6.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/bin/tish +0 -0
- package/crates/tish/Cargo.toml +2 -2
- package/crates/tish/src/cli_help.rs +504 -0
- package/crates/tish/src/main.rs +76 -90
- package/crates/tish/src/repl_completion.rs +1 -1
- package/crates/tish/tests/cargo_example_compile.rs +65 -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 +48 -0
- package/crates/tish_build_utils/src/lib.rs +204 -1
- package/crates/tish_builtins/src/string.rs +248 -0
- package/crates/tish_bytecode/Cargo.toml +1 -0
- package/crates/tish_bytecode/src/compiler.rs +289 -66
- package/crates/tish_bytecode/src/opcode.rs +13 -3
- package/crates/tish_bytecode/src/peephole.rs +21 -16
- package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +277 -93
- package/crates/tish_compile/src/lib.rs +7 -4
- package/crates/tish_compile/src/resolve.rs +418 -40
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +1 -0
- package/crates/tish_core/src/value.rs +1 -0
- package/crates/tish_eval/src/eval.rs +49 -1
- package/crates/tish_eval/src/lib.rs +1 -1
- package/crates/tish_native/src/build.rs +86 -17
- package/crates/tish_native/src/lib.rs +36 -16
- package/crates/tish_runtime/src/lib.rs +4 -0
- package/crates/tish_vm/src/lib.rs +1 -1
- package/crates/tish_vm/src/vm.rs +165 -19
- package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -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
|
@@ -512,9 +512,18 @@ impl Evaluator {
|
|
|
512
512
|
|
|
513
513
|
/// Load and evaluate a module, returning its exports object. Uses cache.
|
|
514
514
|
fn load_module(&mut self, from: &str) -> Result<Value, EvalError> {
|
|
515
|
+
if from.starts_with("cargo:") {
|
|
516
|
+
return Err(EvalError::Error(
|
|
517
|
+
"cargo:… imports are only supported by `tish build` with the Rust native backend.".into(),
|
|
518
|
+
));
|
|
519
|
+
}
|
|
515
520
|
if from.starts_with("tish:") {
|
|
516
521
|
return self.load_builtin_module(from);
|
|
517
522
|
}
|
|
523
|
+
// Scoped native modules (e.g. `@tishlang/waterui`) registered via `TishNativeModule::virtual_builtin_modules`.
|
|
524
|
+
if self.virtual_builtins.borrow().get(from).is_some() {
|
|
525
|
+
return self.load_builtin_module(from);
|
|
526
|
+
}
|
|
518
527
|
let dir = self.current_dir.borrow().clone().ok_or_else(|| {
|
|
519
528
|
EvalError::Error("Cannot resolve imports: no current file directory (use run_file)".to_string())
|
|
520
529
|
})?;
|
|
@@ -595,6 +604,11 @@ impl Evaluator {
|
|
|
595
604
|
|
|
596
605
|
/// Load built-in module (tish:fs, tish:http, tish:process, …) or a virtual module from native crates.
|
|
597
606
|
fn load_builtin_module(&self, spec: &str) -> Result<Value, EvalError> {
|
|
607
|
+
if spec.starts_with("cargo:") {
|
|
608
|
+
return Err(EvalError::Error(
|
|
609
|
+
"cargo:… imports are only supported when compiling with `tish build` and the Rust native backend. They link Cargo crates via package.json tish.rustDependencies and a generated native wrapper — not the interpreter or VM.".into(),
|
|
610
|
+
));
|
|
611
|
+
}
|
|
598
612
|
if let Some(v) = self.virtual_builtins.borrow().get(spec) {
|
|
599
613
|
return Ok(v.clone());
|
|
600
614
|
}
|
|
@@ -1203,6 +1217,9 @@ impl Evaluator {
|
|
|
1203
1217
|
});
|
|
1204
1218
|
return Ok(Value::Number(found.unwrap_or(-1.0)));
|
|
1205
1219
|
}
|
|
1220
|
+
"lastIndexOf" => {
|
|
1221
|
+
return Ok(Self::string_last_index_of_eval(&arg_vals, s));
|
|
1222
|
+
}
|
|
1206
1223
|
"includes" => {
|
|
1207
1224
|
let search = match arg_vals.first() {
|
|
1208
1225
|
Some(Value::String(ss)) => ss.as_ref(),
|
|
@@ -1474,7 +1491,13 @@ impl Evaluator {
|
|
|
1474
1491
|
}
|
|
1475
1492
|
}
|
|
1476
1493
|
|
|
1477
|
-
// Fall through to normal function call
|
|
1494
|
+
// Fall through to normal function call. `get_prop` only implements `length` on
|
|
1495
|
+
// strings, so method calls would otherwise become `call_func(Null)` → Not a function.
|
|
1496
|
+
if let Value::String(s) = &obj {
|
|
1497
|
+
if method_name.as_ref() == "lastIndexOf" {
|
|
1498
|
+
return Ok(Self::string_last_index_of_eval(&arg_vals, s));
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1478
1501
|
let f = self.get_prop(&obj, method_name).map_err(EvalError::Error)?;
|
|
1479
1502
|
return self.call_func(&f, &arg_vals);
|
|
1480
1503
|
}
|
|
@@ -2638,6 +2661,31 @@ impl Evaluator {
|
|
|
2638
2661
|
Self::bind_destruct_pattern_scoped(&self.scope, pattern, value, mutable)
|
|
2639
2662
|
}
|
|
2640
2663
|
|
|
2664
|
+
/// `String.prototype.lastIndexOf` (interpreter). Kept as a helper so dispatch cannot fall
|
|
2665
|
+
/// through to [`Self::get_prop`] + [`Self::call_func`] for string receivers.
|
|
2666
|
+
fn string_last_index_of_eval(arg_vals: &[Value], receiver: &Arc<str>) -> Value {
|
|
2667
|
+
let search = match arg_vals.first() {
|
|
2668
|
+
Some(Value::String(ss)) => ss.as_ref(),
|
|
2669
|
+
_ => return Value::Number(-1.0),
|
|
2670
|
+
};
|
|
2671
|
+
let position_core: tishlang_core::Value = match arg_vals.get(1) {
|
|
2672
|
+
None => tishlang_core::Value::Number(f64::INFINITY),
|
|
2673
|
+
Some(Value::Null) => tishlang_core::Value::Null,
|
|
2674
|
+
Some(Value::Number(n)) => tishlang_core::Value::Number(*n),
|
|
2675
|
+
Some(Value::Bool(b)) => tishlang_core::Value::Bool(*b),
|
|
2676
|
+
Some(_) => tishlang_core::Value::Number(0.0),
|
|
2677
|
+
};
|
|
2678
|
+
let out = tishlang_builtins::string::last_index_of_str(
|
|
2679
|
+
receiver.as_ref(),
|
|
2680
|
+
search,
|
|
2681
|
+
&position_core,
|
|
2682
|
+
);
|
|
2683
|
+
match out {
|
|
2684
|
+
tishlang_core::Value::Number(n) => Value::Number(n),
|
|
2685
|
+
_ => Value::Number(-1.0),
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2641
2689
|
fn get_prop(&self, obj: &Value, key: &str) -> Result<Value, String> {
|
|
2642
2690
|
match obj {
|
|
2643
2691
|
Value::Object(map) => Ok(map.borrow().get(key).cloned().unwrap_or(Value::Null)),
|
|
@@ -5,11 +5,47 @@ use std::path::Path;
|
|
|
5
5
|
|
|
6
6
|
use tishlang_compile::ResolvedNativeModule;
|
|
7
7
|
|
|
8
|
+
/// `tishlang_runtime` Cargo feature names (subset of CLI / compile feature names).
|
|
9
|
+
const RUNTIME_CARGO_FEATURES: &[&str] = &["http", "fs", "process", "regex", "ws"];
|
|
10
|
+
|
|
11
|
+
/// Map CLI/compile features to flags passed to `tishlang_runtime` in the temp crate's Cargo.toml.
|
|
12
|
+
/// `full` enables every optional runtime capability (matches `tish build --feature full` / LANGUAGE.md).
|
|
13
|
+
fn runtime_features_for_cargo(features: &[String]) -> Vec<String> {
|
|
14
|
+
let mut out = Vec::new();
|
|
15
|
+
for f in features {
|
|
16
|
+
if f == "full" {
|
|
17
|
+
for name in RUNTIME_CARGO_FEATURES {
|
|
18
|
+
if !out.iter().any(|x: &String| x == *name) {
|
|
19
|
+
out.push((*name).to_string());
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if RUNTIME_CARGO_FEATURES.contains(&f.as_str()) && !out.contains(f) {
|
|
25
|
+
out.push(f.clone());
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
out
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Inject `mod generated_native;` after the crate attribute so the binary crate can call `crate::generated_native::…`.
|
|
32
|
+
fn inject_generated_native_mod(rust_code: &str) -> String {
|
|
33
|
+
if let Some(pos) = rust_code.find("\n\n") {
|
|
34
|
+
let (a, b) = rust_code.split_at(pos + 2);
|
|
35
|
+
format!("{}mod generated_native;\n{}", a, b)
|
|
36
|
+
} else {
|
|
37
|
+
format!("{}\n\nmod generated_native;\n", rust_code)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
8
41
|
pub fn build_via_cargo(
|
|
9
42
|
rust_code: &str,
|
|
10
43
|
native_modules: Vec<ResolvedNativeModule>,
|
|
11
44
|
output_path: &Path,
|
|
12
45
|
features: &[String],
|
|
46
|
+
extra_dependencies_toml: &str,
|
|
47
|
+
generated_native_rs: Option<&str>,
|
|
48
|
+
project_root: Option<&Path>,
|
|
13
49
|
) -> Result<(), String> {
|
|
14
50
|
let out_name = output_path
|
|
15
51
|
.file_stem()
|
|
@@ -17,17 +53,14 @@ pub fn build_via_cargo(
|
|
|
17
53
|
.unwrap_or("tish_out");
|
|
18
54
|
let build_dir = tishlang_build_utils::create_build_dir("tish_build", out_name)?;
|
|
19
55
|
|
|
20
|
-
let runtime_path = tishlang_build_utils::
|
|
56
|
+
let runtime_path = tishlang_build_utils::find_runtime_path_for_project(project_root)?;
|
|
21
57
|
|
|
22
|
-
let runtime_features
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
.map(String::as_str)
|
|
26
|
-
.collect();
|
|
27
|
-
let features_str = if runtime_features.is_empty() {
|
|
58
|
+
let runtime_features = runtime_features_for_cargo(features);
|
|
59
|
+
let runtime_refs: Vec<&str> = runtime_features.iter().map(String::as_str).collect();
|
|
60
|
+
let features_str = if runtime_refs.is_empty() {
|
|
28
61
|
String::new()
|
|
29
62
|
} else {
|
|
30
|
-
format!(", features = {:?}",
|
|
63
|
+
format!(", features = {:?}", runtime_refs)
|
|
31
64
|
};
|
|
32
65
|
|
|
33
66
|
let needs_tokio = rust_code.contains("#[tokio::main]");
|
|
@@ -39,12 +72,28 @@ pub fn build_via_cargo(
|
|
|
39
72
|
|
|
40
73
|
let native_deps: String = native_modules
|
|
41
74
|
.iter()
|
|
75
|
+
.filter(|m| m.use_path_dependency)
|
|
42
76
|
.map(|m| {
|
|
43
77
|
let path = m.crate_path.display().to_string().replace('\\', "/");
|
|
44
78
|
format!("{} = {{ path = {:?} }}\n", m.package_name, path)
|
|
45
79
|
})
|
|
46
80
|
.collect();
|
|
47
81
|
|
|
82
|
+
let mut more_deps = String::new();
|
|
83
|
+
more_deps.push_str(&tokio_dep);
|
|
84
|
+
if !native_deps.is_empty() {
|
|
85
|
+
more_deps.push_str(&format!("\n{}", native_deps));
|
|
86
|
+
}
|
|
87
|
+
if !extra_dependencies_toml.trim().is_empty() {
|
|
88
|
+
more_deps.push_str(&format!("\n{}", extra_dependencies_toml));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let rust_main = if generated_native_rs.is_some() {
|
|
92
|
+
inject_generated_native_mod(rust_code)
|
|
93
|
+
} else {
|
|
94
|
+
rust_code.to_string()
|
|
95
|
+
};
|
|
96
|
+
|
|
48
97
|
let tish_ui_path = std::path::Path::new(&runtime_path)
|
|
49
98
|
.parent()
|
|
50
99
|
.ok_or_else(|| "invalid tishlang_runtime path (no parent)".to_string())?
|
|
@@ -76,23 +125,22 @@ codegen-units = 1
|
|
|
76
125
|
lto = "thin"
|
|
77
126
|
|
|
78
127
|
[dependencies]
|
|
79
|
-
tishlang_runtime = {{ path = {:?}{} }}
|
|
80
|
-
"#,
|
|
128
|
+
tishlang_runtime = {{ path = {:?}{} }}
|
|
129
|
+
{}{}"#,
|
|
81
130
|
out_name,
|
|
82
131
|
runtime_path,
|
|
83
132
|
features_str,
|
|
84
|
-
|
|
85
|
-
if native_deps.is_empty() {
|
|
86
|
-
String::new()
|
|
87
|
-
} else {
|
|
88
|
-
format!("\n{}", native_deps)
|
|
89
|
-
},
|
|
133
|
+
more_deps,
|
|
90
134
|
ui_dep
|
|
91
135
|
);
|
|
92
136
|
|
|
93
137
|
fs::write(build_dir.join("Cargo.toml"), cargo_toml)
|
|
94
138
|
.map_err(|e| format!("Cannot write Cargo.toml: {}", e))?;
|
|
95
|
-
|
|
139
|
+
if let Some(gen) = generated_native_rs {
|
|
140
|
+
fs::write(build_dir.join("src/generated_native.rs"), gen)
|
|
141
|
+
.map_err(|e| format!("Cannot write generated_native.rs: {}", e))?;
|
|
142
|
+
}
|
|
143
|
+
fs::write(build_dir.join("src/main.rs"), rust_main)
|
|
96
144
|
.map_err(|e| format!("Cannot write main.rs: {}", e))?;
|
|
97
145
|
|
|
98
146
|
let workspace_target = Path::new(&runtime_path)
|
|
@@ -114,3 +162,24 @@ tishlang_runtime = {{ path = {:?}{} }}{}{}{}
|
|
|
114
162
|
Ok(())
|
|
115
163
|
}
|
|
116
164
|
|
|
165
|
+
#[cfg(test)]
|
|
166
|
+
mod tests {
|
|
167
|
+
use super::runtime_features_for_cargo;
|
|
168
|
+
|
|
169
|
+
#[test]
|
|
170
|
+
fn runtime_features_full_expands() {
|
|
171
|
+
let f = runtime_features_for_cargo(&["full".to_string()]);
|
|
172
|
+
assert!(f.contains(&"http".to_string()));
|
|
173
|
+
assert!(f.contains(&"fs".to_string()));
|
|
174
|
+
assert!(f.contains(&"process".to_string()));
|
|
175
|
+
assert!(f.contains(&"regex".to_string()));
|
|
176
|
+
assert!(f.contains(&"ws".to_string()));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#[test]
|
|
180
|
+
fn runtime_features_merges_full_and_specific() {
|
|
181
|
+
let f = runtime_features_for_cargo(&["full".to_string(), "http".to_string()]);
|
|
182
|
+
assert_eq!(f.len(), 5);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
@@ -57,18 +57,27 @@ pub fn compile_to_native(
|
|
|
57
57
|
|
|
58
58
|
match backend {
|
|
59
59
|
Backend::Rust => {
|
|
60
|
-
let (rust_code, native_modules) =
|
|
61
|
-
|
|
60
|
+
let (rust_code, native_modules, effective_features, native_build) =
|
|
61
|
+
tishlang_compile::compile_project_full(
|
|
62
|
+
entry_path,
|
|
63
|
+
project_root,
|
|
64
|
+
features,
|
|
65
|
+
optimize,
|
|
66
|
+
)
|
|
67
|
+
.map_err(|e| NativeError {
|
|
68
|
+
message: e.to_string(),
|
|
69
|
+
})?;
|
|
70
|
+
|
|
71
|
+
crate::build::build_via_cargo(
|
|
72
|
+
&rust_code,
|
|
73
|
+
native_modules,
|
|
74
|
+
output_path,
|
|
75
|
+
&effective_features,
|
|
76
|
+
&native_build.rust_dependencies_toml,
|
|
77
|
+
native_build.generated_native_rs.as_deref(),
|
|
62
78
|
project_root,
|
|
63
|
-
features,
|
|
64
|
-
optimize,
|
|
65
79
|
)
|
|
66
|
-
.map_err(|e| NativeError {
|
|
67
|
-
message: e.to_string(),
|
|
68
|
-
})?;
|
|
69
|
-
|
|
70
|
-
crate::build::build_via_cargo(&rust_code, native_modules, output_path, features)
|
|
71
|
-
.map_err(|e| NativeError { message: e })
|
|
80
|
+
.map_err(|e| NativeError { message: e })
|
|
72
81
|
}
|
|
73
82
|
Backend::Cranelift => {
|
|
74
83
|
let modules = tishlang_compile::resolve_project(entry_path, project_root)
|
|
@@ -91,7 +100,7 @@ pub fn compile_to_native(
|
|
|
91
100
|
|
|
92
101
|
if tishlang_compile::has_external_native_imports(&program) {
|
|
93
102
|
return Err(NativeError {
|
|
94
|
-
message: "Cranelift backend does not support external native imports (tish
|
|
103
|
+
message: "Cranelift backend does not support external native imports (tish:…, cargo:…, @scope/pkg). Built-in tish:fs, tish:http, tish:process are supported. Use --native-backend rust for external modules.".to_string(),
|
|
95
104
|
});
|
|
96
105
|
}
|
|
97
106
|
|
|
@@ -127,7 +136,7 @@ pub fn compile_to_native(
|
|
|
127
136
|
};
|
|
128
137
|
if tishlang_compile::has_external_native_imports(&program) {
|
|
129
138
|
return Err(NativeError {
|
|
130
|
-
message: "LLVM backend does not support external native imports. Built-in tish:fs, tish:http, tish:process are supported.".to_string(),
|
|
139
|
+
message: "LLVM backend does not support external native imports (tish:…, cargo:…, @scope/pkg). Built-in tish:fs, tish:http, tish:process are supported.".to_string(),
|
|
131
140
|
});
|
|
132
141
|
}
|
|
133
142
|
let chunk = if optimize {
|
|
@@ -175,6 +184,8 @@ pub fn compile_program_to_native(
|
|
|
175
184
|
let root = project_root.unwrap_or_else(|| Path::new("."));
|
|
176
185
|
let native_modules = tishlang_compile::resolve_native_modules(&program, root)
|
|
177
186
|
.map_err(|e| NativeError { message: e })?;
|
|
187
|
+
let native_build = tishlang_compile::compute_native_build_artifacts(&program, root, &native_modules)
|
|
188
|
+
.map_err(|e| NativeError { message: e })?;
|
|
178
189
|
let mut all_features = features.to_vec();
|
|
179
190
|
for f in tishlang_compile::extract_native_import_features(&program) {
|
|
180
191
|
if !all_features.contains(&f) {
|
|
@@ -186,18 +197,27 @@ pub fn compile_program_to_native(
|
|
|
186
197
|
project_root,
|
|
187
198
|
&all_features,
|
|
188
199
|
&native_modules,
|
|
200
|
+
&native_build.native_init,
|
|
189
201
|
optimize,
|
|
190
202
|
)
|
|
191
203
|
.map_err(|e| NativeError {
|
|
192
204
|
message: e.message,
|
|
193
205
|
})?;
|
|
194
|
-
crate::build::build_via_cargo(
|
|
195
|
-
|
|
206
|
+
crate::build::build_via_cargo(
|
|
207
|
+
&rust_code,
|
|
208
|
+
native_modules,
|
|
209
|
+
output_path,
|
|
210
|
+
&all_features,
|
|
211
|
+
&native_build.rust_dependencies_toml,
|
|
212
|
+
native_build.generated_native_rs.as_deref(),
|
|
213
|
+
Some(root),
|
|
214
|
+
)
|
|
215
|
+
.map_err(|e| NativeError { message: e })
|
|
196
216
|
}
|
|
197
217
|
Backend::Cranelift => {
|
|
198
218
|
if tishlang_compile::has_external_native_imports(program) {
|
|
199
219
|
return Err(NativeError {
|
|
200
|
-
message: "Cranelift backend does not support external native imports. Built-in tish:fs, tish:http, tish:process are supported.".to_string(),
|
|
220
|
+
message: "Cranelift backend does not support external native imports (tish:…, cargo:…, @scope/pkg). Built-in tish:fs, tish:http, tish:process are supported.".to_string(),
|
|
201
221
|
});
|
|
202
222
|
}
|
|
203
223
|
let program = if optimize { tishlang_opt::optimize(program) } else { program.clone() };
|
|
@@ -213,7 +233,7 @@ pub fn compile_program_to_native(
|
|
|
213
233
|
Backend::Llvm => {
|
|
214
234
|
if tishlang_compile::has_external_native_imports(program) {
|
|
215
235
|
return Err(NativeError {
|
|
216
|
-
message: "LLVM backend does not support external native imports.".to_string(),
|
|
236
|
+
message: "LLVM backend does not support external native imports (tish:…, cargo:…, @scope/pkg).".to_string(),
|
|
217
237
|
});
|
|
218
238
|
}
|
|
219
239
|
let program = if optimize { tishlang_opt::optimize(program) } else { program.clone() };
|
|
@@ -71,6 +71,7 @@ pub use tishlang_builtins::string::{
|
|
|
71
71
|
repeat as string_repeat_impl,
|
|
72
72
|
pad_start as string_pad_start_impl,
|
|
73
73
|
pad_end as string_pad_end_impl,
|
|
74
|
+
last_index_of as string_last_index_of_impl,
|
|
74
75
|
};
|
|
75
76
|
|
|
76
77
|
// Wrapper functions to maintain API compatibility
|
|
@@ -119,6 +120,9 @@ pub fn string_char_code_at(s: &Value, idx: &Value) -> Value { string_char_code_a
|
|
|
119
120
|
pub fn string_repeat(s: &Value, count: &Value) -> Value { string_repeat_impl(s, count) }
|
|
120
121
|
pub fn string_pad_start(s: &Value, target_len: &Value, pad: &Value) -> Value { string_pad_start_impl(s, target_len, pad) }
|
|
121
122
|
pub fn string_pad_end(s: &Value, target_len: &Value, pad: &Value) -> Value { string_pad_end_impl(s, target_len, pad) }
|
|
123
|
+
pub fn string_last_index_of(s: &Value, search: &Value, position: &Value) -> Value {
|
|
124
|
+
string_last_index_of_impl(s, search, position)
|
|
125
|
+
}
|
|
122
126
|
|
|
123
127
|
/// Number.prototype.toFixed(digits) - format number with fixed decimal places (0-20)
|
|
124
128
|
pub fn number_to_fixed(n: &Value, digits: &Value) -> Value {
|