diff --git a/libs/jsruntime/src/backend/bridge.rs b/libs/jsruntime/src/backend/bridge.rs index 843ae3df1..6d0b71bcb 100644 --- a/libs/jsruntime/src/backend/bridge.rs +++ b/libs/jsruntime/src/backend/bridge.rs @@ -1,6 +1,7 @@ use jsgc::Handle; use jsgc::HandleMut; +use crate::Error; use crate::Runtime; use crate::lambda::LambdaKind; use crate::logger; @@ -192,46 +193,35 @@ pub(crate) extern "C" fn runtime_to_object( retv: &mut Value, ) -> Status { logger::debug!(event = "runtime_to_object", ?value); - runtime.value_to_object(value, retv) + match runtime.value_to_object(value) { + Ok(value) => { + *retv = value; + Status::Normal + } + Err(err) => { + *retv = runtime.create_exception(err); + Status::Exception + } + } } impl Runtime { - pub(crate) fn value_to_object(&mut self, value: &Value, retv: &mut Value) -> Status { + pub(crate) fn value_to_object(&mut self, value: &Value) -> Result { logger::debug!(event = "to_object", ?value); match value { Value::None => unreachable!("Value::None"), - Value::Undefined | Value::Null => { - *retv = Value::Object(self.create_type_error(None)); - Status::Exception + Value::Undefined | Value::Null => type_error!(), + Value::Boolean(_value) => { + runtime_todo!("ToObject: not yet implemented for Boolean values") + } + Value::Number(_value) => { + runtime_todo!("ToObject: not yet implemented for Number values") } - Value::Boolean(_value) => runtime_todo!( - self, - "ToObject: not yet implemented for Boolean values", - retv - ), - Value::Number(_value) => runtime_todo!( - self, - "ToObject: not yet implemented for Number values", - retv - ), Value::String(value) => { // TODO(refactor): rewrite using `new String(value)` - match self.create_string_object(None, &[Value::String(*value)], true) { - Ok(Value::Object(object)) => { - *retv = Value::Object(object); - Status::Normal - } - Ok(_) => unreachable!(), - Err(err) => { - *retv = self.create_exception(err); - Status::Exception - } - } - } - Value::Object(_) => { - *retv = value.clone(); - Status::Normal + self.create_string_object(None, &[Value::String(*value)], true) } + Value::Object(_) => Ok(value.clone()), } } } @@ -368,7 +358,7 @@ pub(crate) extern "C" fn runtime_create_string( pub(crate) extern "C" fn runtime_create_capture( runtime: &mut Runtime, target: *mut Value, -) -> *mut Capture { +) -> HandleMut { logger::debug!(event = "runtime_create_capture", ?target); debug_assert!( @@ -386,16 +376,12 @@ pub(crate) extern "C" fn runtime_create_capture( ) }; - let handle = runtime - .heap - .alloc_layout_mut::(LAYOUT, move |ptr| { - // SAFETY: `ptr` is a non-null pointer to a `Capture`. - let capture = unsafe { ptr.cast::().as_mut() }; - capture.target = target; - // `capture.escaped` will be filled with an actual value. - }); - - handle.as_ptr() + runtime.heap.alloc_layout_mut(LAYOUT, move |ptr| { + // SAFETY: `ptr` is a non-null pointer to a `Capture`. + let capture = unsafe { ptr.cast::().as_mut() }; + capture.target = target; + // `capture.escaped` will be filled with an actual value. + }) } pub(crate) extern "C" fn runtime_create_closure( @@ -403,16 +389,14 @@ pub(crate) extern "C" fn runtime_create_closure( lambda: Lambda, lambda_id: u32, num_captures: u16, -) -> *mut Closure { +) -> HandleMut { logger::debug!( event = "runtime_create_closure", ?lambda, lambda_id, num_captures ); - runtime - .create_closure(lambda, lambda_id.into(), num_captures) - .as_ptr() + runtime.create_closure(lambda, lambda_id.into(), num_captures) } pub(crate) extern "C" fn runtime_create_coroutine( @@ -421,7 +405,7 @@ pub(crate) extern "C" fn runtime_create_coroutine( num_locals: u16, scratch_buffer_len: u16, capture_buffer_len: u16, -) -> *mut Coroutine { +) -> HandleMut { logger::debug!( event = "runtime_create_coroutine", ?closure, @@ -430,9 +414,7 @@ pub(crate) extern "C" fn runtime_create_coroutine( capture_buffer_len, ); let closure = HandleMut::from_ptr(closure).expect("closure must be a non-null pointer"); - runtime - .create_coroutine(closure, num_locals, scratch_buffer_len, capture_buffer_len) - .as_ptr() + runtime.create_coroutine(closure, num_locals, scratch_buffer_len, capture_buffer_len) } pub(crate) extern "C" fn runtime_create_promise( @@ -462,26 +444,30 @@ pub(crate) extern "C" fn runtime_emit_promise_resolved( pub(crate) extern "C" fn runtime_create_object( runtime: &mut Runtime, prototype: *mut Object, -) -> *mut Object { - let prototype = HandleMut::from_ptr(prototype); - runtime.create_object(prototype).as_ptr() +) -> HandleMut { + let prototype = HandleMut::from_ptr(prototype).unwrap(); + let mut object = runtime.create_object(); + object.set_prototype(prototype); + object } pub(crate) extern "C" fn runtime_create_reference_error( runtime: &mut Runtime, -) -> *mut Object { - runtime.create_reference_error(None).as_ptr() +) -> HandleMut { + runtime.create_reference_error(None) } -pub(crate) extern "C" fn runtime_create_type_error(runtime: &mut Runtime) -> *mut Object { - runtime.create_type_error(None).as_ptr() +pub(crate) extern "C" fn runtime_create_type_error( + runtime: &mut Runtime, +) -> HandleMut { + runtime.create_type_error(None) } pub(crate) extern "C" fn runtime_create_internal_error( runtime: &mut Runtime, message: Handle, -) -> *mut Object { - runtime.create_internal_error(Some(message)).as_ptr() +) -> HandleMut { + runtime.create_internal_error(Some(message)) } pub(crate) extern "C" fn runtime_get_value_by_symbol( @@ -551,7 +537,7 @@ pub(crate) extern "C" fn runtime_get_value_by_value( let key = match runtime.make_property_key(key) { Ok(key) => key, Err(err) => { - *retv = err; + *retv = runtime.create_exception(err); return Status::Exception; } }; @@ -611,7 +597,7 @@ pub(crate) extern "C" fn runtime_set_value_by_value( let key = match runtime.make_property_key(key) { Ok(key) => key, Err(err) => { - *retv = err; + *retv = runtime.create_exception(err); return Status::Exception; } }; @@ -694,7 +680,7 @@ pub(crate) extern "C" fn runtime_create_data_property_by_value( let key = match runtime.make_property_key(key) { Ok(key) => key, Err(err) => { - *retv = err; + *retv = runtime.create_exception(err); return Status::Exception; } }; @@ -748,8 +734,8 @@ pub(crate) extern "C" fn runtime_push_value( *retv = Value::None; Status::Normal } - Err(exception) => { - *retv = exception; + Err(err) => { + *retv = runtime.create_exception(err); Status::Exception } } diff --git a/libs/jsruntime/src/backend/clir/compiler/mod.rs b/libs/jsruntime/src/backend/clir/compiler/mod.rs index be442337b..5047fb3ea 100644 --- a/libs/jsruntime/src/backend/clir/compiler/mod.rs +++ b/libs/jsruntime/src/backend/clir/compiler/mod.rs @@ -170,7 +170,7 @@ pub fn compile( let program = &runtime.programs[program_id.index()]; for func in program.functions.iter() { let mut session = { - let global_object = runtime.global_object.as_handle(); + let global_object = runtime.builtins.global_object; Session { pref: &runtime.pref, program, @@ -178,9 +178,9 @@ pub fn compile( lambda_registry: &mut runtime.lambda_registry, code_registry: &mut runtime.code_registry, global_object, - object_prototype: runtime.object_prototype.unwrap(), - function_prototype: runtime.function_prototype.unwrap(), - promise_prototype: runtime.promise_prototype.unwrap(), + object_prototype: runtime.builtins.object_prototype, + function_prototype: runtime.builtins.function_prototype, + promise_prototype: runtime.builtins.promise_prototype, } }; context.compile_function(func, &mut session, &program.scope_tree); @@ -215,7 +215,7 @@ pub fn compile_function( let func = &program.functions[function_index]; let mut session = { - let global_object = runtime.global_object.as_handle(); + let global_object = runtime.builtins.global_object; Session { pref: &runtime.pref, program, @@ -223,9 +223,9 @@ pub fn compile_function( lambda_registry: &mut runtime.lambda_registry, code_registry: &mut runtime.code_registry, global_object, - object_prototype: runtime.object_prototype.unwrap(), - function_prototype: runtime.function_prototype.unwrap(), - promise_prototype: runtime.promise_prototype.unwrap(), + object_prototype: runtime.builtins.object_prototype, + function_prototype: runtime.builtins.function_prototype, + promise_prototype: runtime.builtins.promise_prototype, } }; @@ -440,7 +440,7 @@ where self.editor .put_branch(too_deep, then_block, &[], merge_block, &[]); self.editor.switch_to_block(then_block); - self.emit_throw_internal_error(const_string!("Call stack too deep")); + self.emit_throw_internal_error(const_string_handle!("Call stack too deep")); self.editor.put_jump(merge_block, &[]); self.editor.switch_to_block(merge_block); } @@ -1168,7 +1168,7 @@ where fn process_call(&mut self, argc: u16) { // TODO: dynamic allocation if argc > 8 { - self.emit_throw_internal_error(const_string!("TODO: too many arguments")); + self.emit_throw_internal_error(const_string_handle!("TODO: too many arguments")); let len = self.operand_stack.len() - (argc as usize) - 1; self.operand_stack.truncate(len); self.operand_stack.push(Operand::Undefined); // TODO: dummy @@ -1226,7 +1226,7 @@ where fn process_new(&mut self, argc: u16) { // TODO: dynamic allocation if argc > 8 { - self.emit_throw_internal_error(const_string!("TODO: too many arguments")); + self.emit_throw_internal_error(const_string_handle!("TODO: too many arguments")); let len = self.operand_stack.len() - (argc as usize) - 1; self.operand_stack.truncate(len); self.operand_stack.push(Operand::Undefined); // TODO: dummy @@ -1424,7 +1424,7 @@ where .put_runtime_number_to_string(self.support, *value), Operand::String(value, _) => *value, Operand::Object(_) => { - self.emit_throw_internal_error(const_string!("TODO: ToString(object)")); + self.emit_throw_internal_error(const_string_handle!("TODO: ToString(object)")); self.editor.put_create_string(self.support, &[]) } Operand::Any(value, _) => self.editor.put_runtime_to_string(self.support, *value), @@ -1612,7 +1612,7 @@ where // 13.5.1.2 Runtime Semantics: Evaluation fn process_delete(&mut self) { let (_operand, ..) = self.dereference(); - self.emit_throw_internal_error(const_string!("TODO: delete operator")); + self.emit_throw_internal_error(const_string_handle!("TODO: delete operator")); self.process_boolean(true); } @@ -2057,7 +2057,7 @@ where fn process_instanceof(&mut self) { let (_lhs, ..) = self.dereference(); let (_rhs, ..) = self.dereference(); - self.emit_throw_internal_error(const_string!("TODO: instanceof operator")); + self.emit_throw_internal_error(const_string_handle!("TODO: instanceof operator")); self.process_boolean(false); } @@ -2065,7 +2065,7 @@ where fn process_in(&mut self) { let (_lhs, ..) = self.dereference(); let (_rhs, ..) = self.dereference(); - self.emit_throw_internal_error(const_string!("TODO: in operator")); + self.emit_throw_internal_error(const_string_handle!("TODO: in operator")); self.process_boolean(false); } @@ -2286,15 +2286,15 @@ where None } PropertyOwner::Boolean(_) => { - self.emit_throw_internal_error(const_string!("TODO: ToObject(boolean)")); + self.emit_throw_internal_error(const_string_handle!("TODO: ToObject(boolean)")); None } PropertyOwner::Number(_) => { - self.emit_throw_internal_error(const_string!("TODO: ToObject(number)")); + self.emit_throw_internal_error(const_string_handle!("TODO: ToObject(number)")); None } PropertyOwner::String(_) => { - self.emit_throw_internal_error(const_string!("TODO: ToObject(string)")); + self.emit_throw_internal_error(const_string_handle!("TODO: ToObject(string)")); None } PropertyOwner::Object(value) => Some(*value), @@ -3192,7 +3192,7 @@ where Operand::Boolean(value, ..) => *value, Operand::Number(value, ..) => self.editor.put_number_to_boolean(*value), Operand::String(..) => { - self.emit_throw_internal_error(const_string!("TODO: ToBoolean(string)")); + self.emit_throw_internal_error(const_string_handle!("TODO: ToBoolean(string)")); self.editor.put_boolean(false) } Operand::Object(_) => self.editor.put_boolean(true), @@ -3216,11 +3216,11 @@ where Operand::Boolean(value, ..) => self.editor.put_boolean_to_number(*value), Operand::Number(value, ..) => *value, Operand::String(..) => { - self.emit_throw_internal_error(const_string!("TODO: ToNumber(string)")); + self.emit_throw_internal_error(const_string_handle!("TODO: ToNumber(string)")); self.editor.put_number(f64::NAN) } Operand::Object(_) => { - self.emit_throw_internal_error(const_string!("TODO: ToNumber(object)")); + self.emit_throw_internal_error(const_string_handle!("TODO: ToNumber(object)")); self.editor.put_number(f64::NAN) } Operand::Any(value, ..) => self.editor.put_runtime_to_numeric(self.support, *value), diff --git a/libs/jsruntime/src/builtins/.gitignore b/libs/jsruntime/src/builtins/.gitignore index 743c6cbc6..9f22f4b46 100644 --- a/libs/jsruntime/src/builtins/.gitignore +++ b/libs/jsruntime/src/builtins/.gitignore @@ -3,8 +3,11 @@ /error/mod.rs /eval_error/imp.rs /eval_error/mod.rs +/function/mod.rs +/global/mod.rs /internal_error/imp.rs /internal_error/mod.rs +/object/mod.rs /promise/mod.rs /range_error/imp.rs /range_error/mod.rs diff --git a/libs/jsruntime/src/builtins/Makefile b/libs/jsruntime/src/builtins/Makefile index 435ed3e08..e19d9bc94 100644 --- a/libs/jsruntime/src/builtins/Makefile +++ b/libs/jsruntime/src/builtins/Makefile @@ -15,7 +15,7 @@ NATIVE_ERROR_NAMES := \ uri_error NATIVE_ERROR_IMP_RS_FILES := $(addsuffix /imp.rs,$(NATIVE_ERROR_NAMES)) -BUILTINS := error promise string $(NATIVE_ERROR_NAMES) +BUILTINS := error function global object promise string $(NATIVE_ERROR_NAMES) BUILTINS_README_MD_FILES := $(addsuffix /README.md,$(BUILTINS)) BUILTINS_MOD_RS_FILES := $(addsuffix /mod.rs,$(BUILTINS)) BUILTINS_IMP_JSON_FILES := $(addsuffix /imp.json,$(BUILTINS)) @@ -34,6 +34,11 @@ update: $(UPDATE_TARGETS) clean: @rm -f $(CLEAN_TARGETS) +global/mod.rs: global/imp.json global/mod.rs.hbs $(TOOLS_BIN)/handlebars.js + @echo 'Generating $(abspath $@)...' + @cat $< | deno run -q --allow-read=. $(TOOLS_BIN)/handlebars.js --no-escape --input-stdin global/mod.rs.hbs | \ + rustfmt --emit=stdout >$@ + %/mod.rs: %/imp.json builtin_mod.rs.hbs $(TOOLS_BIN)/handlebars.js @echo 'Generating $(abspath $@)...' @cat $< | deno run -q --allow-read=. $(TOOLS_BIN)/handlebars.js --no-escape --input-stdin builtin_mod.rs.hbs | \ diff --git a/libs/jsruntime/src/builtins/builtin_mod.rs.hbs b/libs/jsruntime/src/builtins/builtin_mod.rs.hbs index ab16c0313..3501acedf 100644 --- a/libs/jsruntime/src/builtins/builtin_mod.rs.hbs +++ b/libs/jsruntime/src/builtins/builtin_mod.rs.hbs @@ -22,7 +22,7 @@ impl Runtime { /// Returns `true` if `object` is a {{metadata.class}} object. #[allow(unused)] pub(crate) fn is_{{metadata.id}}_object(&self, object: HandleMut) -> bool { - object.is_instance_of(self.{{metadata.id}}_prototype) + object.is_instance_of(self.builtins.{{metadata.id}}_prototype) } /// Returns `true` if `value` holds a {{metadata.class}} object. @@ -34,22 +34,21 @@ impl Runtime { /// Creates the {{metadata.class}} constructor. pub(super) fn create_{{metadata.id}}_constructor(&mut self) -> HandleMut { logger::debug!(event = "create_{{metadata.id}}_constructor"); - debug_assert!(self.{{metadata.id}}_prototype.is_some()); #[allow(unused_mut)] let mut constructor = self.create_builtin_function(&BuiltinFunctionParams { lambda: constructor::, - name: const_string!("{{metadata.class}}"), + name: const_string_handle!("{{metadata.class}}"), length: 1, slots: &[], - prototype: self.{{metadata.id}}_prototype, + prototype: Some(self.builtins.{{metadata.id}}_prototype), }); {{#each constructorProperties}} {{#if (eq kind "constructor.function")}} let func = self.create_builtin_function(&BuiltinFunctionParams { lambda: {{imp}}, - name: const_string!("{{name}}"), + name: const_string_handle!("{{name}}"), length: {{this.length}}, slots: &[], prototype: None, @@ -59,21 +58,20 @@ impl Runtime { {{/if}} {{/each}} // TODO(refactor): bind outside this function - if let Some(mut prototype) = self.{{metadata.id}}_prototype { - let _ = prototype.define_own_property(Symbol::CONSTRUCTOR.into(), Property::data_wxc(Value::Object(constructor))); - } + let _ = self.builtins.{{metadata.id}}_prototype.define_own_property(Symbol::CONSTRUCTOR.into(), Property::data_wxc(Value::Object(constructor))); constructor } /// Creates the {{metadata.class}} prototype object. - pub(super) fn create_{{metadata.id}}_prototype(&mut self) -> HandleMut { - logger::debug!(event = "creater_{{metadata.id}}_prototype"); - debug_assert!(self.{{metadata.inherits}}_prototype.is_some()); - debug_assert!(self.function_prototype.is_some()); + pub(super) fn init_{{metadata.id}}_prototype(&mut self) { + logger::debug!(event = "init_{{metadata.id}}_prototype"); - #[allow(unused_mut)] - let mut prototype = self.create_object(self.{{metadata.inherits}}_prototype); + #[allow(unused)] + let mut prototype = self.builtins.{{metadata.id}}_prototype; + {{#if metadata.inherits}} + prototype.set_prototype(self.builtins.{{metadata.inherits}}_prototype); + {{/if}} {{#each prototypeProperties}} {{#if (eq kind "prototype.property")}} @@ -82,7 +80,7 @@ impl Runtime { {{else if (eq kind "prototype.function")}} let func = self.create_builtin_function(&BuiltinFunctionParams { lambda: {{imp}}, - name: const_string!("{{name}}"), + name: const_string_handle!("{{name}}"), length: {{this.length}}, slots: &[], prototype: None, @@ -91,7 +89,6 @@ impl Runtime { {{/if}} {{/each}} - prototype } } diff --git a/libs/jsruntime/src/builtins/error/imp.rs b/libs/jsruntime/src/builtins/error/imp.rs index 858c2eab3..49de1989d 100644 --- a/libs/jsruntime/src/builtins/error/imp.rs +++ b/libs/jsruntime/src/builtins/error/imp.rs @@ -1,5 +1,6 @@ //$id error //$class Error +//$inherits object use jsgc::Handle; use jsgc::HandleMut; @@ -21,13 +22,15 @@ pub fn constructor(runtime: &mut Runtime, context: &mut CallContext) -> Re // TODO(feat): NewTarget if !context.is_new() { - return Err(Error::InternalError); + return runtime_todo!(); } let mut object = if let Value::Object(this) = context.this() { *this } else { - runtime.create_object(runtime.error_prototype) + let mut object = runtime.create_object(); + object.set_prototype(runtime.builtins.error_prototype); + object }; object.set_error(); @@ -90,7 +93,7 @@ pub fn error_prototype_to_string( logger::debug!(event = "error_prototype_to_string"); let object = match context.this() { Value::Object(object) => object, - _ => return Err(Error::TypeError), + _ => return type_error!(), }; let name = match object.get_value(&Symbol::NAME.into()) { @@ -108,11 +111,11 @@ pub fn error_prototype_to_string( } else if message.is_empty() { name } else { - let result = runtime.concat_strings(const_string!(&[0x003A, 0x0020]), message); + let result = runtime.concat_strings(const_string_handle!(&[0x003A, 0x0020]), message); runtime.concat_strings(name, result) }; Ok(Value::String(result)) } -const NAME: Handle = const_string!(jsparser::symbol::builtin::names::ERROR); +const NAME: Handle = const_string_handle!(jsparser::symbol::builtin::names::ERROR); diff --git a/libs/jsruntime/src/builtins/function/imp.rs b/libs/jsruntime/src/builtins/function/imp.rs new file mode 100644 index 000000000..07af10a31 --- /dev/null +++ b/libs/jsruntime/src/builtins/function/imp.rs @@ -0,0 +1,18 @@ +//$id function +//$class Function +//$inherits object + +use crate::Error; +use crate::Runtime; +use crate::logger; +use crate::types::CallContext; +use crate::types::Value; + +//#sec-function-p1-p2-pn-body constructor +pub fn constructor( + _runtime: &mut Runtime, + _context: &mut CallContext, +) -> Result { + logger::debug!(event = "function"); + runtime_todo!("TODO: Function constructor") +} diff --git a/libs/jsruntime/src/builtins/function/mod.rs b/libs/jsruntime/src/builtins/function/mod.rs deleted file mode 100644 index 9e77eaba2..000000000 --- a/libs/jsruntime/src/builtins/function/mod.rs +++ /dev/null @@ -1,55 +0,0 @@ -use jsgc::HandleMut; -use jsparser::Symbol; - -use crate::logger; - -use crate::Runtime; -use crate::types::CallContext; -use crate::types::Object; -use crate::types::Property; -use crate::types::Status; -use crate::types::Value; - -use super::BuiltinFunctionParams; - -impl Runtime { - pub(super) fn create_function_constructor(&mut self) -> HandleMut { - logger::debug!(event = "creater_function_constructor"); - self.create_builtin_function(&BuiltinFunctionParams { - lambda: constructor::, - name: const_string!(jsparser::symbol::builtin::names::FUNCTION), - length: 1, - slots: &[], - prototype: self.function_prototype, - }) - } - - pub(super) fn create_function_prototype(&mut self) -> HandleMut { - logger::debug!(event = "creater_function_prototype"); - - // TODO(fix): Function.prototype is a built-in function object. - let mut prototype = self.create_object(self.object_prototype); - let _ = prototype.define_own_property( - Symbol::LENGTH.into(), - Property::data_xxx(Value::Number(0.0)), - ); - let _ = prototype.define_own_property( - Symbol::NAME.into(), - Property::data_xxx(Value::String(crate::types::string::EMPTY)), - ); - - // TODO: Function.prototype.constructor - - prototype - } -} - -// lambda functions - -extern "C" fn constructor( - runtime: &mut Runtime, - _context: &mut CallContext, - retv: &mut Value, -) -> Status { - runtime_todo!(runtime, "TODO: Function constructor", retv) -} diff --git a/libs/jsruntime/src/builtins/global/README.md b/libs/jsruntime/src/builtins/global/README.md new file mode 100644 index 000000000..9c6149f13 --- /dev/null +++ b/libs/jsruntime/src/builtins/global/README.md @@ -0,0 +1,58 @@ +# Global Object + +* [x] [globalThis](https://tc39.es/ecma262/#sec-globalthis) +* [x] [Infinity](https://tc39.es/ecma262/#sec-value-properties-of-the-global-object-infinity) +* [x] [NaN](https://tc39.es/ecma262/#sec-value-properties-of-the-global-object-nan) +* [x] [undefined](https://tc39.es/ecma262/#sec-undefined) +* [x] [eval](https://tc39.es/ecma262/#sec-eval-x) +* [x] [isFinite](https://tc39.es/ecma262/#sec-isfinite-number) +* [x] [isNaN](https://tc39.es/ecma262/#sec-isnan-number) +* [x] [parseFloat](https://tc39.es/ecma262/#sec-parsefloat-string) +* [x] [parseInt](https://tc39.es/ecma262/#sec-parseint-string-radix) +* [x] [AggregateError](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-aggregate-error) +* [ ] [Array](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-array) +* [ ] [ArrayBuffer](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-arraybuffer) +* [ ] [BigInt](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-bigint) +* [ ] [BigInt64Array](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-bigint64array) +* [ ] [BigUint64Array](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-biguint64array) +* [ ] [Boolean](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-boolean) +* [ ] [DataView](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-dataview) +* [ ] [Date](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-date) +* [x] [Error](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-error) +* [x] [EvalError](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-evalerror) +* [ ] [FinalizationRegistry](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-finalization-registry) +* [ ] [Float16Array](https://tc39.es/ecma262/#sec-float16array) +* [ ] [Float32Array](https://tc39.es/ecma262/#sec-float32array) +* [ ] [Float64Array](https://tc39.es/ecma262/#sec-float64array) +* [x] [Function](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-function) +* [ ] [Int8Array](https://tc39.es/ecma262/#sec-int8array) +* [ ] [Int16Array](https://tc39.es/ecma262/#sec-int16array) +* [ ] [Int32Array](https://tc39.es/ecma262/#sec-int32array) +* [x] [InternalError](https://tc39.es/ecma262/#_internalerror) +* [ ] [Iterator](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-iterator) +* [ ] [Map](https://tc39.es/ecma262/#sec-map) +* [ ] [Number](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-number) +* [x] [Object](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-object) +* [x] [Promise](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-promise) +* [ ] [Proxy](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-proxy) +* [x] [RangeError](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-rangeerror) +* [x] [ReferenceError](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-referenceerror) +* [ ] [RegExp](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-regexp) +* [ ] [Set](https://tc39.es/ecma262/#sec-set) +* [ ] [SharedArrayBuffer](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-sharedarraybuffer) +* [x] [String](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-string) +* [ ] [Symbol](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-symbol) +* [x] [SyntaxError](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-syntaxerror) +* [x] [TypeError](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-typeerror) +* [ ] [Uint8Array](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-uint8array) +* [ ] [Uint8ClampedArray](https://tc39.es/ecma262/#sec-uint8clampedarray) +* [ ] [Uint16Array](https://tc39.es/ecma262/#sec-uint16array) +* [ ] [Uint32Array](https://tc39.es/ecma262/#sec-uint32array) +* [x] [URIError](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-urierror) +* [ ] [WeakMap](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-weakmap) +* [ ] [WeakRef](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-weakref) +* [ ] [WeakSet](https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object-weakset) +* [ ] [Atomics](https://tc39.es/ecma262/#sec-atomics) +* [ ] [JSON](https://tc39.es/ecma262/#sec-json) +* [ ] [Math](https://tc39.es/ecma262/#sec-math) +* [ ] [Reflect](https://tc39.es/ecma262/#sec-reflect) diff --git a/libs/jsruntime/src/builtins/global/imp.rs b/libs/jsruntime/src/builtins/global/imp.rs new file mode 100644 index 000000000..3f21f92eb --- /dev/null +++ b/libs/jsruntime/src/builtins/global/imp.rs @@ -0,0 +1,166 @@ +//$id global + +use jsparser::Symbol; + +use crate::Error; +use crate::Runtime; +use crate::types::CallContext; +use crate::types::Value; +use crate::types::object::Property; + +//#sec-globalthis global.property +pub fn global_global_this(runtime: &mut Runtime) { + let prop = Property::data_wxc(Value::Object(runtime.builtins.global_object)); + runtime.define_global_property(Symbol::GLOBAL_THIS, prop); +} + +//#sec-value-properties-of-the-global-object-infinity global.property +pub fn global_infinity(runtime: &mut Runtime) { + let prop = Property::data_xxx(Value::Number(f64::INFINITY)); + runtime.define_global_property(Symbol::INFINITY, prop); +} + +//#sec-value-properties-of-the-global-object-nan global.property +pub fn global_nan(runtime: &mut Runtime) { + let prop = Property::data_xxx(Value::Number(f64::NAN)); + runtime.define_global_property(Symbol::NAN, prop); +} + +//#sec-undefined global.property +pub fn global_undefined(runtime: &mut Runtime) { + let prop = Property::data_xxx(Value::Undefined); + runtime.define_global_property(Symbol::KEYWORD_UNDEFINED, prop); +} + +//#sec-eval-x global.function +pub fn eval(_runtime: &mut Runtime, _context: &mut CallContext) -> Result { + runtime_todo!("eval: not implemented") +} + +//#sec-isfinite-number global.function +pub fn is_finite(runtime: &mut Runtime, context: &mut CallContext) -> Result { + let number = context.args().first().unwrap_or(&Value::Undefined); + let num = runtime.value_to_number(number)?; + Ok(Value::Boolean(num.is_finite())) +} + +//#sec-isnan-number global.function +pub fn is_nan(runtime: &mut Runtime, context: &mut CallContext) -> Result { + let number = context.args().first().unwrap_or(&Value::Undefined); + let num = runtime.value_to_number(number)?; + Ok(Value::Boolean(num.is_nan())) +} + +//#sec-parsefloat-string global.function +pub fn parse_float(runtime: &mut Runtime, context: &mut CallContext) -> Result { + let string = context.args().first().unwrap_or(&Value::Undefined); + let input_string = runtime.value_to_string(string)?; + let trimmed_string = runtime.trim_string(input_string, true, false)?; + // TODO: 11.1.6 Static Semantics: ParseText ( sourceText, goalSymbol ) + let utf8 = char::decode_utf16(trimmed_string.code_units()) + .map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER)) + .collect::(); + match utf8.parse::() { + Ok(n) => Ok(Value::Number(n)), + Err(_) => syntax_error!(), + } +} + +//#sec-parseint-string-radix global.function +pub fn parse_int(runtime: &mut Runtime, context: &mut CallContext) -> Result { + // TODO: impl + let string = context.args().first().unwrap_or(&Value::Undefined); + let input_string = runtime.value_to_string(string)?; + let s = runtime.trim_string(input_string, true, false)?; + let radix = context.args().get(1).unwrap_or(&Value::Undefined); + let radix = runtime.value_to_number(radix)?; + let radix = if radix.is_finite() { radix as i32 } else { 10 }; + if !(2..=36).contains(&radix) { + return Ok(Value::Number(f64::NAN)); + } + let utf8 = char::decode_utf16(s.code_units()) + .map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER)) + .collect::(); + match i64::from_str_radix(&utf8, radix as u32) { + Ok(n) => Ok(Value::Number(n as f64)), + Err(_) => Ok(Value::Number(f64::NAN)), + } +} + +//#sec-constructor-properties-of-the-global-object-aggregate-error global.constructor +pub fn define_aggregate_error_constructor(runtime: &mut Runtime) { + let constructor = runtime.create_aggregate_error_constructor(); + runtime.define_constructor(Symbol::AGGREGATE_ERROR, constructor); +} + +//#sec-constructor-properties-of-the-global-object-error global.constructor +pub fn define_error_constructor(runtime: &mut Runtime) { + let constructor = runtime.create_error_constructor(); + runtime.define_constructor(Symbol::ERROR, constructor); +} + +//#sec-constructor-properties-of-the-global-object-evalerror global.constructor +pub fn define_eval_error_constructor(runtime: &mut Runtime) { + let constructor = runtime.create_eval_error_constructor(); + runtime.define_constructor(Symbol::EVAL_ERROR, constructor); +} + +//#sec-constructor-properties-of-the-global-object-function global.constructor +pub fn define_function_constructor(runtime: &mut Runtime) { + let constructor = runtime.create_function_constructor(); + runtime.define_constructor(Symbol::FUNCTION, constructor); +} + +//#_internalerror global.constructor { "name": "InternalError" } +pub fn define_internal_error_constructor(runtime: &mut Runtime) { + let constructor = runtime.create_internal_error_constructor(); + runtime.define_constructor(Symbol::FUNCTION, constructor); +} + +//#sec-constructor-properties-of-the-global-object-object global.constructor +pub fn define_object_constructor(runtime: &mut Runtime) { + let constructor = runtime.create_object_constructor(); + runtime.define_constructor(Symbol::OBJECT, constructor); +} + +//#sec-constructor-properties-of-the-global-object-promise global.constructor +pub fn define_promise_constructor(runtime: &mut Runtime) { + let constructor = runtime.create_promise_constructor(); + runtime.define_constructor(Symbol::PROMISE, constructor); +} + +//#sec-constructor-properties-of-the-global-object-rangeerror global.constructor +pub fn define_range_error_constructor(runtime: &mut Runtime) { + let constructor = runtime.create_range_error_constructor(); + runtime.define_constructor(Symbol::RANGE_ERROR, constructor); +} + +//#sec-constructor-properties-of-the-global-object-referenceerror global.constructor +pub fn define_reference_error_constructor(runtime: &mut Runtime) { + let constructor = runtime.create_reference_error_constructor(); + runtime.define_constructor(Symbol::REFERENCE_ERROR, constructor); +} + +//#sec-constructor-properties-of-the-global-object-string global.constructor +pub fn define_string_constructor(runtime: &mut Runtime) { + let constructor = runtime.create_string_constructor(); + runtime.define_constructor(Symbol::STRING, constructor); +} + +//#sec-constructor-properties-of-the-global-object-syntaxerror global.constructor +pub fn define_syntax_error_constructor(runtime: &mut Runtime) { + let constructor = runtime.create_syntax_error_constructor(); + runtime.define_constructor(Symbol::SYNTAX_ERROR, constructor); +} + +//#sec-constructor-properties-of-the-global-object-typeerror global.constructor +pub fn define_type_error_constructor(runtime: &mut Runtime) { + let constructor = runtime.create_type_error_constructor(); + runtime.define_constructor(Symbol::TYPE_ERROR, constructor); +} + +//#sec-constructor-properties-of-the-global-object-urierror global.constructor +pub fn define_uri_error_constructor(runtime: &mut Runtime) { + let constructor = runtime.create_uri_error_constructor(); + runtime.define_constructor(Symbol::URI_ERROR, constructor); +} diff --git a/libs/jsruntime/src/builtins/global/mod.rs.hbs b/libs/jsruntime/src/builtins/global/mod.rs.hbs new file mode 100644 index 000000000..cace26260 --- /dev/null +++ b/libs/jsruntime/src/builtins/global/mod.rs.hbs @@ -0,0 +1,81 @@ +// DO NOT EDIT THIS FILE BY HAND. +// +// This file was automagically generated with: +// template: {{@template}} + +mod imp; + +use jsgc::HandleMut; +use jsparser::Symbol; + +use crate::Runtime; +use crate::logger; +use crate::types::CallContext; +use crate::types::Object; +use crate::types::Property; +use crate::types::Status; +use crate::types::Value; + +use super::BuiltinFunctionParams; + +impl Runtime { + pub(super) fn init_global_object(&mut self) { + logger::debug!(event = "init_global_object"); + + // TODO: pass [[Prototype]] of the global object. + + {{#each globalProperties}} + {{#if (eq kind "global.constructor")}} + imp::{{imp}}(self); + + {{else if (eq kind "global.property")}} + imp::{{imp}}(self); + + {{else if (eq kind "global.function")}} + let func = self.create_builtin_function(&BuiltinFunctionParams { + lambda: {{imp}}, + name: const_string_handle!("{{name}}"), + length: {{this.length}}, + slots: &[], + prototype: None, + }); + self.define_global_property(Symbol::{{symbol}}, Property::data_wxc(Value::Object(func))); + + {{/if}} + {{/each}} + } + + fn define_global_property(&mut self, symbol: Symbol, prop: Property) { + let result = self.builtins.global_object.define_own_property(symbol.into(), prop); + debug_assert!(matches!(result, Ok(true))); + } + + fn define_constructor(&mut self, symbol: Symbol, constructor: HandleMut) { + self.define_global_property(symbol, Property::data_xxx(Value::Object(constructor))); + } +} + +// lambda functions +// TODO: use proc-macro + +{{#each globalProperties}} +{{#if (eq kind "global.function")}} + +extern "C" fn {{imp}}( + runtime: &mut Runtime, + context: &mut CallContext, + retv: &mut Value, +) -> Status { + match imp::{{imp}}(runtime, context) { + Ok(value) => { + *retv = value; + Status::Normal + } + Err(err) => { + *retv = runtime.create_exception(err); + Status::Exception + } + } +} +{{/if}} +{{/each}} diff --git a/libs/jsruntime/src/builtins/imp.js b/libs/jsruntime/src/builtins/imp.js index 55012e436..dc2dd3fc7 100644 --- a/libs/jsruntime/src/builtins/imp.js +++ b/libs/jsruntime/src/builtins/imp.js @@ -42,11 +42,12 @@ async function main(args, options) { const json = { metadata: { - inherits: 'object', + inherits: null, }, constructor: null, constructorProperties: [], prototypeProperties: [], + globalProperties: [], }; for await (const data of dataStream(args.impRs)) { @@ -70,6 +71,18 @@ async function main(args, options) { collectFunctionDataFromSpec(spec, data, json.metadata); json.prototypeProperties.push(data); break; + case 'global.constructor': + collectConstructorDataFromSpec(spec, data, json.metadata); + json.globalProperties.push(data); + break; + case 'global.property': + collectPropertyDataFromSpec(spec, data, json.metadata); + json.globalProperties.push(data); + break; + case 'global.function': + collectFunctionDataFromSpec(spec, data, json.metadata); + json.globalProperties.push(data); + break; default: unreachable(); break; @@ -166,9 +179,25 @@ function dataStream(impRs) { })); } +function collectConstructorDataFromSpec(spec, data, metadata) { + if (data.options?.name) { + data.name = data.options.name; + } else { + const clause = spec.window.document.getElementById(data.id); + const signature = parseSignature(clause.firstElementChild.textContent.trim()); + data.name = signature.name; + } + data.symbol = constantCase(data.name); + return data; +} + function collectPropertyDataFromSpec(spec, data, metadata) { - let clause = spec.window.document.getElementById(data.id); - data.property = clause.firstElementChild.textContent.trim(); + if (data.options?.property) { + data.property = data.options.property; + } else { + let clause = spec.window.document.getElementById(data.id); + data.property = clause.firstElementChild.textContent.trim(); + } if (data.property.startsWith('_NativeError_.')) { data.property = data.property.replace('_NativeError_', metadata['class']); } @@ -208,6 +237,14 @@ function collectFunctionDataFromSpec(spec, data, metadata) { data.name = data.signature.name.split('.')[2]; data.symbol = constantCase(data.name); break; + case 'global.function': + data.name = data.signature.name; + if (data.name === 'isNaN') { + data.symbol = 'IS_NAN'; + } else { + data.symbol = constantCase(data.name); + } + break; default: unreachable(); } diff --git a/libs/jsruntime/src/builtins/mod.rs b/libs/jsruntime/src/builtins/mod.rs index ea4e59171..1e25900a7 100644 --- a/libs/jsruntime/src/builtins/mod.rs +++ b/libs/jsruntime/src/builtins/mod.rs @@ -2,6 +2,7 @@ mod aggregate_error; mod error; mod eval_error; mod function; +mod global; mod internal_error; mod object; mod promise; @@ -14,130 +15,101 @@ mod uri_error; use jsgc::Handle; use jsgc::HandleMut; +use jsgc::Heap; use jsparser::Symbol; use crate::Error; +use crate::ErrorKind; use crate::Runtime; use crate::lambda::LambdaId; use crate::logger; -use crate::types::CallContext; use crate::types::Lambda; use crate::types::Object; use crate::types::Property; -use crate::types::Status; use crate::types::String; use crate::types::Value; -impl Runtime { - // 19 The Global Object - pub(crate) fn define_builtin_global_properties(&mut self) { - macro_rules! define { - ($key:expr => $value:expr,) => { - define!(kv: $key, $value); - }; - ($key:expr => $value:expr, $($keys:expr => $values:expr,)+) => { - define!(kv: $key, $value); - define!($($keys => $values,)+); - }; - (kv: $key:expr, $value:expr) => { - let prop = Property::data_xxx($value); - let result = self.global_object.define_own_property($key.into(), prop); - debug_assert!(matches!(result, Ok(true))); - }; - } +// The built-in objects are created in two-phase construction in order to avoid circular +// references. +// +// 1. Create an empty object for each built-in objects. +// 2. Initialize built-in objects. +// +#[derive(jsgc_derive::Trace)] +pub(crate) struct Builtins { + // [[GlobalObject]] + pub(crate) global_object: HandleMut, + // %Object.prototype% + pub(crate) object_prototype: HandleMut, + // %Function.prototype% + pub(crate) function_prototype: HandleMut, + // %String.prototype% + pub(crate) string_prototype: HandleMut, + // %Promise.prototype% + pub(crate) promise_prototype: HandleMut, + // %Error.prototype% + pub(crate) error_prototype: HandleMut, + // %AggregateError.prototype% + pub(crate) aggregate_error_prototype: HandleMut, + // %EvalError.prototype% + pub(crate) eval_error_prototype: HandleMut, + // %InternalError.prototype% + pub(crate) internal_error_prototype: HandleMut, + // %RangeError.prototype% + pub(crate) range_error_prototype: HandleMut, + // %ReferenceError.prototype% + pub(crate) reference_error_prototype: HandleMut, + // %SyntaxError.prototype% + pub(crate) syntax_error_prototype: HandleMut, + // %TypeError.prototype% + pub(crate) type_error_prototype: HandleMut, + // URIError.prototype% + pub(crate) uri_error_prototype: HandleMut, +} - self.object_prototype = Some(self.create_object(None)); - self.function_prototype = Some(self.create_function_prototype()); - self.string_prototype = Some(self.create_string_prototype()); - self.promise_prototype = Some(self.create_promise_prototype()); - self.error_prototype = Some(self.create_error_prototype()); - self.aggregate_error_prototype = Some(self.create_aggregate_error_prototype()); - self.eval_error_prototype = Some(self.create_eval_error_prototype()); - self.internal_error_prototype = Some(self.create_internal_error_prototype()); - self.range_error_prototype = Some(self.create_range_error_prototype()); - self.reference_error_prototype = Some(self.create_reference_error_prototype()); - self.syntax_error_prototype = Some(self.create_syntax_error_prototype()); - self.type_error_prototype = Some(self.create_type_error_prototype()); - self.uri_error_prototype = Some(self.create_uri_error_prototype()); +impl Builtins { + // The first phase of the two-phase construction. + pub(crate) fn new(heap: &mut Heap) -> Self { + Self { + global_object: heap.alloc_mut(Object::new()), + object_prototype: heap.alloc_mut(Object::new()), + function_prototype: heap.alloc_mut(Object::new()), + string_prototype: heap.alloc_mut(Object::new()), + promise_prototype: heap.alloc_mut(Object::new()), + error_prototype: heap.alloc_mut(Object::new()), + aggregate_error_prototype: heap.alloc_mut(Object::new()), + eval_error_prototype: heap.alloc_mut(Object::new()), + internal_error_prototype: heap.alloc_mut(Object::new()), + reference_error_prototype: heap.alloc_mut(Object::new()), + range_error_prototype: heap.alloc_mut(Object::new()), + syntax_error_prototype: heap.alloc_mut(Object::new()), + type_error_prototype: heap.alloc_mut(Object::new()), + uri_error_prototype: heap.alloc_mut(Object::new()), + } + } +} - let this = self.global_object.as_handle(); +impl Runtime { + // The second phase of the two-phase construction. + pub(crate) fn init_builtin_objects(&mut self) { + self.init_intrinsic_objects(); + self.init_global_object(); + } - define! { - // TODO: 19.1.1 globalThis - Symbol::GLOBAL_THIS => Value::Object(this), - // 19.1.2 Infinity - Symbol::INFINITY => Value::Number(f64::INFINITY), - // 19.1.3 NaN - Symbol::NAN => Value::Number(f64::NAN), - // 19.1.4 undefined - Symbol::KEYWORD_UNDEFINED => Value::Undefined, - // 19.2.1 eval ( x ) - Symbol::EVAL => Value::Object(self.create_builtin_function(&BuiltinFunctionParams { - lambda: eval, - name: const_string!(jsparser::symbol::builtin::names::EVAL), - length: 1, - slots: &[], - prototype: self.function_prototype, - })), - // 19.2.2 isFinite ( number ) - Symbol::IS_FINITE => Value::Object(self.create_builtin_function(&BuiltinFunctionParams { - lambda: is_finite, - name: const_string!(jsparser::symbol::builtin::names::IS_FINITE), - length: 1, - slots: &[], - prototype: self.function_prototype, - })), - // 19.2.3 isNaN ( number ) - Symbol::IS_NAN => Value::Object(self.create_builtin_function(&BuiltinFunctionParams { - lambda: is_nan, - name: const_string!(jsparser::symbol::builtin::names::IS_NAN), - length: 1, - slots: &[], - prototype: self.function_prototype, - })), - // 19.2.4 parseFloat ( string ) - Symbol::PARSE_FLOAT => Value::Object(self.create_builtin_function(&BuiltinFunctionParams { - lambda: parse_float, - name: const_string!(jsparser::symbol::builtin::names::PARSE_FLOAT), - length: 1, - slots: &[], - prototype: self.function_prototype, - })), - // 19.2.5 parseInt ( string, radix ) - Symbol::PARSE_INT => Value::Object(self.create_builtin_function(&BuiltinFunctionParams { - lambda: parse_int, - name: const_string!(jsparser::symbol::builtin::names::PARSE_INT), - length: 2, - slots: &[], - prototype: self.function_prototype, - })), - // 19.3.1 AggregateError ( . . . ) - Symbol::AGGREGATE_ERROR => Value::Object(self.create_aggregate_error_constructor()), - // 19.3.10 Error ( . . . ) - Symbol::ERROR => Value::Object(self.create_error_constructor()), - // 19.3.11 EvalError ( . . . ) - Symbol::EVAL_ERROR => Value::Object(self.create_eval_error_constructor()), - // 19.3.16 Function ( . . . ) - Symbol::FUNCTION => Value::Object(self.create_function_constructor()), - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/InternalError - Symbol::INTERNAL_ERROR => Value::Object(self.create_internal_error_constructor()), - // 19.3.23 Object ( . . . ) - Symbol::OBJECT => Value::Object(self.create_object_constructor()), - // 19.3.24 Promise ( . . . ) - Symbol::PROMISE => Value::Object(self.create_promise_constructor()), - // 19.3.26 RangeError ( . . . ) - Symbol::RANGE_ERROR => Value::Object(self.create_range_error_constructor()), - // 19.3.27 ReferenceError ( . . . ) - Symbol::REFERENCE_ERROR => Value::Object(self.create_reference_error_constructor()), - // 19.3.31 String() - Symbol::STRING => Value::Object(self.create_string_constructor()), - // 19.3.33 SyntaxError ( . . . ) - Symbol::SYNTAX_ERROR => Value::Object(self.create_syntax_error_constructor()), - // 19.3.34 TypeError ( . . . ) - Symbol::TYPE_ERROR => Value::Object(self.create_type_error_constructor()), - // 19.3.39 URIError ( . . . ) - Symbol::URI_ERROR => Value::Object(self.create_uri_error_constructor()), - } + fn init_intrinsic_objects(&mut self) { + self.init_object_prototype(); + self.init_function_prototype(); + self.init_string_prototype(); + self.init_promise_prototype(); + self.init_error_prototype(); + self.init_aggregate_error_prototype(); + self.init_eval_error_prototype(); + self.init_internal_error_prototype(); + self.init_range_error_prototype(); + self.init_reference_error_prototype(); + self.init_syntax_error_prototype(); + self.init_type_error_prototype(); + self.init_uri_error_prototype(); } fn create_builtin_function(&mut self, params: &BuiltinFunctionParams) -> HandleMut { @@ -149,9 +121,9 @@ impl Runtime { ?params.slots, ?params.prototype ); - debug_assert!(self.function_prototype.is_some()); let closure = self.create_closure(params.lambda, LambdaId::HOST, 0); - let mut func = self.create_object(self.function_prototype); + let mut func = self.create_object(); + func.set_prototype(self.builtins.function_prototype); func.slots_mut().extend_from_slice(params.slots); func.set_closure(closure); if let Some(prototype) = params.prototype { @@ -192,7 +164,7 @@ impl Runtime { Value::Boolean(true) => Ok(1.0), Value::Boolean(false) => Ok(0.0), Value::Number(value) => Ok(*value), - Value::String(_value) => Err(Error::InternalError), // TODO + Value::String(_value) => runtime_todo!(), // TODO(feat): 7.1.1 ToPrimitive() Value::Object(_) => Ok(f64::NAN), } @@ -233,13 +205,13 @@ impl Runtime { logger::debug!(event = "runtime.value_to_string", ?value); match value { Value::None => unreachable!("Value::None"), - Value::Undefined => Ok(const_string!("undefined")), - Value::Null => Ok(const_string!("null")), - Value::Boolean(true) => Ok(const_string!("true")), - Value::Boolean(false) => Ok(const_string!("false")), + Value::Undefined => Ok(const_string_handle!("undefined")), + Value::Null => Ok(const_string_handle!("null")), + Value::Boolean(true) => Ok(const_string_handle!("true")), + Value::Boolean(false) => Ok(const_string_handle!("false")), Value::Number(value) => Ok(self.number_to_string(*value)), Value::String(value) => Ok(*value), - // TODO(feat): Value::Symbol(_) => Err(Error::TypeError), + // TODO(feat): Value::Symbol(_) => type_error!(), Value::Object(value) => self.object_to_string(*value), } } @@ -249,16 +221,17 @@ impl Runtime { if self.is_string_object(object) { Ok(object.string()) } else { - Ok(const_string!("[object Object]")) + Ok(const_string_handle!("[object Object]")) } } pub(crate) fn create_exception(&mut self, err: Error) -> Value { - Value::Object(match err { - Error::SyntaxError => self.create_syntax_error(None), - Error::TypeError => self.create_type_error(None), - Error::RangeError => self.create_range_error(None), - Error::InternalError => self.create_internal_error(None), + let msg = err.message.map(Handle::from_ref); + Value::Object(match err.kind { + ErrorKind::SyntaxError => self.create_syntax_error(msg), + ErrorKind::TypeError => self.create_type_error(msg), + ErrorKind::RangeError => self.create_range_error(msg), + ErrorKind::InternalError => self.create_internal_error(msg), }) } @@ -292,96 +265,11 @@ impl Runtime { } } -extern "C" fn eval( - runtime: &mut Runtime, - context: &mut CallContext, - retv: &mut Value, -) -> Status { - match imp::eval(runtime, context) { - Ok(value) => { - *retv = value; - Status::Normal - } - Err(err) => { - *retv = runtime.create_exception(err); - Status::Exception - } - } -} - -extern "C" fn is_finite( - runtime: &mut Runtime, - context: &mut CallContext, - retv: &mut Value, -) -> Status { - match imp::is_finite(runtime, context) { - Ok(value) => { - *retv = value; - Status::Normal - } - Err(err) => { - *retv = runtime.create_exception(err); - Status::Exception - } - } -} - -extern "C" fn is_nan( - runtime: &mut Runtime, - context: &mut CallContext, - retv: &mut Value, -) -> Status { - match imp::is_nan(runtime, context) { - Ok(value) => { - *retv = value; - Status::Normal - } - Err(err) => { - *retv = runtime.create_exception(err); - Status::Exception - } - } -} - -extern "C" fn parse_float( - runtime: &mut Runtime, - context: &mut CallContext, - retv: &mut Value, -) -> Status { - match imp::parse_float(runtime, context) { - Ok(value) => { - *retv = value; - Status::Normal - } - Err(err) => { - *retv = runtime.create_exception(err); - Status::Exception - } - } -} - -extern "C" fn parse_int( - runtime: &mut Runtime, - context: &mut CallContext, - retv: &mut Value, -) -> Status { - match imp::parse_int(runtime, context) { - Ok(value) => { - *retv = value; - Status::Normal - } - Err(err) => { - *retv = runtime.create_exception(err); - Status::Exception - } - } -} - // 7.2.1 RequireObjectCoercible ( argument ) fn require_object_coercible(value: &Value) -> Result<(), Error> { match value { Value::None => unreachable!(), - Value::Undefined | Value::Null => Err(Error::TypeError), + Value::Undefined | Value::Null => type_error!(), _ => Ok(()), } } @@ -394,67 +282,3 @@ struct BuiltinFunctionParams<'a, X> { slots: &'a [Value], prototype: Option>, } - -mod imp { - use super::*; - - pub fn eval(_runtime: &mut Runtime, _context: &mut CallContext) -> Result { - // TODO: impl - Err(Error::InternalError) - } - - pub fn is_finite( - runtime: &mut Runtime, - context: &mut CallContext, - ) -> Result { - let number = context.args().first().unwrap_or(&Value::Undefined); - let num = runtime.value_to_number(number)?; - Ok(Value::Boolean(num.is_finite())) - } - - pub fn is_nan(runtime: &mut Runtime, context: &mut CallContext) -> Result { - let number = context.args().first().unwrap_or(&Value::Undefined); - let num = runtime.value_to_number(number)?; - Ok(Value::Boolean(num.is_nan())) - } - - pub fn parse_float( - runtime: &mut Runtime, - context: &mut CallContext, - ) -> Result { - let string = context.args().first().unwrap_or(&Value::Undefined); - let input_string = runtime.value_to_string(string)?; - let trimmed_string = runtime.trim_string(input_string, true, false)?; - // TODO: 11.1.6 Static Semantics: ParseText ( sourceText, goalSymbol ) - let utf8 = char::decode_utf16(trimmed_string.code_units()) - .map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER)) - .collect::(); - match utf8.parse::() { - Ok(n) => Ok(Value::Number(n)), - Err(_) => Err(Error::SyntaxError), - } - } - - pub fn parse_int( - runtime: &mut Runtime, - context: &mut CallContext, - ) -> Result { - // TODO: impl - let string = context.args().first().unwrap_or(&Value::Undefined); - let input_string = runtime.value_to_string(string)?; - let s = runtime.trim_string(input_string, true, false)?; - let radix = context.args().get(1).unwrap_or(&Value::Undefined); - let radix = runtime.value_to_number(radix)?; - let radix = if radix.is_finite() { radix as i32 } else { 10 }; - if !(2..=36).contains(&radix) { - return Ok(Value::Number(f64::NAN)); - } - let utf8 = char::decode_utf16(s.code_units()) - .map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER)) - .collect::(); - match i64::from_str_radix(&utf8, radix as u32) { - Ok(n) => Ok(Value::Number(n as f64)), - Err(_) => Ok(Value::Number(f64::NAN)), - } - } -} diff --git a/libs/jsruntime/src/builtins/native_error_imp.rs.hbs b/libs/jsruntime/src/builtins/native_error_imp.rs.hbs index 7c6c40087..e0809d150 100644 --- a/libs/jsruntime/src/builtins/native_error_imp.rs.hbs +++ b/libs/jsruntime/src/builtins/native_error_imp.rs.hbs @@ -27,13 +27,15 @@ pub fn constructor(runtime: &mut Runtime, context: &mut CallContext) -> Re // TODO(feat): NewTarget if !context.is_new() { - return Err(Error::InternalError); + return runtime_todo!(); } let mut object = if let Value::Object(this) = context.this() { *this } else { - runtime.create_object(runtime.{{id}}_prototype) + let mut object = runtime.create_object(); + object.set_prototype(runtime.builtins.{{id}}_prototype); + object }; object.set_error(); @@ -72,14 +74,15 @@ pub fn {{id}}_prototype_message(_runtime: &mut Runtime, mut prototype: Han pub fn {{id}}_prototype_name(_runtime: &mut Runtime, mut prototype: HandleMut) { let _ = prototype.define_own_property( Symbol::NAME.into(), - Property::data_xxx(Value::String(const_string!("{{class}}"))), + Property::data_xxx(Value::String(const_string_handle!("{{class}}"))), ); } impl Runtime { #[allow(unused)] pub(crate) fn create_{{id}}(&mut self, msg: Option>) -> HandleMut { - let mut object = self.create_object(self.{{id}}_prototype); + let mut object = self.create_object(); + object.set_prototype(self.builtins.{{id}}_prototype); if let Some(msg) = msg { let _ = object.define_own_property( Symbol::MESSAGE.into(), diff --git a/libs/jsruntime/src/builtins/object/README.md b/libs/jsruntime/src/builtins/object/README.md index fbde5df4f..a496d7212 100644 --- a/libs/jsruntime/src/builtins/object/README.md +++ b/libs/jsruntime/src/builtins/object/README.md @@ -1,6 +1,6 @@ # Object -* [ ] [Object](https://tc39.es/ecma262/#sec-object-value) +* [x] [Object](https://tc39.es/ecma262/#sec-object-value) * [ ] [Object.assign](https://tc39.es/ecma262/#sec-object.assign) * [ ] [Object.create](https://tc39.es/ecma262/#sec-object.create) * [ ] [Object.defineProperties](https://tc39.es/ecma262/#sec-object.defineproperties) diff --git a/libs/jsruntime/src/builtins/object/imp.rs b/libs/jsruntime/src/builtins/object/imp.rs new file mode 100644 index 000000000..f30c7c815 --- /dev/null +++ b/libs/jsruntime/src/builtins/object/imp.rs @@ -0,0 +1,51 @@ +//$id object +//$class Object + +use crate::Error; +use crate::Runtime; +use crate::logger; +use crate::types::CallContext; +use crate::types::Value; + +//#sec-object-value constructor +pub fn constructor(runtime: &mut Runtime, context: &mut CallContext) -> Result { + logger::debug!(event = "object"); + let this = context.this(); + let args = context.args(); + let new = context.is_new(); + runtime.create_object_object(Some(this), args, new) +} + +// helpers + +impl Runtime { + pub(crate) fn create_object_object( + &mut self, + this: Option<&Value>, + args: &[Value], + new: bool, + ) -> Result { + // TODO(feat): NewTarget + if new { + let object = if let Some(&Value::Object(this)) = this { + this + } else { + // 10.4.3.4 StringCreate ( value, prototype ) + let mut object = self.create_object(); + object.set_prototype(self.builtins.string_prototype); + object + }; + Ok(Value::Object(object)) + } else { + match args.first() { + None | Some(Value::Undefined) | Some(Value::Null) => { + let mut object = self.create_object(); + object.set_prototype(self.builtins.object_prototype); + // TODO(feat): NewTarget + Ok(Value::Object(object)) + } + Some(value) => self.value_to_object(value), + } + } + } +} diff --git a/libs/jsruntime/src/builtins/object/mod.rs b/libs/jsruntime/src/builtins/object/mod.rs deleted file mode 100644 index 4478c4c0c..000000000 --- a/libs/jsruntime/src/builtins/object/mod.rs +++ /dev/null @@ -1,63 +0,0 @@ -use jsgc::HandleMut; - -use crate::Runtime; -use crate::logger; -use crate::types::CallContext; -use crate::types::Object; -use crate::types::Status; -use crate::types::Value; - -use super::BuiltinFunctionParams; - -impl Runtime { - pub(super) fn create_object_constructor(&mut self) -> HandleMut { - logger::debug!(event = "creater_object_constructor"); - self.create_builtin_function(&BuiltinFunctionParams { - lambda: constructor::, - name: const_string!(jsparser::symbol::builtin::names::OBJECT), - length: 0, - slots: &[], - prototype: self.object_prototype, - }) - } - - fn create_object_object(&mut self, context: &mut CallContext) -> Result { - match context.args().first() { - None | Some(Value::Undefined) | Some(Value::Null) => { - let object = self.create_object(self.object_prototype); - // TODO(feat): NewTarget - Ok(Value::Object(object)) - } - Some(value) => { - let mut retv = Value::None; - match self.value_to_object(value, &mut retv) { - Status::Normal => { - debug_assert!(matches!(retv, Value::Object(_))); - Ok(retv) - } - Status::Exception => Err(retv), - Status::Suspend => unreachable!(), - } - } - } - } -} - -// lambda functions - -extern "C" fn constructor( - runtime: &mut Runtime, - context: &mut CallContext, - retv: &mut Value, -) -> Status { - match runtime.create_object_object(context) { - Ok(value) => { - *retv = value; - Status::Normal - } - Err(value) => { - *retv = value; - Status::Exception - } - } -} diff --git a/libs/jsruntime/src/builtins/promise/imp.rs b/libs/jsruntime/src/builtins/promise/imp.rs index 64254b8d0..df1e1d135 100644 --- a/libs/jsruntime/src/builtins/promise/imp.rs +++ b/libs/jsruntime/src/builtins/promise/imp.rs @@ -1,5 +1,6 @@ //$id promise //$class Promise +//$inherits object use jsgc::HandleMut; @@ -20,14 +21,14 @@ pub fn constructor(runtime: &mut Runtime, context: &mut CallContext) -> Re // TODO(feat): NewTarget if !context.is_new() { - return Err(Error::InternalError); + return runtime_todo!(); } let args = context.args(); let executor = match args.first() { Some(Value::Object(executor)) if executor.is_callable() => *executor, - _ => return Err(Error::TypeError), + _ => return type_error!(), }; let closure = runtime.create_closure(promise_coroutine, LambdaId::HOST, 0); @@ -37,7 +38,9 @@ pub fn constructor(runtime: &mut Runtime, context: &mut CallContext) -> Re let mut object = if let Value::Object(this) = context.this() { *this } else { - runtime.create_object(runtime.promise_prototype) + let mut object = runtime.create_object(); + object.set_prototype(runtime.builtins.promise_prototype); + object }; object.set_promise(promise); @@ -122,17 +125,20 @@ fn promise_resolve_sync( runtime: &mut Runtime, context: &mut CallContext, ) -> Result { - let func = context.func().ok_or(Error::InternalError)?; + let func = match context.func() { + Some(func) => func, + _ => return runtime_todo!(), + }; let promise = match func.slots().first() { Some(Value::Object(promise)) => *promise, - _ => return Err(Error::InternalError), + _ => return runtime_todo!(), }; debug_assert!(runtime.is_promise_object(promise)); let resolution = context.args().first().unwrap_or(&Value::Undefined); match resolution { - Value::Object(object) if *object == promise => Err(Error::TypeError), + Value::Object(object) if *object == promise => type_error!(), // TODO: the 'then' property _ => { // TODO: fullfill_promise diff --git a/libs/jsruntime/src/builtins/string/imp.rs b/libs/jsruntime/src/builtins/string/imp.rs index 9b712389c..f1b110a35 100644 --- a/libs/jsruntime/src/builtins/string/imp.rs +++ b/libs/jsruntime/src/builtins/string/imp.rs @@ -1,5 +1,6 @@ //$id string //$class String +//$inherits object use jsgc::Handle; use jsparser::Symbol; @@ -17,7 +18,7 @@ use crate::types::string::SPACE; //#sec-string-constructor-string-value constructor pub fn constructor(runtime: &mut Runtime, context: &mut CallContext) -> Result { - logger::debug!(event = "promise"); + logger::debug!(event = "string"); let this = context.this(); let args = context.args(); let new = context.is_new(); @@ -50,11 +51,11 @@ pub fn string_from_code_point( for arg in context.args().iter() { let num = crate::types::number::to_number(arg)?; if num.is_infinite() || num.is_nan() || num.fract() != 0.0 { - return Err(Error::RangeError); + return range_error!(); } let cp = num as i64; if !(0..=0x10FFFF).contains(&cp) { - return Err(Error::RangeError); + return range_error!(); } utf16.extend_from_slice(encode_code_point(cp, &mut buf)); } @@ -377,7 +378,7 @@ fn string_padding_builtins_impl( } if int_max_length > u32::MAX as u64 { - return Err(Error::InternalError); + return runtime_todo!(); } let fill_string = args.get(1).unwrap_or(&Value::Undefined); @@ -435,7 +436,7 @@ pub fn string_prototype_repeat( let n = runtime.value_to_integer_or_infinity(count)?; if n < 0.0 || n.is_infinite() { - return Err(Error::RangeError); + return range_error!(); } if n == 0.0 { @@ -443,7 +444,7 @@ pub fn string_prototype_repeat( } if n > u32::MAX as f64 { - return Err(Error::InternalError); + return runtime_todo!(); } let result = runtime.repeat_string(s, n as u32); @@ -603,7 +604,9 @@ impl Runtime { this } else { // 10.4.3.4 StringCreate ( value, prototype ) - self.create_object(self.string_prototype) + let mut object = self.create_object(); + object.set_prototype(self.builtins.string_prototype); + object }; let length = string.len(); object.set_string(string); diff --git a/libs/jsruntime/src/builtins/update_readme_md.js b/libs/jsruntime/src/builtins/update_readme_md.js index 003b2ab2a..3dc663944 100644 --- a/libs/jsruntime/src/builtins/update_readme_md.js +++ b/libs/jsruntime/src/builtins/update_readme_md.js @@ -76,6 +76,23 @@ async function main(args, options) { } continue; } + prop = data.globalProperties.find((prop) => prop.id === id); + if (prop) { + switch (prop.kind) { + case 'global.constructor': + lines.push(`* [x] [${prop.name}](${ECMA262_SPEC_URL_BASE}${prop.id})`); + break; + case 'global.property': + lines.push(`* [x] [${prop.property}](${ECMA262_SPEC_URL_BASE}${prop.id})`); + break; + case 'global.function': + lines.push(`* [x] [${prop.signature.name}](${ECMA262_SPEC_URL_BASE}${prop.id})`); + break; + default: + unreachable(); + } + continue; + } lines.push(line.replace('[x]', '[ ]')); } diff --git a/libs/jsruntime/src/jobs.rs b/libs/jsruntime/src/jobs.rs index 1f6161223..ad76434ff 100644 --- a/libs/jsruntime/src/jobs.rs +++ b/libs/jsruntime/src/jobs.rs @@ -183,7 +183,8 @@ mod tests { let closure = runtime.create_closure(dummy, LambdaId::HOST, 0); let coroutine = runtime.create_coroutine(closure, 0, 0, 0); let promise = runtime.create_promise(coroutine); - let mut object = runtime.create_object(runtime.promise_prototype); + let mut object = runtime.create_object(); + object.set_prototype(runtime.builtins.promise_prototype); object.set_promise(promise); runtime.emit_promise_resolved(object, $value); }; @@ -194,20 +195,21 @@ mod tests { let closure = runtime.create_closure(dummy, LambdaId::HOST, 0); let coroutine = runtime.create_coroutine(closure, 0, 0, 0); let promise = runtime.create_promise(coroutine); - let mut object = runtime.create_object(runtime.promise_prototype); + let mut object = runtime.create_object(); + object.set_prototype(runtime.builtins.promise_prototype); object.set_promise(promise); runtime.emit_promise_rejected(object, $value); }; } - let object = runtime.create_object(None); + let object = runtime.create_object(); resolved!(Value::Undefined); - resolved!(Value::String(const_string!("resolved"))); + resolved!(Value::String(const_string_handle!("resolved"))); resolved!(Value::Object(object)); rejected!(Value::Undefined); - rejected!(Value::String(const_string!("rejected"))); + rejected!(Value::String(const_string_handle!("rejected"))); rejected!(Value::Object(object)); let mut roots = vec![]; diff --git a/libs/jsruntime/src/lib.rs b/libs/jsruntime/src/lib.rs index 76af6e384..a9c14102b 100644 --- a/libs/jsruntime/src/lib.rs +++ b/libs/jsruntime/src/lib.rs @@ -20,6 +20,7 @@ use jsparser::Symbol; use jsparser::SymbolRegistry; use backend::CodeRegistry; +use builtins::Builtins; use jobs::JobRunner; use lambda::LambdaKind; use lambda::LambdaRegistry; @@ -102,37 +103,8 @@ pub struct Runtime { code_registry: CodeRegistry, programs: Vec, heap: Heap, + builtins: Builtins, job_runner: JobRunner, - - // [[GlobalObject]] - global_object: HandleMut, - // %Object.prototype% - object_prototype: Option>, - // %Function.prototype% - function_prototype: Option>, - // %String.prototype% - string_prototype: Option>, - // %Promise.prototype% - promise_prototype: Option>, - // %Error.prototype% - error_prototype: Option>, - // %AggregateError.prototype% - aggregate_error_prototype: Option>, - // %EvalError.prototype% - eval_error_prototype: Option>, - // %InternalError.prototype% - internal_error_prototype: Option>, - // %RangeError.prototype% - range_error_prototype: Option>, - // %ReferenceError.prototype% - reference_error_prototype: Option>, - // %SyntaxError.prototype% - syntax_error_prototype: Option>, - // %TypeError.prototype% - type_error_prototype: Option>, - // URIError.prototype% - uri_error_prototype: Option>, - monitor: Option>, extension: X, } @@ -140,9 +112,7 @@ pub struct Runtime { impl Runtime { pub fn with_extension(extension: X) -> Self { let mut heap = Heap::new(); - - // TODO: pass [[Prototype]] of the global object. - let global_object = heap.alloc_mut(Object::new(Default::default())); + let builtins = Builtins::new(&mut heap); let mut runtime = Self { pref: Default::default(), @@ -151,26 +121,13 @@ impl Runtime { code_registry: CodeRegistry::new(), programs: vec![], heap, + builtins, job_runner: Default::default(), - global_object, - object_prototype: None, - function_prototype: None, - string_prototype: None, - promise_prototype: None, - error_prototype: None, - aggregate_error_prototype: None, - eval_error_prototype: None, - internal_error_prototype: None, - reference_error_prototype: None, - range_error_prototype: None, - syntax_error_prototype: None, - type_error_prototype: None, - uri_error_prototype: None, monitor: None, extension, }; - runtime.define_builtin_global_properties(); + runtime.init_builtin_objects(); runtime } @@ -204,12 +161,16 @@ impl Runtime { logger::debug!(event = "register_host_function", name, ?symbol); let lambda = types::into_lambda(host_fn); let closure = self.create_closure(lambda, LambdaId::HOST, 0); - let mut object = self.create_object(self.function_prototype); + let mut object = self.create_object(); + object.set_prototype(self.builtins.function_prototype); object.set_closure(closure); let value = Value::Object(object); // TODO: add `flags` to the arguments. let prop = Property::data_xxx(value); - let result = self.global_object.define_own_property(symbol.into(), prop); + let result = self + .builtins + .global_object + .define_own_property(symbol.into(), prop); debug_assert!(matches!(result, Ok(true))); } @@ -438,11 +399,11 @@ impl Runtime { self.heap.alloc_mut(Promise::new(coroutine)) } - fn create_object(&mut self, prototype: Option>) -> HandleMut { - self.heap.alloc_mut(Object::new(prototype)) + fn create_object(&mut self) -> HandleMut { + self.heap.alloc_mut(Object::new()) } - fn make_property_key(&mut self, value: &Value) -> Result { + fn make_property_key(&mut self, value: &Value) -> Result { match value { Value::None => unreachable!(), Value::Undefined => Ok(Symbol::KEYWORD_UNDEFINED.into()), @@ -453,10 +414,7 @@ impl Runtime { Value::String(value) => { Ok(self.symbol_registry.intern_utf16(value.make_utf16()).into()) } - Value::Object(_) => { - const MESSAGE: Handle = const_string!("TODO: make_property_key"); - Err(Value::Object(self.create_internal_error(Some(MESSAGE)))) - } + Value::Object(_) => runtime_todo!("TODO: make_property_key"), } } @@ -483,7 +441,7 @@ impl Runtime { Ok(()) } - fn push_value(&mut self, target: &mut Object, value: &Value) -> Result<(), Value> { + fn push_value(&mut self, target: &mut Object, value: &Value) -> Result<(), Error> { const LENGTH: PropertyKey = PropertyKey::Symbol(Symbol::LENGTH); let length = match target.get_value(&LENGTH) { @@ -501,11 +459,6 @@ impl Runtime { target.set_value(&LENGTH, &Value::from(length + 1.0)); Ok(()) } - - fn throw_internal_error(&mut self, message: Handle, retv: &mut Value) -> Status { - *retv = Value::Object(self.create_internal_error(Some(message))); - Status::Exception - } } impl Default for Runtime @@ -528,20 +481,7 @@ impl Drop for Runtime { // TODO(feat): derive(Trace) impl Trace for Runtime { fn trace(&self, visits: &mut jsgc::VisitList) { - self.global_object.trace(visits); - self.object_prototype.trace(visits); - self.function_prototype.trace(visits); - self.string_prototype.trace(visits); - self.promise_prototype.trace(visits); - self.error_prototype.trace(visits); - self.aggregate_error_prototype.trace(visits); - self.eval_error_prototype.trace(visits); - self.internal_error_prototype.trace(visits); - self.range_error_prototype.trace(visits); - self.reference_error_prototype.trace(visits); - self.syntax_error_prototype.trace(visits); - self.type_error_prototype.trace(visits); - self.uri_error_prototype.trace(visits); + self.builtins.trace(visits); // TODO: tracing X if X implements Trace. } } @@ -550,9 +490,30 @@ pub trait Monitor { fn print_function_ir(&mut self, id: LambdaId, ir: &dyn std::fmt::Display); } -#[allow(clippy::enum_variant_names)] -#[derive(Debug)] -pub enum Error { +#[derive(Clone, Debug)] +pub struct Error { + kind: ErrorKind, + message: Option<&'static String>, +} + +impl Error { + /// Returns the corresponding `ErrorKind` of this error. + pub fn kind(&self) -> ErrorKind { + self.kind + } + + /// Returns the optional message of this error. + pub fn message(&self) -> Option<&'static String> { + self.message + } + + fn new(kind: ErrorKind, message: Option<&'static String>) -> Self { + Self { kind, message } + } +} + +#[derive(Clone, Copy, Debug)] +pub enum ErrorKind { SyntaxError, TypeError, RangeError, diff --git a/libs/jsruntime/src/macros.rs b/libs/jsruntime/src/macros.rs index a12a4f612..316c63999 100644 --- a/libs/jsruntime/src/macros.rs +++ b/libs/jsruntime/src/macros.rs @@ -2,16 +2,67 @@ macro_rules! const_string { ($utf8:literal) => {{ const STRING: $crate::types::String = $crate::types::String::new_const(base::utf16!(&$utf8)); - jsgc::Handle::from_ref(&STRING) + &STRING }}; ($slice:expr) => {{ const STRING: $crate::types::String = $crate::types::String::new_const($slice); - jsgc::Handle::from_ref(&STRING) + &STRING }}; } +macro_rules! const_string_handle { + ($utf8:literal) => { + jsgc::Handle::from_ref(const_string!($utf8)) + }; + ($slice:expr) => { + jsgc::Handle::from_ref(const_string!($slice)) + }; +} + +macro_rules! syntax_error { + () => { + Err($crate::Error::new($crate::ErrorKind::SyntaxError, None)) + }; + ($message:literal) => { + Err($crate::Error::new( + $crate::ErrorKind::SyntaxError, + Some(const_string!($message)), + )) + }; +} + +macro_rules! error { + ($kind:expr) => { + Err($crate::Error::new($kind, None)) + }; + ($kind:expr, $message:literal) => { + Err($crate::Error::new($kind, Some(const_string!($message)))) + }; +} + +macro_rules! type_error { + () => { + error!($crate::ErrorKind::TypeError) + }; + ($message:literal) => { + error!($crate::ErrorKind::TypeError, $message) + }; +} + +macro_rules! range_error { + () => { + error!($crate::ErrorKind::RangeError) + }; + ($message:literal) => { + error!($crate::ErrorKind::RangeError, $message) + }; +} + macro_rules! runtime_todo { - ($runtime:expr, $message:literal, $retv:expr) => { - $runtime.throw_internal_error(const_string!($message), $retv) + () => { + error!($crate::ErrorKind::InternalError) + }; + ($message:literal) => { + error!($crate::ErrorKind::InternalError, $message) }; } diff --git a/libs/jsruntime/src/semantics/mod.rs b/libs/jsruntime/src/semantics/mod.rs index c03e9cd06..9ea610ab6 100644 --- a/libs/jsruntime/src/semantics/mod.rs +++ b/libs/jsruntime/src/semantics/mod.rs @@ -289,7 +289,8 @@ impl AnalyzerSupport for Runtime { name: Symbol, property: Property, ) -> Result { - self.global_object + self.builtins + .global_object .define_own_property(name.into(), property) } } diff --git a/libs/jsruntime/src/types/object.rs b/libs/jsruntime/src/types/object.rs index 3e9b310ff..8837b4356 100644 --- a/libs/jsruntime/src/types/object.rs +++ b/libs/jsruntime/src/types/object.rs @@ -239,16 +239,20 @@ impl Object { std::mem::offset_of!(Self, kernel) + Kernel::TRACING_OFFSET; pub(crate) const FLAGS_OFFSET: usize = std::mem::offset_of!(Self, flags); - pub fn new(prototype: Option>) -> Self { + pub fn new() -> Self { Self { kernel: Default::default(), flags: ObjectFlags::empty(), - prototype, + prototype: None, properties: Default::default(), slots: Default::default(), } } + pub fn set_prototype(&mut self, prototype: HandleMut) { + self.prototype = Some(prototype); + } + // TODO(perf): Which one is better? `Option::None` or `&Value::None`. // In JIT-compiled code, we need a `nullptr` check if we choose `Option::None`. // If we choose `&Value::None`, we always need a memory access for the discriminant check of @@ -333,10 +337,9 @@ impl Object { HandleMut::from_mut(self) } - pub fn is_instance_of(&self, prototype: Option>) -> bool { - debug_assert!(prototype.is_some()); + pub fn is_instance_of(&self, prototype: HandleMut) -> bool { // TODO: prototype chain - self.prototype == prototype + matches!(self.prototype, Some(p) if p == prototype) } pub(crate) fn set_constructor(&mut self) { diff --git a/libs/jsruntime/src/types/value.rs b/libs/jsruntime/src/types/value.rs index 50bfe1bef..dc9886c8b 100644 --- a/libs/jsruntime/src/types/value.rs +++ b/libs/jsruntime/src/types/value.rs @@ -61,7 +61,7 @@ impl Value { // 7.1.18 ToObject ( argument ) pub fn to_object(&self) -> Result, Error> { match self { - Self::Undefined | Self::Null => Err(Error::TypeError), + Self::Undefined | Self::Null => type_error!(), Self::Boolean(_value) => unimplemented!("new Boolean(value)"), Self::Number(_value) => unimplemented!("new Number(value)"), Self::String(_value) => unimplemented!("new String(value)"), @@ -83,12 +83,12 @@ impl Value { pub fn get_typeof(&self) -> Handle { match self { Self::None => unreachable!(), - Self::Undefined => const_string!("undefined"), - Self::Boolean(_) => const_string!("boolean"), - Self::Number(_) => const_string!("number"), - Self::String(_) => const_string!("string"), - Self::Object(object) if object.is_callable() => const_string!("function"), - Self::Null | Self::Object(_) => const_string!("object"), + Self::Undefined => const_string_handle!("undefined"), + Self::Boolean(_) => const_string_handle!("boolean"), + Self::Number(_) => const_string_handle!("number"), + Self::String(_) => const_string_handle!("string"), + Self::Object(object) if object.is_callable() => const_string_handle!("function"), + Self::Null | Self::Object(_) => const_string_handle!("object"), } }