feat: 支持 String 对象及成员方法
This commit is contained in:
Generated
+34
@@ -14,6 +14,15 @@ dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
@@ -833,11 +842,13 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"fast-float",
|
||||
"memchr",
|
||||
"oxide_compiler",
|
||||
"oxide_kernel",
|
||||
"oxide_parser",
|
||||
"oxide_types",
|
||||
"proptest",
|
||||
"regex",
|
||||
"ryu",
|
||||
]
|
||||
|
||||
@@ -1054,6 +1065,29 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.10"
|
||||
|
||||
@@ -37,6 +37,8 @@ proptest = "1"
|
||||
rustc-hash = "2"
|
||||
fast-float = "0.2"
|
||||
ryu = "1"
|
||||
memchr = "2"
|
||||
regex = "1"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
all = { level = "deny", priority = -1 }
|
||||
|
||||
@@ -44,6 +44,28 @@ pub struct ErrorMethods {
|
||||
pub eval_error: *const (),
|
||||
}
|
||||
|
||||
pub struct StringMethods {
|
||||
pub index_of: *const (),
|
||||
pub includes: *const (),
|
||||
pub char_at: *const (),
|
||||
pub char_code_at: *const (),
|
||||
pub concat: *const (),
|
||||
pub slice: *const (),
|
||||
pub substring: *const (),
|
||||
pub to_upper_case: *const (),
|
||||
pub to_lower_case: *const (),
|
||||
pub trim: *const (),
|
||||
pub repeat: *const (),
|
||||
pub pad_start: *const (),
|
||||
pub pad_end: *const (),
|
||||
pub starts_with: *const (),
|
||||
pub ends_with: *const (),
|
||||
pub split: *const (),
|
||||
pub replace: *const (),
|
||||
pub match_fn: *const (),
|
||||
pub search: *const (),
|
||||
}
|
||||
|
||||
pub struct BuiltinWorld {
|
||||
pub object_proto: P<JsObject>,
|
||||
pub array_proto: P<JsObject>,
|
||||
@@ -393,6 +415,148 @@ impl BuiltinWorld {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn bind_string_methods(
|
||||
&self,
|
||||
methods: &StringMethods,
|
||||
string_forge: &StringForge,
|
||||
shape_forge: &ShapeForge,
|
||||
) {
|
||||
let proto_ptr = P::as_ptr(&self.string_proto) as *mut JsObject;
|
||||
let proto = unsafe { &mut *proto_ptr };
|
||||
|
||||
let _ = Self::bind_method(
|
||||
proto,
|
||||
shape_forge,
|
||||
string_forge,
|
||||
"indexOf",
|
||||
methods.index_of,
|
||||
1,
|
||||
);
|
||||
let _ = Self::bind_method(
|
||||
proto,
|
||||
shape_forge,
|
||||
string_forge,
|
||||
"includes",
|
||||
methods.includes,
|
||||
1,
|
||||
);
|
||||
let _ = Self::bind_method(
|
||||
proto,
|
||||
shape_forge,
|
||||
string_forge,
|
||||
"charAt",
|
||||
methods.char_at,
|
||||
1,
|
||||
);
|
||||
let _ = Self::bind_method(
|
||||
proto,
|
||||
shape_forge,
|
||||
string_forge,
|
||||
"charCodeAt",
|
||||
methods.char_code_at,
|
||||
1,
|
||||
);
|
||||
let _ = Self::bind_method(
|
||||
proto,
|
||||
shape_forge,
|
||||
string_forge,
|
||||
"concat",
|
||||
methods.concat,
|
||||
1,
|
||||
);
|
||||
let _ = Self::bind_method(proto, shape_forge, string_forge, "slice", methods.slice, 2);
|
||||
let _ = Self::bind_method(
|
||||
proto,
|
||||
shape_forge,
|
||||
string_forge,
|
||||
"substring",
|
||||
methods.substring,
|
||||
2,
|
||||
);
|
||||
let _ = Self::bind_method(
|
||||
proto,
|
||||
shape_forge,
|
||||
string_forge,
|
||||
"toUpperCase",
|
||||
methods.to_upper_case,
|
||||
0,
|
||||
);
|
||||
let _ = Self::bind_method(
|
||||
proto,
|
||||
shape_forge,
|
||||
string_forge,
|
||||
"toLowerCase",
|
||||
methods.to_lower_case,
|
||||
0,
|
||||
);
|
||||
let _ = Self::bind_method(proto, shape_forge, string_forge, "trim", methods.trim, 0);
|
||||
let _ = Self::bind_method(
|
||||
proto,
|
||||
shape_forge,
|
||||
string_forge,
|
||||
"repeat",
|
||||
methods.repeat,
|
||||
1,
|
||||
);
|
||||
let _ = Self::bind_method(
|
||||
proto,
|
||||
shape_forge,
|
||||
string_forge,
|
||||
"padStart",
|
||||
methods.pad_start,
|
||||
1,
|
||||
);
|
||||
let _ = Self::bind_method(
|
||||
proto,
|
||||
shape_forge,
|
||||
string_forge,
|
||||
"padEnd",
|
||||
methods.pad_end,
|
||||
1,
|
||||
);
|
||||
let _ = Self::bind_method(
|
||||
proto,
|
||||
shape_forge,
|
||||
string_forge,
|
||||
"startsWith",
|
||||
methods.starts_with,
|
||||
1,
|
||||
);
|
||||
let _ = Self::bind_method(
|
||||
proto,
|
||||
shape_forge,
|
||||
string_forge,
|
||||
"endsWith",
|
||||
methods.ends_with,
|
||||
1,
|
||||
);
|
||||
let _ = Self::bind_method(proto, shape_forge, string_forge, "split", methods.split, 1);
|
||||
let _ = Self::bind_method(
|
||||
proto,
|
||||
shape_forge,
|
||||
string_forge,
|
||||
"replace",
|
||||
methods.replace,
|
||||
2,
|
||||
);
|
||||
let _ = Self::bind_method(
|
||||
proto,
|
||||
shape_forge,
|
||||
string_forge,
|
||||
"match",
|
||||
methods.match_fn,
|
||||
1,
|
||||
);
|
||||
let _ = Self::bind_method(
|
||||
proto,
|
||||
shape_forge,
|
||||
string_forge,
|
||||
"search",
|
||||
methods.search,
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn bind_method(
|
||||
proto: &mut JsObject,
|
||||
shape_forge: &ShapeForge,
|
||||
|
||||
@@ -13,6 +13,8 @@ oxide_types = { path = "../oxide_types" }
|
||||
bumpalo.workspace = true
|
||||
fast-float.workspace = true
|
||||
ryu.workspace = true
|
||||
memchr.workspace = true
|
||||
regex.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
proptest.workspace = true
|
||||
|
||||
@@ -2,3 +2,4 @@ pub mod array;
|
||||
pub mod error;
|
||||
pub mod global;
|
||||
pub mod object;
|
||||
pub mod string;
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::coercion;
|
||||
use crate::native::NativeResult;
|
||||
use crate::vm::Vm;
|
||||
use memchr::memchr;
|
||||
use regex::Regex;
|
||||
|
||||
fn this_string(vm: &Vm, args: &[u8]) -> String {
|
||||
coercion::to_string(vm.kernel().string_forge().as_ref(), vm.reg(args[0]))
|
||||
}
|
||||
|
||||
fn make_string_array(vm: &mut Vm, parts: &[String]) -> JsValue {
|
||||
let proto = vm.kernel().builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||||
let n = parts.len().min(31);
|
||||
let arr = vm.epoch().alloc(JsObject::new_array(
|
||||
EMPTY_SHAPE_ID,
|
||||
JsValue::from_js_object(proto),
|
||||
n,
|
||||
vm.epoch().bump(),
|
||||
));
|
||||
unsafe {
|
||||
for (i, s) in parts.iter().enumerate().take(n) {
|
||||
let sv = vm.intern(s);
|
||||
(*arr).set_prop(i as u8, sv);
|
||||
}
|
||||
(*arr).set_prop_count(n as u8);
|
||||
}
|
||||
JsValue::from_js_object(arr)
|
||||
}
|
||||
|
||||
fn as_string(vm: &Vm, val: JsValue) -> String {
|
||||
coercion::to_string(vm.kernel().string_forge().as_ref(), val)
|
||||
}
|
||||
|
||||
pub fn string_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::int(-1));
|
||||
}
|
||||
let search = as_string(vm, vm.reg(args[1]));
|
||||
let pos = if args.len() > 2 {
|
||||
(coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as usize)
|
||||
.min(s.len())
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if search.is_empty() {
|
||||
return Ok(JsValue::int(pos as i32));
|
||||
}
|
||||
|
||||
let haystack = &s.as_bytes()[pos..];
|
||||
if search.len() == 1 {
|
||||
if let Some(idx) = memchr(search.as_bytes()[0], haystack) {
|
||||
return Ok(JsValue::int((pos + idx) as i32));
|
||||
}
|
||||
} else if let Some(idx) = s[pos..].find(&search) {
|
||||
return Ok(JsValue::int((pos + idx) as i32));
|
||||
}
|
||||
Ok(JsValue::int(-1))
|
||||
}
|
||||
|
||||
pub fn string_includes(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::bool(false));
|
||||
}
|
||||
let search = as_string(vm, vm.reg(args[1]));
|
||||
let pos = if args.len() > 2 {
|
||||
(coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as usize)
|
||||
.min(s.len())
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if search.is_empty() {
|
||||
return Ok(JsValue::bool(true));
|
||||
}
|
||||
|
||||
let haystack = &s.as_bytes()[pos..];
|
||||
if search.len() == 1 {
|
||||
Ok(JsValue::bool(
|
||||
memchr(search.as_bytes()[0], haystack).is_some(),
|
||||
))
|
||||
} else {
|
||||
Ok(JsValue::bool(s[pos..].contains(&search)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn string_char_at(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 {
|
||||
if s.is_empty() {
|
||||
return Ok(vm.intern(""));
|
||||
}
|
||||
return Ok(vm.intern(&s[0..1]));
|
||||
}
|
||||
let idx = coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as i32;
|
||||
if idx < 0 || idx as usize >= s.len() {
|
||||
return Ok(vm.intern(""));
|
||||
}
|
||||
let ch = &s[(idx as usize)..(idx as usize + 1)];
|
||||
Ok(vm.intern(ch))
|
||||
}
|
||||
|
||||
pub fn string_char_code_at(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 {
|
||||
if s.is_empty() {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
return Ok(JsValue::int(s.as_bytes()[0] as i32));
|
||||
}
|
||||
let idx = coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as i32;
|
||||
if idx < 0 || idx as usize >= s.len() {
|
||||
return Ok(JsValue::float(f64::NAN));
|
||||
}
|
||||
Ok(JsValue::int(s.as_bytes()[idx as usize] as i32))
|
||||
}
|
||||
|
||||
pub fn string_concat(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let mut result = this_string(vm, args);
|
||||
let sf = vm.kernel().string_forge().as_ref();
|
||||
for &arg_reg in args.iter().skip(1) {
|
||||
result.push_str(&coercion::to_string(sf, vm.reg(arg_reg)));
|
||||
}
|
||||
Ok(vm.intern(&result))
|
||||
}
|
||||
|
||||
pub fn string_slice(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
let n = s.len() as i32;
|
||||
let start = if args.len() > 1 {
|
||||
let v = coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as i32;
|
||||
if v < 0 {
|
||||
(n + v).max(0)
|
||||
} else {
|
||||
v.min(n)
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let end = if args.len() > 2 {
|
||||
let v = coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as i32;
|
||||
if v < 0 {
|
||||
(n + v).max(0)
|
||||
} else {
|
||||
v.min(n)
|
||||
}
|
||||
} else {
|
||||
n
|
||||
};
|
||||
let start = start as usize;
|
||||
let end = end as usize;
|
||||
let result = if start < end { &s[start..end] } else { "" };
|
||||
Ok(vm.intern(result))
|
||||
}
|
||||
|
||||
pub fn string_substring(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
let n = s.len() as i32;
|
||||
let mut start = if args.len() > 1 {
|
||||
(coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as i32)
|
||||
.max(0)
|
||||
.min(n)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let mut end = if args.len() > 2 {
|
||||
(coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as i32)
|
||||
.max(0)
|
||||
.min(n)
|
||||
} else {
|
||||
n
|
||||
};
|
||||
if start > end {
|
||||
std::mem::swap(&mut start, &mut end);
|
||||
}
|
||||
let result = &s[start as usize..end as usize];
|
||||
Ok(vm.intern(result))
|
||||
}
|
||||
|
||||
pub fn string_to_upper_case(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
Ok(vm.intern(&s.to_uppercase()))
|
||||
}
|
||||
|
||||
pub fn string_to_lower_case(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
Ok(vm.intern(&s.to_lowercase()))
|
||||
}
|
||||
|
||||
pub fn string_trim(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
Ok(vm.intern(s.trim()))
|
||||
}
|
||||
|
||||
pub fn string_repeat(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
let n = if args.len() > 1 {
|
||||
(coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as usize)
|
||||
.min(10000)
|
||||
} else {
|
||||
1
|
||||
};
|
||||
Ok(vm.intern(&s.repeat(n)))
|
||||
}
|
||||
|
||||
pub fn string_pad_start(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
let target = if args.len() > 1 {
|
||||
coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as usize
|
||||
} else {
|
||||
s.len()
|
||||
};
|
||||
let pad = if args.len() > 2 {
|
||||
as_string(vm, vm.reg(args[2]))
|
||||
} else {
|
||||
" ".to_string()
|
||||
};
|
||||
if s.len() >= target || pad.is_empty() {
|
||||
return Ok(vm.intern(&s));
|
||||
}
|
||||
let needed = target - s.len();
|
||||
let reps = needed.div_ceil(pad.len());
|
||||
let mut out = pad.repeat(reps);
|
||||
out.truncate(needed);
|
||||
out.push_str(&s);
|
||||
Ok(vm.intern(&out))
|
||||
}
|
||||
|
||||
pub fn string_pad_end(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
let target = if args.len() > 1 {
|
||||
coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as usize
|
||||
} else {
|
||||
s.len()
|
||||
};
|
||||
let pad = if args.len() > 2 {
|
||||
as_string(vm, vm.reg(args[2]))
|
||||
} else {
|
||||
" ".to_string()
|
||||
};
|
||||
if s.len() >= target || pad.is_empty() {
|
||||
return Ok(vm.intern(&s));
|
||||
}
|
||||
let needed = target - s.len();
|
||||
let reps = needed.div_ceil(pad.len());
|
||||
let mut out = s;
|
||||
let pad_repeated = pad.repeat(reps);
|
||||
out.push_str(&pad_repeated[..needed]);
|
||||
Ok(vm.intern(&out))
|
||||
}
|
||||
|
||||
pub fn string_starts_with(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::bool(false));
|
||||
}
|
||||
let search = as_string(vm, vm.reg(args[1]));
|
||||
let pos = if args.len() > 2 {
|
||||
(coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as usize)
|
||||
.min(s.len())
|
||||
} else {
|
||||
0
|
||||
};
|
||||
Ok(JsValue::bool(s[pos..].starts_with(&search)))
|
||||
}
|
||||
|
||||
pub fn string_ends_with(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::bool(false));
|
||||
}
|
||||
let search = as_string(vm, vm.reg(args[1]));
|
||||
let end_pos = if args.len() > 2 {
|
||||
(coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as usize)
|
||||
.min(s.len())
|
||||
} else {
|
||||
s.len()
|
||||
};
|
||||
Ok(JsValue::bool(s[..end_pos].ends_with(&search)))
|
||||
}
|
||||
|
||||
pub fn string_split(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 {
|
||||
let parts = vec![s.clone()];
|
||||
return Ok(make_string_array(vm, &parts));
|
||||
}
|
||||
let sep = as_string(vm, vm.reg(args[1]));
|
||||
let limit = if args.len() > 2 {
|
||||
coercion::to_number(vm.reg(args[2]), vm.kernel().string_forge().as_ref()) as usize
|
||||
} else {
|
||||
usize::MAX
|
||||
};
|
||||
|
||||
let parts: Vec<String>;
|
||||
if sep.starts_with('/') && sep.len() > 2 && sep.ends_with('/') {
|
||||
let pattern = &sep[1..sep.len() - 1];
|
||||
if let Ok(re) = Regex::new(pattern) {
|
||||
let raw: Vec<&str> = re.split(&s).collect();
|
||||
parts = raw.iter().map(|p| p.to_string()).take(limit).collect();
|
||||
} else {
|
||||
parts = vec![s.clone()];
|
||||
}
|
||||
} else if sep.is_empty() {
|
||||
parts = s.chars().map(|c| c.to_string()).take(limit).collect();
|
||||
} else {
|
||||
parts = s.split(&sep).map(|p| p.to_string()).take(limit).collect();
|
||||
}
|
||||
Ok(make_string_array(vm, &parts))
|
||||
}
|
||||
|
||||
pub fn string_replace(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Ok(vm.intern(&s));
|
||||
}
|
||||
let pattern = as_string(vm, vm.reg(args[1]));
|
||||
let replacement = if args.len() > 2 {
|
||||
as_string(vm, vm.reg(args[2]))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let result = if pattern.starts_with('/') && pattern.len() > 2 && pattern.ends_with('/') {
|
||||
let pat = &pattern[1..pattern.len() - 1];
|
||||
if let Ok(re) = Regex::new(pat) {
|
||||
re.replace_all(&s, replacement.as_str()).to_string()
|
||||
} else {
|
||||
s.clone()
|
||||
}
|
||||
} else {
|
||||
s.replacen(&pattern, &replacement, 1)
|
||||
};
|
||||
Ok(vm.intern(&result))
|
||||
}
|
||||
|
||||
pub fn string_match_fn(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::undefined());
|
||||
}
|
||||
let pattern = as_string(vm, vm.reg(args[1]));
|
||||
if pattern.starts_with('/') && pattern.len() > 2 && pattern.ends_with('/') {
|
||||
let pat = &pattern[1..pattern.len() - 1];
|
||||
if let Ok(re) = Regex::new(pat) {
|
||||
let matches: Vec<String> = re.find_iter(&s).map(|m| m.as_str().to_string()).collect();
|
||||
return Ok(make_string_array(vm, &matches));
|
||||
}
|
||||
} else if let Some(pos) = s.find(&pattern) {
|
||||
return Ok(make_string_array(
|
||||
vm,
|
||||
&[s[pos..pos + pattern.len()].to_string()],
|
||||
));
|
||||
}
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
pub fn string_search(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 {
|
||||
return Ok(JsValue::int(-1));
|
||||
}
|
||||
let pattern = as_string(vm, vm.reg(args[1]));
|
||||
if pattern.starts_with('/') && pattern.len() > 2 && pattern.ends_with('/') {
|
||||
let pat = &pattern[1..pattern.len() - 1];
|
||||
if let Ok(re) = Regex::new(pat) {
|
||||
if let Some(m) = re.find(&s) {
|
||||
return Ok(JsValue::int(m.start() as i32));
|
||||
}
|
||||
}
|
||||
} else if let Some(pos) = s.find(&pattern) {
|
||||
return Ok(JsValue::int(pos as i32));
|
||||
}
|
||||
Ok(JsValue::int(-1))
|
||||
}
|
||||
@@ -8,7 +8,7 @@ use oxide_compiler::opcode::{self, OpCode};
|
||||
|
||||
use crate::coercion;
|
||||
use crate::native::NativeFn;
|
||||
use oxide_kernel::builtin::{ArrayMethods, ErrorMethods, ObjectMethods};
|
||||
use oxide_kernel::builtin::{ArrayMethods, ErrorMethods, ObjectMethods, StringMethods};
|
||||
use oxide_kernel::kernel::{KernelConfig, OxideKernel};
|
||||
use oxide_kernel::prop_forge::PropTemplate;
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
@@ -131,6 +131,45 @@ pub fn init_kernel_builtins(kernel: &Arc<OxideKernel>) {
|
||||
let bump3 = bumpalo::Bump::new();
|
||||
global.set_prop_expand(cur_count, err_val, &bump3);
|
||||
global.bump_generation();
|
||||
|
||||
let string_methods = StringMethods {
|
||||
index_of: crate::builtins::string::string_index_of as *const (),
|
||||
includes: crate::builtins::string::string_includes as *const (),
|
||||
char_at: crate::builtins::string::string_char_at as *const (),
|
||||
char_code_at: crate::builtins::string::string_char_code_at as *const (),
|
||||
concat: crate::builtins::string::string_concat as *const (),
|
||||
slice: crate::builtins::string::string_slice as *const (),
|
||||
substring: crate::builtins::string::string_substring as *const (),
|
||||
to_upper_case: crate::builtins::string::string_to_upper_case as *const (),
|
||||
to_lower_case: crate::builtins::string::string_to_lower_case as *const (),
|
||||
trim: crate::builtins::string::string_trim as *const (),
|
||||
repeat: crate::builtins::string::string_repeat as *const (),
|
||||
pad_start: crate::builtins::string::string_pad_start as *const (),
|
||||
pad_end: crate::builtins::string::string_pad_end as *const (),
|
||||
starts_with: crate::builtins::string::string_starts_with as *const (),
|
||||
ends_with: crate::builtins::string::string_ends_with as *const (),
|
||||
split: crate::builtins::string::string_split as *const (),
|
||||
replace: crate::builtins::string::string_replace as *const (),
|
||||
match_fn: crate::builtins::string::string_match_fn as *const (),
|
||||
search: crate::builtins::string::string_search as *const (),
|
||||
};
|
||||
kernel.builtin_world().bind_string_methods(
|
||||
&string_methods,
|
||||
kernel.string_forge().as_ref(),
|
||||
kernel.shape_forge().as_ref(),
|
||||
);
|
||||
|
||||
let si_str = kernel.string_forge().intern("String").0;
|
||||
let str_shape = kernel.shape_forge().make_shape(global.shape_id(), si_str);
|
||||
let str_val = JsValue::from_js_object(
|
||||
kernel.builtin_world().string_constructor.as_ptr() as *mut JsObject
|
||||
);
|
||||
let cur_count = global.prop_count();
|
||||
global.set_shape_id(str_shape);
|
||||
global.set_prop_count(cur_count + 1);
|
||||
let bump4 = bumpalo::Bump::new();
|
||||
global.set_prop_expand(cur_count, str_val, &bump4);
|
||||
global.bump_generation();
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
@@ -552,8 +591,20 @@ impl Vm {
|
||||
}
|
||||
|
||||
OpCode::IC_GET_PROP => {
|
||||
let obj_ptr = self.regs[a].as_object_ptr() as *mut JsObject;
|
||||
let val = self.regs[a];
|
||||
let obj_ptr = val.as_object_ptr() as *mut JsObject;
|
||||
if obj_ptr.is_null() {
|
||||
if val.is_string() {
|
||||
let proto_ptr =
|
||||
self.kernel.builtin_world().string_proto.as_ptr() as *mut JsObject;
|
||||
let proto = unsafe { &*proto_ptr };
|
||||
let prop_name_si = self.regs[b].as_string_index();
|
||||
if let Some(resolved) = self.resolve_property(proto, prop_name_si) {
|
||||
self.regs[a] = resolved;
|
||||
self.pc += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return Err("IC_GET_PROP on non-object".into());
|
||||
}
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_types::value::JsValue;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn eval(vm: &mut Vm, source: &str) -> Result<JsValue, String> {
|
||||
let allocator = oxide_parser::Allocator::default();
|
||||
let program =
|
||||
oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
|
||||
let module = Compiler::new()
|
||||
.compile(&program)
|
||||
.map_err(|e| format!("Compile error: {}", e))?;
|
||||
vm.run(&module)
|
||||
}
|
||||
|
||||
fn to_str(vm: &Vm, val: JsValue) -> String {
|
||||
vm.kernel()
|
||||
.string_forge()
|
||||
.lookup(val.as_string_index())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_index_of_found() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'hello'.indexOf('e')").unwrap();
|
||||
assert_eq!(result.as_int(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_index_of_not_found() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'hello'.indexOf('x')").unwrap();
|
||||
assert_eq!(result.as_int(), -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_includes_true() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'hello'.includes('ell')").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_includes_false() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'hello'.includes('xyz')").unwrap();
|
||||
assert!(!result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_char_at() {
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'hello'.charAt(1)").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "e");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_char_code_at() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'hello'.charCodeAt(0)").unwrap();
|
||||
assert_eq!(result.as_int(), 104);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_concat() {
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'hello'.concat(' world')").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "hello world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_slice() {
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'hello'.slice(1, 4)").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "ell");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_substring() {
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'hello'.substring(0, 2)").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "he");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_to_upper_case() {
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'hello'.toUpperCase()").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "HELLO");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_to_lower_case() {
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'HELLO'.toLowerCase()").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_trim() {
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "' hi '.trim()").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "hi");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_repeat() {
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'abc'.repeat(3)").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "abcabcabc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_pad_start() {
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'5'.padStart(4, '0')").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "0005");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_pad_end() {
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'hi'.padEnd(4)").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "hi ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_starts_with() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'hello'.startsWith('hel')").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_ends_with() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'hello'.endsWith('lo')").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_split_comma() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'a,b,c'.split(',')").unwrap();
|
||||
assert!(result.is_object());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_replace_first() {
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'hello'.replace('l', 'L')").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "heLlo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_search_found() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'hello'.search('ll')").unwrap();
|
||||
assert_eq!(result.as_int(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_search_not_found() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'hello'.search('x')").unwrap();
|
||||
assert_eq!(result.as_int(), -1);
|
||||
}
|
||||
Reference in New Issue
Block a user