mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
feat: 支持 RegExpr 与 Symbol 部分方法
This commit is contained in:
@@ -22,6 +22,7 @@ pub(crate) fn is_side_effect_free(expr: &Expression) -> bool {
|
||||
| Expression::BooleanLiteral(_)
|
||||
| Expression::NullLiteral(_)
|
||||
| Expression::Identifier(_)
|
||||
| Expression::RegExpLiteral(_)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -198,6 +199,8 @@ impl CompileCtx {
|
||||
"Date",
|
||||
"Set",
|
||||
"Map",
|
||||
"RegExp",
|
||||
"Symbol",
|
||||
"parseInt",
|
||||
"parseFloat",
|
||||
"isNaN",
|
||||
|
||||
@@ -465,8 +465,13 @@ impl Compiler {
|
||||
}
|
||||
_ => {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1;
|
||||
}
|
||||
},
|
||||
Expression::RegExpLiteral(_) => {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1;
|
||||
}
|
||||
_ => {
|
||||
ctx.alloc_reg();
|
||||
ctx.projected_pc += 1; // LOAD_CONST or LOAD_VAR
|
||||
|
||||
@@ -1041,6 +1041,38 @@ impl Compiler {
|
||||
ctx.emit(opcode::encode(OpCode::LOAD_VAR, r, 254, 0));
|
||||
Ok(r)
|
||||
}
|
||||
Expression::RegExpLiteral(lit) => {
|
||||
if let Some(raw) = &lit.raw {
|
||||
let raw_str = raw.to_string();
|
||||
if raw_str.len() >= 2 && raw_str.starts_with('/') {
|
||||
let last_slash = raw_str.rfind('/').unwrap_or(raw_str.len() - 1);
|
||||
let pattern = &raw_str[1..last_slash];
|
||||
let flags = &raw_str[last_slash + 1..];
|
||||
let ci = ctx.add_constant(Constant::RegExp(
|
||||
pattern.to_string(),
|
||||
flags.to_string(),
|
||||
));
|
||||
let r = ctx.alloc_reg();
|
||||
ctx.emit(opcode::encode(
|
||||
OpCode::LOAD_CONST,
|
||||
r,
|
||||
(ci & 0xFF) as u8,
|
||||
((ci >> 8) & 0xFF) as u8,
|
||||
));
|
||||
Ok(r)
|
||||
} else {
|
||||
Err(format!(
|
||||
"unsupported expression type: {:?}",
|
||||
expr
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(format!(
|
||||
"unsupported expression type: {:?}",
|
||||
expr
|
||||
))
|
||||
}
|
||||
}
|
||||
_ => Err(format!("unsupported expression type: {:?}", expr)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,6 +243,12 @@ fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Expression::RegExpLiteral(lit) => {
|
||||
if let Some(raw) = &lit.raw {
|
||||
raw.to_string().hash(h);
|
||||
}
|
||||
std::mem::discriminant(expr).hash(h);
|
||||
}
|
||||
_ => {
|
||||
std::mem::discriminant(expr).hash(h);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ pub enum Constant {
|
||||
Null,
|
||||
Undefined,
|
||||
BytecodeFunc(u32),
|
||||
RegExp(String, String),
|
||||
}
|
||||
|
||||
pub struct CompiledModule {
|
||||
|
||||
@@ -88,6 +88,12 @@ pub struct StringMethods {
|
||||
pub search: *const (),
|
||||
}
|
||||
|
||||
pub struct RegExpMethods {
|
||||
pub exec: *const (),
|
||||
pub test: *const (),
|
||||
pub to_string: *const (),
|
||||
}
|
||||
|
||||
pub struct NumberMethods {
|
||||
pub is_nan: *const (),
|
||||
pub is_finite: *const (),
|
||||
@@ -134,6 +140,12 @@ pub struct BuiltinWorld {
|
||||
pub set_proto: P<JsObject>,
|
||||
pub map_constructor: P<JsObject>,
|
||||
pub map_proto: P<JsObject>,
|
||||
pub regexp_constructor: P<JsObject>,
|
||||
pub regexp_proto: P<JsObject>,
|
||||
pub sym_match: P<JsObject>,
|
||||
pub sym_replace: P<JsObject>,
|
||||
pub sym_search: P<JsObject>,
|
||||
pub sym_split: P<JsObject>,
|
||||
}
|
||||
|
||||
fn intern_label(string_forge: &StringForge, label: &str) -> u32 {
|
||||
@@ -290,6 +302,19 @@ impl BuiltinWorld {
|
||||
si_constructor,
|
||||
si_name,
|
||||
);
|
||||
let (regexp_proto, regexp_constructor) = make_pair(
|
||||
string_forge,
|
||||
shape_forge,
|
||||
"RegExp",
|
||||
si_prototype,
|
||||
si_constructor,
|
||||
si_name,
|
||||
);
|
||||
|
||||
let sym_match = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||
let sym_replace = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||
let sym_search = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||
let sym_split = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||
|
||||
/// Overwrite placeholder slots (set up by make_pair) with real values.
|
||||
/// ctor.vec[0] = constructor.prototype -> proto
|
||||
@@ -320,6 +345,7 @@ impl BuiltinWorld {
|
||||
wire_ctor_proto(&date_constructor, &date_proto);
|
||||
wire_ctor_proto(&set_constructor, &set_proto);
|
||||
wire_ctor_proto(&map_constructor, &map_proto);
|
||||
wire_ctor_proto(®exp_constructor, ®exp_proto);
|
||||
|
||||
Self {
|
||||
object_proto,
|
||||
@@ -352,6 +378,12 @@ impl BuiltinWorld {
|
||||
set_proto,
|
||||
map_constructor,
|
||||
map_proto,
|
||||
regexp_constructor,
|
||||
regexp_proto,
|
||||
sym_match,
|
||||
sym_replace,
|
||||
sym_search,
|
||||
sym_split,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ const TAG_NULL: u64 = 2;
|
||||
const TAG_UNDEFINED: u64 = 3;
|
||||
const TAG_OBJECT: u64 = 4;
|
||||
const TAG_STRING: u64 = 5;
|
||||
const TAG_SYMBOL: u64 = 6;
|
||||
|
||||
/// 48-bit pointer mask (x86-64 canonical VA)
|
||||
const PTR_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
|
||||
@@ -77,6 +78,9 @@ impl PartialEq for JsValue {
|
||||
}
|
||||
return self.as_string_index() == other.as_string_index();
|
||||
}
|
||||
if self.is_symbol() && other.is_symbol() {
|
||||
return self.as_symbol_index() == other.as_symbol_index();
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -206,6 +210,19 @@ impl JsValue {
|
||||
debug_assert!(self.is_string(), "JsValue is not a string");
|
||||
((self.0 & STRING_HASH_MASK) >> STRING_HASH_SHIFT) as u16
|
||||
}
|
||||
|
||||
pub fn symbol(index: u32) -> Self {
|
||||
Self(make_tag(TAG_SYMBOL) | (index as u64))
|
||||
}
|
||||
|
||||
pub fn is_symbol(&self) -> bool {
|
||||
is_nan_boxed(self.0) && get_tag(self.0) == TAG_SYMBOL
|
||||
}
|
||||
|
||||
pub fn as_symbol_index(&self) -> u32 {
|
||||
debug_assert!(self.is_symbol());
|
||||
(self.0 & STRING_INDEX_MASK) as u32
|
||||
}
|
||||
}
|
||||
|
||||
fn is_nan_bits(bits: u64) -> bool {
|
||||
@@ -213,7 +230,7 @@ fn is_nan_bits(bits: u64) -> bool {
|
||||
}
|
||||
|
||||
fn is_nan_boxed(bits: u64) -> bool {
|
||||
(0xFFF8..=0xFFFD).contains(&((bits >> 48) as u16))
|
||||
(0xFFF8..=0xFFFE).contains(&((bits >> 48) as u16))
|
||||
}
|
||||
|
||||
impl fmt::Display for JsValue {
|
||||
@@ -243,6 +260,8 @@ impl fmt::Display for JsValue {
|
||||
write!(f, "{{object}}")
|
||||
} else if self.is_string() {
|
||||
write!(f, "{{string}}")
|
||||
} else if self.is_symbol() {
|
||||
write!(f, "Symbol(idx={})", self.as_symbol_index())
|
||||
} else {
|
||||
write!(f, "{{unknown}}")
|
||||
}
|
||||
@@ -275,6 +294,8 @@ impl fmt::Debug for JsValue {
|
||||
self.as_string_index(),
|
||||
self.as_string_hash()
|
||||
)
|
||||
} else if self.is_symbol() {
|
||||
write!(f, "JsValue(Symbol(idx={}))", self.as_symbol_index())
|
||||
} else {
|
||||
write!(f, "JsValue(Unknown)")
|
||||
}
|
||||
@@ -342,6 +363,7 @@ mod tests {
|
||||
val.is_undefined(),
|
||||
val.is_object(),
|
||||
val.is_string(),
|
||||
val.is_symbol(),
|
||||
];
|
||||
let count = matched.iter().filter(|&&x| x).count();
|
||||
assert_eq!(count, 1, "bits={bits:#018x} matched {count} types");
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use oxide_kernel::bind_method;
|
||||
use oxide_kernel::kernel::OxideKernel;
|
||||
use oxide_types::object::JsObject;
|
||||
|
||||
use crate::bind_constructor_hash;
|
||||
|
||||
pub fn bind_regexp(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
|
||||
let ctor_ptr = kernel.builtin_world().regexp_constructor.as_ptr() as *mut JsObject;
|
||||
let ctor = unsafe { &mut *ctor_ptr };
|
||||
let proto_ptr = kernel.builtin_world().regexp_proto.as_ptr() as *mut JsObject;
|
||||
let proto = unsafe { &mut *proto_ptr };
|
||||
let sf = kernel.string_forge().as_ref();
|
||||
let sh = kernel.shape_forge().as_ref();
|
||||
|
||||
ctor.set_native_fn(Some(crate::builtins::regexp::regexp_constructor as *const ()));
|
||||
ctor.set_native_arg_count(2);
|
||||
|
||||
bind_method!(
|
||||
kernel.builtin_world(),
|
||||
proto,
|
||||
sf,
|
||||
sh,
|
||||
"exec",
|
||||
crate::builtins::regexp::regexp_exec,
|
||||
1
|
||||
);
|
||||
bind_method!(
|
||||
kernel.builtin_world(),
|
||||
proto,
|
||||
sf,
|
||||
sh,
|
||||
"test",
|
||||
crate::builtins::regexp::regexp_test,
|
||||
1
|
||||
);
|
||||
bind_method!(
|
||||
kernel.builtin_world(),
|
||||
proto,
|
||||
sf,
|
||||
sh,
|
||||
"toString",
|
||||
crate::builtins::regexp::regexp_to_string,
|
||||
0
|
||||
);
|
||||
|
||||
bind_constructor_hash!(
|
||||
kernel,
|
||||
global,
|
||||
"RegExp",
|
||||
ctor_ptr,
|
||||
crate::builtins::regexp::regexp_constructor,
|
||||
2
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use oxide_kernel::bind_method;
|
||||
use oxide_kernel::kernel::OxideKernel;
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::bind_constructor_hash;
|
||||
|
||||
pub fn bind_symbol(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
|
||||
let ctor_ptr = kernel.builtin_world().symbol_constructor.as_ptr() as *mut JsObject;
|
||||
let ctor = unsafe { &mut *ctor_ptr };
|
||||
let proto_ptr = kernel.builtin_world().symbol_proto.as_ptr() as *mut JsObject;
|
||||
let proto = unsafe { &mut *proto_ptr };
|
||||
let sf = kernel.string_forge().as_ref();
|
||||
let sh = kernel.shape_forge().as_ref();
|
||||
|
||||
bind_method!(
|
||||
kernel.builtin_world(),
|
||||
proto,
|
||||
sf,
|
||||
sh,
|
||||
"toString",
|
||||
crate::builtins::symbol::symbol_to_string,
|
||||
0
|
||||
);
|
||||
|
||||
let sym_match_val = JsValue::from_js_object(
|
||||
kernel.builtin_world().sym_match.as_ptr() as *mut JsObject,
|
||||
);
|
||||
bind_well_known_symbol(kernel, ctor, sf, sh, "match", sym_match_val);
|
||||
|
||||
let sym_replace_val = JsValue::from_js_object(
|
||||
kernel.builtin_world().sym_replace.as_ptr() as *mut JsObject,
|
||||
);
|
||||
bind_well_known_symbol(kernel, ctor, sf, sh, "replace", sym_replace_val);
|
||||
|
||||
let sym_search_val = JsValue::from_js_object(
|
||||
kernel.builtin_world().sym_search.as_ptr() as *mut JsObject,
|
||||
);
|
||||
bind_well_known_symbol(kernel, ctor, sf, sh, "search", sym_search_val);
|
||||
|
||||
let sym_split_val = JsValue::from_js_object(
|
||||
kernel.builtin_world().sym_split.as_ptr() as *mut JsObject,
|
||||
);
|
||||
bind_well_known_symbol(kernel, ctor, sf, sh, "split", sym_split_val);
|
||||
|
||||
bind_constructor_hash!(
|
||||
kernel,
|
||||
global,
|
||||
"Symbol",
|
||||
ctor_ptr,
|
||||
crate::builtins::symbol::symbol_constructor,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
fn bind_well_known_symbol(
|
||||
kernel: &Arc<OxideKernel>,
|
||||
ctor: &mut JsObject,
|
||||
_sf: &oxide_kernel::string_forge::StringForge,
|
||||
_sh: &oxide_kernel::shape_forge::ShapeForge,
|
||||
name: &str,
|
||||
val: JsValue,
|
||||
) {
|
||||
let si = kernel.string_forge().intern(name).0;
|
||||
let shape_id = kernel.shape_forge().make_shape(ctor.shape_id(), si);
|
||||
ctor.set_shape_id(shape_id);
|
||||
ctor.ensure_hash_props().push(Box::new(val));
|
||||
}
|
||||
@@ -8,8 +8,10 @@ pub mod bind_map;
|
||||
pub mod bind_math;
|
||||
pub mod bind_number;
|
||||
pub mod bind_object;
|
||||
pub mod bind_regexp;
|
||||
pub mod bind_set;
|
||||
pub mod bind_string;
|
||||
pub mod bind_symbol;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -60,4 +62,6 @@ pub fn init_kernel_builtins(kernel: &Arc<OxideKernel>) {
|
||||
bind_map::bind_map(kernel, global);
|
||||
bind_boolean::bind_boolean(kernel, global);
|
||||
bind_function::bind_function(kernel);
|
||||
bind_regexp::bind_regexp(kernel, global);
|
||||
bind_symbol::bind_symbol(kernel, global);
|
||||
}
|
||||
|
||||
@@ -9,5 +9,7 @@ pub mod map;
|
||||
pub mod math;
|
||||
pub mod number;
|
||||
pub mod object;
|
||||
pub mod regexp;
|
||||
pub mod set;
|
||||
pub mod string;
|
||||
pub mod symbol;
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
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;
|
||||
|
||||
fn get_regexp_ptr(vm: &Vm, args: &[u8]) -> Result<*mut JsObject, JsValue> {
|
||||
let this_val = vm.reg(args[0]);
|
||||
if !this_val.is_object() {
|
||||
return Err(JsValue::string(
|
||||
vm.kernel()
|
||||
.string_forge()
|
||||
.intern("TypeError: RegExp.prototype method called on non-object")
|
||||
.0,
|
||||
0,
|
||||
));
|
||||
}
|
||||
let ptr = this_val.as_js_object_ptr();
|
||||
if ptr.is_null() {
|
||||
return Err(JsValue::string(
|
||||
vm.kernel()
|
||||
.string_forge()
|
||||
.intern("TypeError: null object")
|
||||
.0,
|
||||
0,
|
||||
));
|
||||
}
|
||||
Ok(ptr)
|
||||
}
|
||||
|
||||
fn parse_flags(flags: &str) -> (bool, bool, bool) {
|
||||
let mut global = false;
|
||||
let mut ignore_case = false;
|
||||
let mut multi_line = false;
|
||||
for c in flags.chars() {
|
||||
match c {
|
||||
'g' => global = true,
|
||||
'i' => ignore_case = true,
|
||||
'm' => multi_line = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
(global, ignore_case, multi_line)
|
||||
}
|
||||
|
||||
fn set_prop(obj: &mut JsObject, name: &str, val: JsValue, vm: &Vm) {
|
||||
let si = vm.kernel().string_forge().intern(name).0;
|
||||
let shape_id = vm.kernel().shape_forge().make_shape(obj.shape_id(), si);
|
||||
obj.set_shape_id(shape_id);
|
||||
obj.ensure_hash_props().push(Box::new(val));
|
||||
}
|
||||
|
||||
fn get_prop(obj: &JsObject, idx: usize) -> JsValue {
|
||||
obj.hash_props_vec()
|
||||
.and_then(|v| v.get(idx))
|
||||
.map(|b| **b)
|
||||
.unwrap_or(JsValue::undefined())
|
||||
}
|
||||
|
||||
fn set_prop_at(obj: *mut JsObject, idx: usize, val: JsValue) {
|
||||
unsafe {
|
||||
let re = &mut *obj;
|
||||
if let Some(vec) = re.hash_props_vec() {
|
||||
let ptr = vec.as_ptr() as *mut Box<JsValue>;
|
||||
let v = &mut *ptr.add(idx);
|
||||
**v = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn regexp_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let (pattern, flags) = if args.len() < 2 {
|
||||
(String::new(), String::new())
|
||||
} else if args.len() < 3 {
|
||||
let pat = coercion::to_string(vm.kernel().string_forge().as_ref(), vm.reg(args[1]));
|
||||
(pat, String::new())
|
||||
} else {
|
||||
let pat = coercion::to_string(vm.kernel().string_forge().as_ref(), vm.reg(args[1]));
|
||||
let fl = coercion::to_string(vm.kernel().string_forge().as_ref(), vm.reg(args[2]));
|
||||
(pat, fl)
|
||||
};
|
||||
|
||||
let (global, ignore_case, multi_line) = parse_flags(&flags);
|
||||
|
||||
let mut obj = JsObject::new_empty(
|
||||
EMPTY_SHAPE_ID,
|
||||
JsValue::from_js_object(
|
||||
vm.kernel().builtin_world().regexp_proto.as_ptr() as *mut JsObject,
|
||||
),
|
||||
);
|
||||
|
||||
let compiled = regex::RegexBuilder::new(&pattern)
|
||||
.case_insensitive(ignore_case)
|
||||
.multi_line(multi_line)
|
||||
.build();
|
||||
|
||||
match compiled {
|
||||
Ok(re) => {
|
||||
let re_ptr = Box::into_raw(Box::new(re));
|
||||
obj.set_native_fn(Some(re_ptr as *const ()));
|
||||
}
|
||||
Err(e) => {
|
||||
let msg = format!("SyntaxError: Invalid regular expression: {}", e);
|
||||
return Err(JsValue::string(
|
||||
vm.kernel().string_forge().intern(&msg).0,
|
||||
0,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
set_prop(&mut obj, "lastIndex", JsValue::int(0), vm);
|
||||
set_prop(&mut obj, "source", vm.intern(&pattern), vm);
|
||||
set_prop(&mut obj, "flags", vm.intern(&flags), vm);
|
||||
set_prop(&mut obj, "global", JsValue::bool(global), vm);
|
||||
set_prop(&mut obj, "ignoreCase", JsValue::bool(ignore_case), vm);
|
||||
set_prop(&mut obj, "multiline", JsValue::bool(multi_line), vm);
|
||||
set_prop(&mut obj, "dotAll", JsValue::bool(false), vm);
|
||||
set_prop(&mut obj, "sticky", JsValue::bool(false), vm);
|
||||
set_prop(&mut obj, "unicode", JsValue::bool(false), vm);
|
||||
|
||||
let obj_ptr = vm.epoch().alloc(obj);
|
||||
Ok(JsValue::from_js_object(obj_ptr))
|
||||
}
|
||||
|
||||
pub fn regexp_test(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let re_ptr = get_regexp_ptr(vm, args)?;
|
||||
let re = unsafe { &*re_ptr };
|
||||
|
||||
let fn_ptr = match re.native_fn() {
|
||||
None => {
|
||||
return Err(JsValue::string(
|
||||
vm.kernel()
|
||||
.string_forge()
|
||||
.intern("TypeError: invalid RegExp")
|
||||
.0,
|
||||
0,
|
||||
));
|
||||
}
|
||||
Some(p) => p,
|
||||
};
|
||||
|
||||
let regex = unsafe { &*(fn_ptr as *const regex::Regex) };
|
||||
let haystack = coercion::to_string(
|
||||
vm.kernel().string_forge().as_ref(),
|
||||
vm.reg(if args.len() > 1 { args[1] } else { args[0] }),
|
||||
);
|
||||
let last_index =
|
||||
coercion::to_number(get_prop(re, 0), vm.kernel().string_forge().as_ref()) as usize;
|
||||
let is_global = get_prop(re, 3).as_bool();
|
||||
|
||||
if is_global {
|
||||
let result = regex.find_at(&haystack, last_index).is_some();
|
||||
Ok(JsValue::bool(result))
|
||||
} else {
|
||||
Ok(JsValue::bool(regex.is_match(&haystack)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn regexp_exec(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let re_ptr = get_regexp_ptr(vm, args)?;
|
||||
|
||||
let fn_ptr = {
|
||||
let re = unsafe { &*re_ptr };
|
||||
match re.native_fn() {
|
||||
None => {
|
||||
return Err(JsValue::string(
|
||||
vm.kernel()
|
||||
.string_forge()
|
||||
.intern("TypeError: invalid RegExp")
|
||||
.0,
|
||||
0,
|
||||
));
|
||||
}
|
||||
Some(p) => p,
|
||||
}
|
||||
};
|
||||
|
||||
let regex = unsafe { &*(fn_ptr as *const regex::Regex) };
|
||||
let haystack = coercion::to_string(
|
||||
vm.kernel().string_forge().as_ref(),
|
||||
vm.reg(if args.len() > 1 { args[1] } else { args[0] }),
|
||||
);
|
||||
|
||||
let (last_index, is_global) = {
|
||||
let re = unsafe { &*re_ptr };
|
||||
let li =
|
||||
coercion::to_number(get_prop(re, 0), vm.kernel().string_forge().as_ref()) as usize;
|
||||
let g = get_prop(re, 3).as_bool();
|
||||
(li, g)
|
||||
};
|
||||
|
||||
let match_result = if is_global {
|
||||
if last_index > haystack.len() {
|
||||
return Ok(JsValue::null());
|
||||
}
|
||||
regex.find_at(&haystack, last_index)
|
||||
} else {
|
||||
regex.find(&haystack)
|
||||
};
|
||||
|
||||
if let Some(m) = match_result {
|
||||
let mut match_obj = JsObject::new_empty(
|
||||
EMPTY_SHAPE_ID,
|
||||
JsValue::from_js_object(
|
||||
vm.kernel().builtin_world().array_proto.as_ptr() as *mut JsObject,
|
||||
),
|
||||
);
|
||||
|
||||
let full_match = &haystack[m.start()..m.end()];
|
||||
let full_si = vm.kernel().string_forge().intern(full_match).0;
|
||||
match_obj
|
||||
.ensure_hash_props()
|
||||
.push(Box::new(JsValue::string(full_si, 0)));
|
||||
|
||||
let captures = regex.captures(&haystack[m.start()..m.end()]);
|
||||
if let Some(caps) = &captures {
|
||||
for i in 1..caps.len() {
|
||||
let cap_str = caps.get(i).map(|cm| cm.as_str()).unwrap_or("");
|
||||
let si = vm.kernel().string_forge().intern(cap_str).0;
|
||||
match_obj
|
||||
.ensure_hash_props()
|
||||
.push(Box::new(JsValue::string(si, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
set_prop(&mut match_obj, "index", JsValue::int(m.start() as i32), vm);
|
||||
let haystack_val = vm.intern(&haystack);
|
||||
set_prop(&mut match_obj, "input", haystack_val, vm);
|
||||
set_prop(&mut match_obj, "groups", JsValue::undefined(), vm);
|
||||
|
||||
if is_global {
|
||||
set_prop_at(re_ptr, 0, JsValue::int(m.end() as i32));
|
||||
}
|
||||
|
||||
let obj_ptr = vm.epoch().alloc(match_obj);
|
||||
Ok(JsValue::from_js_object(obj_ptr))
|
||||
} else {
|
||||
if is_global {
|
||||
set_prop_at(re_ptr, 0, JsValue::int(0));
|
||||
}
|
||||
Ok(JsValue::null())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn regexp_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let re_ptr = get_regexp_ptr(vm, args)?;
|
||||
let re = unsafe { &*re_ptr };
|
||||
let source = {
|
||||
let val = get_prop(re, 1);
|
||||
vm.lookup_str(val).unwrap_or_default()
|
||||
};
|
||||
let flags = {
|
||||
let val = get_prop(re, 2);
|
||||
vm.lookup_str(val).unwrap_or_default()
|
||||
};
|
||||
let result = format!("/{}/{}", source, flags);
|
||||
let si = vm.kernel().string_forge().intern(&result).0;
|
||||
Ok(JsValue::string(si, 0))
|
||||
}
|
||||
@@ -6,7 +6,6 @@ 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]))
|
||||
@@ -35,6 +34,27 @@ fn as_string(vm: &Vm, val: JsValue) -> String {
|
||||
coercion::to_string(vm.kernel().string_forge().as_ref(), val)
|
||||
}
|
||||
|
||||
fn is_regexp_obj(val: JsValue, vm: &Vm) -> bool {
|
||||
if !val.is_object() {
|
||||
return false;
|
||||
}
|
||||
let ptr = val.as_js_object_ptr();
|
||||
if ptr.is_null() {
|
||||
return false;
|
||||
}
|
||||
let obj = unsafe { &*ptr };
|
||||
let proto = obj.proto();
|
||||
if !proto.is_object() {
|
||||
return false;
|
||||
}
|
||||
let proto_ptr = proto.as_js_object_ptr();
|
||||
if proto_ptr.is_null() {
|
||||
return false;
|
||||
}
|
||||
let rp = vm.kernel().builtin_world().regexp_proto.as_ptr() as *mut JsObject;
|
||||
std::ptr::eq(proto_ptr, rp)
|
||||
}
|
||||
|
||||
pub fn string_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let s = this_string(vm, args);
|
||||
if args.len() < 2 {
|
||||
@@ -291,27 +311,35 @@ pub fn string_split(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let parts = vec![s.clone()];
|
||||
return Ok(make_string_array(vm, &parts));
|
||||
}
|
||||
let sep = as_string(vm, vm.reg(args[1]));
|
||||
let sep_val = 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();
|
||||
if is_regexp_obj(sep_val, vm) {
|
||||
let re_ptr = sep_val.as_js_object_ptr();
|
||||
let re = unsafe { &*re_ptr };
|
||||
let fn_ptr = match re.native_fn() {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
let parts = vec![s.clone()];
|
||||
return Ok(make_string_array(vm, &parts));
|
||||
}
|
||||
};
|
||||
let regex = unsafe { &*(fn_ptr as *const regex::Regex) };
|
||||
let raw: Vec<&str> = regex.split(&s).collect();
|
||||
let parts: Vec<String> = raw.iter().map(|p| p.to_string()).take(limit).collect();
|
||||
return Ok(make_string_array(vm, &parts));
|
||||
}
|
||||
|
||||
let sep = as_string(vm, sep_val);
|
||||
let parts: Vec<String> = if sep.is_empty() {
|
||||
s.chars().map(|c| c.to_string()).take(limit).collect()
|
||||
} else {
|
||||
s.split(&sep).map(|p| p.to_string()).take(limit).collect()
|
||||
};
|
||||
Ok(make_string_array(vm, &parts))
|
||||
}
|
||||
|
||||
@@ -320,23 +348,27 @@ pub fn string_replace(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return Ok(vm.intern(&s));
|
||||
}
|
||||
let pattern = as_string(vm, vm.reg(args[1]));
|
||||
let pattern_val = 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)
|
||||
};
|
||||
if is_regexp_obj(pattern_val, vm) {
|
||||
let re_ptr = pattern_val.as_js_object_ptr();
|
||||
let re = unsafe { &*re_ptr };
|
||||
let fn_ptr = match re.native_fn() {
|
||||
Some(p) => p,
|
||||
None => return Ok(vm.intern(&s)),
|
||||
};
|
||||
let regex = unsafe { &*(fn_ptr as *const regex::Regex) };
|
||||
let result = regex.replace_all(&s, replacement.as_str()).to_string();
|
||||
return Ok(vm.intern(&result));
|
||||
}
|
||||
|
||||
let pattern = as_string(vm, pattern_val);
|
||||
let result = s.replacen(&pattern, &replacement, 1);
|
||||
Ok(vm.intern(&result))
|
||||
}
|
||||
|
||||
@@ -345,14 +377,22 @@ pub fn string_match_fn(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
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) {
|
||||
let pattern_val = vm.reg(args[1]);
|
||||
|
||||
if is_regexp_obj(pattern_val, vm) {
|
||||
let re_ptr = pattern_val.as_js_object_ptr();
|
||||
let re = unsafe { &*re_ptr };
|
||||
let fn_ptr = match re.native_fn() {
|
||||
Some(p) => p,
|
||||
None => return Ok(JsValue::undefined()),
|
||||
};
|
||||
let regex = unsafe { &*(fn_ptr as *const regex::Regex) };
|
||||
let matches: Vec<String> = regex.find_iter(&s).map(|m| m.as_str().to_string()).collect();
|
||||
return Ok(make_string_array(vm, &matches));
|
||||
}
|
||||
|
||||
let pattern = as_string(vm, pattern_val);
|
||||
if let Some(pos) = s.find(&pattern) {
|
||||
return Ok(make_string_array(
|
||||
vm,
|
||||
&[s[pos..pos + pattern.len()].to_string()],
|
||||
@@ -366,15 +406,24 @@ pub fn string_search(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
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));
|
||||
}
|
||||
let pattern_val = vm.reg(args[1]);
|
||||
|
||||
if is_regexp_obj(pattern_val, vm) {
|
||||
let re_ptr = pattern_val.as_js_object_ptr();
|
||||
let re = unsafe { &*re_ptr };
|
||||
let fn_ptr = match re.native_fn() {
|
||||
Some(p) => p,
|
||||
None => return Ok(JsValue::int(-1)),
|
||||
};
|
||||
let regex = unsafe { &*(fn_ptr as *const regex::Regex) };
|
||||
if let Some(m) = regex.find(&s) {
|
||||
return Ok(JsValue::int(m.start() as i32));
|
||||
}
|
||||
} else if let Some(pos) = s.find(&pattern) {
|
||||
return Ok(JsValue::int(-1));
|
||||
}
|
||||
|
||||
let pattern = as_string(vm, pattern_val);
|
||||
if let Some(pos) = s.find(&pattern) {
|
||||
return Ok(JsValue::int(pos as i32));
|
||||
}
|
||||
Ok(JsValue::int(-1))
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::coercion;
|
||||
use crate::native::NativeResult;
|
||||
use crate::vm::Vm;
|
||||
|
||||
pub fn symbol_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
if this_val.is_object() {
|
||||
let ptr = this_val.as_js_object_ptr();
|
||||
if !ptr.is_null() {
|
||||
let obj = unsafe { &*ptr };
|
||||
let proto = obj.proto();
|
||||
if proto.is_object() {
|
||||
let proto_ptr = proto.as_js_object_ptr();
|
||||
if !proto_ptr.is_null() {
|
||||
let sp = vm.kernel().builtin_world().symbol_proto.as_ptr() as *mut JsObject;
|
||||
if std::ptr::eq(proto_ptr, sp) {
|
||||
return Err(JsValue::string(
|
||||
vm.kernel()
|
||||
.string_forge()
|
||||
.intern("TypeError: Symbol is not a constructor")
|
||||
.0,
|
||||
0,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let description = if args.len() > 1 {
|
||||
coercion::to_string(vm.kernel().string_forge().as_ref(), vm.reg(args[1]))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
vm.symbol_counter = vm.symbol_counter.wrapping_add(1);
|
||||
let idx = vm.symbol_descriptions.len() as u32;
|
||||
vm.symbol_descriptions.push(description);
|
||||
Ok(JsValue::symbol(idx))
|
||||
}
|
||||
|
||||
pub fn symbol_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(args[0]);
|
||||
if !this_val.is_symbol() {
|
||||
return Err(JsValue::string(
|
||||
vm.kernel()
|
||||
.string_forge()
|
||||
.intern("TypeError: Symbol.prototype.toString requires a Symbol")
|
||||
.0,
|
||||
0,
|
||||
));
|
||||
}
|
||||
|
||||
let idx = this_val.as_symbol_index();
|
||||
let desc = vm
|
||||
.symbol_descriptions
|
||||
.get(idx as usize)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let result = format!("Symbol({})", desc);
|
||||
let si = vm.kernel().string_forge().intern(&result).0;
|
||||
Ok(JsValue::string(si, 0))
|
||||
}
|
||||
@@ -101,6 +101,9 @@ pub fn to_boolean(val: JsValue, string_forge: &StringForge) -> bool {
|
||||
if val.is_object() {
|
||||
return true;
|
||||
}
|
||||
if val.is_symbol() {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
@@ -172,6 +175,9 @@ pub fn strict_eq(lhs: JsValue, rhs: JsValue) -> bool {
|
||||
if lhs.is_object() && rhs.is_object() {
|
||||
return lhs.as_ptr() == rhs.as_ptr();
|
||||
}
|
||||
if lhs.is_symbol() && rhs.is_symbol() {
|
||||
return lhs.as_symbol_index() == rhs.as_symbol_index();
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
@@ -260,6 +266,9 @@ pub fn same_value(lhs: JsValue, rhs: JsValue) -> bool {
|
||||
if lhs.is_object() && rhs.is_object() {
|
||||
return lhs.as_ptr() == rhs.as_ptr();
|
||||
}
|
||||
if lhs.is_symbol() && rhs.is_symbol() {
|
||||
return lhs.as_symbol_index() == rhs.as_symbol_index();
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ impl Vm {
|
||||
"number"
|
||||
} else if val.is_string() {
|
||||
"string"
|
||||
} else if val.is_symbol() {
|
||||
"symbol"
|
||||
} else if val.is_object() {
|
||||
let obj = unsafe { &*val.as_js_object_ptr() };
|
||||
if obj.is_function() {
|
||||
|
||||
@@ -172,6 +172,8 @@ pub struct Vm {
|
||||
pub(crate) exception_value: Option<JsValue>,
|
||||
pub(crate) pending_exception: Option<JsValue>,
|
||||
pub(crate) pending_error_kind: Option<&'static str>,
|
||||
pub(crate) symbol_counter: u32,
|
||||
pub(crate) symbol_descriptions: Vec<String>,
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
@@ -199,10 +201,13 @@ impl Vm {
|
||||
exception_value: None,
|
||||
pending_exception: None,
|
||||
pending_error_kind: None,
|
||||
symbol_counter: 0,
|
||||
symbol_descriptions: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_kernel(kernel: Arc<OxideKernel>) -> Self {
|
||||
let obj_proto = P::clone(&kernel.builtin_world().object_proto);
|
||||
Self {
|
||||
regs: [JsValue::undefined(); 256],
|
||||
pc: 0,
|
||||
@@ -210,10 +215,10 @@ impl Vm {
|
||||
constants: Vec::new(),
|
||||
frames: Vec::with_capacity(128),
|
||||
for_in_iters: Vec::new(),
|
||||
kernel: Arc::clone(&kernel),
|
||||
kernel,
|
||||
interned_strings: Vec::new(),
|
||||
epoch: Epoch::new(),
|
||||
object_prototype: P::clone(&kernel.builtin_world().object_proto),
|
||||
object_prototype: obj_proto,
|
||||
math_rng_state: 0,
|
||||
sub_modules: Vec::new(),
|
||||
sub_module_constants: Vec::new(),
|
||||
@@ -223,6 +228,8 @@ impl Vm {
|
||||
exception_value: None,
|
||||
pending_exception: None,
|
||||
pending_error_kind: None,
|
||||
symbol_counter: 0,
|
||||
symbol_descriptions: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,6 +315,34 @@ impl Vm {
|
||||
Constant::Null => JsValue::null(),
|
||||
Constant::Undefined => JsValue::undefined(),
|
||||
Constant::BytecodeFunc(idx) => self.create_function_object(*idx),
|
||||
Constant::RegExp(pattern, flags) => {
|
||||
let pat_si = self.kernel.string_forge().intern(pattern).0;
|
||||
let flags_si = self.kernel.string_forge().intern(flags).0;
|
||||
let pat_val = JsValue::string(pat_si, 0);
|
||||
let flags_val = JsValue::string(flags_si, 0);
|
||||
|
||||
let native_fn = self.kernel.builtin_world().regexp_constructor.as_ptr() as *mut JsObject;
|
||||
let ctor = unsafe { &*native_fn };
|
||||
let ctor_fn = ctor.native_fn();
|
||||
if ctor_fn.is_none() {
|
||||
return JsValue::undefined();
|
||||
}
|
||||
let saved_0 = self.regs[0];
|
||||
let saved_1 = self.regs[1];
|
||||
let saved_2 = self.regs[2];
|
||||
self.regs[0] = JsValue::undefined();
|
||||
self.regs[1] = pat_val;
|
||||
self.regs[2] = flags_val;
|
||||
let func: crate::native::NativeFn = unsafe { std::mem::transmute(ctor_fn.unwrap()) };
|
||||
let result = func(self, &[0, 1, 2]);
|
||||
self.regs[0] = saved_0;
|
||||
self.regs[1] = saved_1;
|
||||
self.regs[2] = saved_2;
|
||||
match result {
|
||||
Ok(val) => val,
|
||||
Err(_) => JsValue::undefined(),
|
||||
}
|
||||
},
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -706,6 +741,7 @@ impl Vm {
|
||||
Constant::BytecodeFunc(idx) => {
|
||||
self.create_function_object(*idx)
|
||||
}
|
||||
Constant::RegExp(_, _) => JsValue::undefined(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
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()
|
||||
}
|
||||
|
||||
// --- RegExp constructor ---
|
||||
|
||||
#[test]
|
||||
fn regexp_constructor_creates_object() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "typeof new RegExp('abc')").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "object");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_constructor_with_flags() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "new RegExp('abc', 'gi').ignoreCase").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_constructor_invalid_pattern_syntax_error() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "new RegExp('[')");
|
||||
assert!(result.is_err() || (result.is_ok() && result.unwrap().is_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_source_property() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "new RegExp('hello', 'g').source").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_flags_property() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "new RegExp('abc', 'gi').flags").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "gi");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_global_property() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "new RegExp('abc', 'g').global").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_multiline_property() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "new RegExp('abc', 'm').multiline").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_last_index_property() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "new RegExp('abc', 'g').lastIndex").unwrap();
|
||||
assert_eq!(result.as_int(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_dotall_sticky_unicode_default_false() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "new RegExp('abc').dotAll").unwrap();
|
||||
assert!(!result.as_bool());
|
||||
}
|
||||
|
||||
// --- RegExp.prototype.test ---
|
||||
|
||||
#[test]
|
||||
fn regexp_test_basic() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "new RegExp('abc').test('abc')").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_test_no_match() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "new RegExp('abc').test('xyz')").unwrap();
|
||||
assert!(!result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_test_case_insensitive() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "new RegExp('abc', 'i').test('ABC')").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_test_global_flag_iteration() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "var re = /a/g; re.test('a')").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
// --- RegExp.prototype.exec ---
|
||||
|
||||
#[test]
|
||||
fn regexp_exec_basic() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "/hello/.exec('hello world') !== null").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_exec_no_match_returns_null() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "/xyz/.exec('hello')").unwrap();
|
||||
assert!(result.is_null());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_exec_returns_index() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "/ell/.exec('hello')").unwrap();
|
||||
assert!(result.is_object());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_exec_returns_input() {
|
||||
let mut vm = Vm::new();
|
||||
let m = eval(&mut vm, "/ell/.exec('hello')").unwrap();
|
||||
assert!(m.is_object());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_exec_global_last_index_advances() {
|
||||
let mut vm = Vm::new();
|
||||
let re = eval(&mut vm, "var re = /a/g; re.exec('aba'); re.lastIndex").unwrap();
|
||||
assert_eq!(re.as_int(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_exec_global_resets_on_exhaustion() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "var re = /a/g; re.exec('a'); re.exec('a') === null").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
// --- RegExp literal compilation ---
|
||||
|
||||
#[test]
|
||||
fn regexp_literal_basic() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "/hello/").unwrap();
|
||||
assert!(result.is_object());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_literal_with_flags() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "/abc/i.test('ABC')").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regexp_literal_global_exec() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "var re = /a/g; re.exec('aba'); re.lastIndex").unwrap();
|
||||
assert_eq!(result.as_int(), 1);
|
||||
}
|
||||
|
||||
// --- String integration ---
|
||||
|
||||
#[test]
|
||||
fn string_match_with_regexp() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'hello world'.match(/ell/)").unwrap();
|
||||
assert!(result.is_object());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_replace_with_regexp() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'hello'.replace(/ell/, 'ipp')").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "hippo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_replace_with_regexp_global() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'aba'.replace(/a/g, 'c')").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "cbc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_search_with_regexp() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'hello'.search(/ell/)").unwrap();
|
||||
assert_eq!(result.as_int(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_search_not_found() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'hello'.search(/xyz/)").unwrap();
|
||||
assert_eq!(result.as_int(), -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_split_with_regexp() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "'a,b,c'.split(/,/)").unwrap();
|
||||
assert!(result.is_object());
|
||||
}
|
||||
|
||||
// --- toString ---
|
||||
|
||||
#[test]
|
||||
fn regexp_to_string() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "/abc/gi.toString()").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "/abc/gi");
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
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()
|
||||
}
|
||||
|
||||
// --- Symbol constructor ---
|
||||
|
||||
#[test]
|
||||
fn symbol_creates_value() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "Symbol()").unwrap();
|
||||
assert!(result.is_symbol());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_with_description() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "Symbol('hello') !== Symbol('hello')").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_unique_identity() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "var a = Symbol('x'); var b = Symbol('x'); a === b").unwrap();
|
||||
assert!(!result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_same_reference_equals() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "var s = Symbol('x'); s === s").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
// --- typeof Symbol ---
|
||||
|
||||
#[test]
|
||||
fn typeof_symbol_returns_symbol() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "typeof Symbol()").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "symbol");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typeof_symbol_variable() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "var s = Symbol('test'); typeof s").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "symbol");
|
||||
}
|
||||
|
||||
// --- Symbol well-known properties ---
|
||||
|
||||
#[test]
|
||||
fn symbol_match_exists() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "typeof Symbol.match").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "object");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_replace_exists() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "typeof Symbol.replace").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "object");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_search_exists() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "typeof Symbol.search").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "object");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_split_exists() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "typeof Symbol.split").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "object");
|
||||
}
|
||||
|
||||
// --- Symbol coercion ---
|
||||
|
||||
#[test]
|
||||
fn symbol_is_truthy() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "Symbol() ? true : false").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
// --- Symbol types ---
|
||||
|
||||
#[test]
|
||||
fn symbol_not_equals_object() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "Symbol() === {}").unwrap();
|
||||
assert!(!result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_not_equals_string() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "Symbol() === 'symbol'").unwrap();
|
||||
assert!(!result.as_bool());
|
||||
}
|
||||
Reference in New Issue
Block a user