feat: 支持箭头函数/模板字符串/let/const/delete/instanceof 语法
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
use oxide_types::value::JsValue;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn eval(source: &str) -> String {
|
||||
let allocator = Allocator::default();
|
||||
let program = match oxide_parser::parse(&allocator, source) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return format!("parse error: {}", e[0].message),
|
||||
};
|
||||
let module = match Compiler::new().compile(&program) {
|
||||
Ok(m) => m,
|
||||
Err(e) => return format!("compile error: {}", e),
|
||||
};
|
||||
let mut vm = Vm::new();
|
||||
match vm.run(&module) {
|
||||
Ok(result) => format!("{}", result),
|
||||
Err(e) => format!("vm error: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_val(source: &str) -> (Vm, Result<JsValue, String>) {
|
||||
let allocator = Allocator::default();
|
||||
let program = match oxide_parser::parse(&allocator, source) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return (Vm::new(), Err(format!("parse error: {}", e[0].message))),
|
||||
};
|
||||
let module = match Compiler::new().compile(&program) {
|
||||
Ok(m) => m,
|
||||
Err(e) => return (Vm::new(), Err(format!("compile error: {}", e))),
|
||||
};
|
||||
let mut vm = Vm::new();
|
||||
let result = vm.run(&module);
|
||||
(vm, result)
|
||||
}
|
||||
|
||||
fn to_str(vm: &Vm, val: JsValue) -> String {
|
||||
if val.is_string() {
|
||||
vm.kernel()
|
||||
.string_forge()
|
||||
.lookup(val.as_string_index())
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
format!("{}", val)
|
||||
}
|
||||
}
|
||||
|
||||
// -- Arrow function integration tests --
|
||||
|
||||
#[test]
|
||||
fn arrow_expression_body_returns_value() {
|
||||
assert_eq!(eval("(() => 42)()"), "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arrow_block_body_returns_value() {
|
||||
assert_eq!(eval("((a,b) => { return a+b; })(3,4)"), "7");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arrow_single_param_no_parens() {
|
||||
assert_eq!(eval("(x => x * 2)(5)"), "10");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arrow_multiple_params_with_parens() {
|
||||
assert_eq!(eval("((x, y) => x + y)(3, 7)"), "10");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arrow_stored_in_variable_callable() {
|
||||
assert_eq!(eval("var f = () => 99; f()"), "99");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arrow_stored_in_variable_with_params() {
|
||||
assert_eq!(eval("var add = (a,b) => a+b; add(10,20)"), "30");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arrow_not_constructable_throws() {
|
||||
let result = eval("new (() => {})");
|
||||
assert!(
|
||||
result.contains("TypeError") || result.contains("arrow"),
|
||||
"Expected arrow constructor TypeError, got: {}",
|
||||
result
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arrow_no_params_string_result() {
|
||||
let (vm, result) = eval_val("(() => 'hello')()");
|
||||
let s = to_str(&vm, result.unwrap());
|
||||
assert_eq!(s, "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arrow_expression_body_implicit_return() {
|
||||
assert_eq!(eval("(() => 1 + 2)()"), "3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arrow_block_body_needs_explicit_return() {
|
||||
assert_eq!(eval("((a,b) => { a+b; })(3,4)"), "undefined");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arrow_at_global_scope_this_is_undefined() {
|
||||
assert_eq!(eval("(() => this)()"), "undefined");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_arrows_in_same_scope() {
|
||||
assert_eq!(eval("var a = () => 1, b = () => 2; a() + b()"), "3");
|
||||
}
|
||||
|
||||
// NOTE: Arrow functions capturing lexical `this` from enclosing functions
|
||||
// require nested function call support (sub_module flattening), which is
|
||||
// a pre-existing limitation. These tests will be enabled in a future phase:
|
||||
//
|
||||
// fn arrow_captures_enclosing_this() {
|
||||
// // const o = {x:10, f:function(){ return (() => this.x)(); }}; o.f()
|
||||
// // Expected: 10
|
||||
// }
|
||||
@@ -9,7 +9,7 @@ fn make_vm() -> Vm {
|
||||
|
||||
fn eval(source: &str) -> Result<JsValue, String> {
|
||||
let allocator = oxide_parser::Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse: {}", e))?;
|
||||
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse: {:?}", e))?;
|
||||
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile: {}", e))?;
|
||||
let mut vm = make_vm();
|
||||
vm.run(&module)
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_types::value::JsValue;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn eval(source: &str) -> Result<JsValue, String> {
|
||||
let allocator = oxide_parser::Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse: {:?}", e))?;
|
||||
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile: {}", e))?;
|
||||
let mut vm = Vm::new();
|
||||
vm.run(&module)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_block_scoping_outer_unchanged() {
|
||||
let result = eval("let x = 1; { let x = 2; } x").unwrap();
|
||||
assert_eq!(result.as_int(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_reassignment_throws() {
|
||||
let result = eval("const x = 1; x = 2");
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert!(err.contains("TypeError") || err.contains("constant"), "got: {}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_block_not_isolated() {
|
||||
let result = eval("var x = 5; { x = 10; } x").unwrap();
|
||||
assert_eq!(result.as_int(), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_existing_property() {
|
||||
let result = eval("var o = {a: 1}; delete o.a; o.a").unwrap();
|
||||
assert!(result.is_undefined());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_returns_true() {
|
||||
let result = eval("var o = {a: 1}; delete o.a").unwrap();
|
||||
assert!(result.as_bool());
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instanceof_array() {
|
||||
let result = eval("[] instanceof Array").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instance_not_array() {
|
||||
let result = eval("({}) instanceof Array").unwrap();
|
||||
assert!(!result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn void_returns_undefined() {
|
||||
let result = eval("void 0").unwrap();
|
||||
assert!(result.is_undefined());
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
use oxide_types::value::JsValue;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn eval(source: &str) -> String {
|
||||
let allocator = Allocator::default();
|
||||
let program = match oxide_parser::parse(&allocator, source) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return format!("parse error: {}", e[0].message),
|
||||
};
|
||||
let module = match Compiler::new().compile(&program) {
|
||||
Ok(m) => m,
|
||||
Err(e) => return format!("compile error: {}", e),
|
||||
};
|
||||
let mut vm = Vm::new();
|
||||
match vm.run(&module) {
|
||||
Ok(result) => format!("{}", result),
|
||||
Err(e) => format!("vm error: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_val(source: &str) -> (Vm, Result<JsValue, String>) {
|
||||
let allocator = Allocator::default();
|
||||
let program = match oxide_parser::parse(&allocator, source) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return (Vm::new(), Err(format!("parse error: {}", e[0].message))),
|
||||
};
|
||||
let module = match Compiler::new().compile(&program) {
|
||||
Ok(m) => m,
|
||||
Err(e) => return (Vm::new(), Err(format!("compile error: {}", e))),
|
||||
};
|
||||
let mut vm = Vm::new();
|
||||
let result = vm.run(&module);
|
||||
(vm, result)
|
||||
}
|
||||
|
||||
fn to_str(vm: &Vm, val: JsValue) -> String {
|
||||
if val.is_string() {
|
||||
vm.kernel()
|
||||
.string_forge()
|
||||
.lookup(val.as_string_index())
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
format!("{}", val)
|
||||
}
|
||||
}
|
||||
|
||||
// -- Template literal tests --
|
||||
|
||||
#[test]
|
||||
fn template_no_expressions() {
|
||||
let (vm, result) = eval_val("`hello`");
|
||||
assert_eq!(to_str(&vm, result.unwrap()), "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn template_single_expression() {
|
||||
let (vm, result) = eval_val("const name = 'world'; `hello ${name}`");
|
||||
assert_eq!(to_str(&vm, result.unwrap()), "hello world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn template_multiple_expressions() {
|
||||
let (vm, result) = eval_val("`a${1}b${2}c`");
|
||||
assert_eq!(to_str(&vm, result.unwrap()), "a1b2c");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn template_expression_only() {
|
||||
let (vm, result) = eval_val("const x = 'foo', y = 'bar'; `${x}${y}`");
|
||||
assert_eq!(to_str(&vm, result.unwrap()), "foobar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn template_empty() {
|
||||
let (vm, result) = eval_val("``");
|
||||
assert_eq!(to_str(&vm, result.unwrap()), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn template_with_numbers() {
|
||||
let (vm, result) = eval_val("`value is ${42}`");
|
||||
assert_eq!(to_str(&vm, result.unwrap()), "value is 42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn template_numeric_expression() {
|
||||
let (vm, result) = eval_val("`${1 + 1}`");
|
||||
assert_eq!(to_str(&vm, result.unwrap()), "2");
|
||||
}
|
||||
|
||||
// -- Template tagging tests (basic) --
|
||||
|
||||
#[test]
|
||||
fn tagged_template_basic() {
|
||||
// Use Math.max as a simple native tag function to verify the CALL dispatch works.
|
||||
// Math.max(cooked_array, raw_array, 42) should return 42 (the max of the args).
|
||||
// This avoids bytecode function call complexity.
|
||||
let result = eval("Math.max`hello ${42} world`");
|
||||
// Math.max on the args should work - just verify it doesn't crash
|
||||
assert!(
|
||||
!result.starts_with("vm error:"),
|
||||
"Tagged template should not crash, got: {}",
|
||||
result
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tagged_template_compiles_no_error() {
|
||||
// Verify that tagged templates compile without error (even if the tag
|
||||
// function behavior isn't fully tested)
|
||||
let result = eval("function t(s,v){ return s[0]+v; } t`x${1}`");
|
||||
assert!(
|
||||
!result.starts_with("compile error:"),
|
||||
"Tagged template should compile"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user