From 3f6ee2276c00a73a8ef67aa8b4357919e08d1902 Mon Sep 17 00:00:00 2001 From: Haled Odat <8566042+HalidOdat@users.noreply.github.com> Date: Wed, 1 May 2024 20:33:49 +0200 Subject: [PATCH] Implement Latin1 JsString (#3450) --- cli/src/debug/function.rs | 9 +- cli/src/debug/limits.rs | 20 +- cli/src/debug/mod.rs | 25 +- cli/src/debug/optimizer.rs | 6 +- cli/src/debug/string.rs | 93 ++ core/engine/src/bigint.rs | 18 +- .../src/builtins/array/array_iterator.rs | 3 +- core/engine/src/builtins/array/mod.rs | 56 +- core/engine/src/builtins/array/tests.rs | 17 +- .../src/builtins/async_generator/mod.rs | 10 +- core/engine/src/builtins/atomics/mod.rs | 7 +- core/engine/src/builtins/bigint/tests.rs | 12 +- core/engine/src/builtins/builder.rs | 18 +- core/engine/src/builtins/date/mod.rs | 124 +- core/engine/src/builtins/date/tests.rs | 11 +- core/engine/src/builtins/date/utils.rs | 179 +-- core/engine/src/builtins/error/aggregate.rs | 11 +- core/engine/src/builtins/error/eval.rs | 9 +- core/engine/src/builtins/error/mod.rs | 21 +- core/engine/src/builtins/error/range.rs | 9 +- core/engine/src/builtins/error/reference.rs | 9 +- core/engine/src/builtins/error/syntax.rs | 9 +- core/engine/src/builtins/error/tests.rs | 58 +- core/engine/src/builtins/error/type.rs | 11 +- core/engine/src/builtins/error/uri.rs | 9 +- core/engine/src/builtins/escape/mod.rs | 8 +- core/engine/src/builtins/eval/mod.rs | 3 +- core/engine/src/builtins/function/mod.rs | 51 +- core/engine/src/builtins/function/tests.rs | 9 +- core/engine/src/builtins/intl/collator/mod.rs | 62 +- .../src/builtins/intl/date_time_format.rs | 33 +- .../src/builtins/intl/list_format/mod.rs | 31 +- core/engine/src/builtins/intl/locale/mod.rs | 25 +- core/engine/src/builtins/intl/locale/utils.rs | 4 +- .../src/builtins/intl/number_format/mod.rs | 14 +- .../builtins/intl/number_format/options.rs | 56 +- core/engine/src/builtins/intl/options.rs | 4 +- .../src/builtins/intl/plural_rules/mod.rs | 60 +- .../src/builtins/intl/segmenter/iterator.rs | 30 +- .../engine/src/builtins/intl/segmenter/mod.rs | 40 +- .../src/builtins/intl/segmenter/segments.rs | 2 +- .../iterable/async_from_sync_iterator.rs | 12 +- core/engine/src/builtins/iterable/mod.rs | 10 +- core/engine/src/builtins/json/mod.rs | 31 +- core/engine/src/builtins/json/tests.rs | 18 +- core/engine/src/builtins/map/mod.rs | 7 +- core/engine/src/builtins/map/tests.rs | 19 +- core/engine/src/builtins/mod.rs | 10 +- core/engine/src/builtins/number/globals.rs | 228 ++-- core/engine/src/builtins/number/mod.rs | 9 +- core/engine/src/builtins/number/tests.rs | 350 +++-- core/engine/src/builtins/object/mod.rs | 49 +- core/engine/src/builtins/object/tests.rs | 27 +- core/engine/src/builtins/options.rs | 4 +- core/engine/src/builtins/promise/mod.rs | 38 +- core/engine/src/builtins/proxy/mod.rs | 33 +- core/engine/src/builtins/reflect/tests.rs | 10 +- core/engine/src/builtins/regexp/mod.rs | 199 +-- .../builtins/regexp/regexp_string_iterator.rs | 6 +- core/engine/src/builtins/regexp/tests.rs | 17 +- core/engine/src/builtins/set/mod.rs | 11 +- core/engine/src/builtins/string/mod.rs | 212 +-- core/engine/src/builtins/string/tests.rs | 110 +- core/engine/src/builtins/symbol/mod.rs | 10 +- core/engine/src/builtins/symbol/tests.rs | 5 +- .../src/builtins/temporal/calendar/mod.rs | 22 +- .../src/builtins/temporal/calendar/object.rs | 46 +- .../src/builtins/temporal/duration/mod.rs | 59 +- .../src/builtins/temporal/instant/mod.rs | 35 +- core/engine/src/builtins/temporal/mod.rs | 4 +- core/engine/src/builtins/temporal/options.rs | 6 +- .../src/builtins/temporal/plain_date/mod.rs | 38 +- .../builtins/temporal/plain_date_time/mod.rs | 43 +- .../src/builtins/temporal/plain_time/mod.rs | 39 +- .../builtins/temporal/plain_year_month/mod.rs | 18 +- core/engine/src/builtins/temporal/tests.rs | 10 +- .../src/builtins/temporal/time_zone/custom.rs | 7 +- .../src/builtins/temporal/time_zone/mod.rs | 4 +- .../src/builtins/typed_array/builtin.rs | 20 +- core/engine/src/builtins/typed_array/mod.rs | 5 +- .../engine/src/builtins/typed_array/object.rs | 4 +- core/engine/src/builtins/uri/mod.rs | 26 +- core/engine/src/builtins/weak_map/mod.rs | 5 +- core/engine/src/builtins/weak_set/mod.rs | 5 +- core/engine/src/bytecompiler/declarations.rs | 6 +- .../engine/src/bytecompiler/expression/mod.rs | 20 +- core/engine/src/bytecompiler/mod.rs | 18 +- core/engine/src/class.rs | 12 +- core/engine/src/context/intrinsics.rs | 19 +- core/engine/src/context/mod.rs | 16 +- core/engine/src/error.rs | 22 +- core/engine/src/lib.rs | 11 +- core/engine/src/module/source.rs | 6 +- core/engine/src/object/builtins/jsdate.rs | 4 +- core/engine/src/object/builtins/jsmap.rs | 68 +- core/engine/src/object/builtins/jspromise.rs | 12 +- core/engine/src/object/builtins/jsproxy.rs | 34 +- .../src/object/internal_methods/string.rs | 3 +- core/engine/src/object/jsobject.rs | 32 +- core/engine/src/object/mod.rs | 11 +- .../src/optimizer/pass/constant_folding.rs | 14 +- core/engine/src/property/mod.rs | 19 +- core/engine/src/string/common.rs | 1198 ++++++++++------- core/engine/src/string/iter.rs | 95 ++ core/engine/src/string/mod.rs | 687 ++++++---- core/engine/src/string/str.rs | 358 +++++ core/engine/src/symbol.rs | 11 +- core/engine/src/tests/control_flow/loops.rs | 21 +- core/engine/src/tests/control_flow/mod.rs | 23 +- core/engine/src/tests/env.rs | 7 +- core/engine/src/tests/function.rs | 13 +- core/engine/src/tests/mod.rs | 7 +- core/engine/src/tests/operators.rs | 32 +- core/engine/src/tests/spread.rs | 9 +- core/engine/src/value/conversions/mod.rs | 10 +- .../src/value/conversions/serde_json.rs | 23 +- core/engine/src/value/display.rs | 14 +- core/engine/src/value/equality.rs | 12 +- core/engine/src/value/mod.rs | 33 +- core/engine/src/value/operations.rs | 6 +- core/engine/src/value/tests.rs | 38 +- core/engine/src/vm/flowgraph/mod.rs | 4 +- core/engine/src/vm/opcode/await/mod.rs | 5 +- core/engine/src/vm/opcode/concat/mod.rs | 5 +- .../src/vm/opcode/define/class/getter.rs | 12 +- .../src/vm/opcode/define/class/setter.rs | 12 +- core/engine/src/vm/opcode/generator/mod.rs | 6 +- core/engine/src/vm/opcode/iteration/for_in.rs | 4 +- .../src/vm/opcode/iteration/iterator.rs | 9 +- .../src/vm/opcode/push/class/private.rs | 7 +- core/engine/src/vm/opcode/set/private.rs | 5 +- core/engine/src/vm/opcode/set/property.rs | 8 +- core/engine/src/vm/opcode/templates/mod.rs | 4 +- core/engine/src/vm/tests.rs | 25 +- core/macros/src/lib.rs | 27 + core/runtime/src/console/mod.rs | 12 +- docs/boa_object.md | 31 + examples/src/bin/classes.rs | 11 +- examples/src/bin/closures.rs | 25 +- examples/src/bin/futures.rs | 6 +- examples/src/bin/host_defined.rs | 6 +- examples/src/bin/jsarray.rs | 18 +- examples/src/bin/jsarraybuffer.rs | 4 +- examples/src/bin/jsmap.rs | 2 +- examples/src/bin/jstypedarray.rs | 14 +- examples/src/bin/module_fetch.rs | 11 +- examples/src/bin/modulehandler.rs | 20 +- examples/src/bin/modules.rs | 11 +- examples/src/bin/synthetic.rs | 22 +- tests/tester/src/exec/js262.rs | 2 +- tests/tester/src/exec/mod.rs | 14 +- 151 files changed, 3778 insertions(+), 2495 deletions(-) create mode 100644 cli/src/debug/string.rs create mode 100644 core/engine/src/string/iter.rs create mode 100644 core/engine/src/string/str.rs diff --git a/cli/src/debug/function.rs b/cli/src/debug/function.rs index 8337ba708b..71326c1294 100644 --- a/cli/src/debug/function.rs +++ b/cli/src/debug/function.rs @@ -1,6 +1,6 @@ use boa_engine::{ builtins::function::OrdinaryFunction, - js_string, + js_str, js_string, object::ObjectInitializer, vm::flowgraph::{Direction, Graph}, Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction, @@ -68,10 +68,9 @@ fn flowgraph(_this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResu let mut direction = Direction::LeftToRight; if let Some(arguments) = args.get(1) { if let Some(arguments) = arguments.as_object() { - format = flowgraph_parse_format_option(&arguments.get(js_string!("format"), context)?)?; - direction = flowgraph_parse_direction_option( - &arguments.get(js_string!("direction"), context)?, - )?; + format = flowgraph_parse_format_option(&arguments.get(js_str!("format"), context)?)?; + direction = + flowgraph_parse_direction_option(&arguments.get(js_str!("direction"), context)?)?; } else if value.is_string() { format = flowgraph_parse_format_option(value)?; } else { diff --git a/cli/src/debug/limits.rs b/cli/src/debug/limits.rs index 1f1868454e..e2be688c74 100644 --- a/cli/src/debug/limits.rs +++ b/cli/src/debug/limits.rs @@ -1,5 +1,5 @@ use boa_engine::{ - js_string, + js_str, object::{FunctionObjectBuilder, ObjectInitializer}, property::Attribute, Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction, @@ -51,51 +51,51 @@ fn set_recursion(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResu pub(super) fn create_object(context: &mut Context) -> JsObject { let get_loop = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_loop)) - .name("get loop") + .name(js_str!("get loop")) .length(0) .build(); let set_loop = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_loop)) - .name("set loop") + .name(js_str!("set loop")) .length(1) .build(); let get_stack = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_stack)) - .name("get stack") + .name(js_str!("get stack")) .length(0) .build(); let set_stack = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_stack)) - .name("set stack") + .name(js_str!("set stack")) .length(1) .build(); let get_recursion = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(get_recursion)) - .name("get recursion") + .name(js_str!("get recursion")) .length(0) .build(); let set_recursion = FunctionObjectBuilder::new(context.realm(), NativeFunction::from_fn_ptr(set_recursion)) - .name("set recursion") + .name(js_str!("set recursion")) .length(1) .build(); ObjectInitializer::new(context) .accessor( - js_string!("loop"), + js_str!("loop"), Some(get_loop), Some(set_loop), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ) .accessor( - js_string!("stack"), + js_str!("stack"), Some(get_stack), Some(set_stack), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ) .accessor( - js_string!("recursion"), + js_str!("recursion"), Some(get_recursion), Some(set_recursion), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, diff --git a/cli/src/debug/mod.rs b/cli/src/debug/mod.rs index 0e62caf3c8..a093f76a40 100644 --- a/cli/src/debug/mod.rs +++ b/cli/src/debug/mod.rs @@ -1,7 +1,7 @@ // Allow lint so it, doesn't warn about `JsResult<>` unneeded return on functions. #![allow(clippy::unnecessary_wraps)] -use boa_engine::{js_string, object::ObjectInitializer, property::Attribute, Context, JsObject}; +use boa_engine::{js_str, object::ObjectInitializer, property::Attribute, Context, JsObject}; mod function; mod gc; @@ -10,6 +10,7 @@ mod object; mod optimizer; mod realm; mod shape; +mod string; fn create_boa_object(context: &mut Context) -> JsObject { let function_module = function::create_object(context); @@ -19,43 +20,49 @@ fn create_boa_object(context: &mut Context) -> JsObject { let gc_module = gc::create_object(context); let realm_module = realm::create_object(context); let limits_module = limits::create_object(context); + let string_module = string::create_string(context); ObjectInitializer::new(context) .property( - js_string!("function"), + js_str!("function"), function_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( - js_string!("object"), + js_str!("object"), object_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( - js_string!("shape"), + js_str!("shape"), shape_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( - js_string!("optimizer"), + js_str!("optimizer"), optimizer_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( - js_string!("gc"), + js_str!("gc"), gc_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( - js_string!("realm"), + js_str!("realm"), realm_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( - js_string!("limits"), + js_str!("limits"), limits_module, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) + .property( + js_str!("string"), + string_module, + Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) .build() } @@ -64,7 +71,7 @@ pub(crate) fn init_boa_debug_object(context: &mut Context) { let boa_object = create_boa_object(context); context .register_global_property( - js_string!("$boa"), + js_str!("$boa"), boa_object, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) diff --git a/cli/src/debug/optimizer.rs b/cli/src/debug/optimizer.rs index 92793e93ba..fdb93e17ec 100644 --- a/cli/src/debug/optimizer.rs +++ b/cli/src/debug/optimizer.rs @@ -1,5 +1,5 @@ use boa_engine::{ - js_string, + js_str, object::{FunctionObjectBuilder, ObjectInitializer}, optimizer::OptimizerOptions, property::Attribute, @@ -64,13 +64,13 @@ pub(super) fn create_object(context: &mut Context) -> JsObject { .build(); ObjectInitializer::new(context) .accessor( - js_string!("constantFolding"), + js_str!("constantFolding"), Some(get_constant_folding), Some(set_constant_folding), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ) .accessor( - js_string!("statistics"), + js_str!("statistics"), Some(get_statistics), Some(set_statistics), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, diff --git a/cli/src/debug/string.rs b/cli/src/debug/string.rs new file mode 100644 index 0000000000..ce064ed2af --- /dev/null +++ b/cli/src/debug/string.rs @@ -0,0 +1,93 @@ +use boa_engine::{ + js_string, object::ObjectInitializer, property::Attribute, string::JsStrVariant, Context, + JsNativeError, JsObject, JsResult, JsValue, NativeFunction, +}; + +fn storage(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { + let Some(value) = args.first() else { + return Err(JsNativeError::typ() + .with_message("expected string argument") + .into()); + }; + + let Some(string) = value.as_string() else { + return Err(JsNativeError::typ() + .with_message(format!("expected string, got {}", value.type_of())) + .into()); + }; + + let storage = if string.is_static() { "static" } else { "heap" }; + Ok(js_string!(storage).into()) +} + +fn encoding(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { + let Some(value) = args.first() else { + return Err(JsNativeError::typ() + .with_message("expected string argument") + .into()); + }; + + let Some(string) = value.as_string() else { + return Err(JsNativeError::typ() + .with_message(format!("expected string, got {}", value.type_of())) + .into()); + }; + + let str = string.as_str(); + let encoding = match str.variant() { + JsStrVariant::Latin1(_) => "latin1", + JsStrVariant::Utf16(_) => "utf16", + }; + Ok(js_string!(encoding).into()) +} + +fn summary(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + let Some(value) = args.first() else { + return Err(JsNativeError::typ() + .with_message("expected string argument") + .into()); + }; + + let Some(string) = value.as_string() else { + return Err(JsNativeError::typ() + .with_message(format!("expected string, got {}", value.type_of())) + .into()); + }; + + let storage = if string.is_static() { "static" } else { "heap" }; + let encoding = match string.as_str().variant() { + JsStrVariant::Latin1(_) => "latin1", + JsStrVariant::Utf16(_) => "utf16", + }; + + let summary = ObjectInitializer::new(context) + .property(js_string!("storage"), js_string!(storage), Attribute::all()) + .property( + js_string!("encoding"), + js_string!(encoding), + Attribute::all(), + ) + .build(); + + Ok(summary.into()) +} + +pub(super) fn create_string(context: &mut Context) -> JsObject { + ObjectInitializer::new(context) + .function( + NativeFunction::from_fn_ptr(storage), + js_string!("storage"), + 1, + ) + .function( + NativeFunction::from_fn_ptr(encoding), + js_string!("encoding"), + 1, + ) + .function( + NativeFunction::from_fn_ptr(summary), + js_string!("summary"), + 1, + ) + .build() +} diff --git a/core/engine/src/bigint.rs b/core/engine/src/bigint.rs index 367ddeda75..87584fa2b9 100644 --- a/core/engine/src/bigint.rs +++ b/core/engine/src/bigint.rs @@ -1,6 +1,6 @@ //! Boa's implementation of ECMAScript's bigint primitive type. -use crate::{builtins::Number, error::JsNativeError, JsData, JsResult}; +use crate::{builtins::Number, error::JsNativeError, JsData, JsResult, JsString}; use boa_gc::{Finalize, Trace}; use num_integer::Integer; use num_traits::{pow::Pow, FromPrimitive, One, ToPrimitive, Zero}; @@ -89,6 +89,22 @@ impl JsBigInt { }) } + /// Abstract operation `StringToBigInt ( str )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-stringtobigint + pub(crate) fn from_js_string(string: &JsString) -> Option { + // 1. Let text be ! StringToCodePoints(str). + // 2. Let literal be ParseText(text, StringIntegerLiteral). + // 3. If literal is a List of errors, return undefined. + // 4. Let mv be the MV of literal. + // 5. Assert: mv is an integer. + // 6. Return ℤ(mv). + JsBigInt::from_string(string.to_std_string().ok().as_ref()?) + } + /// This function takes a string and converts it to `BigInt` type. /// /// More information: diff --git a/core/engine/src/builtins/array/array_iterator.rs b/core/engine/src/builtins/array/array_iterator.rs index d323a7ad60..70393e9340 100644 --- a/core/engine/src/builtins/array/array_iterator.rs +++ b/core/engine/src/builtins/array/array_iterator.rs @@ -20,6 +20,7 @@ use crate::{ Context, JsData, JsResult, }; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; /// The Array Iterator object represents an iteration over an array. It implements the iterator protocol. @@ -52,7 +53,7 @@ impl IntrinsicObject for ArrayIterator { .static_method(Self::next, js_string!("next"), 0) .static_property( JsSymbol::to_string_tag(), - js_string!("Array Iterator"), + js_str!("Array Iterator"), Attribute::CONFIGURABLE, ) .build(); diff --git a/core/engine/src/builtins/array/mod.rs b/core/engine/src/builtins/array/mod.rs index 06185d6954..aa96247040 100644 --- a/core/engine/src/builtins/array/mod.rs +++ b/core/engine/src/builtins/array/mod.rs @@ -10,7 +10,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array use boa_gc::{Finalize, Trace}; -use boa_macros::utf16; +use boa_macros::js_str; use boa_profiler::Profiler; use thin_vec::ThinVec; @@ -161,12 +161,12 @@ impl IntrinsicObject for Array { .method(Self::unshift, js_string!("unshift"), 1) .method(Self::with, js_string!("with"), 2) .property( - utf16!("toString"), + js_string!("toString"), to_string_function, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .property( - utf16!("values"), + js_string!("values"), values_function.clone(), Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) @@ -1012,7 +1012,7 @@ impl Array { for k in 0..len { // a. If k > 0, set R to the string-concatenation of R and sep. if k > 0 { - r.extend_from_slice(&separator); + r.push(separator.clone()); } // b. Let element be ? Get(O, ! ToString(𝔽(k))). let element = o.get(k, context)?; @@ -1023,7 +1023,7 @@ impl Array { element.to_string(context)? }; // d. Set R to the string-concatenation of R and next. - r.extend_from_slice(&next); + r.push(next.clone()); // e. Set k to k + 1. } // 8. Return R. @@ -1051,7 +1051,7 @@ impl Array { // 1. Let array be ? ToObject(this value). let array = this.to_object(context)?; // 2. Let func be ? Get(array, "join"). - let func = array.get(utf16!("join"), context)?; + let func = array.get(js_string!("join"), context)?; // 3. If IsCallable(func) is false, set func to the intrinsic function %Object.prototype.toString%. // 4. Return ? Call(func, array). if let Some(func) = func.as_callable() { @@ -2203,12 +2203,12 @@ impl Array { #[cfg(feature = "intl")] { // TODO: this should eventually return a locale-sensitive separator. - utf16!(", ") + js_str!(", ") } #[cfg(not(feature = "intl"))] { - utf16!(", ") + js_str!(", ") } }; @@ -2221,7 +2221,7 @@ impl Array { // a. If k > 0, then if k > 0 { // i. Set R to the string-concatenation of R and separator. - r.extend_from_slice(separator); + r.extend(separator.iter()); } // b. Let nextElement be ? Get(array, ! ToString(k)). @@ -2231,16 +2231,16 @@ impl Array { if !next.is_null_or_undefined() { // i. Let S be ? ToString(? Invoke(nextElement, "toLocaleString", « locales, options »)). let s = next - .invoke(utf16!("toLocaleString"), args, context)? + .invoke(js_str!("toLocaleString"), args, context)? .to_string(context)?; // ii. Set R to the string-concatenation of R and S. - r.extend_from_slice(&s); + r.extend(s.iter()); } // d. Increase k by 1. } // 7. Return R. - Ok(js_string!(r).into()) + Ok(js_string!(&r[..]).into()) } /// Gets the delete count of a splice operation. @@ -3304,37 +3304,37 @@ impl Array { { let mut obj = unscopable_list.borrow_mut(); // 2. Perform ! CreateDataPropertyOrThrow(unscopableList, "at", true). - obj.insert(utf16!("at"), true_prop.clone()); + obj.insert(js_str!("at"), true_prop.clone()); // 3. Perform ! CreateDataPropertyOrThrow(unscopableList, "copyWithin", true). - obj.insert(utf16!("copyWithin"), true_prop.clone()); + obj.insert(js_str!("copyWithin"), true_prop.clone()); // 4. Perform ! CreateDataPropertyOrThrow(unscopableList, "entries", true). - obj.insert(utf16!("entries"), true_prop.clone()); + obj.insert(js_str!("entries"), true_prop.clone()); // 5. Perform ! CreateDataPropertyOrThrow(unscopableList, "fill", true). - obj.insert(utf16!("fill"), true_prop.clone()); + obj.insert(js_str!("fill"), true_prop.clone()); // 6. Perform ! CreateDataPropertyOrThrow(unscopableList, "find", true). - obj.insert(utf16!("find"), true_prop.clone()); + obj.insert(js_str!("find"), true_prop.clone()); // 7. Perform ! CreateDataPropertyOrThrow(unscopableList, "findIndex", true). - obj.insert(utf16!("findIndex"), true_prop.clone()); + obj.insert(js_str!("findIndex"), true_prop.clone()); // 8. Perform ! CreateDataPropertyOrThrow(unscopableList, "findLast", true). - obj.insert(utf16!("findLast"), true_prop.clone()); + obj.insert(js_str!("findLast"), true_prop.clone()); // 9. Perform ! CreateDataPropertyOrThrow(unscopableList, "findLastIndex", true). - obj.insert(utf16!("findLastIndex"), true_prop.clone()); + obj.insert(js_str!("findLastIndex"), true_prop.clone()); // 10. Perform ! CreateDataPropertyOrThrow(unscopableList, "flat", true). - obj.insert(utf16!("flat"), true_prop.clone()); + obj.insert(js_str!("flat"), true_prop.clone()); // 11. Perform ! CreateDataPropertyOrThrow(unscopableList, "flatMap", true). - obj.insert(utf16!("flatMap"), true_prop.clone()); + obj.insert(js_str!("flatMap"), true_prop.clone()); // 12. Perform ! CreateDataPropertyOrThrow(unscopableList, "includes", true). - obj.insert(utf16!("includes"), true_prop.clone()); + obj.insert(js_str!("includes"), true_prop.clone()); // 13. Perform ! CreateDataPropertyOrThrow(unscopableList, "keys", true). - obj.insert(utf16!("keys"), true_prop.clone()); + obj.insert(js_str!("keys"), true_prop.clone()); // 14. Perform ! CreateDataPropertyOrThrow(unscopableList, "toReversed", true). - obj.insert(utf16!("toReversed"), true_prop.clone()); + obj.insert(js_str!("toReversed"), true_prop.clone()); // 15. Perform ! CreateDataPropertyOrThrow(unscopableList, "toSorted", true). - obj.insert(utf16!("toSorted"), true_prop.clone()); + obj.insert(js_str!("toSorted"), true_prop.clone()); // 16. Perform ! CreateDataPropertyOrThrow(unscopableList, "toSpliced", true). - obj.insert(utf16!("toSpliced"), true_prop.clone()); + obj.insert(js_str!("toSpliced"), true_prop.clone()); // 17. Perform ! CreateDataPropertyOrThrow(unscopableList, "values", true). - obj.insert(utf16!("values"), true_prop); + obj.insert(js_str!("values"), true_prop); } // 13. Return unscopableList. diff --git a/core/engine/src/builtins/array/tests.rs b/core/engine/src/builtins/array/tests.rs index 9f883439f5..4df066ca81 100644 --- a/core/engine/src/builtins/array/tests.rs +++ b/core/engine/src/builtins/array/tests.rs @@ -2,6 +2,7 @@ use super::Array; use crate::{ builtins::Number, js_string, run_test_actions, Context, JsNativeErrorKind, JsValue, TestAction, }; +use boa_macros::js_str; use indoc::indoc; #[test] @@ -38,7 +39,7 @@ fn of() { TestAction::assert("arrayEquals(Array.of(), [])"), TestAction::run("let a = Array.of.call(Date, 'a', undefined, 3);"), TestAction::assert("a instanceof Date"), - TestAction::assert_eq("a[0]", js_string!("a")), + TestAction::assert_eq("a[0]", js_str!("a")), TestAction::assert_eq("a[1]", JsValue::undefined()), TestAction::assert_eq("a[2]", 3), TestAction::assert_eq("a.length", 3), @@ -75,9 +76,9 @@ fn copy_within() { fn join() { run_test_actions([ TestAction::assert_eq("[].join('.')", js_string!()), - TestAction::assert_eq("['a'].join('.')", js_string!("a")), - TestAction::assert_eq("['a', 'b', 'c'].join('.')", js_string!("a.b.c")), - TestAction::assert_eq("let a=[];a[0]=a;a[1]=a;a[2]=a;a.join()", js_string!(",,")), + TestAction::assert_eq("['a'].join('.')", js_str!("a")), + TestAction::assert_eq("['a', 'b', 'c'].join('.')", js_str!("a.b.c")), + TestAction::assert_eq("let a=[];a[0]=a;a[1]=a;a[2]=a;a.join()", js_str!(",,")), ]); } @@ -85,8 +86,8 @@ fn join() { fn to_string() { run_test_actions([ TestAction::assert_eq("[].toString()", js_string!()), - TestAction::assert_eq("['a'].toString()", js_string!("a")), - TestAction::assert_eq("['a', 'b', 'c'].toString()", js_string!("a,b,c")), + TestAction::assert_eq("['a'].toString()", js_str!("a")), + TestAction::assert_eq("['a', 'b', 'c'].toString()", js_str!("a,b,c")), ]); } @@ -116,7 +117,7 @@ fn every() { fn find() { run_test_actions([TestAction::assert_eq( "['a', 'b', 'c'].find(e => e == 'a')", - js_string!("a"), + js_str!("a"), )]); } @@ -350,7 +351,7 @@ fn fill_obj_ref() { let a = new Array(3).fill(obj); obj.hi = 'hi' "#}), - TestAction::assert_eq("a[2].hi", js_string!("hi")), + TestAction::assert_eq("a[2].hi", js_str!("hi")), ]); } diff --git a/core/engine/src/builtins/async_generator/mod.rs b/core/engine/src/builtins/async_generator/mod.rs index 2752f82185..976dab93cf 100644 --- a/core/engine/src/builtins/async_generator/mod.rs +++ b/core/engine/src/builtins/async_generator/mod.rs @@ -7,8 +7,10 @@ use crate::{ builtins::{ - generator::GeneratorContext, iterable::create_iter_result_object, - promise::if_abrupt_reject_promise, promise::PromiseCapability, Promise, + generator::GeneratorContext, + iterable::create_iter_result_object, + promise::{if_abrupt_reject_promise, PromiseCapability}, + Promise, }, context::intrinsics::Intrinsics, error::JsNativeError, @@ -576,7 +578,7 @@ impl AsyncGenerator { generator.clone(), ), ) - .name("") + .name(js_string!("")) .length(1) .build(); @@ -611,7 +613,7 @@ impl AsyncGenerator { generator, ), ) - .name("") + .name(js_string!("")) .length(1) .build(); diff --git a/core/engine/src/builtins/atomics/mod.rs b/core/engine/src/builtins/atomics/mod.rs index bca4509562..db74e6fbca 100644 --- a/core/engine/src/builtins/atomics/mod.rs +++ b/core/engine/src/builtins/atomics/mod.rs @@ -21,6 +21,7 @@ use crate::{ JsString, JsValue, }; +use boa_macros::js_str; use boa_profiler::Profiler; use super::{ @@ -463,9 +464,9 @@ impl Atomics { }; Ok(match result { - futex::AtomicsWaitResult::NotEqual => js_string!("not-equal"), - futex::AtomicsWaitResult::TimedOut => js_string!("timed-out"), - futex::AtomicsWaitResult::Ok => js_string!("ok"), + futex::AtomicsWaitResult::NotEqual => js_str!("not-equal"), + futex::AtomicsWaitResult::TimedOut => js_str!("timed-out"), + futex::AtomicsWaitResult::Ok => js_str!("ok"), } .into()) } diff --git a/core/engine/src/builtins/bigint/tests.rs b/core/engine/src/builtins/bigint/tests.rs index 8200f2cdef..17ab0db1bb 100644 --- a/core/engine/src/builtins/bigint/tests.rs +++ b/core/engine/src/builtins/bigint/tests.rs @@ -1,4 +1,6 @@ -use crate::{js_string, run_test_actions, JsBigInt, JsNativeErrorKind, TestAction}; +use boa_macros::js_str; + +use crate::{run_test_actions, JsBigInt, JsNativeErrorKind, TestAction}; #[test] fn equality() { @@ -147,10 +149,10 @@ fn operations() { #[test] fn to_string() { run_test_actions([ - TestAction::assert_eq("1000n.toString()", js_string!("1000")), - TestAction::assert_eq("1000n.toString(2)", js_string!("1111101000")), - TestAction::assert_eq("255n.toString(16)", js_string!("ff")), - TestAction::assert_eq("1000n.toString(36)", js_string!("rs")), + TestAction::assert_eq("1000n.toString()", js_str!("1000")), + TestAction::assert_eq("1000n.toString(2)", js_str!("1111101000")), + TestAction::assert_eq("255n.toString(16)", js_str!("ff")), + TestAction::assert_eq("1000n.toString(36)", js_str!("rs")), ]); } diff --git a/core/engine/src/builtins/builder.rs b/core/engine/src/builtins/builder.rs index eb6fc0a4aa..d02627a991 100644 --- a/core/engine/src/builtins/builder.rs +++ b/core/engine/src/builtins/builder.rs @@ -1,4 +1,4 @@ -use boa_macros::utf16; +use boa_macros::js_str; use crate::{ js_string, @@ -115,7 +115,7 @@ impl ApplyToObject for Callable { .configurable(true), ); object.insert( - utf16!("name"), + js_str!("name"), PropertyDescriptor::builder() .value(self.name) .writable(false) @@ -363,8 +363,8 @@ impl BuiltInConstructorWithPrototype<'_> { let length = self.length; let name = self.name.clone(); let prototype = self.prototype.clone(); - self = self.static_property(js_string!("length"), length, Attribute::CONFIGURABLE); - self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE); + self = self.static_property(js_str!("length"), length, Attribute::CONFIGURABLE); + self = self.static_property(js_str!("name"), name, Attribute::CONFIGURABLE); self = self.static_property(PROTOTYPE, prototype, Attribute::empty()); let attributes = self.attributes; @@ -411,8 +411,8 @@ impl BuiltInConstructorWithPrototype<'_> { pub(crate) fn build_without_prototype(mut self) { let length = self.length; let name = self.name.clone(); - self = self.static_property(js_string!("length"), length, Attribute::CONFIGURABLE); - self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE); + self = self.static_property(js_str!("length"), length, Attribute::CONFIGURABLE); + self = self.static_property(js_str!("name"), name, Attribute::CONFIGURABLE); let mut object = self.object.borrow_mut(); let function = object @@ -483,7 +483,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { realm, function, length: 0, - name: js_string!(""), + name: js_string!(), } } @@ -496,7 +496,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { object: I::get(realm.intrinsics()), kind: Callable { function, - name: js_string!(""), + name: js_string!(), length: 0, kind: OrdinaryFunction, realm: realm.clone(), @@ -515,7 +515,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { object, kind: Callable { function, - name: js_string!(""), + name: js_string!(), length: 0, kind: OrdinaryFunction, realm: realm.clone(), diff --git a/core/engine/src/builtins/date/mod.rs b/core/engine/src/builtins/date/mod.rs index 4987fe7827..49be23c5f8 100644 --- a/core/engine/src/builtins/date/mod.rs +++ b/core/engine/src/builtins/date/mod.rs @@ -27,12 +27,13 @@ use crate::{ object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, symbol::JsSymbol, value::{JsValue, PreferredType}, Context, JsArgs, JsData, JsError, JsResult, JsString, }; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; pub(crate) mod utils; @@ -1416,34 +1417,40 @@ impl Date { // including all format elements and the UTC offset representation "Z". let year = year_from_time(tv); let year = if year.is_positive() && year >= 10000 { - js_string!(utf16!("+"), &pad_six(year.unsigned_abs())) + js_string!(js_str!("+"), pad_six(year.unsigned_abs(), &mut [0; 6])) } else if year.is_positive() { - JsString::from(&pad_four(year.unsigned_abs())) + pad_four(year.unsigned_abs(), &mut [0; 4]).into() } else { - js_string!(utf16!("-"), &pad_six(year.unsigned_abs())) + js_string!(js_str!("-"), pad_six(year.unsigned_abs(), &mut [0; 6])) }; - let month = pad_two(month_from_time(tv) + 1); - let day = pad_two(date_from_time(tv)); - let hour = pad_two(hour_from_time(tv)); - let minute = pad_two(min_from_time(tv)); - let second = pad_two(sec_from_time(tv)); - let millisecond = pad_three(ms_from_time(tv)); + let mut binding = [0; 2]; + let month = pad_two(month_from_time(tv) + 1, &mut binding); + let mut binding = [0; 2]; + let day = pad_two(date_from_time(tv), &mut binding); + let mut binding = [0; 2]; + let hour = pad_two(hour_from_time(tv), &mut binding); + let mut binding = [0; 2]; + let minute = pad_two(min_from_time(tv), &mut binding); + let mut binding = [0; 2]; + let second = pad_two(sec_from_time(tv), &mut binding); + let mut binding = [0; 3]; + let millisecond = pad_three(ms_from_time(tv), &mut binding); Ok(JsValue::from(js_string!( &year, - utf16!("-"), - &month, - utf16!("-"), - &day, - utf16!("T"), - &hour, - utf16!(":"), - &minute, - utf16!(":"), - &second, - utf16!("."), - &millisecond, - utf16!("Z") + js_str!("-"), + month, + js_str!("-"), + day, + js_str!("T"), + hour, + js_str!(":"), + minute, + js_str!(":"), + second, + js_str!("."), + millisecond, + js_str!("Z") ))) } @@ -1473,7 +1480,7 @@ impl Date { } // 4. Return ? Invoke(O, "toISOString"). - let func = o.get(utf16!("toISOString"), context)?; + let func = o.get(js_string!("toISOString"), context)?; func.call(this, &[], context) } @@ -1632,50 +1639,51 @@ impl Date { // 5. Let weekday be the Name of the entry in Table 63 with the Number WeekDay(tv). let weekday = match week_day(tv) { - 0 => utf16!("Sun"), - 1 => utf16!("Mon"), - 2 => utf16!("Tue"), - 3 => utf16!("Wed"), - 4 => utf16!("Thu"), - 5 => utf16!("Fri"), - 6 => utf16!("Sat"), + 0 => js_str!("Sun"), + 1 => js_str!("Mon"), + 2 => js_str!("Tue"), + 3 => js_str!("Wed"), + 4 => js_str!("Thu"), + 5 => js_str!("Fri"), + 6 => js_str!("Sat"), _ => unreachable!(), }; // 6. Let month be the Name of the entry in Table 64 with the Number MonthFromTime(tv). let month = match month_from_time(tv) { - 0 => utf16!("Jan"), - 1 => utf16!("Feb"), - 2 => utf16!("Mar"), - 3 => utf16!("Apr"), - 4 => utf16!("May"), - 5 => utf16!("Jun"), - 6 => utf16!("Jul"), - 7 => utf16!("Aug"), - 8 => utf16!("Sep"), - 9 => utf16!("Oct"), - 10 => utf16!("Nov"), - 11 => utf16!("Dec"), + 0 => js_str!("Jan"), + 1 => js_str!("Feb"), + 2 => js_str!("Mar"), + 3 => js_str!("Apr"), + 4 => js_str!("May"), + 5 => js_str!("Jun"), + 6 => js_str!("Jul"), + 7 => js_str!("Aug"), + 8 => js_str!("Sep"), + 9 => js_str!("Oct"), + 10 => js_str!("Nov"), + 11 => js_str!("Dec"), _ => unreachable!(), }; // 7. Let day be ToZeroPaddedDecimalString(ℝ(DateFromTime(tv)), 2). - let day = pad_two(date_from_time(tv)); + let mut binding = [0; 2]; + let day = pad_two(date_from_time(tv), &mut binding); // 8. Let yv be YearFromTime(tv). let yv = year_from_time(tv); // 9. If yv is +0𝔽 or yv > +0𝔽, let yearSign be the empty String; otherwise, let yearSign be "-". - let year_sign = if yv >= 0 { utf16!("") } else { utf16!("-") }; + let year_sign = if yv >= 0 { js_str!("") } else { js_str!("-") }; // 10. Let paddedYear be ToZeroPaddedDecimalString(abs(ℝ(yv)), 4). let yv = yv.unsigned_abs(); - let padded_year = if yv >= 100_000 { - js_string!(&pad_six(yv)) + let padded_year: JsString = if yv >= 100_000 { + pad_six(yv, &mut [0; 6]).into() } else if yv >= 10000 { - js_string!(&pad_five(yv)) + pad_five(yv, &mut [0; 5]).into() } else { - js_string!(&pad_four(yv)) + pad_four(yv, &mut [0; 4]).into() }; // 11. Return the string-concatenation of @@ -1692,15 +1700,15 @@ impl Date { // and TimeString(tv). Ok(JsValue::from(js_string!( weekday, - utf16!(","), - utf16!(" "), - &day, - utf16!(" "), + js_str!(","), + js_str!(" "), + day, + js_str!(" "), month, - utf16!(" "), + js_str!(" "), year_sign, &padded_year, - utf16!(" "), + js_str!(" "), &time_string(tv) ))) } @@ -1755,12 +1763,10 @@ impl Date { let try_first = match hint.as_string() { // 3. If hint is "string" or "default", then // a. Let tryFirst be string. - Some(string) if string == utf16!("string") || string == utf16!("default") => { - PreferredType::String - } + Some(string) if string == "string" || string == "default" => PreferredType::String, // 4. Else if hint is "number", then // a. Let tryFirst be number. - Some(number) if number == utf16!("number") => PreferredType::Number, + Some(number) if number == "number" => PreferredType::Number, // 5. Else, throw a TypeError exception. _ => { return Err(JsNativeError::typ() diff --git a/core/engine/src/builtins/date/tests.rs b/core/engine/src/builtins/date/tests.rs index cf7497119d..7950722408 100644 --- a/core/engine/src/builtins/date/tests.rs +++ b/core/engine/src/builtins/date/tests.rs @@ -1,4 +1,5 @@ use crate::{js_string, run_test_actions, JsNativeErrorKind, TestAction}; +use boa_macros::js_str; use indoc::indoc; use time::{macros::format_description, util::local_offset, OffsetDateTime}; @@ -787,7 +788,7 @@ fn date_proto_set_utc_seconds() { fn date_proto_to_date_string() { run_test_actions([TestAction::assert_eq( "new Date(2020, 6, 8, 9, 16, 15, 779).toDateString()", - js_string!("Wed Jul 08 2020"), + js_str!("Wed Jul 08 2020"), )]); } @@ -795,7 +796,7 @@ fn date_proto_to_date_string() { fn date_proto_to_gmt_string() { run_test_actions([TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).toGMTString()", - js_string!("Wed, 08 Jul 2020 09:16:15 GMT"), + js_str!("Wed, 08 Jul 2020 09:16:15 GMT"), )]); } @@ -803,7 +804,7 @@ fn date_proto_to_gmt_string() { fn date_proto_to_iso_string() { run_test_actions([TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).toISOString()", - js_string!("2020-07-08T09:16:15.779Z"), + js_str!("2020-07-08T09:16:15.779Z"), )]); } @@ -811,7 +812,7 @@ fn date_proto_to_iso_string() { fn date_proto_to_json() { run_test_actions([TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).toJSON()", - js_string!("2020-07-08T09:16:15.779Z"), + js_str!("2020-07-08T09:16:15.779Z"), )]); } @@ -849,7 +850,7 @@ fn date_proto_to_time_string() { fn date_proto_to_utc_string() { run_test_actions([TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).toUTCString()", - js_string!("Wed, 08 Jul 2020 09:16:15 GMT"), + js_str!("Wed, 08 Jul 2020 09:16:15 GMT"), )]); } diff --git a/core/engine/src/builtins/date/utils.rs b/core/engine/src/builtins/date/utils.rs index fdabb5d0d1..b8c8f5ebaa 100644 --- a/core/engine/src/builtins/date/utils.rs +++ b/core/engine/src/builtins/date/utils.rs @@ -1,5 +1,5 @@ -use crate::{context::HostHooks, js_string, value::IntegerOrInfinity, JsString}; -use boa_macros::utf16; +use crate::{context::HostHooks, js_string, value::IntegerOrInfinity, JsStr, JsString}; +use boa_macros::js_str; use std::{iter::Peekable, str::Chars}; use time::{macros::format_description, OffsetDateTime, PrimitiveDateTime}; @@ -498,13 +498,16 @@ pub(crate) fn time_clip(time: f64) -> f64 { /// [spec]: https://tc39.es/ecma262/#sec-timestring pub(super) fn time_string(tv: f64) -> JsString { // 1. Let hour be ToZeroPaddedDecimalString(ℝ(HourFromTime(tv)), 2). - let hour = pad_two(hour_from_time(tv)); + let mut binding = [0; 2]; + let hour = pad_two(hour_from_time(tv), &mut binding); // 2. Let minute be ToZeroPaddedDecimalString(ℝ(MinFromTime(tv)), 2). - let minute = pad_two(min_from_time(tv)); + let mut binding = [0; 2]; + let minute = pad_two(min_from_time(tv), &mut binding); - // 3. Let second be ToZeroPaddedDecimalString(ℝ(SecFromTime(tv)), 2). - let second = pad_two(sec_from_time(tv)); + // 3. Let second be ToZeroPaddedDecimalStringbindingFromTime(tv)), 2). + let mut binding = [0; 2]; + let second = pad_two(sec_from_time(tv), &mut binding); // 4. Return the string-concatenation of // hour, @@ -515,12 +518,12 @@ pub(super) fn time_string(tv: f64) -> JsString { // the code unit 0x0020 (SPACE), // and "GMT". js_string!( - &hour, - utf16!(":"), - &minute, - utf16!(":"), - &second, - utf16!(" GMT") + hour, + js_str!(":"), + minute, + js_str!(":"), + second, + js_str!(" GMT") ) } @@ -533,50 +536,51 @@ pub(super) fn time_string(tv: f64) -> JsString { pub(super) fn date_string(tv: f64) -> JsString { // 1. Let weekday be the Name of the entry in Table 63 with the Number WeekDay(tv). let weekday = match week_day(tv) { - 0 => utf16!("Sun"), - 1 => utf16!("Mon"), - 2 => utf16!("Tue"), - 3 => utf16!("Wed"), - 4 => utf16!("Thu"), - 5 => utf16!("Fri"), - 6 => utf16!("Sat"), + 0 => js_str!("Sun"), + 1 => js_str!("Mon"), + 2 => js_str!("Tue"), + 3 => js_str!("Wed"), + 4 => js_str!("Thu"), + 5 => js_str!("Fri"), + 6 => js_str!("Sat"), _ => unreachable!(), }; // 2. Let month be the Name of the entry in Table 64 with the Number MonthFromTime(tv). let month = match month_from_time(tv) { - 0 => utf16!("Jan"), - 1 => utf16!("Feb"), - 2 => utf16!("Mar"), - 3 => utf16!("Apr"), - 4 => utf16!("May"), - 5 => utf16!("Jun"), - 6 => utf16!("Jul"), - 7 => utf16!("Aug"), - 8 => utf16!("Sep"), - 9 => utf16!("Oct"), - 10 => utf16!("Nov"), - 11 => utf16!("Dec"), + 0 => js_str!("Jan"), + 1 => js_str!("Feb"), + 2 => js_str!("Mar"), + 3 => js_str!("Apr"), + 4 => js_str!("May"), + 5 => js_str!("Jun"), + 6 => js_str!("Jul"), + 7 => js_str!("Aug"), + 8 => js_str!("Sep"), + 9 => js_str!("Oct"), + 10 => js_str!("Nov"), + 11 => js_str!("Dec"), _ => unreachable!(), }; // 3. Let day be ToZeroPaddedDecimalString(ℝ(DateFromTime(tv)), 2). - let day = pad_two(date_from_time(tv)); + let mut binding = [0; 2]; + let day = pad_two(date_from_time(tv), &mut binding); // 4. Let yv be YearFromTime(tv). let yv = year_from_time(tv); // 5. If yv is +0𝔽 or yv > +0𝔽, let yearSign be the empty String; otherwise, let yearSign be "-". - let year_sign = if yv >= 0 { utf16!("") } else { utf16!("-") }; + let year_sign = if yv >= 0 { js_str!("") } else { js_str!("-") }; // 6. Let paddedYear be ToZeroPaddedDecimalString(abs(ℝ(yv)), 4). let yv = yv.unsigned_abs(); - let padded_year = if yv >= 100_000 { - js_string!(&pad_six(yv)) + let padded_year: JsString = if yv >= 100_000 { + pad_six(yv, &mut [0; 6]).into() } else if yv >= 10000 { - js_string!(&pad_five(yv)) + pad_five(yv, &mut [0; 5]).into() } else { - js_string!(&pad_four(yv)) + pad_four(yv, &mut [0; 4]).into() }; // 7. Return the string-concatenation of @@ -590,11 +594,11 @@ pub(super) fn date_string(tv: f64) -> JsString { // and paddedYear. js_string!( weekday, - utf16!(" "), + js_str!(" "), month, - utf16!(" "), - &day, - utf16!(" "), + js_str!(" "), + day, + js_str!(" "), year_sign, &padded_year ) @@ -620,26 +624,28 @@ pub(super) fn time_zone_string(t: f64, hooks: &dyn HostHooks) -> JsString { let (offset_sign, abs_offset) = if offset >= 0.0 { // a. Let offsetSign be "+". // b. Let absOffset be offset. - (utf16!("+"), offset) + (js_str!("+"), offset) } // 6. Else, else { // a. Let offsetSign be "-". // b. Let absOffset be -offset. - (utf16!("-"), -offset) + (js_str!("-"), -offset) }; // 7. Let offsetMin be ToZeroPaddedDecimalString(ℝ(MinFromTime(absOffset)), 2). - let offset_min = pad_two(min_from_time(abs_offset)); + let mut binding = [0; 2]; + let offset_min = pad_two(min_from_time(abs_offset), &mut binding); // 8. Let offsetHour be ToZeroPaddedDecimalString(ℝ(HourFromTime(absOffset)), 2). - let offset_hour = pad_two(hour_from_time(abs_offset)); + let mut binding = [0; 2]; + let offset_hour = pad_two(hour_from_time(abs_offset), &mut binding); // 9. Let tzName be an implementation-defined string that is either the empty String or the // string-concatenation of the code unit 0x0020 (SPACE), the code unit 0x0028 (LEFT PARENTHESIS), // an implementation-defined timezone name, and the code unit 0x0029 (RIGHT PARENTHESIS). // 10. Return the string-concatenation of offsetSign, offsetHour, offsetMin, and tzName. - js_string!(offset_sign, &offset_hour, &offset_min) + js_string!(offset_sign, offset_hour, offset_min) } /// Abstract operation `ToDateString ( tv )` @@ -651,7 +657,7 @@ pub(super) fn time_zone_string(t: f64, hooks: &dyn HostHooks) -> JsString { pub(super) fn to_date_string_t(tv: f64, hooks: &dyn HostHooks) -> JsString { // 1. If tv is NaN, return "Invalid Date". if tv.is_nan() { - return JsString::from("Invalid Date"); + return js_string!("Invalid Date"); } // 2. Let t be LocalTime(tv). @@ -664,7 +670,7 @@ pub(super) fn to_date_string_t(tv: f64, hooks: &dyn HostHooks) -> JsString { // and TimeZoneString(tv). js_string!( &date_string(t), - utf16!(" "), + js_str!(" "), &time_string(t), &time_zone_string(t, hooks) ) @@ -676,46 +682,61 @@ fn local_timezone_offset_seconds(t: f64, hooks: &dyn HostHooks) -> i32 { hooks.local_timezone_offset_seconds(seconds) } -pub(super) fn pad_two(t: u8) -> [u16; 2] { - if t < 10 { - [0x30, 0x30 + u16::from(t)] +pub(super) fn pad_two(t: u8, output: &mut [u8; 2]) -> JsStr<'_> { + *output = if t < 10 { + [b'0', b'0' + t] } else { - [0x30 + (u16::from(t) / 10), 0x30 + (u16::from(t) % 10)] - } + [b'0' + (t / 10), b'0' + (t % 10)] + }; + debug_assert!(output.is_ascii()); + + JsStr::latin1(output) } -pub(super) fn pad_three(t: u16) -> [u16; 3] { - [0x30 + t / 100, 0x30 + ((t / 10) % 10), 0x30 + (t % 10)] +pub(super) fn pad_three(t: u16, output: &mut [u8; 3]) -> JsStr<'_> { + *output = [ + b'0' + (t / 100) as u8, + b'0' + ((t / 10) % 10) as u8, + b'0' + (t % 10) as u8, + ]; + + JsStr::latin1(output) } -pub(super) fn pad_four(t: u32) -> [u16; 4] { - [ - 0x30 + (t / 1000) as u16, - 0x30 + ((t / 100) % 10) as u16, - 0x30 + ((t / 10) % 10) as u16, - 0x30 + (t % 10) as u16, - ] +pub(super) fn pad_four(t: u32, output: &mut [u8; 4]) -> JsStr<'_> { + *output = [ + b'0' + (t / 1000) as u8, + b'0' + ((t / 100) % 10) as u8, + b'0' + ((t / 10) % 10) as u8, + b'0' + (t % 10) as u8, + ]; + + JsStr::latin1(output) } -pub(super) fn pad_five(t: u32) -> [u16; 5] { - [ - 0x30 + (t / 10_000) as u16, - 0x30 + ((t / 1000) % 10) as u16, - 0x30 + ((t / 100) % 10) as u16, - 0x30 + ((t / 10) % 10) as u16, - 0x30 + (t % 10) as u16, - ] +pub(super) fn pad_five(t: u32, output: &mut [u8; 5]) -> JsStr<'_> { + *output = [ + b'0' + (t / 10_000) as u8, + b'0' + ((t / 1000) % 10) as u8, + b'0' + ((t / 100) % 10) as u8, + b'0' + ((t / 10) % 10) as u8, + b'0' + (t % 10) as u8, + ]; + + JsStr::latin1(output) } -pub(super) fn pad_six(t: u32) -> [u16; 6] { - [ - 0x30 + (t / 100_000) as u16, - 0x30 + ((t / 10_000) % 10) as u16, - 0x30 + ((t / 1000) % 10) as u16, - 0x30 + ((t / 100) % 10) as u16, - 0x30 + ((t / 10) % 10) as u16, - 0x30 + (t % 10) as u16, - ] +pub(super) fn pad_six(t: u32, output: &mut [u8; 6]) -> JsStr<'_> { + *output = [ + b'0' + (t / 100_000) as u8, + b'0' + ((t / 10_000) % 10) as u8, + b'0' + ((t / 1000) % 10) as u8, + b'0' + ((t / 100) % 10) as u8, + b'0' + ((t / 10) % 10) as u8, + b'0' + (t % 10) as u8, + ]; + + JsStr::latin1(output) } /// Parse a date string according to the steps specified in [`Date.parse`][spec]. diff --git a/core/engine/src/builtins/error/aggregate.rs b/core/engine/src/builtins/error/aggregate.rs index 8abeefd3bc..bc4b9c3292 100644 --- a/core/engine/src/builtins/error/aggregate.rs +++ b/core/engine/src/builtins/error/aggregate.rs @@ -17,9 +17,10 @@ use crate::{ object::{internal_methods::get_prototype_from_constructor, JsObject}, property::{Attribute, PropertyDescriptorBuilder}, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, Context, JsArgs, JsResult, JsString, JsValue, }; +use boa_macros::js_str; use boa_profiler::Profiler; use super::{Error, ErrorObject}; @@ -35,8 +36,8 @@ impl IntrinsicObject for AggregateError { BuiltInBuilder::from_standard_constructor::(realm) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) - .property(utf16!("name"), Self::NAME, attribute) - .property(utf16!("message"), js_string!(), attribute) + .property(js_str!("name"), Self::NAME, attribute) + .property(js_str!("message"), js_string!(), attribute) .build(); } @@ -95,7 +96,7 @@ impl BuiltInConstructor for AggregateError { let msg = message.to_string(context)?; // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). - o.create_non_enumerable_data_property_or_throw(utf16!("message"), msg, context); + o.create_non_enumerable_data_property_or_throw(js_str!("message"), msg, context); } // 4. Perform ? InstallErrorCause(O, options). @@ -112,7 +113,7 @@ impl BuiltInConstructor for AggregateError { // [[Value]]: CreateArrayFromList(errorsList) // }). o.define_property_or_throw( - utf16!("errors"), + js_str!("errors"), PropertyDescriptorBuilder::new() .configurable(true) .enumerable(false) diff --git a/core/engine/src/builtins/error/eval.rs b/core/engine/src/builtins/error/eval.rs index ece3277f4a..266fd82053 100644 --- a/core/engine/src/builtins/error/eval.rs +++ b/core/engine/src/builtins/error/eval.rs @@ -18,9 +18,10 @@ use crate::{ object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, Context, JsArgs, JsResult, JsString, JsValue, }; +use boa_macros::js_str; use boa_profiler::Profiler; use super::{Error, ErrorObject}; @@ -37,8 +38,8 @@ impl IntrinsicObject for EvalError { BuiltInBuilder::from_standard_constructor::(realm) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) - .property(utf16!("name"), Self::NAME, attribute) - .property(utf16!("message"), js_string!(), attribute) + .property(js_str!("name"), Self::NAME, attribute) + .property(js_str!("message"), js_string!(), attribute) .build(); } @@ -94,7 +95,7 @@ impl BuiltInConstructor for EvalError { let msg = message.to_string(context)?; // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). - o.create_non_enumerable_data_property_or_throw(utf16!("message"), msg, context); + o.create_non_enumerable_data_property_or_throw(js_str!("message"), msg, context); } // 4. Perform ? InstallErrorCause(O, options). diff --git a/core/engine/src/builtins/error/mod.rs b/core/engine/src/builtins/error/mod.rs index ebc57adee1..ed005d0e38 100644 --- a/core/engine/src/builtins/error/mod.rs +++ b/core/engine/src/builtins/error/mod.rs @@ -18,10 +18,11 @@ use crate::{ object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, Context, JsArgs, JsData, JsResult, JsString, JsValue, }; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; pub(crate) mod aggregate; @@ -136,8 +137,8 @@ impl IntrinsicObject for Error { let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; BuiltInBuilder::from_standard_constructor::(realm) - .property(utf16!("name"), Self::NAME, attribute) - .property(utf16!("message"), js_string!(), attribute) + .property(js_string!("name"), Self::NAME, attribute) + .property(js_string!("message"), js_string!(), attribute) .method(Self::to_string, js_string!("toString"), 0) .build(); } @@ -191,7 +192,7 @@ impl BuiltInConstructor for Error { let msg = message.to_string(context)?; // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). - o.create_non_enumerable_data_property_or_throw(utf16!("message"), msg, context); + o.create_non_enumerable_data_property_or_throw(js_string!("message"), msg, context); } // 4. Perform ? InstallErrorCause(O, options). @@ -210,12 +211,12 @@ impl Error { ) -> JsResult<()> { // 1. If Type(options) is Object and ? HasProperty(options, "cause") is true, then if let Some(options) = options.as_object() { - if options.has_property(utf16!("cause"), context)? { + if options.has_property(js_str!("cause"), context)? { // a. Let cause be ? Get(options, "cause"). - let cause = options.get(utf16!("cause"), context)?; + let cause = options.get(js_str!("cause"), context)?; // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "cause", cause). - o.create_non_enumerable_data_property_or_throw(utf16!("cause"), cause, context); + o.create_non_enumerable_data_property_or_throw(js_str!("cause"), cause, context); } } @@ -246,7 +247,7 @@ impl Error { .ok_or_else(|| JsNativeError::typ().with_message("'this' is not an Object"))?; // 3. Let name be ? Get(O, "name"). - let name = o.get(js_string!("name"), context)?; + let name = o.get(js_str!("name"), context)?; // 4. If name is undefined, set name to "Error"; otherwise set name to ? ToString(name). let name = if name.is_undefined() { @@ -256,7 +257,7 @@ impl Error { }; // 5. Let msg be ? Get(O, "message"). - let msg = o.get(js_string!("message"), context)?; + let msg = o.get(js_str!("message"), context)?; // 6. If msg is undefined, set msg to the empty String; otherwise set msg to ? ToString(msg). let msg = if msg.is_undefined() { @@ -277,6 +278,6 @@ impl Error { // 9. Return the string-concatenation of name, the code unit 0x003A (COLON), // the code unit 0x0020 (SPACE), and msg. - Ok(js_string!(&name, utf16!(": "), &msg).into()) + Ok(js_string!(&name, js_str!(": "), &msg).into()) } } diff --git a/core/engine/src/builtins/error/range.rs b/core/engine/src/builtins/error/range.rs index 985269df82..cc192396c3 100644 --- a/core/engine/src/builtins/error/range.rs +++ b/core/engine/src/builtins/error/range.rs @@ -16,9 +16,10 @@ use crate::{ object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, Context, JsArgs, JsResult, JsString, JsValue, }; +use boa_macros::js_str; use boa_profiler::Profiler; use super::{Error, ErrorObject}; @@ -35,8 +36,8 @@ impl IntrinsicObject for RangeError { BuiltInBuilder::from_standard_constructor::(realm) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) - .property(utf16!("name"), Self::NAME, attribute) - .property(utf16!("message"), js_string!(), attribute) + .property(js_str!("name"), Self::NAME, attribute) + .property(js_str!("message"), js_string!(), attribute) .build(); } @@ -92,7 +93,7 @@ impl BuiltInConstructor for RangeError { let msg = message.to_string(context)?; // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). - o.create_non_enumerable_data_property_or_throw(utf16!("message"), msg, context); + o.create_non_enumerable_data_property_or_throw(js_str!("message"), msg, context); } // 4. Perform ? InstallErrorCause(O, options). diff --git a/core/engine/src/builtins/error/reference.rs b/core/engine/src/builtins/error/reference.rs index 5785bda863..eb8a115862 100644 --- a/core/engine/src/builtins/error/reference.rs +++ b/core/engine/src/builtins/error/reference.rs @@ -16,9 +16,10 @@ use crate::{ object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, Context, JsArgs, JsResult, JsString, JsValue, }; +use boa_macros::js_str; use boa_profiler::Profiler; use super::{Error, ErrorObject}; @@ -34,8 +35,8 @@ impl IntrinsicObject for ReferenceError { BuiltInBuilder::from_standard_constructor::(realm) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) - .property(js_string!("name"), Self::NAME, attribute) - .property(js_string!("message"), js_string!(), attribute) + .property(js_str!("name"), Self::NAME, attribute) + .property(js_str!("message"), js_string!(), attribute) .build(); } @@ -94,7 +95,7 @@ impl BuiltInConstructor for ReferenceError { let msg = message.to_string(context)?; // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). - o.create_non_enumerable_data_property_or_throw(utf16!("message"), msg, context); + o.create_non_enumerable_data_property_or_throw(js_str!("message"), msg, context); } // 4. Perform ? InstallErrorCause(O, options). diff --git a/core/engine/src/builtins/error/syntax.rs b/core/engine/src/builtins/error/syntax.rs index cf35acc44c..9074856694 100644 --- a/core/engine/src/builtins/error/syntax.rs +++ b/core/engine/src/builtins/error/syntax.rs @@ -18,9 +18,10 @@ use crate::{ object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, Context, JsArgs, JsResult, JsString, JsValue, }; +use boa_macros::js_str; use boa_profiler::Profiler; use super::{Error, ErrorObject}; @@ -37,8 +38,8 @@ impl IntrinsicObject for SyntaxError { BuiltInBuilder::from_standard_constructor::(realm) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) - .property(utf16!("name"), Self::NAME, attribute) - .property(utf16!("message"), js_string!(), attribute) + .property(js_str!("name"), Self::NAME, attribute) + .property(js_str!("message"), js_string!(), attribute) .build(); } @@ -97,7 +98,7 @@ impl BuiltInConstructor for SyntaxError { let msg = message.to_string(context)?; // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). - o.create_non_enumerable_data_property_or_throw(utf16!("message"), msg, context); + o.create_non_enumerable_data_property_or_throw(js_str!("message"), msg, context); } // 4. Perform ? InstallErrorCause(O, options). diff --git a/core/engine/src/builtins/error/tests.rs b/core/engine/src/builtins/error/tests.rs index ea3950b304..f5e4a06051 100644 --- a/core/engine/src/builtins/error/tests.rs +++ b/core/engine/src/builtins/error/tests.rs @@ -1,42 +1,34 @@ -use crate::{js_string, run_test_actions, TestAction}; +use crate::{run_test_actions, TestAction}; +use boa_macros::js_str; use indoc::indoc; #[test] fn error_to_string() { run_test_actions([ - TestAction::assert_eq("(new Error('1')).toString()", js_string!("Error: 1")), - TestAction::assert_eq( - "(new RangeError('2')).toString()", - js_string!("RangeError: 2"), - ), + TestAction::assert_eq("(new Error('1')).toString()", js_str!("Error: 1")), + TestAction::assert_eq("(new RangeError('2')).toString()", js_str!("RangeError: 2")), TestAction::assert_eq( "(new ReferenceError('3')).toString()", - js_string!("ReferenceError: 3"), + js_str!("ReferenceError: 3"), ), TestAction::assert_eq( "(new SyntaxError('4')).toString()", - js_string!("SyntaxError: 4"), - ), - TestAction::assert_eq( - "(new TypeError('5')).toString()", - js_string!("TypeError: 5"), - ), - TestAction::assert_eq( - "(new EvalError('6')).toString()", - js_string!("EvalError: 6"), + js_str!("SyntaxError: 4"), ), - TestAction::assert_eq("(new URIError('7')).toString()", js_string!("URIError: 7")), + TestAction::assert_eq("(new TypeError('5')).toString()", js_str!("TypeError: 5")), + TestAction::assert_eq("(new EvalError('6')).toString()", js_str!("EvalError: 6")), + TestAction::assert_eq("(new URIError('7')).toString()", js_str!("URIError: 7")), // no message - TestAction::assert_eq("(new Error()).toString()", js_string!("Error")), - TestAction::assert_eq("(new RangeError()).toString()", js_string!("RangeError")), + TestAction::assert_eq("(new Error()).toString()", js_str!("Error")), + TestAction::assert_eq("(new RangeError()).toString()", js_str!("RangeError")), TestAction::assert_eq( "(new ReferenceError()).toString()", - js_string!("ReferenceError"), + js_str!("ReferenceError"), ), - TestAction::assert_eq("(new SyntaxError()).toString()", js_string!("SyntaxError")), - TestAction::assert_eq("(new TypeError()).toString()", js_string!("TypeError")), - TestAction::assert_eq("(new EvalError()).toString()", js_string!("EvalError")), - TestAction::assert_eq("(new URIError()).toString()", js_string!("URIError")), + TestAction::assert_eq("(new SyntaxError()).toString()", js_str!("SyntaxError")), + TestAction::assert_eq("(new TypeError()).toString()", js_str!("TypeError")), + TestAction::assert_eq("(new EvalError()).toString()", js_str!("EvalError")), + TestAction::assert_eq("(new URIError()).toString()", js_str!("URIError")), // no name TestAction::assert_eq( indoc! {r#" @@ -44,7 +36,7 @@ fn error_to_string() { message.name = ''; message.toString() "#}, - js_string!("message"), + js_str!("message"), ), ]); } @@ -52,14 +44,14 @@ fn error_to_string() { #[test] fn error_names() { run_test_actions([ - TestAction::assert_eq("Error.name", js_string!("Error")), - TestAction::assert_eq("EvalError.name", js_string!("EvalError")), - TestAction::assert_eq("RangeError.name", js_string!("RangeError")), - TestAction::assert_eq("ReferenceError.name", js_string!("ReferenceError")), - TestAction::assert_eq("SyntaxError.name", js_string!("SyntaxError")), - TestAction::assert_eq("URIError.name", js_string!("URIError")), - TestAction::assert_eq("TypeError.name", js_string!("TypeError")), - TestAction::assert_eq("AggregateError.name", js_string!("AggregateError")), + TestAction::assert_eq("Error.name", js_str!("Error")), + TestAction::assert_eq("EvalError.name", js_str!("EvalError")), + TestAction::assert_eq("RangeError.name", js_str!("RangeError")), + TestAction::assert_eq("ReferenceError.name", js_str!("ReferenceError")), + TestAction::assert_eq("SyntaxError.name", js_str!("SyntaxError")), + TestAction::assert_eq("URIError.name", js_str!("URIError")), + TestAction::assert_eq("TypeError.name", js_str!("TypeError")), + TestAction::assert_eq("AggregateError.name", js_str!("AggregateError")), ]); } diff --git a/core/engine/src/builtins/error/type.rs b/core/engine/src/builtins/error/type.rs index 02fea36f85..3b3223d0fb 100644 --- a/core/engine/src/builtins/error/type.rs +++ b/core/engine/src/builtins/error/type.rs @@ -24,9 +24,10 @@ use crate::{ object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, Context, JsArgs, JsResult, JsString, JsValue, NativeFunction, }; +use boa_macros::js_str; use boa_profiler::Profiler; use super::{Error, ErrorObject}; @@ -43,8 +44,8 @@ impl IntrinsicObject for TypeError { BuiltInBuilder::from_standard_constructor::(realm) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) - .property(utf16!("name"), Self::NAME, attribute) - .property(utf16!("message"), js_string!(), attribute) + .property(js_str!("name"), Self::NAME, attribute) + .property(js_str!("message"), js_string!(), attribute) .build(); } @@ -100,7 +101,7 @@ impl BuiltInConstructor for TypeError { let msg = message.to_string(context)?; // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). - o.create_non_enumerable_data_property_or_throw(utf16!("message"), msg, context); + o.create_non_enumerable_data_property_or_throw(js_str!("message"), msg, context); } // 4. Perform ? InstallErrorCause(O, options). @@ -119,7 +120,7 @@ impl IntrinsicObject for ThrowTypeError { let obj = BuiltInBuilder::with_intrinsic::(realm) .prototype(realm.intrinsics().constructors().function().prototype()) .static_property(StaticJsStrings::LENGTH, 0, Attribute::empty()) - .static_property(utf16!("name"), js_string!(), Attribute::empty()) + .static_property(js_str!("name"), js_string!(), Attribute::empty()) .build(); let mut obj = obj.borrow_mut(); diff --git a/core/engine/src/builtins/error/uri.rs b/core/engine/src/builtins/error/uri.rs index 73e41ee9e4..fcf7b1d208 100644 --- a/core/engine/src/builtins/error/uri.rs +++ b/core/engine/src/builtins/error/uri.rs @@ -17,9 +17,10 @@ use crate::{ object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, Context, JsArgs, JsResult, JsString, JsValue, }; +use boa_macros::js_str; use boa_profiler::Profiler; use super::{Error, ErrorObject}; @@ -36,8 +37,8 @@ impl IntrinsicObject for UriError { BuiltInBuilder::from_standard_constructor::(realm) .prototype(realm.intrinsics().constructors().error().constructor()) .inherits(Some(realm.intrinsics().constructors().error().prototype())) - .property(utf16!("name"), Self::NAME, attribute) - .property(utf16!("message"), js_string!(), attribute) + .property(js_str!("name"), Self::NAME, attribute) + .property(js_str!("message"), js_string!(), attribute) .build(); } @@ -93,7 +94,7 @@ impl BuiltInConstructor for UriError { let msg = message.to_string(context)?; // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). - o.create_non_enumerable_data_property_or_throw(utf16!("message"), msg, context); + o.create_non_enumerable_data_property_or_throw(js_str!("message"), msg, context); } // 4. Perform ? InstallErrorCause(O, options). diff --git a/core/engine/src/builtins/escape/mod.rs b/core/engine/src/builtins/escape/mod.rs index fa8c855204..e3fcf1cb2b 100644 --- a/core/engine/src/builtins/escape/mod.rs +++ b/core/engine/src/builtins/escape/mod.rs @@ -59,7 +59,7 @@ fn escape(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult JsResult JsResult>::new(string.iter().copied()); + let mut codepoints = >::new(string.iter()); // 2. Let len be the length of string. // 4. Let k be 0. @@ -192,7 +192,7 @@ fn unescape(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult + let parameters = itertools::Itertools::intersperse( + param_list.iter().map(JsString::iter), + js_str!(",").iter(), + ) + .flatten() + .collect::>(); let mut parser = Parser::new(Source::from_utf16(¶meters)); parser.set_identifier(context.next_parser_identifier()); @@ -518,7 +529,7 @@ impl BuiltInFunctionObject { // 14. Let bodyParseString be the string-concatenation of 0x000A (LINE FEED), bodyString, and 0x000A (LINE FEED). let mut body_parse = Vec::with_capacity(body.len()); body_parse.push(u16::from(b'\n')); - body_parse.extend_from_slice(&body); + body_parse.extend(body.iter()); body_parse.push(u16::from(b'\n')); // 19. Let body be ParseText(StringToCodePoints(bodyParseString), bodySym). @@ -743,7 +754,7 @@ impl BuiltInFunctionObject { .expect("defining the `length` property for a new object should not fail"); // 8. Let targetName be ? Get(Target, "name"). - let target_name = target.get(utf16!("name"), context)?; + let target_name = target.get(js_str!("name"), context)?; // 9. If Type(targetName) is not String, set targetName to the empty String. let target_name = target_name @@ -751,7 +762,7 @@ impl BuiltInFunctionObject { .map_or_else(JsString::default, Clone::clone); // 10. Perform SetFunctionName(F, targetName, "bound"). - set_function_name(&f, &target_name.into(), Some(js_string!("bound")), context); + set_function_name(&f, &target_name.into(), Some(js_str!("bound")), context); // 11. Return F. Ok(f.into()) @@ -816,7 +827,7 @@ impl BuiltInFunctionObject { let name = { // Is there a case here where if there is no name field on a value // name should default to None? Do all functions have names set? - let value = object.get(utf16!("name"), &mut *context)?; + let value = object.get(js_str!("name"), &mut *context)?; if value.is_null_or_undefined() { js_string!() } else { @@ -824,10 +835,10 @@ impl BuiltInFunctionObject { } }; return Ok( - js_string!(utf16!("function "), &name, utf16!("() { [native code] }")).into(), + js_string!(js_str!("function "), &name, js_str!("() { [native code] }")).into(), ); } else if object_borrow.is::() || object_borrow.is::() { - return Ok(js_string!(utf16!("function () { [native code] }")).into()); + return Ok(js_string!("function () { [native code] }").into()); } let function = object_borrow @@ -837,9 +848,9 @@ impl BuiltInFunctionObject { let code = function.codeblock(); Ok(js_string!( - utf16!("function "), + js_str!("function "), code.name(), - utf16!("() { [native code] }") + js_str!("() { [native code] }") ) .into()) } @@ -871,7 +882,7 @@ impl BuiltInFunctionObject { pub(crate) fn set_function_name( function: &JsObject, name: &PropertyKey, - prefix: Option, + prefix: Option>, context: &mut Context, ) { // 1. Assert: F is an extensible object that does not have a "name" own property. @@ -883,7 +894,7 @@ pub(crate) fn set_function_name( // c. Else, set name to the string-concatenation of "[", description, and "]". sym.description().map_or_else( || js_string!(), - |desc| js_string!(utf16!("["), &desc, utf16!("]")), + |desc| js_string!(js_str!("["), &desc, js_str!("]")), ) } PropertyKey::String(string) => string.clone(), @@ -900,7 +911,7 @@ pub(crate) fn set_function_name( // 5. If prefix is present, then if let Some(prefix) = prefix { - name = js_string!(&prefix, utf16!(" "), &name); + name = js_string!(prefix, js_str!(" "), &name); // b. If F has an [[InitialName]] internal slot, then // i. Optionally, set F.[[InitialName]] to name. // todo: implement [[InitialName]] for builtins @@ -910,7 +921,7 @@ pub(crate) fn set_function_name( // [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }). function .define_property_or_throw( - utf16!("name"), + js_str!("name"), PropertyDescriptor::builder() .value(name) .writable(false) diff --git a/core/engine/src/builtins/function/tests.rs b/core/engine/src/builtins/function/tests.rs index d28f663a05..fc59ced992 100644 --- a/core/engine/src/builtins/function/tests.rs +++ b/core/engine/src/builtins/function/tests.rs @@ -6,6 +6,7 @@ use crate::{ property::{Attribute, PropertyDescriptor}, run_test_actions, JsNativeErrorKind, JsValue, TestAction, }; +use boa_macros::js_str; use indoc::indoc; #[allow(clippy::float_cmp)] @@ -69,7 +70,7 @@ fn function_prototype() { fn function_prototype_call() { run_test_actions([TestAction::assert_eq( "Object.prototype.toString.call(new Error())", - js_string!("[object Error]"), + js_str!("[object Error]"), )]); } @@ -168,13 +169,13 @@ fn closure_capture_clone() { (string, object), ), ) - .name("closure") + .name(js_str!("closure")) .build(); - ctx.register_global_property(js_string!("closure"), func, Attribute::default()) + ctx.register_global_property(js_str!("closure"), func, Attribute::default()) .unwrap(); }), - TestAction::assert_eq("closure()", js_string!("Hello world!")), + TestAction::assert_eq("closure()", js_str!("Hello world!")), ]); } diff --git a/core/engine/src/builtins/intl/collator/mod.rs b/core/engine/src/builtins/intl/collator/mod.rs index b1817b762e..a99aec57a6 100644 --- a/core/engine/src/builtins/intl/collator/mod.rs +++ b/core/engine/src/builtins/intl/collator/mod.rs @@ -1,4 +1,5 @@ use boa_gc::{custom_trace, Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; use icu_collator::{ provider::CollationMetadataV1Marker, AlternateHandling, CaseFirst, Collator as NativeCollator, @@ -28,7 +29,7 @@ use crate::{ }, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, symbol::JsSymbol, Context, JsArgs, JsData, JsNativeError, JsResult, JsString, JsValue, }; @@ -241,28 +242,28 @@ impl BuiltInConstructor for Collator { // a. Let localeData be %Collator%.[[SortLocaleData]]. // 6. Else, // a. Let localeData be %Collator%.[[SearchLocaleData]]. - let usage = get_option(&options, utf16!("usage"), context)?.unwrap_or_default(); + let usage = get_option(&options, js_str!("usage"), context)?.unwrap_or_default(); // 7. Let opt be a new Record. // 8. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit"). // 9. Set opt.[[localeMatcher]] to matcher. - let matcher = get_option(&options, utf16!("localeMatcher"), context)?.unwrap_or_default(); + let matcher = get_option(&options, js_str!("localeMatcher"), context)?.unwrap_or_default(); // 10. Let collation be ? GetOption(options, "collation", string, empty, undefined). // 11. If collation is not undefined, then // a. If collation does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception. // 12. Set opt.[[co]] to collation. - let collation = get_option(&options, utf16!("collation"), context)?; + let collation = get_option(&options, js_str!("collation"), context)?; // 13. Let numeric be ? GetOption(options, "numeric", boolean, empty, undefined). // 14. If numeric is not undefined, then // a. Let numeric be ! ToString(numeric). // 15. Set opt.[[kn]] to numeric. - let numeric = get_option(&options, utf16!("numeric"), context)?; + let numeric = get_option(&options, js_str!("numeric"), context)?; // 16. Let caseFirst be ? GetOption(options, "caseFirst", string, « "upper", "lower", "false" », undefined). // 17. Set opt.[[kf]] to caseFirst. - let case_first = get_option(&options, utf16!("caseFirst"), context)?; + let case_first = get_option(&options, js_str!("caseFirst"), context)?; let mut intl_options = IntlOptions { matcher, @@ -314,7 +315,7 @@ impl BuiltInConstructor for Collator { // 26. Let sensitivity be ? GetOption(options, "sensitivity", string, « "base", "accent", "case", "variant" », undefined). // 28. Set collator.[[Sensitivity]] to sensitivity. - let sensitivity = get_option(&options, utf16!("sensitivity"), context)? + let sensitivity = get_option(&options, js_str!("sensitivity"), context)? // 27. If sensitivity is undefined, then // a. If usage is "sort", then // i. Let sensitivity be "variant". @@ -327,7 +328,7 @@ impl BuiltInConstructor for Collator { // 29. Let ignorePunctuation be ? GetOption(options, "ignorePunctuation", boolean, empty, false). // 30. Set collator.[[IgnorePunctuation]] to ignorePunctuation. let ignore_punctuation: bool = - get_option(&options, utf16!("ignorePunctuation"), context)?.unwrap_or_default(); + get_option(&options, js_str!("ignorePunctuation"), context)?.unwrap_or_default(); let (strength, case_level) = sensitivity.map(Sensitivity::to_collator_options).unzip(); @@ -442,13 +443,22 @@ impl Collator { // 3. If x is not provided, let x be undefined. // 5. Let X be ? ToString(x). - let x = args.get_or_undefined(0).to_string(context)?; + let x = args + .get_or_undefined(0) + .to_string(context)? + .iter() + .collect::>(); // 4. If y is not provided, let y be undefined. // 6. Let Y be ? ToString(y). - let y = args.get_or_undefined(1).to_string(context)?; + let y = args + .get_or_undefined(1) + .to_string(context)? + .iter() + .collect::>(); // 7. Return CompareStrings(collator, X, Y). + let result = collator.collator.compare_utf16(&x, &y) as i32; Ok(result.into()) @@ -507,58 +517,58 @@ impl Collator { // 5. Return options. options .create_data_property_or_throw( - utf16!("locale"), + js_str!("locale"), js_string!(collator.locale.to_string()), context, ) .expect("operation must not fail per the spec"); options .create_data_property_or_throw( - utf16!("usage"), + js_str!("usage"), match collator.usage { - Usage::Search => js_string!("search"), - Usage::Sort => js_string!("sort"), + Usage::Search => js_str!("search"), + Usage::Sort => js_str!("sort"), }, context, ) .expect("operation must not fail per the spec"); options .create_data_property_or_throw( - utf16!("sensitivity"), + js_str!("sensitivity"), match collator.sensitivity { - Sensitivity::Base => js_string!("base"), - Sensitivity::Accent => js_string!("accent"), - Sensitivity::Case => js_string!("case"), - Sensitivity::Variant => js_string!("variant"), + Sensitivity::Base => js_str!("base"), + Sensitivity::Accent => js_str!("accent"), + Sensitivity::Case => js_str!("case"), + Sensitivity::Variant => js_str!("variant"), }, context, ) .expect("operation must not fail per the spec"); options .create_data_property_or_throw( - js_string!("ignorePunctuation"), + js_str!("ignorePunctuation"), collator.ignore_punctuation, context, ) .expect("operation must not fail per the spec"); options .create_data_property_or_throw( - js_string!("collation"), + js_str!("collation"), js_string!(collator.collation.to_string()), context, ) .expect("operation must not fail per the spec"); options - .create_data_property_or_throw(utf16!("numeric"), collator.numeric, context) + .create_data_property_or_throw(js_str!("numeric"), collator.numeric, context) .expect("operation must not fail per the spec"); if let Some(kf) = collator.case_first { options .create_data_property_or_throw( - js_string!("caseFirst"), + js_str!("caseFirst"), match kf { - CaseFirst::Off => js_string!("false"), - CaseFirst::LowerFirst => js_string!("lower"), - CaseFirst::UpperFirst => js_string!("upper"), + CaseFirst::Off => js_str!("false"), + CaseFirst::LowerFirst => js_str!("lower"), + CaseFirst::UpperFirst => js_str!("upper"), _ => unreachable!(), }, context, diff --git a/core/engine/src/builtins/intl/date_time_format.rs b/core/engine/src/builtins/intl/date_time_format.rs index 01c8d84fc8..0dff2fe4dd 100644 --- a/core/engine/src/builtins/intl/date_time_format.rs +++ b/core/engine/src/builtins/intl/date_time_format.rs @@ -17,11 +17,12 @@ use crate::{ js_string, object::{internal_methods::get_prototype_from_constructor, JsObject}, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, Context, JsData, JsResult, JsString, JsValue, }; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; use icu_datetime::options::preferences::HourCycle; @@ -205,10 +206,10 @@ pub(crate) fn to_date_time_options( if [DateTimeReqs::Date, DateTimeReqs::AnyAll].contains(required) { // a. For each property name prop of « "weekday", "year", "month", "day" », do for property in [ - utf16!("weekday"), - utf16!("year"), - utf16!("month"), - utf16!("day"), + js_str!("weekday"), + js_str!("year"), + js_str!("month"), + js_str!("day"), ] { // i. Let value be ? Get(options, prop). let value = options.get(property, context)?; @@ -225,11 +226,11 @@ pub(crate) fn to_date_time_options( // a. For each property name prop of « "dayPeriod", "hour", "minute", "second", // "fractionalSecondDigits" », do for property in [ - utf16!("dayPeriod"), - utf16!("hour"), - utf16!("minute"), - utf16!("second"), - utf16!("fractionalSecondDigits"), + js_str!("dayPeriod"), + js_str!("hour"), + js_str!("minute"), + js_str!("second"), + js_str!("fractionalSecondDigits"), ] { // i. Let value be ? Get(options, prop). let value = options.get(property, context)?; @@ -242,10 +243,10 @@ pub(crate) fn to_date_time_options( } // 6. Let dateStyle be ? Get(options, "dateStyle"). - let date_style = options.get(utf16!("dateStyle"), context)?; + let date_style = options.get(js_str!("dateStyle"), context)?; // 7. Let timeStyle be ? Get(options, "timeStyle"). - let time_style = options.get(utf16!("timeStyle"), context)?; + let time_style = options.get(js_str!("timeStyle"), context)?; // 8. If dateStyle is not undefined or timeStyle is not undefined, let needDefaults be false. if !date_style.is_undefined() || !time_style.is_undefined() { @@ -271,18 +272,18 @@ pub(crate) fn to_date_time_options( // 11. If needDefaults is true and defaults is either "date" or "all", then if need_defaults && [DateTimeReqs::Date, DateTimeReqs::AnyAll].contains(defaults) { // a. For each property name prop of « "year", "month", "day" », do - for property in [utf16!("year"), utf16!("month"), utf16!("day")] { + for property in [js_str!("year"), js_str!("month"), js_str!("day")] { // i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric"). - options.create_data_property_or_throw(property, js_string!("numeric"), context)?; + options.create_data_property_or_throw(property, js_str!("numeric"), context)?; } } // 12. If needDefaults is true and defaults is either "time" or "all", then if need_defaults && [DateTimeReqs::Time, DateTimeReqs::AnyAll].contains(defaults) { // a. For each property name prop of « "hour", "minute", "second" », do - for property in [utf16!("hour"), utf16!("minute"), utf16!("second")] { + for property in [js_str!("hour"), js_str!("minute"), js_str!("second")] { // i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric"). - options.create_data_property_or_throw(property, js_string!("numeric"), context)?; + options.create_data_property_or_throw(property, js_str!("numeric"), context)?; } } diff --git a/core/engine/src/builtins/intl/list_format/mod.rs b/core/engine/src/builtins/intl/list_format/mod.rs index 55c2105322..c76889ffae 100644 --- a/core/engine/src/builtins/intl/list_format/mod.rs +++ b/core/engine/src/builtins/intl/list_format/mod.rs @@ -1,6 +1,7 @@ use std::fmt::Write; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; use icu_list::{provider::AndListV1Marker, ListFormatter, ListLength}; use icu_locid::Locale; @@ -16,7 +17,7 @@ use crate::{ object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, symbol::JsSymbol, Context, JsArgs, JsData, JsNativeError, JsResult, JsString, JsValue, }; @@ -114,7 +115,7 @@ impl BuiltInConstructor for ListFormat { // 5. Let opt be a new Record. // 6. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit"). - let matcher = get_option(&options, utf16!("localeMatcher"), context)?.unwrap_or_default(); + let matcher = get_option(&options, js_str!("localeMatcher"), context)?.unwrap_or_default(); // 7. Set opt.[[localeMatcher]] to matcher. // 8. Let localeData be %ListFormat%.[[LocaleData]]. @@ -131,11 +132,11 @@ impl BuiltInConstructor for ListFormat { // 11. Let type be ? GetOption(options, "type", string, « "conjunction", "disjunction", "unit" », "conjunction"). // 12. Set listFormat.[[Type]] to type. - let typ = get_option(&options, utf16!("type"), context)?.unwrap_or_default(); + let typ = get_option(&options, js_str!("type"), context)?.unwrap_or_default(); // 13. Let style be ? GetOption(options, "style", string, « "long", "short", "narrow" », "long"). // 14. Set listFormat.[[Style]] to style. - let style = get_option(&options, utf16!("style"), context)?.unwrap_or(ListLength::Wide); + let style = get_option(&options, js_str!("style"), context)?.unwrap_or(ListLength::Wide); // 15. Let dataLocale be r.[[dataLocale]]. // 16. Let dataLocaleData be localeData.[[]]. @@ -380,11 +381,11 @@ impl ListFormat { .create(OrdinaryObject, vec![]); // b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]). - o.create_data_property_or_throw(utf16!("type"), js_string!(part.typ()), context) + o.create_data_property_or_throw(js_str!("type"), js_string!(part.typ()), context) .expect("operation must not fail per the spec"); // c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]). - o.create_data_property_or_throw(utf16!("value"), js_string!(part.value()), context) + o.create_data_property_or_throw(js_str!("value"), js_string!(part.value()), context) .expect("operation must not fail per the spec"); // d. Perform ! CreateDataPropertyOrThrow(result, ! ToString(n), O). @@ -435,29 +436,29 @@ impl ListFormat { // d. Perform ! CreateDataPropertyOrThrow(options, p, v). options .create_data_property_or_throw( - utf16!("locale"), + js_str!("locale"), js_string!(lf.locale.to_string()), context, ) .expect("operation must not fail per the spec"); options .create_data_property_or_throw( - js_string!("type"), + js_str!("type"), match lf.typ { - ListFormatType::Conjunction => js_string!("conjunction"), - ListFormatType::Disjunction => js_string!("disjunction"), - ListFormatType::Unit => js_string!("unit"), + ListFormatType::Conjunction => js_str!("conjunction"), + ListFormatType::Disjunction => js_str!("disjunction"), + ListFormatType::Unit => js_str!("unit"), }, context, ) .expect("operation must not fail per the spec"); options .create_data_property_or_throw( - js_string!("style"), + js_str!("style"), match lf.style { - ListLength::Wide => js_string!("long"), - ListLength::Short => js_string!("short"), - ListLength::Narrow => js_string!("narrow"), + ListLength::Wide => js_str!("long"), + ListLength::Short => js_str!("short"), + ListLength::Narrow => js_str!("narrow"), _ => unreachable!(), }, context, diff --git a/core/engine/src/builtins/intl/locale/mod.rs b/core/engine/src/builtins/intl/locale/mod.rs index 8f5a288e66..a70ab770f6 100644 --- a/core/engine/src/builtins/intl/locale/mod.rs +++ b/core/engine/src/builtins/intl/locale/mod.rs @@ -1,8 +1,5 @@ -use crate::{ - builtins::options::get_option, - realm::Realm, - string::{common::StaticJsStrings, utf16}, -}; +use crate::{builtins::options::get_option, realm::Realm, string::common::StaticJsStrings}; +use boa_macros::js_str; use boa_profiler::Profiler; use icu_collator::CaseFirst; use icu_datetime::options::preferences::HourCycle; @@ -236,17 +233,17 @@ impl BuiltInConstructor for Locale { // 4. Let language be ? GetOption(options, "language", string, empty, undefined). // 5. If language is not undefined, then // a. If language does not match the unicode_language_subtag production, throw a RangeError exception. - let language = get_option(options, utf16!("language"), context)?; + let language = get_option(options, js_str!("language"), context)?; // 6. Let script be ? GetOption(options, "script", string, empty, undefined). // 7. If script is not undefined, then // a. If script does not match the unicode_script_subtag production, throw a RangeError exception. - let script = get_option(options, utf16!("script"), context)?; + let script = get_option(options, js_str!("script"), context)?; // 8. Let region be ? GetOption(options, "region", string, empty, undefined). // 9. If region is not undefined, then // a. If region does not match the unicode_region_subtag production, throw a RangeError exception. - let region = get_option(options, utf16!("region"), context)?; + let region = get_option(options, js_str!("region"), context)?; // 10. Set tag to ! CanonicalizeUnicodeLocaleId(tag). context @@ -294,17 +291,17 @@ impl BuiltInConstructor for Locale { // 14. If calendar is not undefined, then // 15. Set opt.[[ca]] to calendar. // a. If calendar does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception. - let ca = get_option(options, utf16!("calendar"), context)?; + let ca = get_option(options, js_str!("calendar"), context)?; // 16. Let collation be ? GetOption(options, "collation", string, empty, undefined). // 17. If collation is not undefined, then // 18. Set opt.[[co]] to collation. // a. If collation does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception. - let co = get_option(options, utf16!("collation"), context)?; + let co = get_option(options, js_str!("collation"), context)?; // 19. Let hc be ? GetOption(options, "hourCycle", string, « "h11", "h12", "h23", "h24" », undefined). // 20. Set opt.[[hc]] to hc. - let hc = get_option(options, utf16!("hourCycle"), context)?.map(|hc| match hc { + let hc = get_option(options, js_str!("hourCycle"), context)?.map(|hc| match hc { HourCycle::H24 => value!("h24"), HourCycle::H23 => value!("h23"), HourCycle::H12 => value!("h12"), @@ -313,7 +310,7 @@ impl BuiltInConstructor for Locale { // 21. Let kf be ? GetOption(options, "caseFirst", string, « "upper", "lower", "false" », undefined). // 22. Set opt.[[kf]] to kf. - let kf = get_option(options, utf16!("caseFirst"), context)?.map(|kf| match kf { + let kf = get_option(options, js_str!("caseFirst"), context)?.map(|kf| match kf { CaseFirst::UpperFirst => value!("upper"), CaseFirst::LowerFirst => value!("lower"), CaseFirst::Off => value!("false"), @@ -323,7 +320,7 @@ impl BuiltInConstructor for Locale { // 23. Let kn be ? GetOption(options, "numeric", boolean, empty, undefined). // 24. If kn is not undefined, set kn to ! ToString(kn). // 25. Set opt.[[kn]] to kn. - let kn = get_option(options, utf16!("numeric"), context)?.map(|b| { + let kn = get_option(options, js_str!("numeric"), context)?.map(|b| { if b { value!("true") } else { @@ -335,7 +332,7 @@ impl BuiltInConstructor for Locale { // 27. If numberingSystem is not undefined, then // 28. Set opt.[[nu]] to numberingSystem. // a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception. - let nu = get_option(options, utf16!("numberingSystem"), context)?; + let nu = get_option(options, js_str!("numberingSystem"), context)?; // 29. Let r be ! ApplyUnicodeExtensionToTag(tag, opt, relevantExtensionKeys). // 30. Set locale.[[Locale]] to r.[[locale]]. diff --git a/core/engine/src/builtins/intl/locale/utils.rs b/core/engine/src/builtins/intl/locale/utils.rs index 423908cd46..24f4c0ad2c 100644 --- a/core/engine/src/builtins/intl/locale/utils.rs +++ b/core/engine/src/builtins/intl/locale/utils.rs @@ -10,10 +10,10 @@ use crate::{ context::icu::IntlProvider, js_string, object::JsObject, - string::utf16, Context, JsNativeError, JsResult, JsValue, }; +use boa_macros::js_str; use icu_collator::provider::CollationMetadataV1Marker; use icu_locid::{ extensions::unicode::{Key, Value}, @@ -551,7 +551,7 @@ where let options = coerce_options_to_object(options, context)?; // 2. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit"). - let matcher = get_option(&options, utf16!("localeMatcher"), context)?.unwrap_or_default(); + let matcher = get_option(&options, js_str!("localeMatcher"), context)?.unwrap_or_default(); let elements = match matcher { // 4. Else, diff --git a/core/engine/src/builtins/intl/number_format/mod.rs b/core/engine/src/builtins/intl/number_format/mod.rs index 66bbea6283..3b63fca375 100644 --- a/core/engine/src/builtins/intl/number_format/mod.rs +++ b/core/engine/src/builtins/intl/number_format/mod.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use boa_gc::{Finalize, Trace}; -use boa_macros::utf16; +use boa_macros::js_str; use boa_profiler::Profiler; use fixed_decimal::{FixedDecimal, FloatPrecision, SignDisplay}; use icu_decimal::{ @@ -221,13 +221,13 @@ impl BuiltInConstructor for NumberFormat { // 4. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit"). // 5. Set opt.[[localeMatcher]] to matcher. - let matcher = get_option(&options, utf16!("localeMatcher"), context)?.unwrap_or_default(); + let matcher = get_option(&options, js_str!("localeMatcher"), context)?.unwrap_or_default(); // 6. Let numberingSystem be ? GetOption(options, "numberingSystem", string, empty, undefined). // 7. If numberingSystem is not undefined, then // a. If numberingSystem cannot be matched by the type Unicode locale nonterminal, throw a RangeError exception. // 8. Set opt.[[nu]] to numberingSystem. - let numbering_system = get_option(&options, utf16!("numberingSystem"), context)?; + let numbering_system = get_option(&options, js_str!("numberingSystem"), context)?; let mut intl_options = IntlOptions { matcher, @@ -277,7 +277,7 @@ impl BuiltInConstructor for NumberFormat { // 18. Let notation be ? GetOption(options, "notation", string, « "standard", "scientific", "engineering", "compact" », "standard"). // 19. Set numberFormat.[[Notation]] to notation. - let notation = get_option(&options, utf16!("notation"), context)?.unwrap_or_default(); + let notation = get_option(&options, js_str!("notation"), context)?.unwrap_or_default(); // 20. Perform ? SetNumberFormatDigitOptions(numberFormat, options, mnfdDefault, mxfdDefault, notation). let digit_options = DigitFormatOptions::from_options( @@ -290,7 +290,7 @@ impl BuiltInConstructor for NumberFormat { // 21. Let compactDisplay be ? GetOption(options, "compactDisplay", string, « "short", "long" », "short"). let compact_display = - get_option(&options, utf16!("compactDisplay"), context)?.unwrap_or_default(); + get_option(&options, js_str!("compactDisplay"), context)?.unwrap_or_default(); // 22. Let defaultUseGrouping be "auto". let mut default_use_grouping = GroupingStrategy::Auto; @@ -325,7 +325,7 @@ impl BuiltInConstructor for NumberFormat { // // 1. Let value be ? Get(options, property). - let value = options.get(utf16!("useGrouping"), context)?; + let value = options.get(js_str!("useGrouping"), context)?; // 2. If value is undefined, return fallback. if value.is_undefined() { @@ -363,7 +363,7 @@ impl BuiltInConstructor for NumberFormat { // 29. Let signDisplay be ? GetOption(options, "signDisplay", string, « "auto", "never", "always", "exceptZero", "negative" », "auto"). // 30. Set numberFormat.[[SignDisplay]] to signDisplay. let sign_display = - get_option(&options, utf16!("signDisplay"), context)?.unwrap_or(SignDisplay::Auto); + get_option(&options, js_str!("signDisplay"), context)?.unwrap_or(SignDisplay::Auto); let formatter = FixedDecimalFormatter::try_new_unstable( context.intl_provider(), diff --git a/core/engine/src/builtins/intl/number_format/options.rs b/core/engine/src/builtins/intl/number_format/options.rs index 74dac33194..b5e85ce4d4 100644 --- a/core/engine/src/builtins/intl/number_format/options.rs +++ b/core/engine/src/builtins/intl/number_format/options.rs @@ -2,7 +2,7 @@ use std::fmt; use fixed_decimal::{FixedDecimal, FloatPrecision, RoundingIncrement as BaseMultiple, SignDisplay}; -use boa_macros::utf16; +use boa_macros::js_str; use tinystr::TinyAsciiStr; use crate::{ @@ -10,7 +10,7 @@ use crate::{ intl::options::{default_number_option, get_number_option}, options::{get_option, OptionType, ParsableOptionType, RoundingMode}, }, - js_string, Context, JsNativeError, JsObject, JsResult, JsString, + js_string, Context, JsNativeError, JsObject, JsResult, JsStr, JsString, }; #[derive(Debug, Copy, Clone, Default, Eq, PartialEq)] @@ -245,9 +245,9 @@ impl ParsableOptionType for Currency {} #[derive(Debug, Eq, PartialEq)] pub(crate) struct Unit { // INVARIANT: `numerator` must only contain ASCII lowercase alphabetic letters or `-`. - numerator: &'static str, + numerator: JsStr<'static>, // INVARIANT: if `denominator` is not empty, it must only contain ASCII lowercase alphabetic letters or `-` - denominator: &'static str, + denominator: JsStr<'static>, } impl Unit { @@ -258,9 +258,7 @@ impl Unit { } else { // TODO: this is not optimal for now, but the new JS strings should // allow us to optimize this to simple casts from ASCII to JsString. - let numerator: Vec = self.numerator.encode_utf16().collect(); - let denominator: Vec = self.denominator.encode_utf16().collect(); - js_string!(&numerator, utf16!("-per-"), &denominator) + js_string!(self.numerator, js_str!("-per-"), self.denominator) } } } @@ -281,7 +279,7 @@ impl std::str::FromStr for Unit { /// /// [spec]: https://tc39.es/ecma402/#sec-iswellformedunitidentifier fn from_str(s: &str) -> Result { - static SANCTIONED_UNITS: [&str; 45] = [ + const SANCTIONED_UNITS: [&str; 45] = [ "acre", "bit", "byte", @@ -339,13 +337,17 @@ impl std::str::FromStr for Unit { .map(|i| SANCTIONED_UNITS[i]) .map_err(|_| ParseUnitError)?; + let num = JsStr::latin1(num.as_bytes()); + let den = if den.is_empty() { - "" + JsStr::EMPTY } else { - SANCTIONED_UNITS + let value = SANCTIONED_UNITS .binary_search(&den) .map(|i| SANCTIONED_UNITS[i]) - .map_err(|_| ParseUnitError)? + .map_err(|_| ParseUnitError)?; + + JsStr::latin1(value.as_bytes()) }; Ok(Self { @@ -390,12 +392,12 @@ impl UnitFormatOptions { pub(crate) fn from_options(options: &JsObject, context: &mut Context) -> JsResult { // 1. Let style be ? GetOption(options, "style", string, « "decimal", "percent", "currency", "unit" », "decimal"). // 2. Set intlObj.[[Style]] to style. - let style: Style = get_option(options, utf16!("style"), context)?.unwrap_or_default(); + let style: Style = get_option(options, js_str!("style"), context)?.unwrap_or_default(); // 3. Let currency be ? GetOption(options, "currency", string, empty, undefined). // 5. Else, // a. If IsWellFormedCurrencyCode(currency) is false, throw a RangeError exception. - let currency = get_option(options, utf16!("currency"), context)?; + let currency = get_option(options, js_str!("currency"), context)?; // 4. If currency is undefined, then if currency.is_none() { @@ -411,16 +413,16 @@ impl UnitFormatOptions { // 6. Let currencyDisplay be ? GetOption(options, "currencyDisplay", string, « "code", "symbol", "narrowSymbol", "name" », "symbol"). let currency_display = - get_option(options, utf16!("currencyDisplay"), context)?.unwrap_or_default(); + get_option(options, js_str!("currencyDisplay"), context)?.unwrap_or_default(); // 7. Let currencySign be ? GetOption(options, "currencySign", string, « "standard", "accounting" », "standard"). let currency_sign = - get_option(options, utf16!("currencySign"), context)?.unwrap_or_default(); + get_option(options, js_str!("currencySign"), context)?.unwrap_or_default(); // 8. Let unit be ? GetOption(options, "unit", string, empty, undefined). // 10. Else, // a. If IsWellFormedUnitIdentifier(unit) is false, throw a RangeError exception. - let unit = get_option(options, utf16!("unit"), context)?; + let unit = get_option(options, js_str!("unit"), context)?; // 9. If unit is undefined, then if unit.is_none() { // a. If style is "unit", throw a TypeError exception. @@ -434,7 +436,8 @@ impl UnitFormatOptions { } // 11. Let unitDisplay be ? GetOption(options, "unitDisplay", string, « "short", "narrow", "long" », "short"). - let unit_display = get_option(options, utf16!("unitDisplay"), context)?.unwrap_or_default(); + let unit_display = + get_option(options, js_str!("unitDisplay"), context)?.unwrap_or_default(); // 14. Return unused. Ok(match style { @@ -489,25 +492,26 @@ impl DigitFormatOptions { ) -> JsResult { // 1. Let mnid be ? GetNumberOption(options, "minimumIntegerDigits,", 1, 21, 1). let minimum_integer_digits = - get_number_option(options, utf16!("minimumIntegerDigits"), 1, 21, context)? + get_number_option(options, js_str!("minimumIntegerDigits"), 1, 21, context)? .unwrap_or(1); // 2. Let mnfd be ? Get(options, "minimumFractionDigits"). - let min_float_digits = options.get(utf16!("minimumFractionDigits"), context)?; + let min_float_digits = options.get(js_str!("minimumFractionDigits"), context)?; // 3. Let mxfd be ? Get(options, "maximumFractionDigits"). - let max_float_digits = options.get(utf16!("maximumFractionDigits"), context)?; + let max_float_digits = options.get(js_str!("maximumFractionDigits"), context)?; // 4. Let mnsd be ? Get(options, "minimumSignificantDigits"). - let min_sig_digits = options.get(utf16!("minimumSignificantDigits"), context)?; + let min_sig_digits = options.get(js_str!("minimumSignificantDigits"), context)?; // 5. Let mxsd be ? Get(options, "maximumSignificantDigits"). - let max_sig_digits = options.get(utf16!("maximumSignificantDigits"), context)?; + let max_sig_digits = options.get(js_str!("maximumSignificantDigits"), context)?; // 7. Let roundingPriority be ? GetOption(options, "roundingPriority", string, « "auto", "morePrecision", "lessPrecision" », "auto"). let mut rounding_priority = - get_option(options, utf16!("roundingPriority"), context)?.unwrap_or_default(); + get_option(options, js_str!("roundingPriority"), context)?.unwrap_or_default(); // 8. Let roundingIncrement be ? GetNumberOption(options, "roundingIncrement", 1, 5000, 1). // 9. If roundingIncrement is not in « 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000 », throw a RangeError exception. let rounding_increment = - get_number_option(options, utf16!("roundingIncrement"), 1, 5000, context)?.unwrap_or(1); + get_number_option(options, js_str!("roundingIncrement"), 1, 5000, context)? + .unwrap_or(1); let rounding_increment = RoundingIncrement::from_u16(rounding_increment).ok_or_else(|| { @@ -516,11 +520,11 @@ impl DigitFormatOptions { // 10. Let roundingMode be ? GetOption(options, "roundingMode", string, « "ceil", "floor", "expand", "trunc", "halfCeil", "halfFloor", "halfExpand", "halfTrunc", "halfEven" », "halfExpand"). let rounding_mode = - get_option(options, utf16!("roundingMode"), context)?.unwrap_or_default(); + get_option(options, js_str!("roundingMode"), context)?.unwrap_or_default(); // 11. Let trailingZeroDisplay be ? GetOption(options, "trailingZeroDisplay", string, « "auto", "stripIfInteger" », "auto"). let trailing_zero_display = - get_option(options, utf16!("trailingZeroDisplay"), context)?.unwrap_or_default(); + get_option(options, js_str!("trailingZeroDisplay"), context)?.unwrap_or_default(); // 12. NOTE: All fields required by SetNumberFormatDigitOptions have now been read from options. The remainder of this AO interprets the options and may throw exceptions. diff --git a/core/engine/src/builtins/intl/options.rs b/core/engine/src/builtins/intl/options.rs index bef7c8b332..da0803126c 100644 --- a/core/engine/src/builtins/intl/options.rs +++ b/core/engine/src/builtins/intl/options.rs @@ -5,7 +5,7 @@ use num_traits::FromPrimitive; use crate::{ builtins::{options::ParsableOptionType, OrdinaryObject}, object::JsObject, - Context, JsNativeError, JsResult, JsValue, + Context, JsNativeError, JsResult, JsStr, JsValue, }; /// `IntlOptions` aggregates the `locale_matcher` selector and any other object @@ -60,7 +60,7 @@ impl ParsableOptionType for LocaleMatcher {} /// [spec]: https://tc39.es/ecma402/#sec-getnumberoption pub(super) fn get_number_option( options: &JsObject, - property: &[u16], + property: JsStr<'_>, minimum: T, maximum: T, context: &mut Context, diff --git a/core/engine/src/builtins/intl/plural_rules/mod.rs b/core/engine/src/builtins/intl/plural_rules/mod.rs index 74a07c736e..d0dad83b30 100644 --- a/core/engine/src/builtins/intl/plural_rules/mod.rs +++ b/core/engine/src/builtins/intl/plural_rules/mod.rs @@ -1,7 +1,7 @@ mod options; use boa_gc::{Finalize, Trace}; -use boa_macros::utf16; +use boa_macros::js_str; use boa_profiler::Profiler; use fixed_decimal::FixedDecimal; use icu_locid::Locale; @@ -22,7 +22,7 @@ use crate::{ property::Attribute, realm::Realm, string::common::StaticJsStrings, - Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, + Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsStr, JsString, JsSymbol, JsValue, }; use super::{ @@ -121,12 +121,12 @@ impl BuiltInConstructor for PluralRules { // 3. Let opt be a new Record. // 4. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit"). // 5. Set opt.[[localeMatcher]] to matcher. - let matcher = get_option(&options, utf16!("localeMatcher"), context)?.unwrap_or_default(); + let matcher = get_option(&options, js_str!("localeMatcher"), context)?.unwrap_or_default(); // 6. Let t be ? GetOption(options, "type", string, « "cardinal", "ordinal" », "cardinal"). // 7. Set pluralRules.[[Type]] to t. let rule_type = - get_option(&options, utf16!("type"), context)?.unwrap_or(PluralRuleType::Cardinal); + get_option(&options, js_str!("type"), context)?.unwrap_or(PluralRuleType::Cardinal); // 8. Perform ? SetNumberFormatDigitOptions(pluralRules, options, +0𝔽, 3𝔽, "standard"). let format_options = @@ -330,21 +330,21 @@ impl PluralRules { let mut options = ObjectInitializer::new(context); options .property( - js_string!("locale"), + js_str!("locale"), js_string!(plural_rules.locale.to_string()), Attribute::all(), ) .property( - js_string!("type"), + js_str!("type"), match plural_rules.rule_type { - PluralRuleType::Cardinal => js_string!("cardinal"), - PluralRuleType::Ordinal => js_string!("ordinal"), - _ => js_string!("unknown"), + PluralRuleType::Cardinal => js_str!("cardinal"), + PluralRuleType::Ordinal => js_str!("ordinal"), + _ => js_str!("unknown"), }, Attribute::all(), ) .property( - js_string!("minimumIntegerDigits"), + js_str!("minimumIntegerDigits"), plural_rules.format_options.minimum_integer_digits, Attribute::all(), ); @@ -353,16 +353,8 @@ impl PluralRules { plural_rules.format_options.rounding_type.fraction_digits() { options - .property( - js_string!("minimumFractionDigits"), - minimum, - Attribute::all(), - ) - .property( - js_string!("maximumFractionDigits"), - maximum, - Attribute::all(), - ); + .property(js_str!("minimumFractionDigits"), minimum, Attribute::all()) + .property(js_str!("maximumFractionDigits"), maximum, Attribute::all()); } if let Some(Extrema { minimum, maximum }) = plural_rules @@ -372,12 +364,12 @@ impl PluralRules { { options .property( - js_string!("minimumSignificantDigits"), + js_str!("minimumSignificantDigits"), minimum, Attribute::all(), ) .property( - js_string!("maximumSignificantDigits"), + js_str!("maximumSignificantDigits"), maximum, Attribute::all(), ); @@ -385,17 +377,17 @@ impl PluralRules { options .property( - js_string!("roundingMode"), + js_str!("roundingMode"), js_string!(plural_rules.format_options.rounding_mode.to_js_string()), Attribute::all(), ) .property( - js_string!("roundingIncrement"), + js_str!("roundingIncrement"), plural_rules.format_options.rounding_increment.to_u16(), Attribute::all(), ) .property( - js_string!("trailingZeroDisplay"), + js_str!("trailingZeroDisplay"), plural_rules .format_options .trailing_zero_display @@ -416,7 +408,7 @@ impl PluralRules { // 6. Perform ! CreateDataProperty(options, "pluralCategories", CreateArrayFromList(pluralCategories)). options.property( - js_string!("pluralCategories"), + js_str!("pluralCategories"), plural_categories, Attribute::all(), ); @@ -428,7 +420,7 @@ impl PluralRules { // 9. Else, // a. Perform ! CreateDataPropertyOrThrow(options, "roundingPriority", "auto"). options.property( - js_string!("roundingPriority"), + js_str!("roundingPriority"), js_string!(plural_rules.format_options.rounding_priority.to_js_string()), Attribute::all(), ); @@ -480,13 +472,13 @@ fn resolve_plural(plural_rules: &PluralRules, n: f64) -> ResolvedPlural { } } -fn plural_category_to_js_string(category: PluralCategory) -> JsString { +fn plural_category_to_js_string(category: PluralCategory) -> JsStr<'static> { match category { - PluralCategory::Zero => js_string!("zero"), - PluralCategory::One => js_string!("one"), - PluralCategory::Two => js_string!("two"), - PluralCategory::Few => js_string!("few"), - PluralCategory::Many => js_string!("many"), - PluralCategory::Other => js_string!("other"), + PluralCategory::Zero => js_str!("zero"), + PluralCategory::One => js_str!("one"), + PluralCategory::Two => js_str!("two"), + PluralCategory::Few => js_str!("few"), + PluralCategory::Many => js_str!("many"), + PluralCategory::Other => js_str!("other"), } } diff --git a/core/engine/src/builtins/intl/segmenter/iterator.rs b/core/engine/src/builtins/intl/segmenter/iterator.rs index b7e6dfe133..7ed7ef663c 100644 --- a/core/engine/src/builtins/intl/segmenter/iterator.rs +++ b/core/engine/src/builtins/intl/segmenter/iterator.rs @@ -1,7 +1,9 @@ use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use icu_segmenter::{ - GraphemeClusterBreakIteratorUtf16, SentenceBreakIteratorUtf16, WordBreakIteratorUtf16, + GraphemeClusterBreakIteratorLatin1, GraphemeClusterBreakIteratorUtf16, + SentenceBreakIteratorLatin1, SentenceBreakIteratorUtf16, WordBreakIteratorLatin1, + WordBreakIteratorUtf16, }; use crate::{ @@ -16,9 +18,12 @@ use crate::{ use super::{create_segment_data_object, Segmenter}; pub(crate) enum NativeSegmentIterator<'l, 's> { - Grapheme(GraphemeClusterBreakIteratorUtf16<'l, 's>), - Word(WordBreakIteratorUtf16<'l, 's>), - Sentence(SentenceBreakIteratorUtf16<'l, 's>), + GraphemeUtf16(GraphemeClusterBreakIteratorUtf16<'l, 's>), + WordUtf16(WordBreakIteratorUtf16<'l, 's>), + SentenceUtf16(SentenceBreakIteratorUtf16<'l, 's>), + GraphemeLatin1(GraphemeClusterBreakIteratorLatin1<'l, 's>), + WordLatin1(WordBreakIteratorLatin1<'l, 's>), + SentenceLatin1(SentenceBreakIteratorLatin1<'l, 's>), } impl Iterator for NativeSegmentIterator<'_, '_> { @@ -26,9 +31,12 @@ impl Iterator for NativeSegmentIterator<'_, '_> { fn next(&mut self) -> Option { match self { - NativeSegmentIterator::Grapheme(g) => g.next(), - NativeSegmentIterator::Word(w) => w.next(), - NativeSegmentIterator::Sentence(s) => s.next(), + NativeSegmentIterator::GraphemeUtf16(g) => g.next(), + NativeSegmentIterator::WordUtf16(w) => w.next(), + NativeSegmentIterator::SentenceUtf16(s) => s.next(), + NativeSegmentIterator::GraphemeLatin1(g) => g.next(), + NativeSegmentIterator::WordLatin1(w) => w.next(), + NativeSegmentIterator::SentenceLatin1(s) => s.next(), } } } @@ -37,10 +45,10 @@ impl NativeSegmentIterator<'_, '_> { /// If the iterator is a word break iterator, returns `Some(true)` when the segment preceding /// the current boundary is word-like. pub(crate) fn is_word_like(&self) -> Option { - if let Self::Word(w) = self { - Some(w.is_word_like()) - } else { - None + match self { + Self::WordLatin1(w) => Some(w.is_word_like()), + Self::WordUtf16(w) => Some(w.is_word_like()), + _ => None, } } } diff --git a/core/engine/src/builtins/intl/segmenter/mod.rs b/core/engine/src/builtins/intl/segmenter/mod.rs index 2ff7f03e56..517e359b68 100644 --- a/core/engine/src/builtins/intl/segmenter/mod.rs +++ b/core/engine/src/builtins/intl/segmenter/mod.rs @@ -1,7 +1,7 @@ use std::ops::Range; use boa_gc::{Finalize, Trace}; -use boa_macros::utf16; +use boa_macros::js_str; use boa_profiler::Profiler; use icu_locid::Locale; use icu_segmenter::{ @@ -19,7 +19,7 @@ use crate::{ property::Attribute, realm::Realm, string::common::StaticJsStrings, - Context, JsArgs, JsData, JsNativeError, JsResult, JsString, JsSymbol, JsValue, + Context, JsArgs, JsData, JsNativeError, JsResult, JsStr, JsString, JsSymbol, JsValue, }; mod iterator; @@ -62,11 +62,18 @@ impl NativeSegmenter { /// Segment the passed string, returning an iterator with the index boundaries /// of the segments. - pub(crate) fn segment<'l, 's>(&'l self, input: &'s [u16]) -> NativeSegmentIterator<'l, 's> { - match self { - Self::Grapheme(g) => NativeSegmentIterator::Grapheme(g.segment_utf16(input)), - Self::Word(w) => NativeSegmentIterator::Word(w.segment_utf16(input)), - Self::Sentence(s) => NativeSegmentIterator::Sentence(s.segment_utf16(input)), + pub(crate) fn segment<'l, 's>(&'l self, input: JsStr<'s>) -> NativeSegmentIterator<'l, 's> { + match input.variant() { + crate::string::JsStrVariant::Latin1(input) => match self { + Self::Grapheme(g) => NativeSegmentIterator::GraphemeLatin1(g.segment_latin1(input)), + Self::Word(w) => NativeSegmentIterator::WordLatin1(w.segment_latin1(input)), + Self::Sentence(s) => NativeSegmentIterator::SentenceLatin1(s.segment_latin1(input)), + }, + crate::string::JsStrVariant::Utf16(input) => match self { + Self::Grapheme(g) => NativeSegmentIterator::GraphemeUtf16(g.segment_utf16(input)), + Self::Word(w) => NativeSegmentIterator::WordUtf16(w.segment_utf16(input)), + Self::Sentence(s) => NativeSegmentIterator::SentenceUtf16(s.segment_utf16(input)), + }, } } } @@ -134,7 +141,7 @@ impl BuiltInConstructor for Segmenter { // 6. Let opt be a new Record. // 7. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit"). - let matcher = get_option(&options, utf16!("localeMatcher"), context)?.unwrap_or_default(); + let matcher = get_option(&options, js_str!("localeMatcher"), context)?.unwrap_or_default(); // 8. Set opt.[[localeMatcher]] to matcher. // 9. Let localeData be %Segmenter%.[[LocaleData]]. @@ -150,7 +157,8 @@ impl BuiltInConstructor for Segmenter { ); // 12. Let granularity be ? GetOption(options, "granularity", string, « "grapheme", "word", "sentence" », "grapheme"). - let granularity = get_option(&options, utf16!("granularity"), context)?.unwrap_or_default(); + let granularity = + get_option(&options, js_str!("granularity"), context)?.unwrap_or_default(); // 13. Set segmenter.[[SegmenterGranularity]] to granularity. let native = match granularity { @@ -241,12 +249,12 @@ impl Segmenter { // d. Perform ! CreateDataPropertyOrThrow(options, p, v). let options = ObjectInitializer::new(context) .property( - js_string!("locale"), + js_str!("locale"), js_string!(segmenter.locale.to_string()), Attribute::all(), ) .property( - js_string!("granularity"), + js_str!("granularity"), js_string!(segmenter.native.granularity().to_string()), Attribute::all(), ) @@ -301,25 +309,25 @@ fn create_segment_data_object( let start = range.start; // 6. Let segment be the substring of string from startIndex to endIndex. - let segment = js_string!(&string[range]); + let segment = string.get(range).expect("range already checked"); // 5. Let result be OrdinaryObjectCreate(%Object.prototype%). let object = &mut ObjectInitializer::new(context); object // 7. Perform ! CreateDataPropertyOrThrow(result, "segment", segment). - .property(js_string!("segment"), segment, Attribute::all()) + .property(js_str!("segment"), segment, Attribute::all()) // 8. Perform ! CreateDataPropertyOrThrow(result, "index", 𝔽(startIndex)). - .property(js_string!("index"), start, Attribute::all()) + .property(js_str!("index"), start, Attribute::all()) // 9. Perform ! CreateDataPropertyOrThrow(result, "input", string). - .property(js_string!("input"), string, Attribute::all()); + .property(js_str!("input"), string, Attribute::all()); // 10. Let granularity be segmenter.[[SegmenterGranularity]]. // 11. If granularity is "word", then if let Some(is_word_like) = is_word_like { // a. Let isWordLike be a Boolean value indicating whether the segment in string is "word-like" according to locale segmenter.[[Locale]]. // b. Perform ! CreateDataPropertyOrThrow(result, "isWordLike", isWordLike). - object.property(js_string!("isWordLike"), is_word_like, Attribute::all()); + object.property(js_str!("isWordLike"), is_word_like, Attribute::all()); } // 12. Return result. diff --git a/core/engine/src/builtins/intl/segmenter/segments.rs b/core/engine/src/builtins/intl/segmenter/segments.rs index 7710904cff..65487c362d 100644 --- a/core/engine/src/builtins/intl/segmenter/segments.rs +++ b/core/engine/src/builtins/intl/segmenter/segments.rs @@ -89,7 +89,7 @@ impl Segments { // 8. Let startIndex be ! FindBoundary(segmenter, string, n, before). // 9. Let endIndex be ! FindBoundary(segmenter, string, n, after). let (range, is_word_like) = { - let mut segments = segmenter.native.segment(&segments.string); + let mut segments = segmenter.native.segment(segments.string.as_str()); std::iter::from_fn(|| segments.next().map(|i| (i, segments.is_word_like()))) .tuple_windows() .find(|((i, _), (j, _))| (*i..*j).contains(&n)) diff --git a/core/engine/src/builtins/iterable/async_from_sync_iterator.rs b/core/engine/src/builtins/iterable/async_from_sync_iterator.rs index 7ccf4bb5d0..8854bdf00e 100644 --- a/core/engine/src/builtins/iterable/async_from_sync_iterator.rs +++ b/core/engine/src/builtins/iterable/async_from_sync_iterator.rs @@ -9,10 +9,10 @@ use crate::{ native_function::NativeFunction, object::{FunctionObjectBuilder, JsObject}, realm::Realm, - string::utf16, Context, JsArgs, JsData, JsError, JsNativeError, JsResult, JsValue, }; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; /// `%AsyncFromSyncIteratorPrototype%` object. @@ -80,7 +80,7 @@ impl AsyncFromSyncIterator { // 3. Let nextMethod be ! Get(asyncIterator, "next"). let next_method = async_iterator - .get(utf16!("next"), context) + .get(js_str!("next"), context) .expect("async from sync iterator prototype must have next method"); // 4. Let iteratorRecord be the Iterator Record { [[Iterator]]: asyncIterator, [[NextMethod]]: nextMethod, [[Done]]: false }. @@ -166,7 +166,7 @@ impl AsyncFromSyncIterator { .expect("cannot fail with promise constructor"); // 6. Let return be Completion(GetMethod(syncIterator, "return")). - let r#return = sync_iterator.get_method(utf16!("return"), context); + let r#return = sync_iterator.get_method(js_str!("return"), context); // 7. IfAbruptRejectPromise(return, promiseCapability). let r#return = if_abrupt_reject_promise!(r#return, promise_capability, context); @@ -243,7 +243,7 @@ impl AsyncFromSyncIterator { .expect("cannot fail with promise constructor"); // 6. Let throw be Completion(GetMethod(syncIterator, "throw")). - let throw = sync_iterator.get_method(utf16!("throw"), context); + let throw = sync_iterator.get_method(js_str!("throw"), context); // 7. IfAbruptRejectPromise(throw, promiseCapability). let throw = if_abrupt_reject_promise!(throw, promise_capability, context); @@ -368,7 +368,7 @@ impl AsyncFromSyncIterator { )) }), ) - .name("") + .name(js_str!("")) .length(1) .build(); @@ -401,7 +401,7 @@ impl AsyncFromSyncIterator { sync_iterator_record, ), ) - .name("") + .name(js_str!("")) .length(1) .build(), ) diff --git a/core/engine/src/builtins/iterable/mod.rs b/core/engine/src/builtins/iterable/mod.rs index 2d15325171..63b21a87b4 100644 --- a/core/engine/src/builtins/iterable/mod.rs +++ b/core/engine/src/builtins/iterable/mod.rs @@ -4,13 +4,13 @@ use crate::{ builtins::{BuiltInBuilder, IntrinsicObject}, context::intrinsics::Intrinsics, error::JsNativeError, - js_string, object::JsObject, realm::Realm, symbol::JsSymbol, Context, JsResult, JsValue, }; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; mod async_from_sync_iterator; @@ -282,7 +282,7 @@ impl JsValue { })?; // 5. Let nextMethod be ? GetV(iterator, "next"). - let next_method = iterator.get_v(js_string!("next"), context)?; + let next_method = iterator.get_v(js_str!("next"), context)?; // 6. Let iteratorRecord be the Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }. // 7. Return iteratorRecord. @@ -326,7 +326,7 @@ impl IteratorResult { #[inline] pub fn complete(&self, context: &mut Context) -> JsResult { // 1. Return ToBoolean(? Get(iterResult, "done")). - Ok(self.object.get(js_string!("done"), context)?.to_boolean()) + Ok(self.object.get(js_str!("done"), context)?.to_boolean()) } /// `IteratorValue ( iterResult )` @@ -342,7 +342,7 @@ impl IteratorResult { #[inline] pub fn value(&self, context: &mut Context) -> JsResult { // 1. Return ? Get(iterResult, "value"). - self.object.get(js_string!("value"), context) + self.object.get(js_str!("value"), context) } } @@ -524,7 +524,7 @@ impl IteratorRecord { let iterator = &self.iterator; // 3. Let innerResult be Completion(GetMethod(iterator, "return")). - let inner_result = iterator.get_method(js_string!("return"), context); + let inner_result = iterator.get_method(js_str!("return"), context); // 4. If innerResult.[[Type]] is normal, then let inner_result = match inner_result { diff --git a/core/engine/src/builtins/json/mod.rs b/core/engine/src/builtins/json/mod.rs index 3ba82ce549..af6a4513ba 100644 --- a/core/engine/src/builtins/json/mod.rs +++ b/core/engine/src/builtins/json/mod.rs @@ -15,6 +15,7 @@ use std::{borrow::Cow, iter::once}; +use boa_macros::{js_str, utf16}; use itertools::Itertools; use crate::{ @@ -26,7 +27,7 @@ use crate::{ object::{internal_methods::InternalMethodContext, JsObject}, property::{Attribute, PropertyNameKind}, realm::Realm, - string::{common::StaticJsStrings, utf16, CodePoint}, + string::{common::StaticJsStrings, CodePoint}, symbol::JsSymbol, value::IntegerOrInfinity, vm::{CallFrame, CallFrameFlags}, @@ -148,11 +149,11 @@ impl Json { // b. Let rootName be the empty String. // c. Perform ! CreateDataPropertyOrThrow(root, rootName, unfiltered). - root.create_data_property_or_throw(utf16!(""), unfiltered, context) + root.create_data_property_or_throw(js_str!(""), unfiltered, context) .expect("CreateDataPropertyOrThrow should never throw here"); // d. Return ? InternalizeJSONProperty(root, rootName, reviver). - Self::internalize_json_property(&root, "".into(), obj, context) + Self::internalize_json_property(&root, js_string!(), obj, context) } else { // 12. Else, // a. Return unfiltered. @@ -368,7 +369,7 @@ impl Json { // 7. Else if Type(space) is String, then } else if let Some(s) = space.as_string() { // a. If the length of space is 10 or less, let gap be space; otherwise let gap be the substring of space from 0 to 10. - js_string!(s.get(..10).unwrap_or(s)) + js_string!(s.get(..10).unwrap_or(s.as_str())) // 8. Else, } else { // a. Let gap be the empty String. @@ -380,7 +381,7 @@ impl Json { // 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value). wrapper - .create_data_property_or_throw(utf16!(""), args.get_or_undefined(0).clone(), context) + .create_data_property_or_throw(js_str!(""), args.get_or_undefined(0).clone(), context) .expect("CreateDataPropertyOrThrow should never fail here"); // 11. Let state be the Record { [[ReplacerFunction]]: ReplacerFunction, [[Stack]]: stack, [[Indent]]: indent, [[Gap]]: gap, [[PropertyList]]: PropertyList }. @@ -418,7 +419,7 @@ impl Json { // 2. If Type(value) is Object or BigInt, then if value.is_object() || value.is_bigint() { // a. Let toJSON be ? GetV(value, "toJSON"). - let to_json = value.get_v(utf16!("toJSON"), context)?; + let to_json = value.get_v(js_str!("toJSON"), context)?; // b. If IsCallable(toJSON) is true, then if let Some(obj) = to_json.as_object() { @@ -627,7 +628,7 @@ impl Json { // b. If strP is not undefined, then if let Some(str_p) = str_p { // i. Let member be QuoteJSONString(P). - let mut member = Self::quote_json_string(p).to_vec(); + let mut member = Self::quote_json_string(p).iter().collect::>(); // ii. Set member to the string-concatenation of member and ":". member.push(':' as u16); @@ -639,7 +640,7 @@ impl Json { } // iv. Set member to the string-concatenation of member and strP. - member.extend_from_slice(&str_p); + member.extend(str_p.iter()); // v. Append member to partial. partial.push(member); @@ -674,20 +675,20 @@ impl Json { // i. Let separator be the string-concatenation of the code unit 0x002C (COMMA), // the code unit 0x000A (LINE FEED), and state.[[Indent]]. let mut separator = utf16!(",\n").to_vec(); - separator.extend_from_slice(&state.indent); + separator.extend(state.indent.iter()); // ii. Let properties be the String value formed by concatenating all the element Strings of partial // with each adjacent pair of Strings separated with separator. // The separator String is not inserted either before the first String or after the last String. // iii. Let final be the string-concatenation of "{", the code // unit 0x000A (LINE FEED), state.[[Indent]], properties, // the code unit 0x000A (LINE FEED), stepback, and "}". - let result = [utf16!("{\n"), &state.indent[..]] + let result = [utf16!("{\n"), &state.indent.to_vec()[..]] .into_iter() .chain(Itertools::intersperse( partial.iter().map(Vec::as_slice), &separator, )) - .chain([utf16!("\n"), &stepback[..], utf16!("}")]) + .chain([utf16!("\n"), &stepback.to_vec()[..], utf16!("}")]) .flatten() .copied() .collect::>(); @@ -750,7 +751,7 @@ impl Json { // b. If strP is undefined, then if let Some(str_p) = str_p { // i. Append strP to partial. - partial.push(Cow::Owned(str_p.to_vec())); + partial.push(Cow::Owned(str_p.iter().collect::<_>())); // c. Else, } else { // i. Append "null" to partial. @@ -789,18 +790,18 @@ impl Json { // i. Let separator be the string-concatenation of the code unit 0x002C (COMMA), // the code unit 0x000A (LINE FEED), and state.[[Indent]]. let mut separator = utf16!(",\n").to_vec(); - separator.extend_from_slice(&state.indent); + separator.extend(state.indent.iter()); // ii. Let properties be the String value formed by concatenating all the element Strings of partial // with each adjacent pair of Strings separated with separator. // The separator String is not inserted either before the first String or after the last String. // iii. Let final be the string-concatenation of "[", the code unit 0x000A (LINE FEED), state.[[Indent]], properties, the code unit 0x000A (LINE FEED), stepback, and "]". - let result = [utf16!("[\n"), &state.indent[..]] + let result = [utf16!("[\n"), &state.indent.to_vec()[..]] .into_iter() .chain(Itertools::intersperse( partial.iter().map(Cow::as_ref), &separator, )) - .chain([utf16!("\n"), &stepback[..], utf16!("]")]) + .chain([utf16!("\n"), &stepback.to_vec()[..], utf16!("]")]) .flatten() .copied() .collect::>(); diff --git a/core/engine/src/builtins/json/tests.rs b/core/engine/src/builtins/json/tests.rs index 2300260036..805c0b8c65 100644 --- a/core/engine/src/builtins/json/tests.rs +++ b/core/engine/src/builtins/json/tests.rs @@ -1,3 +1,4 @@ +use boa_macros::js_str; use indoc::indoc; use crate::{js_string, run_test_actions, JsNativeErrorKind, JsValue, TestAction}; @@ -5,7 +6,7 @@ use crate::{js_string, run_test_actions, JsNativeErrorKind, JsValue, TestAction} #[test] fn json_sanity() { run_test_actions([ - TestAction::assert_eq(r#"JSON.parse('{"aaa":"bbb"}').aaa"#, js_string!("bbb")), + TestAction::assert_eq(r#"JSON.parse('{"aaa":"bbb"}').aaa"#, js_str!("bbb")), TestAction::assert_eq( r#"JSON.stringify({aaa: 'bbb'})"#, js_string!(r#"{"aaa":"bbb"}"#), @@ -89,7 +90,7 @@ fn json_stringify_object_array() { fn json_stringify_array_converts_undefined_to_null() { run_test_actions([TestAction::assert_eq( "JSON.stringify([undefined])", - js_string!("[null]"), + js_str!("[null]"), )]); } @@ -97,7 +98,7 @@ fn json_stringify_array_converts_undefined_to_null() { fn json_stringify_array_converts_function_to_null() { run_test_actions([TestAction::assert_eq( "JSON.stringify([() => {}])", - js_string!("[null]"), + js_str!("[null]"), )]); } @@ -105,7 +106,7 @@ fn json_stringify_array_converts_function_to_null() { fn json_stringify_array_converts_symbol_to_null() { run_test_actions([TestAction::assert_eq( "JSON.stringify([Symbol()])", - js_string!("[null]"), + js_str!("[null]"), )]); } #[test] @@ -150,10 +151,7 @@ fn json_stringify_no_args() { #[test] fn json_stringify_fractional_numbers() { - run_test_actions([TestAction::assert_eq( - "JSON.stringify(1.2)", - js_string!("1.2"), - )]); + run_test_actions([TestAction::assert_eq("JSON.stringify(1.2)", js_str!("1.2"))]); } #[test] @@ -278,8 +276,8 @@ fn json_parse_object_with_reviver() { var jsonObj = JSON.parse(jsonString, dataReviver); "#}), - TestAction::assert_eq("jsonObj.firstname", js_string!("boa")), - TestAction::assert_eq("jsonObj.lastname", js_string!("interpreter")), + TestAction::assert_eq("jsonObj.firstname", js_str!("boa")), + TestAction::assert_eq("jsonObj.lastname", js_str!("interpreter")), ]); } diff --git a/core/engine/src/builtins/map/mod.rs b/core/engine/src/builtins/map/mod.rs index 5d5ba03a99..c5c4edee46 100644 --- a/core/engine/src/builtins/map/mod.rs +++ b/core/engine/src/builtins/map/mod.rs @@ -18,10 +18,11 @@ use crate::{ object::{internal_methods::get_prototype_from_constructor, JsObject}, property::{Attribute, PropertyNameKind}, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, symbol::JsSymbol, Context, JsArgs, JsResult, JsString, JsValue, }; +use boa_macros::js_str; use boa_profiler::Profiler; use num_traits::Zero; @@ -63,7 +64,7 @@ impl IntrinsicObject for Map { Attribute::CONFIGURABLE, ) .property( - utf16!("entries"), + js_string!("entries"), entries_function.clone(), Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) @@ -148,7 +149,7 @@ impl BuiltInConstructor for Map { }; // 5. Let adder be ? Get(map, "set"). - let adder = map.get(utf16!("set"), context)?; + let adder = map.get(js_str!("set"), context)?; // 6. Return ? AddEntriesFromIterable(map, iterable, adder). add_entries_from_iterable(&map, iterable, &adder, context) diff --git a/core/engine/src/builtins/map/tests.rs b/core/engine/src/builtins/map/tests.rs index 89d2e5cb84..cbb2722791 100644 --- a/core/engine/src/builtins/map/tests.rs +++ b/core/engine/src/builtins/map/tests.rs @@ -1,4 +1,5 @@ -use crate::{js_string, run_test_actions, JsNativeErrorKind, JsValue, TestAction}; +use crate::{run_test_actions, JsNativeErrorKind, JsValue, TestAction}; +use boa_macros::js_str; use indoc::indoc; #[test] @@ -74,7 +75,7 @@ fn merge() { let merged2 = new Map([...second, ...third]); "#}), TestAction::assert_eq("merged1.size", 3), - TestAction::assert_eq("merged1.get('2')", js_string!("second two")), + TestAction::assert_eq("merged1.get('2')", js_str!("second two")), TestAction::assert_eq("merged2.size", 4), ]); } @@ -85,8 +86,8 @@ fn get() { TestAction::run(indoc! {r#" let map = new Map([["1", "one"], ["2", "two"]]); "#}), - TestAction::assert_eq("map.get('1')", js_string!("one")), - TestAction::assert_eq("map.get('2')", js_string!("two")), + TestAction::assert_eq("map.get('1')", js_str!("one")), + TestAction::assert_eq("map.get('2')", js_str!("two")), TestAction::assert_eq("map.get('3')", JsValue::undefined()), TestAction::assert_eq("map.get()", JsValue::undefined()), ]); @@ -98,7 +99,7 @@ fn set() { TestAction::run("let map = new Map();"), TestAction::assert("map.set(); map.has(undefined)"), TestAction::assert_eq("map.get()", JsValue::undefined()), - TestAction::assert_eq("map.set('1', 'one'); map.get('1')", js_string!("one")), + TestAction::assert_eq("map.set('1', 'one'); map.get('1')", js_str!("one")), TestAction::assert("map.set('2'); map.has('2')"), TestAction::assert_eq("map.get('2')", JsValue::undefined()), ]); @@ -148,7 +149,7 @@ fn keys() { let item2 = keysIterator.next(); let item3 = keysIterator.next(); "#}), - TestAction::assert_eq("item1.value", js_string!("0")), + TestAction::assert_eq("item1.value", js_str!("0")), TestAction::assert_eq("item2.value", 1), TestAction::assert("item3.done"), ]); @@ -187,8 +188,8 @@ fn values() { let item2 = valuesIterator.next(); let item3 = valuesIterator.next(); "#}), - TestAction::assert_eq("item1.value", js_string!("foo")), - TestAction::assert_eq("item2.value", js_string!("bar")), + TestAction::assert_eq("item1.value", js_str!("foo")), + TestAction::assert_eq("item2.value", js_str!("bar")), TestAction::assert("item3.done"), ]); } @@ -201,7 +202,7 @@ fn modify_key() { let map = new Map([[obj, "one"]]); obj.field = "Value"; "#}), - TestAction::assert_eq("map.get(obj)", js_string!("one")), + TestAction::assert_eq("map.get(obj)", js_str!("one")), ]); } diff --git a/core/engine/src/builtins/mod.rs b/core/engine/src/builtins/mod.rs index 0cbe765894..17e723ce5d 100644 --- a/core/engine/src/builtins/mod.rs +++ b/core/engine/src/builtins/mod.rs @@ -36,6 +36,7 @@ pub mod weak_set; mod builder; +use boa_macros::js_str; use builder::BuiltInBuilder; #[cfg(feature = "annex-b")] @@ -108,7 +109,6 @@ use crate::{ object::JsObject, property::{Attribute, PropertyDescriptor}, realm::Realm, - string::utf16, Context, JsResult, JsString, JsValue, }; @@ -307,7 +307,7 @@ pub(crate) fn set_default_global_bindings(context: &mut Context) -> JsResult<()> let global_object = context.global_object(); global_object.define_property_or_throw( - utf16!("globalThis"), + js_str!("globalThis"), PropertyDescriptor::builder() .value(context.realm().global_this().clone()) .writable(true) @@ -320,17 +320,17 @@ pub(crate) fn set_default_global_bindings(context: &mut Context) -> JsResult<()> .enumerable(false) .configurable(false); global_object.define_property_or_throw( - utf16!("Infinity"), + js_str!("Infinity"), restricted.clone().value(f64::INFINITY), context, )?; global_object.define_property_or_throw( - utf16!("NaN"), + js_str!("NaN"), restricted.clone().value(f64::NAN), context, )?; global_object.define_property_or_throw( - utf16!("undefined"), + js_str!("undefined"), restricted.value(JsValue::undefined()), context, )?; diff --git a/core/engine/src/builtins/number/globals.rs b/core/engine/src/builtins/number/globals.rs index 46c16a9323..1900fa850c 100644 --- a/core/engine/src/builtins/number/globals.rs +++ b/core/engine/src/builtins/number/globals.rs @@ -1,15 +1,13 @@ -use boa_macros::utf16; - use crate::{ builtins::{string::is_trimmable_whitespace, BuiltInBuilder, BuiltInObject, IntrinsicObject}, context::intrinsics::Intrinsics, object::JsObject, realm::Realm, - string::{common::StaticJsStrings, Utf16Trim}, - Context, JsArgs, JsResult, JsString, JsValue, + string::common::StaticJsStrings, + Context, JsArgs, JsResult, JsStr, JsString, JsValue, }; -use num_traits::Num; +use boa_macros::js_str; /// Builtin javascript 'isFinite(number)' function. /// @@ -95,6 +93,55 @@ impl BuiltInObject for IsNaN { const NAME: JsString = StaticJsStrings::IS_NAN; } +fn from_js_str_radix(src: JsStr<'_>, radix: u8) -> Option { + /// Determines if a string of text of that length of that radix could be guaranteed to be + /// stored in the given type T. + /// Note that if the radix is known to the compiler, it is just the check of digits.len that + /// is done at runtime. + fn can_not_overflow(radix: u8, digits_len: usize) -> bool { + usize::from(radix) <= 16 && digits_len <= std::mem::size_of::() * 2 + } + + const fn to_digit(input: u8, radix: u8) -> Option { + // If not a digit, a number greater than radix will be created. + let mut digit = input.wrapping_sub(b'0'); + if radix > 10 { + debug_assert!(radix <= 36, "to_digit: radix is too high (maximum 36)"); + if digit < 10 { + return Some(digit); + } + // Force the 6th bit to be set to ensure ascii is lower case. + digit = (input | 0b10_0000).wrapping_sub(b'a').saturating_add(10); + } + // FIXME: once then_some is const fn, use it here + if digit < radix { + Some(digit) + } else { + None + } + } + + let src = src + .iter() + .map(|x| u8::try_from(x).expect("should be ascii string")); + + let result = if can_not_overflow(radix, src.len()) { + let mut result = 0; + for c in src { + result = result * u64::from(radix) + u64::from(to_digit(c, radix)?); + } + result as f64 + } else { + let mut result = 0f64; + for c in src { + result = result * f64::from(radix) + f64::from(to_digit(c, radix)?); + } + result + }; + + Some(result) +} + /// Builtin javascript 'parseInt(str, radix)' function. /// /// Parses the given string as an integer using the given radix as a base. @@ -110,107 +157,108 @@ impl BuiltInObject for IsNaN { /// [spec]: https://tc39.es/ecma262/#sec-parseint-string-radix /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt pub(crate) fn parse_int(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - if let (Some(val), radix) = (args.first(), args.get_or_undefined(1)) { - // 1. Let inputString be ? ToString(string). - let input_string = val.to_string(context)?; - - // 2. Let S be ! TrimString(inputString, start). - let mut var_s = input_string.trim_start(); - - // 3. Let sign be 1. - // 4. If S is not empty and the first code unit of S is the code unit 0x002D (HYPHEN-MINUS), - // set sign to -1. - let sign = if !var_s.is_empty() && var_s.starts_with(utf16!("-")) { - -1 - } else { - 1 - }; + let (Some(val), radix) = (args.first(), args.get_or_undefined(1)) else { + // Not enough arguments to parseInt. + return Ok(JsValue::nan()); + }; - // 5. If S is not empty and the first code unit of S is the code unit 0x002B (PLUS SIGN) or - // the code unit 0x002D (HYPHEN-MINUS), remove the first code unit from S. - if !var_s.is_empty() && (var_s.starts_with(utf16!("+")) || var_s.starts_with(utf16!("-"))) { - var_s = &var_s[1..]; - } + // 1. Let inputString be ? ToString(string). + let input_string = val.to_string(context)?; - // 6. Let R be ℝ(? ToInt32(radix)). - let mut var_r = radix.to_i32(context)?; + // 2. Let S be ! TrimString(inputString, start). + let mut s = input_string.trim_start(); + // let mut - // 7. Let stripPrefix be true. - let mut strip_prefix = true; + // 3. Let sign be 1. + // 4. If S is not empty and the first code unit of S is the code unit 0x002D (HYPHEN-MINUS), + // set sign to -1. + let sign = if !s.is_empty() && s.starts_with(js_str!("-")) { + -1 + } else { + 1 + }; - // 8. If R ≠ 0, then - #[allow(clippy::if_not_else)] - if var_r != 0 { - // a. If R < 2 or R > 36, return NaN. - if !(2..=36).contains(&var_r) { - return Ok(JsValue::nan()); - } + // 5. If S is not empty and the first code unit of S is the code unit 0x002B (PLUS SIGN) or + // the code unit 0x002D (HYPHEN-MINUS), remove the first code unit from S. + if !s.is_empty() && (s.starts_with(js_str!("+")) || s.starts_with(js_str!("-"))) { + s = s.get(1..).expect("already checked that it's not empty"); + } - // b. If R ≠ 16, set stripPrefix to false. - if var_r != 16 { - strip_prefix = false; - } - } else { - // 9. Else, - // a. Set R to 10. - var_r = 10; - } + // 6. Let R be ℝ(? ToInt32(radix)). + let r = radix.to_i32(context)?; - // 10. If stripPrefix is true, then - // a. If the length of S is at least 2 and the first two code units of S are either "0x" or "0X", then - // i. Remove the first two code units from S. - // ii. Set R to 16. - if strip_prefix - && var_s.len() >= 2 - && (var_s.starts_with(utf16!("0x")) || var_s.starts_with(utf16!("0X"))) - { - var_s = &var_s[2..]; + // 7. Let stripPrefix be true. + let mut strip_prefix = true; - var_r = 16; + // 8. If R ≠ 0, then + #[allow(clippy::if_not_else)] + let mut r = if r != 0 { + // a. If R < 2 or R > 36, return NaN. + if !(2..=36).contains(&r) { + return Ok(JsValue::nan()); } - // 11. If S contains a code unit that is not a radix-R digit, let end be the index within S of the - // first such code unit; otherwise, let end be the length of S. - let end = char::decode_utf16(var_s.iter().copied()) - .position(|code| !code.is_ok_and(|c| c.is_digit(var_r as u32))) - .unwrap_or(var_s.len()); + // b. If R ≠ 16, set stripPrefix to false. + if r != 16 { + strip_prefix = false; + } + r as u8 + } else { + // 9. Else, + // a. Set R to 10. + 10 + }; + + // 10. If stripPrefix is true, then + // a. If the length of S is at least 2 and the first two code units of S are either "0x" or "0X", then + // i. Remove the first two code units from S. + // ii. Set R to 16. + if strip_prefix + && s.len() >= 2 + && (s.starts_with(js_str!("0x")) || s.starts_with(js_str!("0X"))) + { + s = s + .get(2..) + .expect("already checked that it contains at least two chars"); + + r = 16; + } - // 12. Let Z be the substring of S from 0 to end. - let var_z = String::from_utf16_lossy(&var_s[..end]); + // 11. If S contains a code unit that is not a radix-R digit, let end be the index within S of the + // first such code unit; otherwise, let end be the length of S. + let end = char::decode_utf16(s.iter()) + .position(|code| !code.is_ok_and(|c| c.is_digit(u32::from(r)))) + .unwrap_or(s.len()); - // 13. If Z is empty, return NaN. - if var_z.is_empty() { - return Ok(JsValue::nan()); - } + // 12. Let Z be the substring of S from 0 to end. + let z = s.get(..end).expect("should be in range"); - // 14. Let mathInt be the integer value that is represented by Z in radix-R notation, using the - // letters A-Z and a-z for digits with values 10 through 35. (However, if R is 10 and Z contains - // more than 20 significant digits, every significant digit after the 20th may be replaced by a - // 0 digit, at the option of the implementation; and if R is not 2, 4, 8, 10, 16, or 32, then - // mathInt may be an implementation-approximated value representing the integer value that is - // represented by Z in radix-R notation.) - let math_int = u64::from_str_radix(&var_z, var_r as u32).map_or_else( - |_| f64::from_str_radix(&var_z, var_r as u32).expect("invalid_float_conversion"), - |i| i as f64, - ); - - // 15. If mathInt = 0, then - // a. If sign = -1, return -0𝔽. - // b. Return +0𝔽. - if math_int == 0_f64 { - if sign == -1 { - return Ok(JsValue::new(-0_f64)); - } + // 13. If Z is empty, return NaN. + if z.is_empty() { + return Ok(JsValue::nan()); + } - return Ok(JsValue::new(0_f64)); + // 14. Let mathInt be the integer value that is represented by Z in radix-R notation, using the + // letters A-Z and a-z for digits with values 10 through 35. (However, if R is 10 and Z contains + // more than 20 significant digits, every significant digit after the 20th may be replaced by a + // 0 digit, at the option of the implementation; and if R is not 2, 4, 8, 10, 16, or 32, then + // mathInt may be an implementation-approximated value representing the integer value that is + // represented by Z in radix-R notation.) + let math_int = from_js_str_radix(z, r).expect("Already checked"); + + // 15. If mathInt = 0, then + // a. If sign = -1, return -0𝔽. + // b. Return +0𝔽. + if math_int == 0_f64 { + if sign == -1 { + return Ok(JsValue::new(-0_f64)); } - // 16. Return 𝔽(sign × mathInt). - Ok(JsValue::new(f64::from(sign) * math_int)) - } else { - // Not enough arguments to parseInt. - Ok(JsValue::nan()) + return Ok(JsValue::new(0_f64)); } + + // 16. Return 𝔽(sign × mathInt). + Ok(JsValue::new(f64::from(sign) * math_int)) } pub(crate) struct ParseInt; diff --git a/core/engine/src/builtins/number/mod.rs b/core/engine/src/builtins/number/mod.rs index 592f157b52..025c25acf6 100644 --- a/core/engine/src/builtins/number/mod.rs +++ b/core/engine/src/builtins/number/mod.rs @@ -25,6 +25,7 @@ use crate::{ value::{AbstractRelation, IntegerOrInfinity, JsValue}, Context, JsArgs, JsResult, JsString, }; +use boa_macros::js_str; use boa_profiler::Profiler; use num_traits::float::FloatCore; @@ -690,13 +691,13 @@ impl Number { } if x == -0. { - return Ok(JsValue::new(js_string!("0"))); + return Ok(JsValue::new(js_str!("0"))); } else if x.is_nan() { - return Ok(JsValue::new(js_string!("NaN"))); + return Ok(JsValue::new(js_str!("NaN"))); } else if x.is_infinite() && x.is_sign_positive() { - return Ok(JsValue::new(js_string!("Infinity"))); + return Ok(JsValue::new(js_str!("Infinity"))); } else if x.is_infinite() && x.is_sign_negative() { - return Ok(JsValue::new(js_string!("-Infinity"))); + return Ok(JsValue::new(js_str!("-Infinity"))); } // This is a Optimization from the v8 source code to print values that can fit in a single character diff --git a/core/engine/src/builtins/number/tests.rs b/core/engine/src/builtins/number/tests.rs index 290c4fb9a2..7b2e1170f2 100644 --- a/core/engine/src/builtins/number/tests.rs +++ b/core/engine/src/builtins/number/tests.rs @@ -1,11 +1,12 @@ +use boa_macros::js_str; + use crate::{ - builtins::Number, js_string, run_test_actions, value::AbstractRelation, JsNativeErrorKind, - TestAction, + builtins::Number, run_test_actions, value::AbstractRelation, JsNativeErrorKind, TestAction, }; #[test] fn integer_number_primitive_to_number_object() { - run_test_actions([TestAction::assert_eq("(100).toString()", js_string!("100"))]); + run_test_actions([TestAction::assert_eq("(100).toString()", js_str!("100"))]); } #[test] @@ -25,136 +26,133 @@ fn call_number() { #[test] fn to_exponential() { run_test_actions([ - TestAction::assert_eq("Number().toExponential()", js_string!("0e+0")), - TestAction::assert_eq("Number(5).toExponential()", js_string!("5e+0")), - TestAction::assert_eq("Number(1.234).toExponential()", js_string!("1.234e+0")), - TestAction::assert_eq("Number(1234).toExponential()", js_string!("1.234e+3")), + TestAction::assert_eq("Number().toExponential()", js_str!("0e+0")), + TestAction::assert_eq("Number(5).toExponential()", js_str!("5e+0")), + TestAction::assert_eq("Number(1.234).toExponential()", js_str!("1.234e+0")), + TestAction::assert_eq("Number(1234).toExponential()", js_str!("1.234e+3")), TestAction::assert_eq( "Number('I am also not a number').toExponential()", - js_string!("NaN"), + js_str!("NaN"), ), - TestAction::assert_eq("Number('1.23e+2').toExponential()", js_string!("1.23e+2")), + TestAction::assert_eq("Number('1.23e+2').toExponential()", js_str!("1.23e+2")), ]); } #[test] fn to_fixed() { run_test_actions([ - TestAction::assert_eq("Number().toFixed()", js_string!("0")), - TestAction::assert_eq("Number('3.456e+4').toFixed()", js_string!("34560")), - TestAction::assert_eq("Number('3.456e-4').toFixed()", js_string!("0")), - TestAction::assert_eq("Number(5).toFixed()", js_string!("5")), - TestAction::assert_eq( - "Number('I am also not a number').toFixed()", - js_string!("NaN"), - ), - TestAction::assert_eq("(1.35).toFixed(1)", js_string!("1.4")), + TestAction::assert_eq("Number().toFixed()", js_str!("0")), + TestAction::assert_eq("Number('3.456e+4').toFixed()", js_str!("34560")), + TestAction::assert_eq("Number('3.456e-4').toFixed()", js_str!("0")), + TestAction::assert_eq("Number(5).toFixed()", js_str!("5")), + TestAction::assert_eq("Number('I am also not a number').toFixed()", js_str!("NaN")), + TestAction::assert_eq("(1.35).toFixed(1)", js_str!("1.4")), // Test cases from https://source.chromium.org/chromium/chromium/src/+/main:v8/test/mjsunit/number-tostring-func.js;l=157-240;drc=aa3518a0f37245ebe8f062dce97ee492e2a41652 - TestAction::assert_eq("(NaN).toFixed(2)", js_string!("NaN")), - TestAction::assert_eq("(1/0).toFixed(2)", js_string!("Infinity")), - TestAction::assert_eq("(-1/0).toFixed(2)", js_string!("-Infinity")), + TestAction::assert_eq("(NaN).toFixed(2)", js_str!("NaN")), + TestAction::assert_eq("(1/0).toFixed(2)", js_str!("Infinity")), + TestAction::assert_eq("(-1/0).toFixed(2)", js_str!("-Infinity")), TestAction::assert_eq( "(1111111111111111111111).toFixed(8)", - js_string!("1.1111111111111111e+21"), - ), - TestAction::assert_eq("(0.1).toFixed(1)", js_string!("0.1")), - TestAction::assert_eq("(0.1).toFixed(2)", js_string!("0.10")), - TestAction::assert_eq("(0.1).toFixed(3)", js_string!("0.100")), - TestAction::assert_eq("(0.01).toFixed(2)", js_string!("0.01")), - TestAction::assert_eq("(0.01).toFixed(3)", js_string!("0.010")), - TestAction::assert_eq("(0.01).toFixed(4)", js_string!("0.0100")), - TestAction::assert_eq("(0.001).toFixed(2)", js_string!("0.00")), - TestAction::assert_eq("(0.001).toFixed(3)", js_string!("0.001")), - TestAction::assert_eq("(0.001).toFixed(4)", js_string!("0.0010")), - TestAction::assert_eq("(1).toFixed(4)", js_string!("1.0000")), - TestAction::assert_eq("(1).toFixed(1)", js_string!("1.0")), - TestAction::assert_eq("(1).toFixed(0)", js_string!("1")), - TestAction::assert_eq("(12).toFixed(0)", js_string!("12")), - TestAction::assert_eq("(1.1).toFixed(0)", js_string!("1")), - TestAction::assert_eq("(12.1).toFixed(0)", js_string!("12")), - TestAction::assert_eq("(1.12).toFixed(0)", js_string!("1")), - TestAction::assert_eq("(12.12).toFixed(0)", js_string!("12")), - TestAction::assert_eq("(0.0000006).toFixed(7)", js_string!("0.0000006")), - TestAction::assert_eq("(0.00000006).toFixed(8)", js_string!("0.00000006")), - TestAction::assert_eq("(0.00000006).toFixed(9)", js_string!("0.000000060")), - TestAction::assert_eq("(0.00000006).toFixed(10)", js_string!("0.0000000600")), - TestAction::assert_eq("(0).toFixed(0)", js_string!("0")), - TestAction::assert_eq("(0).toFixed(1)", js_string!("0.0")), - TestAction::assert_eq("(0).toFixed(2)", js_string!("0.00")), + js_str!("1.1111111111111111e+21"), + ), + TestAction::assert_eq("(0.1).toFixed(1)", js_str!("0.1")), + TestAction::assert_eq("(0.1).toFixed(2)", js_str!("0.10")), + TestAction::assert_eq("(0.1).toFixed(3)", js_str!("0.100")), + TestAction::assert_eq("(0.01).toFixed(2)", js_str!("0.01")), + TestAction::assert_eq("(0.01).toFixed(3)", js_str!("0.010")), + TestAction::assert_eq("(0.01).toFixed(4)", js_str!("0.0100")), + TestAction::assert_eq("(0.001).toFixed(2)", js_str!("0.00")), + TestAction::assert_eq("(0.001).toFixed(3)", js_str!("0.001")), + TestAction::assert_eq("(0.001).toFixed(4)", js_str!("0.0010")), + TestAction::assert_eq("(1).toFixed(4)", js_str!("1.0000")), + TestAction::assert_eq("(1).toFixed(1)", js_str!("1.0")), + TestAction::assert_eq("(1).toFixed(0)", js_str!("1")), + TestAction::assert_eq("(12).toFixed(0)", js_str!("12")), + TestAction::assert_eq("(1.1).toFixed(0)", js_str!("1")), + TestAction::assert_eq("(12.1).toFixed(0)", js_str!("12")), + TestAction::assert_eq("(1.12).toFixed(0)", js_str!("1")), + TestAction::assert_eq("(12.12).toFixed(0)", js_str!("12")), + TestAction::assert_eq("(0.0000006).toFixed(7)", js_str!("0.0000006")), + TestAction::assert_eq("(0.00000006).toFixed(8)", js_str!("0.00000006")), + TestAction::assert_eq("(0.00000006).toFixed(9)", js_str!("0.000000060")), + TestAction::assert_eq("(0.00000006).toFixed(10)", js_str!("0.0000000600")), + TestAction::assert_eq("(0).toFixed(0)", js_str!("0")), + TestAction::assert_eq("(0).toFixed(1)", js_str!("0.0")), + TestAction::assert_eq("(0).toFixed(2)", js_str!("0.00")), TestAction::assert_eq( "(-1111111111111111111111).toFixed(8)", - js_string!("-1.1111111111111111e+21"), - ), - TestAction::assert_eq("(-0.1).toFixed(1)", js_string!("-0.1")), - TestAction::assert_eq("(-0.1).toFixed(2)", js_string!("-0.10")), - TestAction::assert_eq("(-0.1).toFixed(3)", js_string!("-0.100")), - TestAction::assert_eq("(-0.01).toFixed(2)", js_string!("-0.01")), - TestAction::assert_eq("(-0.01).toFixed(3)", js_string!("-0.010")), - TestAction::assert_eq("(-0.01).toFixed(4)", js_string!("-0.0100")), - TestAction::assert_eq("(-0.001).toFixed(2)", js_string!("-0.00")), - TestAction::assert_eq("(-0.001).toFixed(3)", js_string!("-0.001")), - TestAction::assert_eq("(-0.001).toFixed(4)", js_string!("-0.0010")), - TestAction::assert_eq("(-1).toFixed(4)", js_string!("-1.0000")), - TestAction::assert_eq("(-1).toFixed(1)", js_string!("-1.0")), - TestAction::assert_eq("(-1).toFixed(0)", js_string!("-1")), - TestAction::assert_eq("(-1.1).toFixed(0)", js_string!("-1")), - TestAction::assert_eq("(-12.1).toFixed(0)", js_string!("-12")), - TestAction::assert_eq("(-1.12).toFixed(0)", js_string!("-1")), - TestAction::assert_eq("(-12.12).toFixed(0)", js_string!("-12")), - TestAction::assert_eq("(-0.0000006).toFixed(7)", js_string!("-0.0000006")), - TestAction::assert_eq("(-0.00000006).toFixed(8)", js_string!("-0.00000006")), - TestAction::assert_eq("(-0.00000006).toFixed(9)", js_string!("-0.000000060")), - TestAction::assert_eq("(-0.00000006).toFixed(10)", js_string!("-0.0000000600")), - TestAction::assert_eq("(-0).toFixed(0)", js_string!("0")), - TestAction::assert_eq("(-0).toFixed(1)", js_string!("0.0")), - TestAction::assert_eq("(-0).toFixed(2)", js_string!("0.00")), - TestAction::assert_eq("(0.00001).toFixed(5)", js_string!("0.00001")), + js_str!("-1.1111111111111111e+21"), + ), + TestAction::assert_eq("(-0.1).toFixed(1)", js_str!("-0.1")), + TestAction::assert_eq("(-0.1).toFixed(2)", js_str!("-0.10")), + TestAction::assert_eq("(-0.1).toFixed(3)", js_str!("-0.100")), + TestAction::assert_eq("(-0.01).toFixed(2)", js_str!("-0.01")), + TestAction::assert_eq("(-0.01).toFixed(3)", js_str!("-0.010")), + TestAction::assert_eq("(-0.01).toFixed(4)", js_str!("-0.0100")), + TestAction::assert_eq("(-0.001).toFixed(2)", js_str!("-0.00")), + TestAction::assert_eq("(-0.001).toFixed(3)", js_str!("-0.001")), + TestAction::assert_eq("(-0.001).toFixed(4)", js_str!("-0.0010")), + TestAction::assert_eq("(-1).toFixed(4)", js_str!("-1.0000")), + TestAction::assert_eq("(-1).toFixed(1)", js_str!("-1.0")), + TestAction::assert_eq("(-1).toFixed(0)", js_str!("-1")), + TestAction::assert_eq("(-1.1).toFixed(0)", js_str!("-1")), + TestAction::assert_eq("(-12.1).toFixed(0)", js_str!("-12")), + TestAction::assert_eq("(-1.12).toFixed(0)", js_str!("-1")), + TestAction::assert_eq("(-12.12).toFixed(0)", js_str!("-12")), + TestAction::assert_eq("(-0.0000006).toFixed(7)", js_str!("-0.0000006")), + TestAction::assert_eq("(-0.00000006).toFixed(8)", js_str!("-0.00000006")), + TestAction::assert_eq("(-0.00000006).toFixed(9)", js_str!("-0.000000060")), + TestAction::assert_eq("(-0.00000006).toFixed(10)", js_str!("-0.0000000600")), + TestAction::assert_eq("(-0).toFixed(0)", js_str!("0")), + TestAction::assert_eq("(-0).toFixed(1)", js_str!("0.0")), + TestAction::assert_eq("(-0).toFixed(2)", js_str!("0.00")), + TestAction::assert_eq("(0.00001).toFixed(5)", js_str!("0.00001")), TestAction::assert_eq( "(0.0000000000000000001).toFixed(20)", - js_string!("0.00000000000000000010"), + js_str!("0.00000000000000000010"), ), - TestAction::assert_eq("(0.00001).toFixed(17)", js_string!("0.00001000000000000")), - TestAction::assert_eq("(1).toFixed(17)", js_string!("1.00000000000000000")), + TestAction::assert_eq("(0.00001).toFixed(17)", js_str!("0.00001000000000000")), + TestAction::assert_eq("(1).toFixed(17)", js_str!("1.00000000000000000")), TestAction::assert_eq( "(100000000000000128).toFixed(1)", - js_string!("100000000000000128.0"), + js_str!("100000000000000128.0"), ), TestAction::assert_eq( "(10000000000000128).toFixed(2)", - js_string!("10000000000000128.00"), + js_str!("10000000000000128.00"), ), TestAction::assert_eq( "(10000000000000128).toFixed(20)", - js_string!("10000000000000128.00000000000000000000"), + js_str!("10000000000000128.00000000000000000000"), ), - TestAction::assert_eq("(-42).toFixed(3)", js_string!("-42.000")), + TestAction::assert_eq("(-42).toFixed(3)", js_str!("-42.000")), TestAction::assert_eq( "(-0.0000000000000000001).toFixed(20)", - js_string!("-0.00000000000000000010"), + js_str!("-0.00000000000000000010"), ), TestAction::assert_eq( "(0.123123123123123).toFixed(20)", - js_string!("0.12312312312312299889"), + js_str!("0.12312312312312299889"), ), TestAction::assert_eq( "(-1000000000000000128).toFixed()", - js_string!("-1000000000000000128"), + js_str!("-1000000000000000128"), ), - TestAction::assert_eq("(0).toFixed()", js_string!("0")), + TestAction::assert_eq("(0).toFixed()", js_str!("0")), TestAction::assert_eq( "(1000000000000000128).toFixed()", - js_string!("1000000000000000128"), + js_str!("1000000000000000128"), ), - TestAction::assert_eq("(1000).toFixed()", js_string!("1000")), - TestAction::assert_eq("(0.00001).toFixed()", js_string!("0")), + TestAction::assert_eq("(1000).toFixed()", js_str!("1000")), + TestAction::assert_eq("(0.00001).toFixed()", js_str!("0")), // Test that we round up even when the last digit generated is even. // dtoa does not do this in its original form. - TestAction::assert_eq("(0.5).toFixed(0)", js_string!("1")), - TestAction::assert_eq("(-0.5).toFixed(0)", js_string!("-1")), - TestAction::assert_eq("(1.25).toFixed(1)", js_string!("1.3")), + TestAction::assert_eq("(0.5).toFixed(0)", js_str!("1")), + TestAction::assert_eq("(-0.5).toFixed(0)", js_str!("-1")), + TestAction::assert_eq("(1.25).toFixed(1)", js_str!("1.3")), // This is bizare, but Spidermonkey and KJS behave the same. - TestAction::assert_eq("(234.2040).toFixed(4)", js_string!("234.2040")), - TestAction::assert_eq("(234.2040506).toFixed(4)", js_string!("234.2041")), + TestAction::assert_eq("(234.2040).toFixed(4)", js_str!("234.2040")), + TestAction::assert_eq("(234.2040506).toFixed(4)", js_str!("234.2041")), ]); } @@ -162,8 +160,8 @@ fn to_fixed() { #[test] fn issue_2609() { run_test_actions([ - TestAction::assert_eq("(1.25).toFixed(1)", js_string!("1.3")), - TestAction::assert_eq("(1.35).toFixed(1)", js_string!("1.4")), + TestAction::assert_eq("(1.25).toFixed(1)", js_str!("1.3")), + TestAction::assert_eq("(1.35).toFixed(1)", js_str!("1.4")), ]); } @@ -172,10 +170,10 @@ fn to_locale_string() { // TODO: We don't actually do any locale checking here // To honor the spec we should print numbers according to user locale. run_test_actions([ - TestAction::assert_eq("Number().toLocaleString()", js_string!("0")), - TestAction::assert_eq("Number(5).toLocaleString()", js_string!("5")), - TestAction::assert_eq("Number('345600').toLocaleString()", js_string!("345600")), - TestAction::assert_eq("Number(-25).toLocaleString()", js_string!("-25")), + TestAction::assert_eq("Number().toLocaleString()", js_str!("0")), + TestAction::assert_eq("Number(5).toLocaleString()", js_str!("5")), + TestAction::assert_eq("Number('345600').toLocaleString()", js_str!("345600")), + TestAction::assert_eq("Number(-25).toLocaleString()", js_str!("-25")), ]); } @@ -183,21 +181,21 @@ fn to_locale_string() { fn to_precision() { const ERROR: &str = "precision must be an integer at least 1 and no greater than 100"; run_test_actions([ - TestAction::assert_eq("(1/0).toPrecision(3)", js_string!("Infinity")), - TestAction::assert_eq("Number().toPrecision()", js_string!("0")), - TestAction::assert_eq("Number().toPrecision(undefined)", js_string!("0")), - TestAction::assert_eq("(123456789).toPrecision(1)", js_string!("1e+8")), - TestAction::assert_eq("(123456789).toPrecision(4)", js_string!("1.235e+8")), - TestAction::assert_eq("(123456789).toPrecision(9)", js_string!("123456789")), - TestAction::assert_eq("(-123456789).toPrecision(4)", js_string!("-1.235e+8")), + TestAction::assert_eq("(1/0).toPrecision(3)", js_str!("Infinity")), + TestAction::assert_eq("Number().toPrecision()", js_str!("0")), + TestAction::assert_eq("Number().toPrecision(undefined)", js_str!("0")), + TestAction::assert_eq("(123456789).toPrecision(1)", js_str!("1e+8")), + TestAction::assert_eq("(123456789).toPrecision(4)", js_str!("1.235e+8")), + TestAction::assert_eq("(123456789).toPrecision(9)", js_str!("123456789")), + TestAction::assert_eq("(-123456789).toPrecision(4)", js_str!("-1.235e+8")), TestAction::assert_eq( "(123456789).toPrecision(50)", - js_string!("123456789.00000000000000000000000000000000000000000"), + js_str!("123456789.00000000000000000000000000000000000000000"), ), - TestAction::assert_eq("(0.1).toPrecision(4)", js_string!("0.1000")), + TestAction::assert_eq("(0.1).toPrecision(4)", js_str!("0.1000")), TestAction::assert_eq( "(1/3).toPrecision(60)", - js_string!("0.333333333333333314829616256247390992939472198486328125000000"), + js_str!("0.333333333333333314829616256247390992939472198486328125000000"), ), TestAction::assert_native_error("(1).toPrecision(101)", JsNativeErrorKind::Range, ERROR), TestAction::assert_native_error("(1).toPrecision(0)", JsNativeErrorKind::Range, ERROR), @@ -209,132 +207,126 @@ fn to_precision() { #[test] fn to_string() { run_test_actions([ - TestAction::assert_eq("Number(NaN).toString()", js_string!("NaN")), - TestAction::assert_eq("Number(1/0).toString()", js_string!("Infinity")), - TestAction::assert_eq("Number(-1/0).toString()", js_string!("-Infinity")), - TestAction::assert_eq("Number(0).toString()", js_string!("0")), - TestAction::assert_eq("Number(9).toString()", js_string!("9")), - TestAction::assert_eq("Number(90).toString()", js_string!("90")), - TestAction::assert_eq("Number(90.12).toString()", js_string!("90.12")), - TestAction::assert_eq("Number(0.1).toString()", js_string!("0.1")), - TestAction::assert_eq("Number(0.01).toString()", js_string!("0.01")), - TestAction::assert_eq("Number(0.0123).toString()", js_string!("0.0123")), - TestAction::assert_eq("Number(0.00001).toString()", js_string!("0.00001")), - TestAction::assert_eq("Number(0.000001).toString()", js_string!("0.000001")), - TestAction::assert_eq("Number(NaN).toString(16)", js_string!("NaN")), - TestAction::assert_eq("Number(1/0).toString(16)", js_string!("Infinity")), - TestAction::assert_eq("Number(-1/0).toString(16)", js_string!("-Infinity")), - TestAction::assert_eq("Number(0).toString(16)", js_string!("0")), - TestAction::assert_eq("Number(9).toString(16)", js_string!("9")), - TestAction::assert_eq("Number(90).toString(16)", js_string!("5a")), - TestAction::assert_eq("Number(90.12).toString(16)", js_string!("5a.1eb851eb852")), - TestAction::assert_eq("Number(0.1).toString(16)", js_string!("0.1999999999999a")), - TestAction::assert_eq("Number(0.01).toString(16)", js_string!("0.028f5c28f5c28f6")), - TestAction::assert_eq( - "Number(0.0123).toString(16)", - js_string!("0.032617c1bda511a"), - ), + TestAction::assert_eq("Number(NaN).toString()", js_str!("NaN")), + TestAction::assert_eq("Number(1/0).toString()", js_str!("Infinity")), + TestAction::assert_eq("Number(-1/0).toString()", js_str!("-Infinity")), + TestAction::assert_eq("Number(0).toString()", js_str!("0")), + TestAction::assert_eq("Number(9).toString()", js_str!("9")), + TestAction::assert_eq("Number(90).toString()", js_str!("90")), + TestAction::assert_eq("Number(90.12).toString()", js_str!("90.12")), + TestAction::assert_eq("Number(0.1).toString()", js_str!("0.1")), + TestAction::assert_eq("Number(0.01).toString()", js_str!("0.01")), + TestAction::assert_eq("Number(0.0123).toString()", js_str!("0.0123")), + TestAction::assert_eq("Number(0.00001).toString()", js_str!("0.00001")), + TestAction::assert_eq("Number(0.000001).toString()", js_str!("0.000001")), + TestAction::assert_eq("Number(NaN).toString(16)", js_str!("NaN")), + TestAction::assert_eq("Number(1/0).toString(16)", js_str!("Infinity")), + TestAction::assert_eq("Number(-1/0).toString(16)", js_str!("-Infinity")), + TestAction::assert_eq("Number(0).toString(16)", js_str!("0")), + TestAction::assert_eq("Number(9).toString(16)", js_str!("9")), + TestAction::assert_eq("Number(90).toString(16)", js_str!("5a")), + TestAction::assert_eq("Number(90.12).toString(16)", js_str!("5a.1eb851eb852")), + TestAction::assert_eq("Number(0.1).toString(16)", js_str!("0.1999999999999a")), + TestAction::assert_eq("Number(0.01).toString(16)", js_str!("0.028f5c28f5c28f6")), + TestAction::assert_eq("Number(0.0123).toString(16)", js_str!("0.032617c1bda511a")), TestAction::assert_eq( "Number(111111111111111111111).toString(16)", - js_string!("605f9f6dd18bc8000"), + js_str!("605f9f6dd18bc8000"), ), TestAction::assert_eq( "Number(1111111111111111111111).toString(16)", - js_string!("3c3bc3a4a2f75c0000"), + js_str!("3c3bc3a4a2f75c0000"), ), TestAction::assert_eq( "Number(11111111111111111111111).toString(16)", - js_string!("25a55a46e5da9a00000"), + js_str!("25a55a46e5da9a00000"), ), TestAction::assert_eq( "Number(0.00001).toString(16)", - js_string!("0.0000a7c5ac471b4788"), + js_str!("0.0000a7c5ac471b4788"), ), TestAction::assert_eq( "Number(0.000001).toString(16)", - js_string!("0.000010c6f7a0b5ed8d"), + js_str!("0.000010c6f7a0b5ed8d"), ), TestAction::assert_eq( "Number(0.0000001).toString(16)", - js_string!("0.000001ad7f29abcaf48"), + js_str!("0.000001ad7f29abcaf48"), ), TestAction::assert_eq( "Number(0.00000012).toString(16)", - js_string!("0.000002036565348d256"), + js_str!("0.000002036565348d256"), ), TestAction::assert_eq( "Number(0.000000123).toString(16)", - js_string!("0.0000021047ee22aa466"), + js_str!("0.0000021047ee22aa466"), ), TestAction::assert_eq( "Number(0.00000001).toString(16)", - js_string!("0.0000002af31dc4611874"), + js_str!("0.0000002af31dc4611874"), ), TestAction::assert_eq( "Number(0.000000012).toString(16)", - js_string!("0.000000338a23b87483be"), + js_str!("0.000000338a23b87483be"), ), TestAction::assert_eq( "Number(0.0000000123).toString(16)", - js_string!("0.00000034d3fe36aaa0a2"), + js_str!("0.00000034d3fe36aaa0a2"), ), - TestAction::assert_eq("Number(-0).toString(16)", js_string!("0")), - TestAction::assert_eq("Number(-9).toString(16)", js_string!("-9")), + TestAction::assert_eq("Number(-0).toString(16)", js_str!("0")), + TestAction::assert_eq("Number(-9).toString(16)", js_str!("-9")), // - TestAction::assert_eq("Number(-90).toString(16)", js_string!("-5a")), - TestAction::assert_eq("Number(-90.12).toString(16)", js_string!("-5a.1eb851eb852")), - TestAction::assert_eq("Number(-0.1).toString(16)", js_string!("-0.1999999999999a")), - TestAction::assert_eq( - "Number(-0.01).toString(16)", - js_string!("-0.028f5c28f5c28f6"), - ), + TestAction::assert_eq("Number(-90).toString(16)", js_str!("-5a")), + TestAction::assert_eq("Number(-90.12).toString(16)", js_str!("-5a.1eb851eb852")), + TestAction::assert_eq("Number(-0.1).toString(16)", js_str!("-0.1999999999999a")), + TestAction::assert_eq("Number(-0.01).toString(16)", js_str!("-0.028f5c28f5c28f6")), TestAction::assert_eq( "Number(-0.0123).toString(16)", - js_string!("-0.032617c1bda511a"), + js_str!("-0.032617c1bda511a"), ), TestAction::assert_eq( "Number(-111111111111111111111).toString(16)", - js_string!("-605f9f6dd18bc8000"), + js_str!("-605f9f6dd18bc8000"), ), TestAction::assert_eq( "Number(-1111111111111111111111).toString(16)", - js_string!("-3c3bc3a4a2f75c0000"), + js_str!("-3c3bc3a4a2f75c0000"), ), TestAction::assert_eq( "Number(-11111111111111111111111).toString(16)", - js_string!("-25a55a46e5da9a00000"), + js_str!("-25a55a46e5da9a00000"), ), TestAction::assert_eq( "Number(-0.00001).toString(16)", - js_string!("-0.0000a7c5ac471b4788"), + js_str!("-0.0000a7c5ac471b4788"), ), TestAction::assert_eq( "Number(-0.000001).toString(16)", - js_string!("-0.000010c6f7a0b5ed8d"), + js_str!("-0.000010c6f7a0b5ed8d"), ), TestAction::assert_eq( "Number(-0.0000001).toString(16)", - js_string!("-0.000001ad7f29abcaf48"), + js_str!("-0.000001ad7f29abcaf48"), ), TestAction::assert_eq( "Number(-0.00000012).toString(16)", - js_string!("-0.000002036565348d256"), + js_str!("-0.000002036565348d256"), ), TestAction::assert_eq( "Number(-0.000000123).toString(16)", - js_string!("-0.0000021047ee22aa466"), + js_str!("-0.0000021047ee22aa466"), ), TestAction::assert_eq( "Number(-0.00000001).toString(16)", - js_string!("-0.0000002af31dc4611874"), + js_str!("-0.0000002af31dc4611874"), ), TestAction::assert_eq( "Number(-0.000000012).toString(16)", - js_string!("-0.000000338a23b87483be"), + js_str!("-0.000000338a23b87483be"), ), TestAction::assert_eq( "Number(-0.0000000123).toString(16)", - js_string!("-0.00000034d3fe36aaa0a2"), + js_str!("-0.00000034d3fe36aaa0a2"), ), ]); } @@ -342,26 +334,26 @@ fn to_string() { #[test] fn num_to_string_exponential() { run_test_actions([ - TestAction::assert_eq("(0).toString()", js_string!("0")), - TestAction::assert_eq("(-0).toString()", js_string!("0")), + TestAction::assert_eq("(0).toString()", js_str!("0")), + TestAction::assert_eq("(-0).toString()", js_str!("0")), TestAction::assert_eq( "(111111111111111111111).toString()", - js_string!("111111111111111110000"), + js_str!("111111111111111110000"), ), TestAction::assert_eq( "(1111111111111111111111).toString()", - js_string!("1.1111111111111111e+21"), + js_str!("1.1111111111111111e+21"), ), TestAction::assert_eq( "(11111111111111111111111).toString()", - js_string!("1.1111111111111111e+22"), - ), - TestAction::assert_eq("(0.0000001).toString()", js_string!("1e-7")), - TestAction::assert_eq("(0.00000012).toString()", js_string!("1.2e-7")), - TestAction::assert_eq("(0.000000123).toString()", js_string!("1.23e-7")), - TestAction::assert_eq("(0.00000001).toString()", js_string!("1e-8")), - TestAction::assert_eq("(0.000000012).toString()", js_string!("1.2e-8")), - TestAction::assert_eq("(0.0000000123).toString()", js_string!("1.23e-8")), + js_str!("1.1111111111111111e+22"), + ), + TestAction::assert_eq("(0.0000001).toString()", js_str!("1e-7")), + TestAction::assert_eq("(0.00000012).toString()", js_str!("1.2e-7")), + TestAction::assert_eq("(0.000000123).toString()", js_str!("1.23e-7")), + TestAction::assert_eq("(0.00000001).toString()", js_str!("1e-8")), + TestAction::assert_eq("(0.000000012).toString()", js_str!("1.2e-8")), + TestAction::assert_eq("(0.0000000123).toString()", js_str!("1.23e-8")), ]); } @@ -642,11 +634,11 @@ fn issue_2717() { run_test_actions([ TestAction::assert_eq( "(0.1600057092765239).toString(36)", - js_string!("0.5rd85dm1ixq"), + js_str!("0.5rd85dm1ixq"), ), TestAction::assert_eq( "(0.23046743672210102).toString(36)", - js_string!("0.8aoosla2phj"), + js_str!("0.8aoosla2phj"), ), ]); } diff --git a/core/engine/src/builtins/object/mod.rs b/core/engine/src/builtins/object/mod.rs index 9476038ca7..a0fe635191 100644 --- a/core/engine/src/builtins/object/mod.rs +++ b/core/engine/src/builtins/object/mod.rs @@ -13,8 +13,6 @@ //! [spec]: https://tc39.es/ecma262/#sec-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object -use std::ops::Deref; - use super::{ error::ErrorObject, Array, BuiltInBuilder, BuiltInConstructor, Date, IntrinsicObject, RegExp, }; @@ -30,12 +28,13 @@ use crate::{ }, property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind}, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, symbol::JsSymbol, value::JsValue, Context, JsArgs, JsData, JsResult, JsString, }; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; use tap::{Conv, Pipe}; @@ -63,7 +62,7 @@ impl IntrinsicObject for OrdinaryObject { BuiltInBuilder::from_standard_constructor::(realm) .inherits(None) .accessor( - utf16!("__proto__"), + js_string!("__proto__"), Some(legacy_proto_getter), Some(legacy_setter_proto), Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, @@ -574,42 +573,42 @@ impl OrdinaryObject { // 4. If Desc has a [[Value]] field, then if let Some(value) = desc.value() { // a. Perform ! CreateDataPropertyOrThrow(obj, "value", Desc.[[Value]]). - obj.create_data_property_or_throw(utf16!("value"), value.clone(), context) + obj.create_data_property_or_throw(js_str!("value"), value.clone(), context) .expect("CreateDataPropertyOrThrow cannot fail here"); } // 5. If Desc has a [[Writable]] field, then if let Some(writable) = desc.writable() { // a. Perform ! CreateDataPropertyOrThrow(obj, "writable", Desc.[[Writable]]). - obj.create_data_property_or_throw(utf16!("writable"), writable, context) + obj.create_data_property_or_throw(js_str!("writable"), writable, context) .expect("CreateDataPropertyOrThrow cannot fail here"); } // 6. If Desc has a [[Get]] field, then if let Some(get) = desc.get() { // a. Perform ! CreateDataPropertyOrThrow(obj, "get", Desc.[[Get]]). - obj.create_data_property_or_throw(utf16!("get"), get.clone(), context) + obj.create_data_property_or_throw(js_str!("get"), get.clone(), context) .expect("CreateDataPropertyOrThrow cannot fail here"); } // 7. If Desc has a [[Set]] field, then if let Some(set) = desc.set() { // a. Perform ! CreateDataPropertyOrThrow(obj, "set", Desc.[[Set]]). - obj.create_data_property_or_throw(utf16!("set"), set.clone(), context) + obj.create_data_property_or_throw(js_str!("set"), set.clone(), context) .expect("CreateDataPropertyOrThrow cannot fail here"); } // 8. If Desc has an [[Enumerable]] field, then if let Some(enumerable) = desc.enumerable() { // a. Perform ! CreateDataPropertyOrThrow(obj, "enumerable", Desc.[[Enumerable]]). - obj.create_data_property_or_throw(utf16!("enumerable"), enumerable, context) + obj.create_data_property_or_throw(js_str!("enumerable"), enumerable, context) .expect("CreateDataPropertyOrThrow cannot fail here"); } // 9. If Desc has a [[Configurable]] field, then if let Some(configurable) = desc.configurable() { // a. Perform ! CreateDataPropertyOrThrow(obj, "configurable", Desc.[[Configurable]]). - obj.create_data_property_or_throw(utf16!("configurable"), configurable, context) + obj.create_data_property_or_throw(js_str!("configurable"), configurable, context) .expect("CreateDataPropertyOrThrow cannot fail here"); } @@ -825,11 +824,11 @@ impl OrdinaryObject { pub fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. If the this value is undefined, return "[object Undefined]". if this.is_undefined() { - return Ok(js_string!("[object Undefined]").into()); + return Ok(js_str!("[object Undefined]").into()); } // 2. If the this value is null, return "[object Null]". if this.is_null() { - return Ok(js_string!("[object Null]").into()); + return Ok(js_str!("[object Null]").into()); } // 3. Let O be ! ToObject(this value). let o = this.to_object(context).expect("toObject cannot fail here"); @@ -837,37 +836,37 @@ impl OrdinaryObject { // 4. Let isArray be ? IsArray(O). // 5. If isArray is true, let builtinTag be "Array". let builtin_tag = if o.is_array_abstract()? { - utf16!("Array") + js_str!("Array") } else { let o_borrow = o.borrow(); if o_borrow.is_arguments() { // 6. Else if O has a [[ParameterMap]] internal slot, let builtinTag be "Arguments". - utf16!("Arguments") + js_str!("Arguments") } else if o.is_callable() { // 7. Else if O has a [[Call]] internal method, let builtinTag be "Function". - utf16!("Function") + js_str!("Function") } else if o.is::() { // 8. Else if O has an [[ErrorData]] internal slot, let builtinTag be "Error". - utf16!("Error") + js_str!("Error") } else if o.is::() { // 9. Else if O has a [[BooleanData]] internal slot, let builtinTag be "Boolean". - utf16!("Boolean") + js_str!("Boolean") } else if o.is::() { // 10. Else if O has a [[NumberData]] internal slot, let builtinTag be "Number". - utf16!("Number") + js_str!("Number") } else if o.is::() { // 11. Else if O has a [[StringData]] internal slot, let builtinTag be "String". - utf16!("String") + js_str!("String") } else if o.is::() { // 12. Else if O has a [[DateValue]] internal slot, let builtinTag be "Date". - utf16!("Date") + js_str!("Date") } else if o.is::() { // 13. Else if O has a [[RegExpMatcher]] internal slot, let builtinTag be "RegExp". - utf16!("RegExp") + js_str!("RegExp") } else { // 14. Else, let builtinTag be "Object". - utf16!("Object") + js_str!("Object") } }; @@ -875,10 +874,10 @@ impl OrdinaryObject { let tag = o.get(JsSymbol::to_string_tag(), context)?; // 16. If Type(tag) is not String, set tag to builtinTag. - let tag_str = tag.as_string().map_or(builtin_tag, JsString::deref); + let tag_str = tag.as_string().map_or(builtin_tag, JsString::as_str); // 17. Return the string-concatenation of "[object ", tag, and "]". - Ok(js_string!(utf16!("[object "), tag_str, utf16!("]")).into()) + Ok(js_string!(js_str!("[object "), tag_str, js_str!("]")).into()) } /// `Object.prototype.toLocaleString( [ reserved1 [ , reserved2 ] ] )` @@ -897,7 +896,7 @@ impl OrdinaryObject { ) -> JsResult { // 1. Let O be the this value. // 2. Return ? Invoke(O, "toString"). - this.invoke(utf16!("toString"), &[], context) + this.invoke(js_str!("toString"), &[], context) } /// `Object.prototype.hasOwnProperty( property )` diff --git a/core/engine/src/builtins/object/tests.rs b/core/engine/src/builtins/object/tests.rs index 17c42c1100..151f599538 100644 --- a/core/engine/src/builtins/object/tests.rs +++ b/core/engine/src/builtins/object/tests.rs @@ -1,4 +1,5 @@ -use crate::{js_string, run_test_actions, JsNativeErrorKind, JsValue, TestAction}; +use crate::{run_test_actions, JsNativeErrorKind, JsValue, TestAction}; +use boa_macros::js_str; use indoc::indoc; #[test] @@ -137,21 +138,21 @@ fn object_to_string() { "#}), TestAction::assert_eq( "Object.prototype.toString.call(undefined)", - js_string!("[object Undefined]"), + js_str!("[object Undefined]"), ), TestAction::assert_eq( "Object.prototype.toString.call(null)", - js_string!("[object Null]"), + js_str!("[object Null]"), ), - TestAction::assert_eq("[].toString()", js_string!("[object Array]")), - TestAction::assert_eq("(() => {}).toString()", js_string!("[object Function]")), - TestAction::assert_eq("(new Error('')).toString()", js_string!("[object Error]")), - TestAction::assert_eq("Boolean().toString()", js_string!("[object Boolean]")), - TestAction::assert_eq("Number(42).toString()", js_string!("[object Number]")), - TestAction::assert_eq("String('boa').toString()", js_string!("[object String]")), - TestAction::assert_eq("(new Date()).toString()", js_string!("[object Date]")), - TestAction::assert_eq("/boa/.toString()", js_string!("[object RegExp]")), - TestAction::assert_eq("({}).toString()", js_string!("[object Object]")), + TestAction::assert_eq("[].toString()", js_str!("[object Array]")), + TestAction::assert_eq("(() => {}).toString()", js_str!("[object Function]")), + TestAction::assert_eq("(new Error('')).toString()", js_str!("[object Error]")), + TestAction::assert_eq("Boolean().toString()", js_str!("[object Boolean]")), + TestAction::assert_eq("Number(42).toString()", js_str!("[object Number]")), + TestAction::assert_eq("String('boa').toString()", js_str!("[object String]")), + TestAction::assert_eq("(new Date()).toString()", js_str!("[object Date]")), + TestAction::assert_eq("/boa/.toString()", js_str!("[object RegExp]")), + TestAction::assert_eq("({}).toString()", js_str!("[object Object]")), ]); } @@ -163,7 +164,7 @@ fn define_symbol_property() { let sym = Symbol("key"); Object.defineProperty(obj, sym, { value: "val" }); "#}), - TestAction::assert_eq("obj[sym]", js_string!("val")), + TestAction::assert_eq("obj[sym]", js_str!("val")), ]); } diff --git a/core/engine/src/builtins/options.rs b/core/engine/src/builtins/options.rs index cae5b7f483..6b6777a0cf 100644 --- a/core/engine/src/builtins/options.rs +++ b/core/engine/src/builtins/options.rs @@ -2,7 +2,7 @@ use std::{fmt, str::FromStr}; -use crate::{object::JsObject, Context, JsNativeError, JsResult, JsString, JsValue}; +use crate::{object::JsObject, string::JsStr, Context, JsNativeError, JsResult, JsString, JsValue}; /// A type used as an option parameter for [`get_option`]. pub(crate) trait OptionType: Sized { @@ -50,7 +50,7 @@ where /// [spec]: https://tc39.es/ecma402/#sec-getoption pub(crate) fn get_option( options: &JsObject, - property: &[u16], + property: JsStr<'_>, context: &mut Context, ) -> JsResult> { // 1. Let value be ? Get(options, property). diff --git a/core/engine/src/builtins/promise/mod.rs b/core/engine/src/builtins/promise/mod.rs index c48ed69dec..8d1b45beb6 100644 --- a/core/engine/src/builtins/promise/mod.rs +++ b/core/engine/src/builtins/promise/mod.rs @@ -17,13 +17,13 @@ use crate::{ }, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, symbol::JsSymbol, value::JsValue, Context, JsArgs, JsError, JsResult, JsString, }; use boa_gc::{custom_trace, Finalize, Gc, GcRefCell, Trace}; -use boa_macros::JsData; +use boa_macros::{js_str, JsData}; use boa_profiler::Profiler; use std::{cell::Cell, rc::Rc}; use tap::{Conv, Pipe}; @@ -768,7 +768,7 @@ impl Promise { // s. Perform ? Invoke(nextPromise, "then", « onFulfilled, resultCapability.[[Reject]] »). next_promise.invoke( - utf16!("then"), + js_str!("then"), &[ on_fulfilled.into(), result_capability.functions.reject.clone().into(), @@ -957,15 +957,15 @@ impl Promise { // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "fulfilled"). obj.create_data_property_or_throw( - utf16!("status"), - js_string!("fulfilled"), + js_str!("status"), + js_str!("fulfilled"), context, ) .expect("cannot fail per spec"); // 11. Perform ! CreateDataPropertyOrThrow(obj, "value", x). obj.create_data_property_or_throw( - utf16!("value"), + js_str!("value"), args.get_or_undefined(0).clone(), context, ) @@ -1047,15 +1047,15 @@ impl Promise { // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "rejected"). obj.create_data_property_or_throw( - utf16!("status"), - js_string!("rejected"), + js_str!("status"), + js_str!("rejected"), context, ) .expect("cannot fail per spec"); // 11. Perform ! CreateDataPropertyOrThrow(obj, "reason", x). obj.create_data_property_or_throw( - utf16!("reason"), + js_str!("reason"), args.get_or_undefined(0).clone(), context, ) @@ -1107,7 +1107,7 @@ impl Promise { // ab. Perform ? Invoke(nextPromise, "then", « onFulfilled, onRejected »). next_promise.invoke( - utf16!("then"), + js_str!("then"), &[on_fulfilled.into(), on_rejected.into()], context, )?; @@ -1343,7 +1343,7 @@ impl Promise { // s. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], onRejected »). next_promise.invoke( - utf16!("then"), + js_str!("then"), &[ result_capability.functions.resolve.clone().into(), on_rejected.into(), @@ -1466,7 +1466,7 @@ impl Promise { // i. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], resultCapability.[[Reject]] »). next_promise.invoke( - utf16!("then"), + js_str!("then"), &[ result_capability.functions.resolve.clone().into(), result_capability.functions.reject.clone().into(), @@ -1621,7 +1621,7 @@ impl Promise { let promise = this; // 2. Return ? Invoke(promise, "then", « undefined, onRejected »). promise.invoke( - utf16!("then"), + js_str!("then"), &[JsValue::undefined(), on_rejected.clone()], context, ) @@ -1667,7 +1667,7 @@ impl Promise { // a. Let thenFinally be onFinally. // b. Let catchFinally be onFinally. // 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »). - let then = promise.get(utf16!("then"), context)?; + let then = promise.get(js_str!("then"), context)?; return then.call(this, &[on_finally.clone(), on_finally.clone()], context); }; @@ -1675,7 +1675,7 @@ impl Promise { Self::then_catch_finally_closures(c, on_finally, context); // 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »). - let then = promise.get(utf16!("then"), context)?; + let then = promise.get(js_str!("then"), context)?; then.call(this, &[then_finally.into(), catch_finally.into()], context) } @@ -1730,7 +1730,7 @@ impl Promise { let value_thunk = return_value.length(0).name("").build(); // v. Return ? Invoke(promise, "then", « valueThunk »). - promise.invoke(utf16!("then"), &[value_thunk.into()], context) + promise.invoke(js_str!("then"), &[value_thunk.into()], context) }, FinallyCaptures { on_finally: on_finally.clone(), @@ -1781,7 +1781,7 @@ impl Promise { let thrower = throw_reason.length(0).name("").build(); // v. Return ? Invoke(promise, "then", « thrower »). - promise.invoke(utf16!("then"), &[thrower.into()], context) + promise.invoke(js_str!("then"), &[thrower.into()], context) }, FinallyCaptures { on_finally, c }, ), @@ -1986,7 +1986,7 @@ impl Promise { context: &mut Context, ) -> JsResult { // 1. Let promiseResolve be ? Get(promiseConstructor, "resolve"). - let promise_resolve = promise_constructor.get(utf16!("resolve"), context)?; + let promise_resolve = promise_constructor.get(js_str!("resolve"), context)?; // 2. If IsCallable(promiseResolve) is false, throw a TypeError exception. promise_resolve.as_callable().cloned().ok_or_else(|| { @@ -2184,7 +2184,7 @@ impl Promise { }; // 9. Let then be Completion(Get(resolution, "then")). - let then_action = match then.get(utf16!("then"), context) { + let then_action = match then.get(js_str!("then"), context) { // 10. If then is an abrupt completion, then Err(e) => { // a. Perform RejectPromise(promise, then.[[Value]]). diff --git a/core/engine/src/builtins/proxy/mod.rs b/core/engine/src/builtins/proxy/mod.rs index 0d654c23ae..ea0d032910 100644 --- a/core/engine/src/builtins/proxy/mod.rs +++ b/core/engine/src/builtins/proxy/mod.rs @@ -26,11 +26,12 @@ use crate::{ }, property::{PropertyDescriptor, PropertyKey}, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, value::Type, Context, JsArgs, JsResult, JsString, JsValue, }; use boa_gc::{Finalize, GcRefCell, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; use rustc_hash::FxHashSet; @@ -233,12 +234,12 @@ impl Proxy { // 6. Perform ! CreateDataPropertyOrThrow(result, "proxy", p). result - .create_data_property_or_throw(utf16!("proxy"), p, context) + .create_data_property_or_throw(js_str!("proxy"), p, context) .expect("CreateDataPropertyOrThrow cannot fail here"); // 7. Perform ! CreateDataPropertyOrThrow(result, "revoke", revoker). result - .create_data_property_or_throw(utf16!("revoke"), revoker, context) + .create_data_property_or_throw(js_str!("revoke"), revoker, context) .expect("CreateDataPropertyOrThrow cannot fail here"); // 8. Return result. @@ -266,7 +267,7 @@ pub(crate) fn proxy_exotic_get_prototype_of( .try_data()?; // 5. Let trap be ? GetMethod(handler, "getPrototypeOf"). - let Some(trap) = handler.get_method(utf16!("getPrototypeOf"), context)? else { + let Some(trap) = handler.get_method(js_str!("getPrototypeOf"), context)? else { // 6. If trap is undefined, then // a. Return ? target.[[GetPrototypeOf]](). return target.__get_prototype_of__(context); @@ -327,7 +328,7 @@ pub(crate) fn proxy_exotic_set_prototype_of( .try_data()?; // 5. Let trap be ? GetMethod(handler, "setPrototypeOf"). - let Some(trap) = handler.get_method(utf16!("setPrototypeOf"), context)? else { + let Some(trap) = handler.get_method(js_str!("setPrototypeOf"), context)? else { // 6. If trap is undefined, then // a. Return ? target.[[SetPrototypeOf]](V). return target.__set_prototype_of__(val, context); @@ -386,7 +387,7 @@ pub(crate) fn proxy_exotic_is_extensible(obj: &JsObject, context: &mut Context) .try_data()?; // 5. Let trap be ? GetMethod(handler, "isExtensible"). - let Some(trap) = handler.get_method(utf16!("isExtensible"), context)? else { + let Some(trap) = handler.get_method(js_str!("isExtensible"), context)? else { // 6. If trap is undefined, then // a. Return ? IsExtensible(target). return target.is_extensible(context); @@ -431,7 +432,7 @@ pub(crate) fn proxy_exotic_prevent_extensions( .try_data()?; // 5. Let trap be ? GetMethod(handler, "preventExtensions"). - let Some(trap) = handler.get_method(utf16!("preventExtensions"), context)? else { + let Some(trap) = handler.get_method(js_str!("preventExtensions"), context)? else { // 6. If trap is undefined, then // a. Return ? target.[[PreventExtensions]](). return target.__prevent_extensions__(context); @@ -478,7 +479,7 @@ pub(crate) fn proxy_exotic_get_own_property( .try_data()?; // 5. Let trap be ? GetMethod(handler, "getOwnPropertyDescriptor"). - let Some(trap) = handler.get_method(utf16!("getOwnPropertyDescriptor"), context)? else { + let Some(trap) = handler.get_method(js_str!("getOwnPropertyDescriptor"), context)? else { // 6. If trap is undefined, then // a. Return ? target.[[GetOwnProperty]](P). return target.__get_own_property__(key, context); @@ -603,7 +604,7 @@ pub(crate) fn proxy_exotic_define_own_property( .try_data()?; // 5. Let trap be ? GetMethod(handler, "defineProperty"). - let Some(trap) = handler.get_method(utf16!("defineProperty"), context)? else { + let Some(trap) = handler.get_method(js_str!("defineProperty"), context)? else { // 6. If trap is undefined, then // a. Return ? target.[[DefineOwnProperty]](P, Desc). return target.__define_own_property__(key, desc, context); @@ -715,7 +716,7 @@ pub(crate) fn proxy_exotic_has_property( .try_data()?; // 5. Let trap be ? GetMethod(handler, "has"). - let Some(trap) = handler.get_method(utf16!("has"), context)? else { + let Some(trap) = handler.get_method(js_str!("has"), context)? else { // 6. If trap is undefined, then // a. Return ? target.[[HasProperty]](P). return target.has_property(key.clone(), context); @@ -783,7 +784,7 @@ pub(crate) fn proxy_exotic_get( .try_data()?; // 5. Let trap be ? GetMethod(handler, "get"). - let Some(trap) = handler.get_method(utf16!("get"), context)? else { + let Some(trap) = handler.get_method(js_str!("get"), context)? else { // 6. If trap is undefined, then // a. Return ? target.[[Get]](P, Receiver). return target.__get__(key, receiver, context); @@ -853,7 +854,7 @@ pub(crate) fn proxy_exotic_set( .try_data()?; // 5. Let trap be ? GetMethod(handler, "set"). - let Some(trap) = handler.get_method(utf16!("set"), context)? else { + let Some(trap) = handler.get_method(js_str!("set"), context)? else { // 6. If trap is undefined, then // a. Return ? target.[[Set]](P, V, Receiver). return target.__set__(key, value, receiver, context); @@ -933,7 +934,7 @@ pub(crate) fn proxy_exotic_delete( .try_data()?; // 5. Let trap be ? GetMethod(handler, "deleteProperty"). - let Some(trap) = handler.get_method(utf16!("deleteProperty"), context)? else { + let Some(trap) = handler.get_method(js_str!("deleteProperty"), context)? else { // 6. If trap is undefined, then // a. Return ? target.[[Delete]](P). return target.__delete__(key, context); @@ -998,7 +999,7 @@ pub(crate) fn proxy_exotic_own_property_keys( .try_data()?; // 5. Let trap be ? GetMethod(handler, "ownKeys"). - let Some(trap) = handler.get_method(utf16!("ownKeys"), context)? else { + let Some(trap) = handler.get_method(js_str!("ownKeys"), context)? else { // 6. If trap is undefined, then // a. Return ? target.[[OwnPropertyKeys]](). return target.__own_property_keys__(context); @@ -1132,7 +1133,7 @@ fn proxy_exotic_call( .try_data()?; // 5. Let trap be ? GetMethod(handler, "apply"). - let Some(trap) = handler.get_method(utf16!("apply"), context)? else { + let Some(trap) = handler.get_method(js_str!("apply"), context)? else { // 6. If trap is undefined, then // a. Return ? Call(target, thisArgument, argumentsList). return Ok(target.__call__(argument_count)); @@ -1180,7 +1181,7 @@ fn proxy_exotic_construct( assert!(target.is_constructor()); // 6. Let trap be ? GetMethod(handler, "construct"). - let Some(trap) = handler.get_method(utf16!("construct"), context)? else { + let Some(trap) = handler.get_method(js_str!("construct"), context)? else { // 7. If trap is undefined, then // a. Return ? Construct(target, argumentsList, newTarget). return Ok(target.__construct__(argument_count)); diff --git a/core/engine/src/builtins/reflect/tests.rs b/core/engine/src/builtins/reflect/tests.rs index 41798586c5..320f63da9d 100644 --- a/core/engine/src/builtins/reflect/tests.rs +++ b/core/engine/src/builtins/reflect/tests.rs @@ -1,4 +1,5 @@ -use crate::{js_string, run_test_actions, JsValue, TestAction}; +use crate::{run_test_actions, JsValue, TestAction}; +use boa_macros::js_str; use indoc::indoc; #[test] @@ -68,10 +69,7 @@ fn get_prototype_of() { function F() { this.p = 42 }; let f = new F(); "#}), - TestAction::assert_eq( - "Reflect.getPrototypeOf(f).constructor.name", - js_string!("F"), - ), + TestAction::assert_eq("Reflect.getPrototypeOf(f).constructor.name", js_str!("F")), ]); } @@ -134,6 +132,6 @@ fn set_prototype_of() { let obj = {} Reflect.setPrototypeOf(obj, F); "#}), - TestAction::assert_eq("Reflect.getPrototypeOf(obj).name", js_string!("F")), + TestAction::assert_eq("Reflect.getPrototypeOf(obj).name", js_str!("F")), ]); } diff --git a/core/engine/src/builtins/regexp/mod.rs b/core/engine/src/builtins/regexp/mod.rs index f75552b512..34be1ef965 100644 --- a/core/engine/src/builtins/regexp/mod.rs +++ b/core/engine/src/builtins/regexp/mod.rs @@ -17,12 +17,13 @@ use crate::{ object::{internal_methods::get_prototype_from_constructor, JsObject, CONSTRUCTOR}, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16, CodePoint}, + string::{common::StaticJsStrings, CodePoint, JsStrVariant}, symbol::JsSymbol, value::JsValue, Context, JsArgs, JsData, JsResult, JsString, }; use boa_gc::{Finalize, Trace}; +use boa_macros::{js_str, utf16}; use boa_parser::lexer::regex::RegExpFlags; use boa_profiler::Profiler; use regress::{Flags, Range, Regex}; @@ -238,12 +239,12 @@ impl BuiltInConstructor for RegExp { (p, f) } else if let Some(pattern) = pattern_is_regexp { // a. Let P be ? Get(pattern, "source"). - let p = pattern.get(js_string!("source"), context)?; + let p = pattern.get(js_str!("source"), context)?; // b. If flags is undefined, then let f = if flags.is_undefined() { // i. Let F be ? Get(pattern, "flags"). - pattern.get(js_string!("flags"), context)? + pattern.get(js_str!("flags"), context)? // c. Else, } else { // i. Let F be flags. @@ -636,54 +637,54 @@ impl RegExp { }; // 3. Let codeUnits be a new empty List. - let mut code_units = Vec::new(); + let mut code_units = String::new(); // 4. Let hasIndices be ToBoolean(? Get(R, "hasIndices")). // 5. If hasIndices is true, append the code unit 0x0064 (LATIN SMALL LETTER D) to codeUnits. - if object.get(utf16!("hasIndices"), context)?.to_boolean() { - code_units.extend_from_slice(utf16!("d")); + if object.get(js_str!("hasIndices"), context)?.to_boolean() { + code_units.push('d'); } // 6. Let global be ToBoolean(? Get(R, "global")). // 7. If global is true, append the code unit 0x0067 (LATIN SMALL LETTER G) to codeUnits. - if object.get(utf16!("global"), context)?.to_boolean() { - code_units.extend_from_slice(utf16!("g")); + if object.get(js_str!("global"), context)?.to_boolean() { + code_units.push('g'); } // 8. Let ignoreCase be ToBoolean(? Get(R, "ignoreCase")). // 9. If ignoreCase is true, append the code unit 0x0069 (LATIN SMALL LETTER I) to codeUnits. - if object.get(utf16!("ignoreCase"), context)?.to_boolean() { - code_units.extend_from_slice(utf16!("i")); + if object.get(js_str!("ignoreCase"), context)?.to_boolean() { + code_units.push('i'); } // 10. Let multiline be ToBoolean(? Get(R, "multiline")). // 11. If multiline is true, append the code unit 0x006D (LATIN SMALL LETTER M) to codeUnits. - if object.get(utf16!("multiline"), context)?.to_boolean() { - code_units.extend_from_slice(utf16!("m")); + if object.get(js_str!("multiline"), context)?.to_boolean() { + code_units.push('m'); } // 12. Let dotAll be ToBoolean(? Get(R, "dotAll")). // 13. If dotAll is true, append the code unit 0x0073 (LATIN SMALL LETTER S) to codeUnits. - if object.get(utf16!("dotAll"), context)?.to_boolean() { - code_units.extend_from_slice(utf16!("s")); + if object.get(js_str!("dotAll"), context)?.to_boolean() { + code_units.push('s'); } // 14. Let unicode be ToBoolean(? Get(R, "unicode")). // 15. If unicode is true, append the code unit 0x0075 (LATIN SMALL LETTER U) to codeUnits. - if object.get(utf16!("unicode"), context)?.to_boolean() { - code_units.extend_from_slice(utf16!("u")); + if object.get(js_str!("unicode"), context)?.to_boolean() { + code_units.push('u'); } // 16. Let unicodeSets be ToBoolean(? Get(R, "unicodeSets")). // 17. If unicodeSets is true, append the code unit 0x0076 (LATIN SMALL LETTER V) to codeUnits. - if object.get(utf16!("unicodeSets"), context)?.to_boolean() { - code_units.extend_from_slice(utf16!("v")); + if object.get(js_str!("unicodeSets"), context)?.to_boolean() { + code_units.push('v'); } // 18. Let sticky be ToBoolean(? Get(R, "sticky")). // 19. If sticky is true, append the code unit 0x0079 (LATIN SMALL LETTER Y) to codeUnits. - if object.get(utf16!("sticky"), context)?.to_boolean() { - code_units.extend_from_slice(utf16!("y")); + if object.get(js_str!("sticky"), context)?.to_boolean() { + code_units.push('y'); } // 20. Return the String value whose code units are the elements of the List codeUnits. @@ -724,7 +725,7 @@ impl RegExp { this, &JsValue::new(context.intrinsics().constructors().regexp().prototype()), ) { - Ok(JsValue::new(js_string!("(?:)"))) + Ok(JsValue::new(js_str!("(?:)"))) } else { Err(JsNativeError::typ() .with_message("RegExp.prototype.source method called on incompatible value") @@ -752,7 +753,7 @@ impl RegExp { /// [spec]: https://tc39.es/ecma262/#sec-escaperegexppattern fn escape_pattern(src: &JsString, _flags: &JsString) -> JsValue { if src.is_empty() { - js_string!("(?:)").into() + js_str!("(?:)").into() } else { let mut s = Vec::with_capacity(src.len()); let mut buf = [0; 2]; @@ -863,7 +864,7 @@ impl RegExp { // 2. Assert: Type(S) is String. // 3. Let exec be ? Get(R, "exec"). - let exec = this.get(utf16!("exec"), context)?; + let exec = this.get(js_str!("exec"), context)?; // 4. If IsCallable(exec) is true, then if let Some(exec) = exec.as_callable() { @@ -915,19 +916,21 @@ impl RegExp { let length = input.len() as u64; // 2. Let lastIndex be ℝ(? ToLength(? Get(R, "lastIndex"))). - let mut last_index = this.get(utf16!("lastIndex"), context)?.to_length(context)?; + let mut last_index = this + .get(js_str!("lastIndex"), context)? + .to_length(context)?; // 3. Let flags be R.[[OriginalFlags]]. let flags = &rx.original_flags; // 4. If flags contains "g", let global be true; else let global be false. - let global = flags.contains(&('g' as u16)); + let global = flags.contains(b'g'); // 5. If flags contains "y", let sticky be true; else let sticky be false. - let sticky = flags.contains(&('y' as u16)); + let sticky = flags.contains(b'y'); // 6. If flags contains "d", let hasIndices be true; else let hasIndices be false. - let has_indices = flags.contains(&('d' as u16)); + let has_indices = flags.contains(b'd'); // 7. If global is false and sticky is false, set lastIndex to 0. if !global && !sticky { @@ -938,7 +941,7 @@ impl RegExp { let matcher = &rx.matcher; // 9. If flags contains "u" or flags contains "v", let fullUnicode be true; else let fullUnicode be false. - let full_unicode = flags.contains(&('u' as u16)) || flags.contains(&('v' as u16)); + let full_unicode = flags.contains(b'u') || flags.contains(b'v'); // NOTE: The following steps are take care of by regress: // @@ -952,7 +955,7 @@ impl RegExp { // i. If global is true or sticky is true, then if global || sticky { // 1. Perform ? Set(R, "lastIndex", +0𝔽, true). - this.set(utf16!("lastIndex"), 0, true, context)?; + this.set(js_str!("lastIndex"), 0, true, context)?; } // ii. Return null. @@ -961,10 +964,20 @@ impl RegExp { // 13.b. Let inputIndex be the index into input of the character that was obtained from element lastIndex of S. // 13.c. Let r be matcher(input, inputIndex). - let r: Option = if full_unicode { - matcher.find_from_utf16(input, last_index as usize).next() - } else { - matcher.find_from_ucs2(input, last_index as usize).next() + let r: Option = match (full_unicode, input.as_str().variant()) { + (true | false, JsStrVariant::Latin1(_)) => { + // TODO: Currently regress does not support latin1 encoding. + let input = input.to_vec(); + + // NOTE: We can use the faster ucs2 variant since there will never be two byte unicode. + matcher.find_from_ucs2(&input, last_index as usize).next() + } + (true, JsStrVariant::Utf16(input)) => { + matcher.find_from_utf16(input, last_index as usize).next() + } + (false, JsStrVariant::Utf16(input)) => { + matcher.find_from_ucs2(input, last_index as usize).next() + } }; let Some(match_value) = r else { @@ -975,7 +988,7 @@ impl RegExp { // 13.a.i. If global is true or sticky is true, then if global || sticky { // 1. Perform ? Set(R, "lastIndex", +0𝔽, true). - this.set(utf16!("lastIndex"), 0, true, context)?; + this.set(js_str!("lastIndex"), 0, true, context)?; } // MOVE: ii. Set lastIndex to AdvanceStringIndex(S, lastIndex, fullUnicode). @@ -994,7 +1007,7 @@ impl RegExp { // NOTE: regress currently doesn't support the sticky flag so we have to emulate it. if sticky && match_value.start() != last_index as usize { // 1. Perform ? Set(R, "lastIndex", +0𝔽, true). - this.set(utf16!("lastIndex"), 0, true, context)?; + this.set(js_str!("lastIndex"), 0, true, context)?; // 2. Return null. return Ok(None); @@ -1012,7 +1025,7 @@ impl RegExp { // 16. If global is true or sticky is true, then if global || sticky { // a. Perform ? Set(R, "lastIndex", 𝔽(e), true). - this.set(utf16!("lastIndex"), e, true, context)?; + this.set(js_str!("lastIndex"), e, true, context)?; } // 17. Let n be the number of elements in r's captures List. @@ -1026,11 +1039,11 @@ impl RegExp { let a = Array::array_create(n + 1, None, context)?; // 22. Perform ! CreateDataPropertyOrThrow(A, "index", 𝔽(lastIndex)). - a.create_data_property_or_throw(utf16!("index"), last_index, context) + a.create_data_property_or_throw(js_str!("index"), last_index, context) .expect("this CreateDataPropertyOrThrow call must not fail"); // 23. Perform ! CreateDataPropertyOrThrow(A, "input", S). - a.create_data_property_or_throw(utf16!("input"), input.clone(), context) + a.create_data_property_or_throw(js_str!("input"), input.clone(), context) .expect("this CreateDataPropertyOrThrow call must not fail"); // 24. Let match be the Match Record { [[StartIndex]]: lastIndex, [[EndIndex]]: e }. @@ -1051,7 +1064,7 @@ impl RegExp { .expect("this CreateDataPropertyOrThrow call must not fail"); // 28. Let matchedSubstr be GetMatchString(S, match). - let matched_substr = js_string!(&input[(last_index as usize)..(e)]); + let matched_substr = input.get_expect((last_index as usize)..(e)); // 29. Perform ! CreateDataPropertyOrThrow(A, "0", matchedSubstr). a.create_data_property_or_throw(0, matched_substr, context) @@ -1081,7 +1094,7 @@ impl RegExp { for (name, range) in named_groups { let name = js_string!(name); if let Some(range) = range { - let value = js_string!(&input[range.clone()]); + let value = input.get_expect(range.clone()); groups .create_data_property_or_throw(name.clone(), value, context) @@ -1126,11 +1139,11 @@ impl RegExp { // 22.2.7.8 MakeMatchIndicesIndexPairArray ( S, indices, groupNames, hasGroups ) // 8. Perform ! CreateDataPropertyOrThrow(A, "groups", groups). indices - .create_data_property_or_throw(utf16!("groups"), group_names, context) + .create_data_property_or_throw(js_str!("groups"), group_names, context) .expect("this CreateDataPropertyOrThrow call must not fail"); // 32. Perform ! CreateDataPropertyOrThrow(A, "groups", groups). - a.create_data_property_or_throw(utf16!("groups"), groups, context) + a.create_data_property_or_throw(js_str!("groups"), groups, context) .expect("this CreateDataPropertyOrThrow call must not fail"); // 27. For each integer i such that i ≥ 1 and i ≤ n, in ascending order, do @@ -1141,9 +1154,9 @@ impl RegExp { // b. If captureI is undefined, let capturedValue be undefined. // c. Else if fullUnicode is true, then // d. Else, - let captured_value = capture - .clone() - .map_or_else(JsValue::undefined, |range| js_string!(&input[range]).into()); + let captured_value = capture.clone().map_or_else(JsValue::undefined, |range| { + js_string!(input.get_expect(range)).into() + }); // e. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(i)), capturedValue). a.create_data_property_or_throw(i, captured_value.clone(), context) @@ -1171,7 +1184,7 @@ impl RegExp { // a. Let indicesArray be MakeMatchIndicesIndexPairArray(S, indices, groupNames, hasGroups). // b. Perform ! CreateDataPropertyOrThrow(A, "indices", indicesArray). if has_indices { - a.create_data_property_or_throw(utf16!("indices"), indices, context) + a.create_data_property_or_throw(js_str!("indices"), indices, context) .expect("this CreateDataPropertyOrThrow call must not fail"); } @@ -1206,10 +1219,10 @@ impl RegExp { let arg_str = args.get_or_undefined(0).to_string(context)?; // 4. Let flags be ? ToString(? Get(rx, "flags")). - let flags = rx.get(utf16!("flags"), context)?.to_string(context)?; + let flags = rx.get(js_str!("flags"), context)?.to_string(context)?; // 5. If flags does not contain "g", then - if !flags.contains(&103) { + if !flags.contains(b'g') { // a. Return ? RegExpExec(rx, S). return (Self::abstract_exec(rx, arg_str, context)?) .map_or_else(|| Ok(JsValue::null()), |v| Ok(v.into())); @@ -1218,10 +1231,10 @@ impl RegExp { // 6. Else, // a. If flags contains "u" or flags contains "v", let fullUnicode be true. Otherwise, let fullUnicode be false. - let full_unicode = flags.contains(&117) || flags.contains(&118); + let full_unicode = flags.contains(b'u') || flags.contains(b'v'); // b. Perform ? Set(rx, "lastIndex", +0𝔽, true). - rx.set(utf16!("lastIndex"), 0, true, context)?; + rx.set(js_str!("lastIndex"), 0, true, context)?; // c. Let A be ! ArrayCreate(0). let a = Array::array_create(0, None, context).expect("this ArrayCreate call must not fail"); @@ -1247,13 +1260,18 @@ impl RegExp { // 3. If matchStr is the empty String, then if match_str.is_empty() { // a. Let thisIndex be ℝ(? ToLength(? Get(rx, "lastIndex"))). - let this_index = rx.get(utf16!("lastIndex"), context)?.to_length(context)?; + let this_index = rx.get(js_str!("lastIndex"), context)?.to_length(context)?; // b. Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode). let next_index = advance_string_index(&arg_str, this_index, full_unicode); // c. Perform ? Set(rx, "lastIndex", 𝔽(nextIndex), true). - rx.set(utf16!("lastIndex"), JsValue::new(next_index), true, context)?; + rx.set( + js_str!("lastIndex"), + JsValue::new(next_index), + true, + context, + )?; } // 4. Set n to n + 1. @@ -1293,14 +1311,14 @@ impl RegExp { })?; // 3. Let pattern be ? ToString(? Get(R, "source")). - let pattern = regexp.get(utf16!("source"), context)?.to_string(context)?; + let pattern = regexp.get(js_str!("source"), context)?.to_string(context)?; // 4. Let flags be ? ToString(? Get(R, "flags")). - let flags = regexp.get(utf16!("flags"), context)?.to_string(context)?; + let flags = regexp.get(js_str!("flags"), context)?.to_string(context)?; // 5. Let result be the string-concatenation of "/", pattern, "/", and flags. // 6. Return result. - Ok(js_string!(utf16!("/"), &pattern, utf16!("/"), &flags).into()) + Ok(js_string!(js_str!("/"), &pattern, js_str!("/"), &flags).into()) } /// `RegExp.prototype[ @@matchAll ]( string )` @@ -1332,26 +1350,26 @@ impl RegExp { let c = regexp.species_constructor(StandardConstructors::regexp, context)?; // 5. Let flags be ? ToString(? Get(R, "flags")). - let flags = regexp.get(utf16!("flags"), context)?.to_string(context)?; + let flags = regexp.get(js_str!("flags"), context)?.to_string(context)?; // 6. Let matcher be ? Construct(C, « R, flags »). let matcher = c.construct(&[this.clone(), flags.clone().into()], Some(&c), context)?; // 7. Let lastIndex be ? ToLength(? Get(R, "lastIndex")). let last_index = regexp - .get(utf16!("lastIndex"), context)? + .get(js_str!("lastIndex"), context)? .to_length(context)?; // 8. Perform ? Set(matcher, "lastIndex", lastIndex, true). - matcher.set(utf16!("lastIndex"), last_index, true, context)?; + matcher.set(js_str!("lastIndex"), last_index, true, context)?; // 9. If flags contains "g", let global be true. // 10. Else, let global be false. - let global = flags.contains(&('g' as u16)); + let global = flags.contains(b'g'); // 11. If flags contains "u", let fullUnicode be true. // 12. Else, let fullUnicode be false. - let unicode = flags.contains(&('u' as u16)); + let unicode = flags.contains(b'u'); // 13. Return ! CreateRegExpStringIterator(matcher, S, global, fullUnicode). Ok(RegExpStringIterator::create_regexp_string_iterator( @@ -1414,18 +1432,18 @@ impl RegExp { }; // 7. Let flags be ? ToString(? Get(rx, "flags")). - let flags = rx.get(utf16!("flags"), context)?.to_string(context)?; + let flags = rx.get(js_str!("flags"), context)?.to_string(context)?; // 8. If flags contains "g", let global be true. Otherwise, let global be false. - let global = flags.as_slice().contains(&u16::from(b'g')); + let global = flags.contains(b'g'); // 9. If global is true, then let full_unicode = if global { // a. If flags contains "u", let fullUnicode be true. Otherwise, let fullUnicode be false. - let full_unicode = flags.contains(&u16::from(b'u')); + let full_unicode = flags.contains(b'u'); // b. Perform ? Set(rx, "lastIndex", +0𝔽, true). - rx.set(utf16!("lastIndex"), 0, true, context)?; + rx.set(js_str!("lastIndex"), 0, true, context)?; full_unicode } else { @@ -1467,13 +1485,18 @@ impl RegExp { // 2. If matchStr is the empty String, then if match_str.is_empty() { // a. Let thisIndex be ℝ(? ToLength(? Get(rx, "lastIndex"))). - let this_index = rx.get(utf16!("lastIndex"), context)?.to_length(context)?; + let this_index = rx.get(js_str!("lastIndex"), context)?.to_length(context)?; // b. Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode). let next_index = advance_string_index(&s, this_index, full_unicode); // c. Perform ? Set(rx, "lastIndex", 𝔽(nextIndex), true). - rx.set(utf16!("lastIndex"), JsValue::new(next_index), true, context)?; + rx.set( + js_str!("lastIndex"), + JsValue::new(next_index), + true, + context, + )?; } } @@ -1502,7 +1525,7 @@ impl RegExp { // e. Let position be ? ToIntegerOrInfinity(? Get(result, "index")). let position = result - .get(utf16!("index"), context)? + .get(js_str!("index"), context)? .to_integer_or_infinity(context)?; // f. Set position to the result of clamping position between 0 and lengthS. @@ -1534,7 +1557,7 @@ impl RegExp { } // j. Let namedCaptures be ? Get(result, "groups"). - let mut named_captures = result.get(utf16!("groups"), context)?; + let mut named_captures = result.get(js_str!("groups"), context)?; let replacement = match replace_value { // k. If functionalReplace is true, then @@ -1587,8 +1610,8 @@ impl RegExp { // In such cases, the corresponding substitution is ignored. // ii. Set accumulatedResult to the string-concatenation of accumulatedResult, the substring of S from nextSourcePosition to position, and replacement. - accumulated_result.extend_from_slice(&s[next_source_position..position]); - accumulated_result.extend_from_slice(&replacement); + accumulated_result.extend(s.get_expect(next_source_position..position).iter()); + accumulated_result.extend(replacement.iter()); // iii. Set nextSourcePosition to position + matchLength. next_source_position = position + match_length; @@ -1597,11 +1620,15 @@ impl RegExp { // 16. If nextSourcePosition ≥ lengthS, return accumulatedResult. if next_source_position >= length_s { - return Ok(js_string!(accumulated_result).into()); + return Ok(js_string!(&accumulated_result[..]).into()); } // 17. Return the string-concatenation of accumulatedResult and the substring of S from nextSourcePosition. - Ok(js_string!(&accumulated_result[..], &s[next_source_position..]).into()) + Ok(js_string!( + &JsString::from(&accumulated_result[..]), + s.get_expect(next_source_position..) + ) + .into()) } /// `RegExp.prototype[ @@search ]( string )` @@ -1630,31 +1657,31 @@ impl RegExp { let arg_str = args.get_or_undefined(0).to_string(context)?; // 4. Let previousLastIndex be ? Get(rx, "lastIndex"). - let previous_last_index = rx.get(utf16!("lastIndex"), context)?; + let previous_last_index = rx.get(js_str!("lastIndex"), context)?; // 5. If SameValue(previousLastIndex, +0𝔽) is false, then if !JsValue::same_value(&previous_last_index, &JsValue::new(0)) { // a. Perform ? Set(rx, "lastIndex", +0𝔽, true). - rx.set(utf16!("lastIndex"), 0, true, context)?; + rx.set(js_str!("lastIndex"), 0, true, context)?; } // 6. Let result be ? RegExpExec(rx, S). let result = Self::abstract_exec(rx, arg_str, context)?; // 7. Let currentLastIndex be ? Get(rx, "lastIndex"). - let current_last_index = rx.get(utf16!("lastIndex"), context)?; + let current_last_index = rx.get(js_str!("lastIndex"), context)?; // 8. If SameValue(currentLastIndex, previousLastIndex) is false, then if !JsValue::same_value(¤t_last_index, &previous_last_index) { // a. Perform ? Set(rx, "lastIndex", previousLastIndex, true). - rx.set(utf16!("lastIndex"), previous_last_index, true, context)?; + rx.set(js_str!("lastIndex"), previous_last_index, true, context)?; } // 9. If result is null, return -1𝔽. // 10. Return ? Get(result, "index"). result.map_or_else( || Ok(JsValue::new(-1)), - |result| result.get(utf16!("index"), context), + |result| result.get(js_str!("index"), context), ) } @@ -1687,18 +1714,18 @@ impl RegExp { let constructor = rx.species_constructor(StandardConstructors::regexp, context)?; // 5. Let flags be ? ToString(? Get(rx, "flags")). - let flags = rx.get(utf16!("flags"), context)?.to_string(context)?; + let flags = rx.get(js_str!("flags"), context)?.to_string(context)?; // 6. If flags contains "u", let unicodeMatching be true. // 7. Else, let unicodeMatching be false. - let unicode = flags.contains(&('u' as u16)); + let unicode = flags.contains(b'u'); // 8. If flags contains "y", let newFlags be flags. // 9. Else, let newFlags be the string-concatenation of flags and "y". - let new_flags = if flags.contains(&('y' as u16)) { + let new_flags = if flags.contains(b'y') { flags } else { - js_string!(&flags, utf16!("y")) + js_string!(&flags, js_str!("y")) }; // 10. Let splitter be ? Construct(C, « rx, newFlags »). @@ -1756,7 +1783,7 @@ impl RegExp { // 19. Repeat, while q < size, while q < size { // a. Perform ? Set(splitter, "lastIndex", 𝔽(q), true). - splitter.set(utf16!("lastIndex"), JsValue::new(q), true, context)?; + splitter.set(js_str!("lastIndex"), JsValue::new(q), true, context)?; // b. Let z be ? RegExpExec(splitter, S). let result = Self::abstract_exec(&splitter, arg_str.clone(), context)?; @@ -1766,7 +1793,7 @@ impl RegExp { if let Some(result) = result { // i. Let e be ℝ(? ToLength(? Get(splitter, "lastIndex"))). let mut e = splitter - .get(utf16!("lastIndex"), context)? + .get(js_str!("lastIndex"), context)? .to_length(context)?; // ii. Set e to min(e, size). @@ -1778,7 +1805,7 @@ impl RegExp { q = advance_string_index(&arg_str, q, unicode); } else { // 1. Let T be the substring of S from p to q. - let arg_str_substring = js_string!(&arg_str[p as usize..q as usize]); + let arg_str_substring = arg_str.get_expect(p as usize..q as usize); // 2. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(lengthA)), T). a.create_data_property_or_throw(length_a, arg_str_substring, context) @@ -1829,7 +1856,7 @@ impl RegExp { } // 20. Let T be the substring of S from p to size. - let arg_str_substring = js_string!(&arg_str[p as usize..size as usize]); + let arg_str_substring = arg_str.get_expect(p as usize..size as usize); // 21. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(lengthA)), T). a.create_data_property_or_throw(length_a, arg_str_substring, context) @@ -1890,7 +1917,7 @@ impl RegExp { .expect("already checked that the object was a RegExp") = regexp; } - this.set(utf16!("lastIndex"), 0, true, context)?; + this.set(js_str!("lastIndex"), 0, true, context)?; Ok(this.into()) } diff --git a/core/engine/src/builtins/regexp/regexp_string_iterator.rs b/core/engine/src/builtins/regexp/regexp_string_iterator.rs index 79772ab710..2cb8620a8a 100644 --- a/core/engine/src/builtins/regexp/regexp_string_iterator.rs +++ b/core/engine/src/builtins/regexp/regexp_string_iterator.rs @@ -18,11 +18,11 @@ use crate::{ object::JsObject, property::Attribute, realm::Realm, - string::utf16, symbol::JsSymbol, Context, JsData, JsResult, JsString, JsValue, }; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; use regexp::{advance_string_index, RegExp}; @@ -160,7 +160,7 @@ impl RegExpStringIterator { // 1. Let thisIndex be ℝ(? ToLength(? Get(R, "lastIndex"))). let this_index = iterator .matcher - .get(utf16!("lastIndex"), context)? + .get(js_str!("lastIndex"), context)? .to_length(context)?; // 2. Let nextIndex be ! AdvanceStringIndex(S, thisIndex, fullUnicode). @@ -170,7 +170,7 @@ impl RegExpStringIterator { // 3. Perform ? Set(R, "lastIndex", 𝔽(nextIndex), true). iterator .matcher - .set(utf16!("lastIndex"), next_index, true, context)?; + .set(js_str!("lastIndex"), next_index, true, context)?; } // vi. Perform ? Yield(match). diff --git a/core/engine/src/builtins/regexp/tests.rs b/core/engine/src/builtins/regexp/tests.rs index ac711fd0ec..dd0c45a2e0 100644 --- a/core/engine/src/builtins/regexp/tests.rs +++ b/core/engine/src/builtins/regexp/tests.rs @@ -2,6 +2,7 @@ use crate::{ js_string, native_function::NativeFunctionObject, run_test_actions, JsNativeErrorKind, JsObject, JsValue, TestAction, }; +use boa_macros::js_str; use indoc::indoc; #[test] @@ -36,7 +37,7 @@ fn species() { // return-value TestAction::assert("Object.is(accessor.call(thisVal), thisVal)"), // symbol-species-name - TestAction::assert_eq("name.value", js_string!("get [Symbol.species]")), + TestAction::assert_eq("name.value", js_str!("get [Symbol.species]")), TestAction::assert("!name.enumerable"), TestAction::assert("!name.writable"), TestAction::assert("name.configurable"), @@ -65,7 +66,7 @@ fn flags() { TestAction::assert("!re_gi.dotAll"), TestAction::assert("!re_gi.unicode"), TestAction::assert("!re_gi.sticky"), - TestAction::assert_eq("re_gi.flags", js_string!("gi")), + TestAction::assert_eq("re_gi.flags", js_str!("gi")), // TestAction::assert("!re_sm.global"), TestAction::assert("!re_sm.ignoreCase"), @@ -73,7 +74,7 @@ fn flags() { TestAction::assert("re_sm.dotAll"), TestAction::assert("!re_sm.unicode"), TestAction::assert("!re_sm.sticky"), - TestAction::assert_eq("re_sm.flags", js_string!("ms")), + TestAction::assert_eq("re_sm.flags", js_str!("ms")), // TestAction::assert("!re_u.global"), TestAction::assert("!re_u.ignoreCase"), @@ -81,7 +82,7 @@ fn flags() { TestAction::assert("!re_u.dotAll"), TestAction::assert("re_u.unicode"), TestAction::assert("!re_u.sticky"), - TestAction::assert_eq("re_u.flags", js_string!("u")), + TestAction::assert_eq("re_u.flags", js_str!("u")), ]); } @@ -114,7 +115,7 @@ fn exec() { TestAction::assert_eq("result.index", 4), TestAction::assert_eq( "result.input", - js_string!("The Quick Brown Fox Jumps Over The Lazy Dog"), + js_str!("The Quick Brown Fox Jumps Over The Lazy Dog"), ), ]); } @@ -138,8 +139,8 @@ fn no_panic_on_parse_fail() { #[test] fn to_string() { run_test_actions([ - TestAction::assert_eq("(new RegExp('a+b+c')).toString()", js_string!("/a+b+c/")), - TestAction::assert_eq("(new RegExp('bar', 'g')).toString()", js_string!("/bar/g")), + TestAction::assert_eq("(new RegExp('a+b+c')).toString()", js_str!("/a+b+c/")), + TestAction::assert_eq("(new RegExp('bar', 'g')).toString()", js_str!("/bar/g")), TestAction::assert_eq(r"(new RegExp('\\n', 'g')).toString()", js_string!(r"/\n/g")), TestAction::assert_eq(r"/\n/g.toString()", js_string!(r"/\n/g")), TestAction::assert_eq(r"/,\;/.toString()", js_string!(r"/,\;/")), @@ -164,7 +165,7 @@ fn search() { TestAction::assert("!length.writable"), TestAction::assert("length.configurable"), // name - TestAction::assert_eq("name.value", js_string!("[Symbol.search]")), + TestAction::assert_eq("name.value", js_str!("[Symbol.search]")), TestAction::assert("!name.enumerable"), TestAction::assert("!name.writable"), TestAction::assert("name.configurable"), diff --git a/core/engine/src/builtins/set/mod.rs b/core/engine/src/builtins/set/mod.rs index 4c3d52d208..a82f8c973f 100644 --- a/core/engine/src/builtins/set/mod.rs +++ b/core/engine/src/builtins/set/mod.rs @@ -26,10 +26,11 @@ use crate::{ object::{internal_methods::get_prototype_from_constructor, JsObject}, property::{Attribute, PropertyNameKind}, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, symbol::JsSymbol, Context, JsArgs, JsResult, JsString, JsValue, }; +use boa_macros::js_str; use boa_profiler::Profiler; use num_traits::Zero; @@ -71,18 +72,18 @@ impl IntrinsicObject for Set { .method(Self::for_each, js_string!("forEach"), 1) .method(Self::has, js_string!("has"), 1) .property( - utf16!("keys"), + js_string!("keys"), values_function.clone(), Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .accessor( - utf16!("size"), + js_string!("size"), Some(size_getter), None, Attribute::CONFIGURABLE, ) .property( - utf16!("values"), + js_string!("values"), values_function.clone(), Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) @@ -138,7 +139,7 @@ impl BuiltInConstructor for Set { } // 5. Let adder be ? Get(set, "add"). - let adder = set.get(utf16!("add"), context)?; + let adder = set.get(js_str!("add"), context)?; // 6. If IsCallable(adder) is false, throw a TypeError exception. let adder = adder.as_callable().ok_or_else(|| { diff --git a/core/engine/src/builtins/string/mod.rs b/core/engine/src/builtins/string/mod.rs index 18b30bae14..a49f08a186 100644 --- a/core/engine/src/builtins/string/mod.rs +++ b/core/engine/src/builtins/string/mod.rs @@ -17,12 +17,12 @@ use crate::{ object::{internal_methods::get_prototype_from_constructor, JsObject}, property::{Attribute, PropertyDescriptor}, realm::Realm, - string::{common::StaticJsStrings, utf16}, - string::{CodePoint, Utf16Trim}, + string::{common::StaticJsStrings, CodePoint}, symbol::JsSymbol, value::IntegerOrInfinity, Context, JsArgs, JsResult, JsString, JsValue, }; +use boa_macros::{js_str, utf16}; use boa_profiler::Profiler; use icu_normalizer::{ComposingNormalizer, DecomposingNormalizer}; use std::cmp::{max, min}; @@ -32,6 +32,9 @@ use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; mod string_iterator; pub(crate) use string_iterator::StringIterator; +#[cfg(feature = "annex-b")] +pub use crate::JsStr; + /// The set of normalizers required for the `String.prototype.normalize` function. #[derive(Debug)] pub(crate) struct StringNormalizers { @@ -70,6 +73,23 @@ pub(crate) const fn is_trimmable_whitespace(c: char) -> bool { ) } +/// Helper function to check if a `u8` latin1 character is trimmable. +pub(crate) const fn is_trimmable_whitespace_latin1(c: u8) -> bool { + // The rust implementation of `trim` does not regard the same characters whitespace as ecma standard does + // + // Rust uses \p{White_Space} by default, which also includes: + // `\u{0085}' (next line) + // And does not include: + // '\u{FEFF}' (zero width non-breaking space) + // Explicit whitespace: https://tc39.es/ecma262/#sec-white-space + matches!( + c, + 0x09 | 0x0B | 0x0C | 0x20 | 0xA0 | + // Line terminators: https://tc39.es/ecma262/#sec-line-terminators + 0x0A | 0x0D + ) +} + /// JavaScript `String` implementation. #[derive(Debug, Clone, Copy)] pub(crate) struct String; @@ -382,7 +402,7 @@ impl String { let cooked = args.get_or_undefined(0).to_object(context)?; // 3. Let raw be ? ToObject(? Get(cooked, "raw")). - let raw = cooked.get(utf16!("raw"), context)?.to_object(context)?; + let raw = cooked.get(js_str!("raw"), context)?.to_object(context)?; // 4. Let literalSegments be ? LengthOfArrayLike(raw). let literal_segments = raw.length_of_array_like(context)?; @@ -407,13 +427,13 @@ impl String { let next_seg = raw.get(next_key, context)?.to_string(context)?; // c. Append the code unit elements of nextSeg to the end of stringElements. - string_elements.extend(next_seg.iter().copied()); + string_elements.extend(next_seg.iter()); // d. If nextIndex + 1 = literalSegments, then if next_index + 1 == literal_segments { // i. Return the String value whose code units are the elements in the List stringElements. // If stringElements has no elements, the empty String is returned. - return Ok(js_string!(string_elements).into()); + return Ok(js_string!(&string_elements[..]).into()); } // e. If nextIndex < numberOfSubstitutions, let next be substitutions[nextIndex]. @@ -429,7 +449,7 @@ impl String { let next_sub = next.to_string(context)?; // h. Append the code unit elements of nextSub to the end of stringElements. - string_elements.extend(next_sub.iter().copied()); + string_elements.extend(next_sub.iter()); // i. Set nextIndex to nextIndex + 1. next_index += 1; @@ -461,7 +481,7 @@ impl String { } // 3. Return result. - Ok(js_string!(result).into()) + Ok(js_string!(&result[..]).into()) } /// `String.prototype.toString ( )` @@ -511,7 +531,7 @@ impl String { // 6. Return the substring of S from position to position + 1. IntegerOrInfinity::Integer(i) if i >= 0 && i < string.len() as i64 => { let i = i as usize; - Ok(js_string!(&string[i..=i]).into()) + Ok(js_string!(string.get_expect(i..=i)).into()) } // 5. If position < 0 or position ≥ size, return the empty String. _ => Ok(js_string!().into()), @@ -553,7 +573,7 @@ impl String { }; // 8. Return the substring of S from k to k + 1. - Ok(js_string!(&s[k..=k]).into()) + Ok(js_string!(s.get_expect(k..=k)).into()) } /// `String.prototype.codePointAt( index )` @@ -629,9 +649,11 @@ impl String { match position { // 4. Let size be the length of S. - IntegerOrInfinity::Integer(i) if i >= 0 && i < string.len() as i64 => { + IntegerOrInfinity::Integer(i) if i >= 0 => { // 6. Return the Number value for the numeric value of the code unit at index position within the String S. - Ok(u32::from(string[i as usize]).into()) + Ok(string + .get(i as usize) + .map_or_else(JsValue::nan, JsValue::from)) } // 5. If position < 0 or position ≥ size, return NaN. _ => Ok(JsValue::nan()), @@ -708,14 +730,14 @@ impl String { return Ok(js_string!().into()); } let n = n as usize; - let mut result = Vec::with_capacity(n * len); + let mut result = Vec::with_capacity(n); - std::iter::repeat(&string[..]) + std::iter::repeat(string.as_str()) .take(n) - .for_each(|s| result.extend_from_slice(s)); + .for_each(|s| result.push(s)); // 6. Return the String value that is made from n copies of S appended together. - Ok(js_string!(result).into()) + Ok(JsString::concat_array(&result).into()) } // 5. If n is 0, return the empty String. IntegerOrInfinity::Integer(0) => Ok(js_string!().into()), @@ -790,7 +812,7 @@ impl String { Ok(js_string!().into()) } else { // 13. Return the substring of S from from to to. - Ok(js_string!(&string[from..to]).into()) + Ok(js_string!(string.get_expect(from..to)).into()) } } @@ -859,7 +881,7 @@ impl String { // 14. Return ! SameValueNonNumeric(substring, searchStr). // `SameValueNonNumeric` forwards to `==`, so directly check // equality to avoid converting to `JsValue` - Ok(JsValue::new(search_string == string[start..end])) + Ok(JsValue::new(search_string == string.get_expect(start..end))) } } @@ -922,7 +944,7 @@ impl String { // 14. Return ! SameValueNonNumeric(substring, searchStr). // `SameValueNonNumeric` forwards to `==`, so directly check // equality to avoid converting to `JsValue` - Ok(JsValue::new(search_str == string[start..end])) + Ok(JsValue::new(search_str == string.get_expect(start..end))) } else { // 12. If start < 0, return false. Ok(false.into()) @@ -973,7 +995,7 @@ impl String { // 10. Let index be ! StringIndexOf(S, searchStr, start). // 11. If index is not -1, return true. // 12. Return false. - Ok(string.index_of(&search_str, start).is_some().into()) + Ok(string.index_of(search_str.as_str(), start).is_some().into()) } /// `String.prototype.replace( regexp|substr, newSubstr|function )` @@ -1042,12 +1064,12 @@ impl String { // 8. Let position be ! StringIndexOf(string, searchString, 0). // 9. If position is -1, return string. - let Some(position) = string.index_of(&search_string, 0) else { + let Some(position) = string.index_of(search_string.as_str(), 0) else { return Ok(string.into()); }; // 10. Let preserved be the substring of string from 0 to position. - let preserved = &string[..position]; + let preserved = JsString::from(string.get_expect(..position)); let replacement = match replace_value { // 11. If functionalReplace is true, then @@ -1081,7 +1103,12 @@ impl String { }; // 13. Return the string-concatenation of preserved, replacement, and the substring of string from position + searchLength. - Ok(js_string!(preserved, &replacement, &string[position + search_length..]).into()) + Ok(js_string!( + &preserved, + &replacement, + &JsString::from(string.get_expect(position + search_length..)) + ) + .into()) } /// `22.1.3.18 String.prototype.replaceAll ( searchValue, replaceValue )` @@ -1117,13 +1144,13 @@ impl String { // b. If isRegExp is true, then if let Some(obj) = RegExp::is_reg_exp(search_value, context)? { // i. Let flags be ? Get(searchValue, "flags"). - let flags = obj.get(utf16!("flags"), context)?; + let flags = obj.get(js_str!("flags"), context)?; // ii. Perform ? RequireObjectCoercible(flags). flags.require_object_coercible()?; // iii. If ? ToString(flags) does not contain "g", throw a TypeError exception. - if !flags.to_string(context)?.contains(&u16::from(b'g')) { + if !flags.to_string(context)?.contains(b'g') { return Err(JsNativeError::typ() .with_message( "String.prototype.replaceAll called with a non-global RegExp argument", @@ -1167,7 +1194,7 @@ impl String { let mut match_positions = Vec::new(); // 10. Let position be ! StringIndexOf(string, searchString, 0). - let mut position = string.index_of(&search_string, 0); + let mut position = string.index_of(search_string.as_str(), 0); // 11. Repeat, while position is not -1, while let Some(p) = position { @@ -1175,7 +1202,7 @@ impl String { match_positions.push(p); // b. Set position to ! StringIndexOf(string, searchString, position + advanceBy). - position = string.index_of(&search_string, p + advance_by); + position = string.index_of(search_string.as_str(), p + advance_by); } // 12. Let endOfLastMatch be 0. @@ -1187,7 +1214,7 @@ impl String { // 14. For each element p of matchPositions, do for p in match_positions { // a. Let preserved be the substring of string from endOfLastMatch to p. - let preserved = &string[end_of_last_match..p]; + let preserved = string.get_expect(end_of_last_match..p); // c. Else, let replacement = match replace { @@ -1222,8 +1249,8 @@ impl String { }; // d. Set result to the string-concatenation of result, preserved, and replacement. - result.extend_from_slice(preserved); - result.extend_from_slice(&replacement); + result.extend(preserved.iter()); + result.extend(replacement.iter()); // e. Set endOfLastMatch to p + searchLength. end_of_last_match = p + search_length; @@ -1232,11 +1259,11 @@ impl String { // 15. If endOfLastMatch < the length of string, then if end_of_last_match < string.len() { // a. Set result to the string-concatenation of result and the substring of string from endOfLastMatch. - result.extend_from_slice(&string[end_of_last_match..]); + result.extend(string.get_expect(end_of_last_match..).iter()); } // 16. Return result. - Ok(js_string!(result).into()) + Ok(js_string!(&result[..]).into()) } /// `String.prototype.indexOf( searchValue[, fromIndex] )` @@ -1278,7 +1305,7 @@ impl String { // 8. Return 𝔽(! StringIndexOf(S, searchStr, start)). Ok(string - .index_of(&search_str, start) + .index_of(search_str.as_str(), start) .map_or(-1, |i| i as i64) .into()) } @@ -1368,10 +1395,10 @@ impl String { // 11. For each non-negative integer i starting with start such that i ≤ len - searchLen, in descending order, do for i in (0..=min(start, end)).rev() { // a. Let candidate be the substring of S from i to i + searchLen. - let candidate = &string[i..i + search_len]; + let candidate = string.get_expect(i..i + search_len); // b. If candidate is the same sequence of code units as searchStr, return 𝔽(i). - if candidate == &search_str { + if candidate == search_str { return Ok(i.into()); } } @@ -1431,6 +1458,9 @@ impl String { .expect("constructor must return a `Collator` object") .collator(); + let s = s.iter().collect::>(); + let that_value = that_value.iter().collect::>(); + collator.compare_utf16(&s, &that_value) as i8 } @@ -1544,15 +1574,15 @@ impl String { } }; - let truncated_string_filler = filler.repeat(repetitions as usize); - let truncated_string_filler = &truncated_string_filler[..fill_len as usize]; + let truncated_string_filler = filler.to_vec().repeat(repetitions as usize); + let truncated_string_filler = JsString::from(&truncated_string_filler[..fill_len as usize]); // 10. If placement is start, return the string-concatenation of truncatedStringFiller and S. if placement == Placement::Start { - Ok(js_string!(truncated_string_filler, &string).into()) + Ok(js_string!(&truncated_string_filler, &string).into()) } else { // 11. Else, return the string-concatenation of S and truncatedStringFiller. - Ok(js_string!(&string, truncated_string_filler).into()) + Ok(js_string!(&string, &truncated_string_filler).into()) } } @@ -1876,7 +1906,7 @@ impl String { let to = max(final_start, final_end); // 10. Return the substring of S from from to to. - Ok(js_string!(&string[from..to]).into()) + Ok(js_string!(string.get_expect(from..to)).into()) } /// `String.prototype.split ( separator, limit )` @@ -1946,9 +1976,10 @@ impl String { // b. Let codeUnits be a List consisting of the sequence of code units that are the elements of head. let head = this_str .get(..lim) - .unwrap_or(&this_str[..]) + .unwrap_or(this_str.as_str()) .iter() - .map(|code| js_string!(std::slice::from_ref(code)).into()); + .map(|code| js_string!(std::slice::from_ref(&code)).into()); + // c. Return ! CreateArrayFromList(codeUnits). return Ok(Array::create_array_from_list(head, context).into()); } @@ -1965,13 +1996,13 @@ impl String { let mut i = 0; // 13. Let j be ! StringIndexOf(S, R, 0). - let mut j = this_str.index_of(&separator_str, 0); + let mut j = this_str.index_of(separator_str.as_str(), 0); // 14. Repeat, while j is not -1 while let Some(index) = j { // a. Let T be the substring of S from i to j. // b. Append T as the last element of substrings. - substrings.push(js_string!(&this_str[i..index])); + substrings.push(this_str.get_expect(i..index).into()); // c. If the number of elements of substrings is lim, return ! CreateArrayFromList(substrings). if substrings.len() == lim { @@ -1985,12 +2016,12 @@ impl String { i = index + separator_length; // e. Set j to ! StringIndexOf(S, R, i). - j = this_str.index_of(&separator_str, i); + j = this_str.index_of(separator_str.as_str(), i); } // 15. Let T be the substring of S from i. // 16. Append T to substrings. - substrings.push(js_string!(&this_str[i..])); + substrings.push(JsString::from(this_str.get_expect(i..))); // 17. Return ! CreateArrayFromList(substrings). Ok( @@ -2045,13 +2076,13 @@ impl String { // b. If isRegExp is true, then if let Some(regexp) = RegExp::is_reg_exp(regexp, context)? { // i. Let flags be ? Get(regexp, "flags"). - let flags = regexp.get(utf16!("flags"), context)?; + let flags = regexp.get(js_str!("flags"), context)?; // ii. Perform ? RequireObjectCoercible(flags). flags.require_object_coercible()?; // iii. If ? ToString(flags) does not contain "g", throw a TypeError exception. - if !flags.to_string(context)?.contains(&u16::from(b'g')) { + if !flags.to_string(context)?.contains(b'g') { return Err(JsNativeError::typ() .with_message( "String.prototype.matchAll called with a non-global RegExp argument", @@ -2071,7 +2102,7 @@ impl String { let s = o.to_string(context)?; // 4. Let rx be ? RegExpCreate(regexp, "g"). - let rx = RegExp::create(regexp, &JsValue::new(js_string!("g")), context)?; + let rx = RegExp::create(regexp, &JsValue::new(js_str!("g")), context)?; // 5. Return ? Invoke(rx, @@matchAll, « S »). rx.invoke(JsSymbol::match_all(), &[JsValue::new(s)], context) @@ -2115,10 +2146,10 @@ impl String { &JsValue::Undefined => Normalization::Nfc, // 4. Else, let f be ? ToString(form). f => match f.to_string(context)? { - ntype if &ntype == utf16!("NFC") => Normalization::Nfc, - ntype if &ntype == utf16!("NFD") => Normalization::Nfd, - ntype if &ntype == utf16!("NFKC") => Normalization::Nfkc, - ntype if &ntype == utf16!("NFKD") => Normalization::Nfkd, + ntype if &ntype == "NFC" => Normalization::Nfc, + ntype if &ntype == "NFD" => Normalization::Nfd, + ntype if &ntype == "NFKC" => Normalization::Nfkc, + ntype if &ntype == "NFKD" => Normalization::Nfkd, // 5. If f is not one of "NFC", "NFD", "NFKC", or "NFKD", throw a RangeError exception. _ => { return Err(JsNativeError::range() @@ -2147,6 +2178,8 @@ impl String { } }; + let s = s.iter().collect::>(); + let result = match normalization { Normalization::Nfc => normalizers.nfc.normalize_utf16(&s), Normalization::Nfd => normalizers.nfd.normalize_utf16(&s), @@ -2155,7 +2188,7 @@ impl String { }; // 7. Return ns. - Ok(js_string!(result).into()) + Ok(js_string!(&result[..]).into()) } /// `String.prototype.search( regexp )` @@ -2276,7 +2309,7 @@ impl String { // 11. Return the substring of S from intStart to intEnd. if let Some(substr) = s.get(int_start..int_end) { - Ok(js_string!(substr).into()) + Ok(substr.into()) } else { Ok(js_string!().into()) } @@ -2290,8 +2323,8 @@ impl String { /// [spec]: https://tc39.es/ecma262/#sec-createhtml pub(crate) fn create_html( string: &JsValue, - tag: &[u16], - attribute_and_value: Option<(&[u16], &JsValue)>, + tag: JsStr<'_>, + attribute_and_value: Option<(JsStr<'_>, &JsValue)>, context: &mut Context, ) -> JsResult { // 1. Let str be ? RequireObjectCoercible(string). @@ -2301,7 +2334,7 @@ impl String { let s = str.to_string(context)?; // 3. Let p1 be the string-concatenation of "<" and tag. - let mut p1 = JsString::concat_array(&[utf16!("<"), tag]); + let mut p1 = js_string!(js_str!("<"), tag); // 4. If attribute is not the empty String, then if let Some((attribute, value)) = attribute_and_value { @@ -2312,7 +2345,7 @@ impl String { // of the code unit 0x0022 (QUOTATION MARK) in V has been replaced with the six // code unit sequence """. let mut escaped_v = Vec::with_capacity(v.len()); - for c in v.as_slice().iter().copied() { + for c in &v { if c == 0x0022 { escaped_v.extend(utf16!(""")); continue; @@ -2328,27 +2361,20 @@ impl String { // the code unit 0x0022 (QUOTATION MARK) // escapedV // the code unit 0x0022 (QUOTATION MARK) - p1 = JsString::concat_array(&[ - p1.as_slice(), - utf16!(" "), + p1 = js_string!( + &p1, + js_str!(" "), attribute, - utf16!("=\""), - escaped_v.as_slice(), - utf16!("\""), - ]); + js_str!("=\""), + &JsString::from(&escaped_v[..]), + js_str!("\"") + ); } // 5. Let p2 be the string-concatenation of p1 and ">". // 6. Let p3 be the string-concatenation of p2 and S. // 7. Let p4 be the string-concatenation of p3, "". - let p4 = JsString::concat_array(&[ - p1.as_slice(), - utf16!(">"), - s.as_slice(), - utf16!(""), - ]); + let p4 = js_string!(&p1, js_str!(">"), &s, js_str!("")); // 8. Return p4. Ok(p4.into()) @@ -2372,7 +2398,7 @@ impl String { // 1. Let S be the this value. let s = this; // 2. Return ? CreateHTML(S, "a", "name", name). - Self::create_html(s, utf16!("a"), Some((utf16!("name"), name)), context) + Self::create_html(s, js_str!("a"), Some((js_str!("name"), name)), context) } /// `String.prototype.big( )` @@ -2387,7 +2413,7 @@ impl String { // 1. Let S be the this value. let s = this; // 2. Return ? CreateHTML(S, "big", "", ""). - Self::create_html(s, utf16!("big"), None, context) + Self::create_html(s, js_str!("big"), None, context) } /// `String.prototype.blink( )` @@ -2402,7 +2428,7 @@ impl String { // 1. Let S be the this value. let s = this; // 2. Return ? CreateHTML(S, "blink", "", ""). - Self::create_html(s, utf16!("blink"), None, context) + Self::create_html(s, js_str!("blink"), None, context) } /// `String.prototype.bold( )` @@ -2417,7 +2443,7 @@ impl String { // 1. Let S be the this value. let s = this; // 2. Return ? CreateHTML(S, "b", "", ""). - Self::create_html(s, utf16!("b"), None, context) + Self::create_html(s, js_str!("b"), None, context) } /// `String.prototype.fixed( )` @@ -2432,7 +2458,7 @@ impl String { // 1. Let S be the this value. let s = this; // 2. Return ? CreateHTML(S, "big", "", ""). - Self::create_html(s, utf16!("tt"), None, context) + Self::create_html(s, js_str!("tt"), None, context) } /// `String.prototype.fontcolor( color )` @@ -2453,7 +2479,7 @@ impl String { // 1. Let S be the this value. let s = this; // 2. Return ? CreateHTML(S, "font", "color", color). - Self::create_html(s, utf16!("font"), Some((utf16!("color"), color)), context) + Self::create_html(s, js_str!("font"), Some((js_str!("color"), color)), context) } /// `String.prototype.fontsize( size )` @@ -2474,7 +2500,7 @@ impl String { // 1. Let S be the this value. let s = this; // 2. Return ? CreateHTML(S, "font", "size", size). - Self::create_html(s, utf16!("font"), Some((utf16!("size"), size)), context) + Self::create_html(s, js_str!("font"), Some((js_str!("size"), size)), context) } /// `String.prototype.italics( )` @@ -2493,7 +2519,7 @@ impl String { // 1. Let S be the this value. let s = this; // 2. Return ? CreateHTML(S, "i", "", ""). - Self::create_html(s, utf16!("i"), None, context) + Self::create_html(s, js_str!("i"), None, context) } /// `String.prototype.link( url )` @@ -2514,7 +2540,7 @@ impl String { // 1. Let S be the this value. let s = this; // 2. Return ? CreateHTML(S, "a", "href", url). - Self::create_html(s, utf16!("a"), Some((utf16!("href"), url)), context) + Self::create_html(s, js_str!("a"), Some((js_str!("href"), url)), context) } /// `String.prototype.small( )` @@ -2529,7 +2555,7 @@ impl String { // 1. Let S be the this value. let s = this; // 2. Return ? CreateHTML(S, "small", "", ""). - Self::create_html(s, utf16!("small"), None, context) + Self::create_html(s, js_str!("small"), None, context) } /// `String.prototype.strike( )` @@ -2548,7 +2574,7 @@ impl String { // 1. Let S be the this value. let s = this; // 2. Return ? CreateHTML(S, "strike", "", ""). - Self::create_html(s, utf16!("strike"), None, context) + Self::create_html(s, js_str!("strike"), None, context) } /// `String.prototype.sub( )` @@ -2563,7 +2589,7 @@ impl String { // 1. Let S be the this value. let s = this; // 2. Return ? CreateHTML(S, "sub", "", ""). - Self::create_html(s, utf16!("sub"), None, context) + Self::create_html(s, js_str!("sub"), None, context) } /// `String.prototype.sup( )` @@ -2578,7 +2604,7 @@ impl String { // 1. Let S be the this value. let s = this; // 2. Return ? CreateHTML(S, "sup", "", ""). - Self::create_html(s, utf16!("sup"), None, context) + Self::create_html(s, js_str!("sup"), None, context) } } @@ -2639,19 +2665,19 @@ pub(crate) fn get_substitution( // $& Some(CodePoint::Unicode('&')) => { // matched - result.extend_from_slice(matched); + result.extend(matched.iter()); } // $` Some(CodePoint::Unicode('`')) => { // The replacement is the substring of str from 0 to position. - result.extend_from_slice(&str[..position]); + result.extend(str.get_expect(..position).iter()); } // $' Some(CodePoint::Unicode('\'')) => { // If tailPos ≥ stringLength, the replacement is the empty String. // Otherwise the replacement is the substring of str from tailPos. if tail_pos < str_length { - result.extend_from_slice(&str[tail_pos..]); + result.extend(str.get_expect(tail_pos..).iter()); } } // $nn @@ -2698,7 +2724,7 @@ pub(crate) fn get_substitution( // a. Let refReplacement be capture. if let Some(capture) = captures.get(index - 1) { if let Some(s) = capture.as_string() { - result.extend_from_slice(s); + result.extend(s.iter()); } } @@ -2742,14 +2768,14 @@ pub(crate) fn get_substitution( // d. Else, } else { // i. Let groupName be the enclosed substring. - let group_name = js_string!(group_name); + let group_name = js_string!(&group_name[..]); // ii. Let capture be ? Get(namedCaptures, groupName). let capture = named_captures.get(group_name, context)?; // iii. If capture is undefined, replace the text through > with the empty String. // iv. Otherwise, replace the text through > with ? ToString(capture). if !capture.is_undefined() { - result.extend_from_slice(&capture.to_string(context)?); + result.extend(capture.to_string(context)?.iter()); } } } @@ -2768,5 +2794,5 @@ pub(crate) fn get_substitution( } // 11. Return result. - Ok(js_string!(result)) + Ok(js_string!(&result[..])) } diff --git a/core/engine/src/builtins/string/tests.rs b/core/engine/src/builtins/string/tests.rs index 5adc115c19..80d4e42f34 100644 --- a/core/engine/src/builtins/string/tests.rs +++ b/core/engine/src/builtins/string/tests.rs @@ -1,3 +1,4 @@ +use boa_macros::js_str; use indoc::indoc; use crate::{js_string, run_test_actions, JsNativeErrorKind, JsValue, TestAction}; @@ -56,11 +57,11 @@ fn concat() { "#}), TestAction::assert_eq( "hello.concat(world, nice)", - js_string!("Hello, world! Have a nice day."), + js_str!("Hello, world! Have a nice day."), ), TestAction::assert_eq( "hello + world + nice", - js_string!("Hello, world! Have a nice day."), + js_str!("Hello, world! Have a nice day."), ), ]); } @@ -72,10 +73,7 @@ fn generic_concat() { Number.prototype.concat = String.prototype.concat; let number = new Number(100); "#}), - TestAction::assert_eq( - "number.concat(' - 50', ' = 50')", - js_string!("100 - 50 = 50"), - ), + TestAction::assert_eq("number.concat(' - 50', ' = 50')", js_str!("100 - 50 = 50")), ]); } @@ -100,8 +98,8 @@ fn repeat() { TestAction::assert_eq("empty.repeat(1)", js_string!()), TestAction::assert_eq("en.repeat(0)", js_string!()), TestAction::assert_eq("zh.repeat(0)", js_string!()), - TestAction::assert_eq("en.repeat(1)", js_string!("english")), - TestAction::assert_eq("zh.repeat(2)", js_string!("中文中文")), + TestAction::assert_eq("en.repeat(1)", js_str!("english")), + TestAction::assert_eq("zh.repeat(2)", js_str!("中文中文")), ]); } @@ -140,9 +138,9 @@ fn repeat_generic() { run_test_actions([ TestAction::run("Number.prototype.repeat = String.prototype.repeat;"), TestAction::assert_eq("(0).repeat(0)", js_string!()), - TestAction::assert_eq("(1).repeat(1)", js_string!("1")), - TestAction::assert_eq("(1).repeat(5)", js_string!("11111")), - TestAction::assert_eq("(12).repeat(3)", js_string!("121212")), + TestAction::assert_eq("(1).repeat(1)", js_str!("1")), + TestAction::assert_eq("(1).repeat(5)", js_str!("11111")), + TestAction::assert_eq("(12).repeat(3)", js_str!("121212")), ]); } @@ -152,7 +150,7 @@ fn replace() { indoc! {r#" "abc".replace("a", "2") "#}, - js_string!("2bc"), + js_str!("2bc"), )]); } @@ -162,7 +160,7 @@ fn replace_no_match() { indoc! {r#" "abc".replace(/d/, "$&$&") "#}, - js_string!("abc"), + js_str!("abc"), )]); } @@ -172,7 +170,7 @@ fn replace_with_capture_groups() { indoc! {r#" "John Smith".replace(/(\w+)\s(\w+)/, '$2, $1') "#}, - js_string!("Smith, John"), + js_str!("Smith, John"), )]); } @@ -183,7 +181,7 @@ fn replace_with_tenth_capture_group() { var re = /(\d)(\d)(\d)(\d)(\d)(\d)(\d)(\d)(\d)(\d)/; "0123456789".replace(re, '$10') "#}, - js_string!("9"), + js_str!("9"), )]); } @@ -199,11 +197,11 @@ fn replace_substitutions() { var end = a.replace(re, " $' "); var no_sub = a.replace(re, " $_ "); "#}), - TestAction::assert_eq("a.replace(re, \" $$ \")", js_string!("one $ three")), - TestAction::assert_eq("a.replace(re, \"$&$&\")", js_string!("one two two three")), - TestAction::assert_eq("a.replace(re, \" $` \")", js_string!("one one three")), - TestAction::assert_eq("a.replace(re, \" $' \")", js_string!("one three three")), - TestAction::assert_eq("a.replace(re, \" $_ \")", js_string!("one $_ three")), + TestAction::assert_eq("a.replace(re, \" $$ \")", js_str!("one $ three")), + TestAction::assert_eq("a.replace(re, \"$&$&\")", js_str!("one two two three")), + TestAction::assert_eq("a.replace(re, \" $` \")", js_str!("one one three")), + TestAction::assert_eq("a.replace(re, \" $' \")", js_str!("one three three")), + TestAction::assert_eq("a.replace(re, \" $_ \")", js_str!("one $_ three")), ]); } @@ -222,11 +220,11 @@ fn replace_with_function() { "#}), TestAction::assert_eq( "\"ecmascript is cool\".replace(/c(o)(o)(l)/, replacer)", - js_string!("ecmascript is awesome!"), + js_str!("ecmascript is awesome!"), ), - TestAction::assert_eq("p1", js_string!("o")), - TestAction::assert_eq("p2", js_string!("o")), - TestAction::assert_eq("p3", js_string!("l")), + TestAction::assert_eq("p1", js_str!("o")), + TestAction::assert_eq("p2", js_str!("o")), + TestAction::assert_eq("p3", js_str!("l")), TestAction::assert_eq("length", 14), ]); } @@ -329,7 +327,7 @@ fn match_all_one() { ) "#}), TestAction::assert_eq("m1.value.index", 0), - TestAction::assert_eq("m1.value.input", js_string!("test1test2")), + TestAction::assert_eq("m1.value.input", js_str!("test1test2")), TestAction::assert_eq("m1.value.groups", JsValue::undefined()), TestAction::assert(indoc! {r#" arrayEquals( @@ -338,7 +336,7 @@ fn match_all_one() { ) "#}), TestAction::assert_eq("m2.value.index", 5), - TestAction::assert_eq("m2.value.input", js_string!("test1test2")), + TestAction::assert_eq("m2.value.input", js_str!("test1test2")), TestAction::assert_eq("m2.value.groups", JsValue::undefined()), TestAction::assert_eq("m3.value", JsValue::undefined()), ]); @@ -366,7 +364,7 @@ fn match_all_two() { ) "#}), TestAction::assert_eq("m1.value.index", 6), - TestAction::assert_eq("m1.value.input", js_string!("table football, foosball")), + TestAction::assert_eq("m1.value.input", js_str!("table football, foosball")), TestAction::assert_eq("m1.value.groups", JsValue::undefined()), TestAction::assert(indoc! {r#" arrayEquals( @@ -375,7 +373,7 @@ fn match_all_two() { ) "#}), TestAction::assert_eq("m2.value.index", 16), - TestAction::assert_eq("m2.value.input", js_string!("table football, foosball")), + TestAction::assert_eq("m2.value.input", js_str!("table football, foosball")), TestAction::assert_eq("m2.value.groups", JsValue::undefined()), TestAction::assert_eq("m3.value", JsValue::undefined()), ]); @@ -401,7 +399,7 @@ fn test_match() { TestAction::assert_eq("result1.index", 4), TestAction::assert_eq( "result1.input", - js_string!("The Quick Brown Fox Jumps Over The Lazy Dog"), + js_str!("The Quick Brown Fox Jumps Over The Lazy Dog"), ), TestAction::assert(indoc! {r#" arrayEquals( @@ -418,7 +416,7 @@ fn test_match() { TestAction::assert_eq("result3.index", 0), TestAction::assert_eq( "result3.input", - js_string!("The Quick Brown Fox Jumps Over The Lazy Dog"), + js_str!("The Quick Brown Fox Jumps Over The Lazy Dog"), ), TestAction::assert(indoc! {r#" arrayEquals( @@ -432,30 +430,30 @@ fn test_match() { #[test] fn trim() { run_test_actions([ - TestAction::assert_eq(r"'Hello'.trim()", js_string!("Hello")), - TestAction::assert_eq(r"' \nHello'.trim()", js_string!("Hello")), - TestAction::assert_eq(r"'Hello \n\r'.trim()", js_string!("Hello")), - TestAction::assert_eq(r"' Hello '.trim()", js_string!("Hello")), + TestAction::assert_eq(r"'Hello'.trim()", js_str!("Hello")), + TestAction::assert_eq(r"' \nHello'.trim()", js_str!("Hello")), + TestAction::assert_eq(r"'Hello \n\r'.trim()", js_str!("Hello")), + TestAction::assert_eq(r"' Hello '.trim()", js_str!("Hello")), ]); } #[test] fn trim_start() { run_test_actions([ - TestAction::assert_eq(r"'Hello'.trimStart()", js_string!("Hello")), - TestAction::assert_eq(r"' \nHello'.trimStart()", js_string!("Hello")), - TestAction::assert_eq(r"'Hello \n\r'.trimStart()", js_string!("Hello \n\r")), - TestAction::assert_eq(r"' Hello '.trimStart()", js_string!("Hello ")), + TestAction::assert_eq(r"'Hello'.trimStart()", js_str!("Hello")), + TestAction::assert_eq(r"' \nHello'.trimStart()", js_str!("Hello")), + TestAction::assert_eq(r"'Hello \n\r'.trimStart()", js_str!("Hello \n\r")), + TestAction::assert_eq(r"' Hello '.trimStart()", js_str!("Hello ")), ]); } #[test] fn trim_end() { run_test_actions([ - TestAction::assert_eq(r"'Hello'.trimEnd()", js_string!("Hello")), - TestAction::assert_eq(r"' \nHello'.trimEnd()", js_string!(" \nHello")), - TestAction::assert_eq(r"'Hello \n\r'.trimEnd()", js_string!("Hello")), - TestAction::assert_eq(r"' Hello '.trimEnd()", js_string!(" Hello")), + TestAction::assert_eq(r"'Hello'.trimEnd()", js_str!("Hello")), + TestAction::assert_eq(r"' \nHello'.trimEnd()", js_str!(" \nHello")), + TestAction::assert_eq(r"'Hello \n\r'.trimEnd()", js_str!("Hello")), + TestAction::assert_eq(r"' Hello '.trimEnd()", js_str!(" Hello")), ]); } @@ -578,7 +576,7 @@ fn split_with_symbol_split_method() { sep_a[Symbol.split] = function(s, limit) { return s + limit.toString(); }; 'hello'.split(sep_a, 10) "#}, - js_string!("hello10"), + js_str!("hello10"), ), TestAction::assert(indoc! {r#" let sep_b = {}; @@ -752,10 +750,10 @@ fn last_index_non_integer_position_argument() { fn char_at() { run_test_actions([ TestAction::assert_eq("'abc'.charAt(-1)", js_string!()), - TestAction::assert_eq("'abc'.charAt(1)", js_string!("b")), + TestAction::assert_eq("'abc'.charAt(1)", js_str!("b")), TestAction::assert_eq("'abc'.charAt(9)", js_string!()), - TestAction::assert_eq("'abc'.charAt()", js_string!("a")), - TestAction::assert_eq("'abc'.charAt(null)", js_string!("a")), + TestAction::assert_eq("'abc'.charAt()", js_str!("a")), + TestAction::assert_eq("'abc'.charAt(null)", js_str!("a")), TestAction::assert_eq(r"'\uDBFF'.charAt(0)", js_string!(&[0xDBFFu16])), ]); } @@ -794,10 +792,10 @@ fn code_point_at() { #[test] fn slice() { run_test_actions([ - TestAction::assert_eq("'abc'.slice()", js_string!("abc")), - TestAction::assert_eq("'abc'.slice(1)", js_string!("bc")), - TestAction::assert_eq("'abc'.slice(-1)", js_string!("c")), - TestAction::assert_eq("'abc'.slice(0, 9)", js_string!("abc")), + TestAction::assert_eq("'abc'.slice()", js_str!("abc")), + TestAction::assert_eq("'abc'.slice(1)", js_str!("bc")), + TestAction::assert_eq("'abc'.slice(-1)", js_str!("c")), + TestAction::assert_eq("'abc'.slice(0, 9)", js_str!("abc")), TestAction::assert_eq("'abc'.slice(9, 10)", js_string!()), ]); } @@ -844,8 +842,8 @@ fn unicode_iter() { fn string_get_property() { run_test_actions([ TestAction::assert_eq("'abc'[-1]", JsValue::undefined()), - TestAction::assert_eq("'abc'[1]", js_string!("b")), - TestAction::assert_eq("'abc'[2]", js_string!("c")), + TestAction::assert_eq("'abc'[1]", js_str!("b")), + TestAction::assert_eq("'abc'[2]", js_str!("c")), TestAction::assert_eq("'abc'[3]", JsValue::undefined()), TestAction::assert_eq("'abc'['foo']", JsValue::undefined()), TestAction::assert_eq("'😀'[0]", js_string!(&[0xD83D])), @@ -866,9 +864,9 @@ fn search() { fn from_code_point() { // Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint run_test_actions([ - TestAction::assert_eq("String.fromCodePoint(42)", js_string!("*")), - TestAction::assert_eq("String.fromCodePoint(65, 90)", js_string!("AZ")), - TestAction::assert_eq("String.fromCodePoint(0x404)", js_string!("Є")), + TestAction::assert_eq("String.fromCodePoint(42)", js_str!("*")), + TestAction::assert_eq("String.fromCodePoint(65, 90)", js_str!("AZ")), + TestAction::assert_eq("String.fromCodePoint(0x404)", js_str!("Є")), TestAction::assert_eq( "String.fromCodePoint(0x2f804)", js_string!(&[0xD87E, 0xDC04]), @@ -884,7 +882,7 @@ fn from_code_point() { ), TestAction::assert_eq( "String.fromCodePoint(9731, 9733, 9842, 0x4F60)", - js_string!("☃★♲你"), + js_str!("☃★♲你"), ), TestAction::assert_native_error( "String.fromCodePoint('_')", diff --git a/core/engine/src/builtins/symbol/mod.rs b/core/engine/src/builtins/symbol/mod.rs index 0efe1080c8..dd50afb104 100644 --- a/core/engine/src/builtins/symbol/mod.rs +++ b/core/engine/src/builtins/symbol/mod.rs @@ -64,8 +64,8 @@ impl GlobalSymbolRegistry { } fn get_or_create_symbol(&self, key: &JsString) -> JsResult { - let slice = &**key; - if let Some(symbol) = self.keys.get(slice) { + let slice = key.iter().collect::>(); + if let Some(symbol) = self.keys.get(&slice[..]) { return Ok(symbol.clone()); } @@ -73,8 +73,10 @@ impl GlobalSymbolRegistry { JsNativeError::range() .with_message("reached the maximum number of symbols that can be created") })?; - self.keys.insert(slice.into(), symbol.clone()); - self.symbols.insert(symbol.clone(), slice.into()); + self.keys + .insert(slice.clone().into_boxed_slice(), symbol.clone()); + self.symbols + .insert(symbol.clone(), slice.into_boxed_slice()); Ok(symbol) } diff --git a/core/engine/src/builtins/symbol/tests.rs b/core/engine/src/builtins/symbol/tests.rs index 1ee48cd21f..415cdd5eb1 100644 --- a/core/engine/src/builtins/symbol/tests.rs +++ b/core/engine/src/builtins/symbol/tests.rs @@ -1,4 +1,5 @@ -use crate::{js_string, run_test_actions, JsValue, TestAction}; +use crate::{run_test_actions, JsValue, TestAction}; +use boa_macros::js_str; use indoc::indoc; #[test] @@ -12,7 +13,7 @@ fn call_symbol_and_check_return_type() { fn print_symbol_expect_description() { run_test_actions([TestAction::assert_eq( "String(Symbol('Hello'))", - js_string!("Symbol(Hello)"), + js_str!("Symbol(Hello)"), )]); } diff --git a/core/engine/src/builtins/temporal/calendar/mod.rs b/core/engine/src/builtins/temporal/calendar/mod.rs index cdfe449d66..e479ec9881 100644 --- a/core/engine/src/builtins/temporal/calendar/mod.rs +++ b/core/engine/src/builtins/temporal/calendar/mod.rs @@ -18,10 +18,11 @@ use crate::{ object::internal_methods::get_prototype_from_constructor, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use boa_gc::{custom_trace, Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; use temporal_rs::{ components::calendar::{ @@ -75,7 +76,12 @@ impl IntrinsicObject for Calendar { Self::NAME, Attribute::CONFIGURABLE, ) - .accessor(utf16!("id"), Some(get_id), None, Attribute::CONFIGURABLE) + .accessor( + js_string!("id"), + Some(get_id), + None, + Attribute::CONFIGURABLE, + ) .static_method(Self::from, js_string!("from"), 1) .method(Self::date_from_fields, js_string!("dateFromFields"), 2) .method( @@ -244,7 +250,7 @@ impl Calendar { }; // 8. Let overflow be ? ToTemporalOverflow(options). - let overflow = get_option(&options, utf16!("overflow"), context)? + let overflow = get_option(&options, js_str!("overflow"), context)? .unwrap_or(ArithmeticOverflow::Constrain); // NOTE: implement the below on the calenar itself @@ -327,7 +333,7 @@ impl Calendar { }; // 7. Let overflow be ? ToTemporalOverflow(options). - let overflow = get_option::(&options, utf16!("overflow"), context)? + let overflow = get_option::(&options, js_str!("overflow"), context)? .unwrap_or(ArithmeticOverflow::Constrain); let result = calendar @@ -403,7 +409,7 @@ impl Calendar { }; // 8. Let overflow be ? ToTemporalOverflow(options). - let overflow = get_option(&options, utf16!("overflow"), context)? + let overflow = get_option(&options, js_str!("overflow"), context)? .unwrap_or(ArithmeticOverflow::Constrain); let result = calendar @@ -439,7 +445,7 @@ impl Calendar { let options_obj = get_options_object(options)?; // 7. Let overflow be ? ToTemporalOverflow(options). - let overflow = get_option(&options_obj, utf16!("overflow"), context)? + let overflow = get_option(&options_obj, js_str!("overflow"), context)? .unwrap_or(ArithmeticOverflow::Constrain); // 8. Let balanceResult be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], @@ -476,7 +482,7 @@ impl Calendar { // 8. If largestUnit is "auto", set largestUnit to "day". let largest_unit = super::options::get_temporal_unit( &options, - utf16!("largestUnit"), + js_str!("largestUnit"), TemporalUnitGroup::Date, None, context, @@ -1033,7 +1039,7 @@ pub(crate) fn get_temporal_calendar_slot_value_with_default( } // 2. Let calendarLike be ? Get(item, "calendar"). - let calendar_like = item.get(utf16!("calendar"), context)?; + let calendar_like = item.get(js_str!("calendar"), context)?; // 3. Return ? ToTemporalCalendarSlotValue(calendarLike, "iso8601"). to_temporal_calendar_slot_value(&calendar_like, context) diff --git a/core/engine/src/builtins/temporal/calendar/object.rs b/core/engine/src/builtins/temporal/calendar/object.rs index 51f0d2c3e3..c50e63b230 100644 --- a/core/engine/src/builtins/temporal/calendar/object.rs +++ b/core/engine/src/builtins/temporal/calendar/object.rs @@ -13,7 +13,7 @@ use crate::{ Context, JsObject, JsString, JsValue, }; -use boa_macros::utf16; +use boa_macros::js_str; use num_traits::ToPrimitive; use plain_date::PlainDate; use plain_date_time::PlainDateTime; @@ -41,7 +41,7 @@ impl CalendarProtocol for JsObject { context: &mut Context, ) -> TemporalResult> { let method = self - .get(utf16!("dateFromFields"), context) + .get(js_str!("dateFromFields"), context) .expect("method must exist on a object that implements the CalendarProtocol."); let fields = JsObject::from_temporal_fields(fields, context) @@ -51,7 +51,7 @@ impl CalendarProtocol for JsObject { overflow_obj .create_data_property_or_throw( - utf16!("overflow"), + js_str!("overflow"), JsString::from(overflow.to_string()), context, ) @@ -88,7 +88,7 @@ impl CalendarProtocol for JsObject { context: &mut Context, ) -> TemporalResult> { let method = self - .get(utf16!("yearMonthFromFields"), context) + .get(js_str!("yearMonthFromFields"), context) .expect("method must exist on a object that implements the CalendarProtocol."); let fields = JsObject::from_temporal_fields(fields, context) @@ -98,7 +98,7 @@ impl CalendarProtocol for JsObject { overflow_obj .create_data_property_or_throw( - utf16!("overflow"), + js_str!("overflow"), JsString::from(overflow.to_string()), context, ) @@ -137,7 +137,7 @@ impl CalendarProtocol for JsObject { context: &mut Context, ) -> TemporalResult> { let method = self - .get(utf16!("yearMonthFromFields"), context) + .get(js_str!("yearMonthFromFields"), context) .expect("method must exist on a object that implements the CalendarProtocol."); let fields = JsObject::from_temporal_fields(fields, context) @@ -147,7 +147,7 @@ impl CalendarProtocol for JsObject { overflow_obj .create_data_property_or_throw( - utf16!("overflow"), + js_str!("overflow"), JsString::from(overflow.to_string()), context, ) @@ -227,7 +227,7 @@ impl CalendarProtocol for JsObject { let date_like = date_like_to_object(date_like, context)?; let method = self - .get(PropertyKey::from(utf16!("year")), context) + .get(PropertyKey::from(js_str!("year")), context) .expect("method must exist on a object that implements the CalendarProtocol."); let val = method @@ -269,7 +269,7 @@ impl CalendarProtocol for JsObject { let date_like = date_like_to_object(date_like, context)?; let method = self - .get(PropertyKey::from(utf16!("month")), context) + .get(PropertyKey::from(js_str!("month")), context) .expect("method must exist on a object that implements the CalendarProtocol."); let val = method @@ -311,7 +311,7 @@ impl CalendarProtocol for JsObject { let date_like = date_like_to_object(date_like, context)?; let method = self - .get(PropertyKey::from(utf16!("monthCode")), context) + .get(PropertyKey::from(js_str!("monthCode")), context) .expect("method must exist on a object that implements the CalendarProtocol."); let val = method @@ -338,7 +338,7 @@ impl CalendarProtocol for JsObject { let date_like = date_like_to_object(date_like, context)?; let method = self - .get(PropertyKey::from(utf16!("day")), context) + .get(PropertyKey::from(js_str!("day")), context) .expect("method must exist on a object that implements the CalendarProtocol."); let val = method @@ -380,7 +380,7 @@ impl CalendarProtocol for JsObject { let date_like = date_like_to_object(date_like, context)?; let method = self - .get(PropertyKey::from(utf16!("dayOfWeek")), context) + .get(PropertyKey::from(js_str!("dayOfWeek")), context) .expect("method must exist on a object that implements the CalendarProtocol."); let val = method @@ -424,7 +424,7 @@ impl CalendarProtocol for JsObject { let date_like = date_like_to_object(date_like, context)?; let method = self - .get(PropertyKey::from(utf16!("dayOfYear")), context) + .get(PropertyKey::from(js_str!("dayOfYear")), context) .expect("method must exist on a object that implements the CalendarProtocol."); let val = method @@ -468,7 +468,7 @@ impl CalendarProtocol for JsObject { let date_like = date_like_to_object(date_like, context)?; let method = self - .get(PropertyKey::from(utf16!("weekOfYear")), context) + .get(PropertyKey::from(js_str!("weekOfYear")), context) .expect("method must exist on a object that implements the CalendarProtocol."); let val = method @@ -512,7 +512,7 @@ impl CalendarProtocol for JsObject { let date_like = date_like_to_object(date_like, context)?; let method = self - .get(PropertyKey::from(utf16!("yearOfWeek")), context) + .get(PropertyKey::from(js_str!("yearOfWeek")), context) .expect("method must exist on a object that implements the CalendarProtocol."); let val = method @@ -549,7 +549,7 @@ impl CalendarProtocol for JsObject { let date_like = date_like_to_object(date_like, context)?; let method = self - .get(PropertyKey::from(utf16!("daysInWeek")), context) + .get(PropertyKey::from(js_str!("daysInWeek")), context) .expect("method must exist on a object that implements the CalendarProtocol."); let val = method @@ -593,7 +593,7 @@ impl CalendarProtocol for JsObject { let date_like = date_like_to_object(date_like, context)?; let method = self - .get(PropertyKey::from(utf16!("daysInMonth")), context) + .get(PropertyKey::from(js_str!("daysInMonth")), context) .expect("method must exist on a object that implements the CalendarProtocol."); let val = method .as_callable() @@ -638,7 +638,7 @@ impl CalendarProtocol for JsObject { let date_like = date_like_to_object(date_like, context)?; let method = self - .get(PropertyKey::from(utf16!("daysInYear")), context) + .get(PropertyKey::from(js_str!("daysInYear")), context) .expect("method must exist on a object that implements the CalendarProtocol."); let val = method @@ -682,7 +682,7 @@ impl CalendarProtocol for JsObject { let date_like = date_like_to_object(date_like, context)?; let method = self - .get(PropertyKey::from(utf16!("monthsInYear")), context) + .get(PropertyKey::from(js_str!("monthsInYear")), context) .expect("method must exist on a object that implements the CalendarProtocol."); let val = method @@ -728,7 +728,7 @@ impl CalendarProtocol for JsObject { let date_like = date_like_to_object(date_like, context)?; let method = self - .get(PropertyKey::from(utf16!("inLeapYear")), context) + .get(PropertyKey::from(js_str!("inLeapYear")), context) .expect("method must exist on a object that implements the CalendarProtocol."); let val = method @@ -753,7 +753,7 @@ impl CalendarProtocol for JsObject { ); let method = self - .get(PropertyKey::from(utf16!("fields")), context) + .get(PropertyKey::from(js_str!("fields")), context) .expect("method must exist on an object that implements the CalendarProtocol."); let result = method @@ -799,7 +799,7 @@ impl CalendarProtocol for JsObject { .map_err(|e| TemporalError::general(e.to_string()))?; let method = self - .get(PropertyKey::from(utf16!("mergeFields")), context) + .get(PropertyKey::from(js_str!("mergeFields")), context) .expect("method must exist on an object that implements the CalendarProtocol."); let value = method @@ -824,7 +824,7 @@ impl CalendarProtocol for JsObject { fn identifier(&self, context: &mut Context) -> TemporalResult { let identifier = self .__get__( - &PropertyKey::from(utf16!("id")), + &PropertyKey::from(js_str!("id")), self.clone().into(), &mut context.into(), ) diff --git a/core/engine/src/builtins/temporal/duration/mod.rs b/core/engine/src/builtins/temporal/duration/mod.rs index 4f0c3dc822..8e242ae44a 100644 --- a/core/engine/src/builtins/temporal/duration/mod.rs +++ b/core/engine/src/builtins/temporal/duration/mod.rs @@ -10,10 +10,11 @@ use crate::{ object::internal_methods::get_prototype_from_constructor, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; use temporal_rs::{ components::Duration as InnerDuration, @@ -108,73 +109,73 @@ impl IntrinsicObject for Duration { Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .accessor( - utf16!("years"), + js_string!("years"), Some(get_years), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("months"), + js_string!("months"), Some(get_months), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("weeks"), + js_string!("weeks"), Some(get_weeks), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("days"), + js_string!("days"), Some(get_days), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("hours"), + js_string!("hours"), Some(get_hours), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("minutes"), + js_string!("minutes"), Some(get_minutes), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("seconds"), + js_string!("seconds"), Some(get_seconds), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("milliseconds"), + js_string!("milliseconds"), Some(get_milliseconds), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("microseconds"), + js_string!("microseconds"), Some(get_microseconds), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("nanoseconds"), + js_string!("nanoseconds"), Some(get_nanoseconds), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("sign"), + js_string!("sign"), Some(get_sign), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("blank"), + js_string!("blank"), Some(is_blank), None, Attribute::CONFIGURABLE, @@ -623,7 +624,7 @@ impl Duration { let new_round_to = JsObject::with_null_proto(); // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString). new_round_to.create_data_property_or_throw( - utf16!("smallestUnit"), + js_str!("smallestUnit"), param_string, context, )?; @@ -644,7 +645,7 @@ impl Duration { // 9. Let largestUnit be ? GetTemporalUnit(roundTo, "largestUnit", datetime, undefined, « "auto" »). let largest_unit = get_temporal_unit( &round_to, - utf16!("largestUnit"), + js_str!("largestUnit"), TemporalUnitGroup::DateTime, Some([TemporalUnit::Auto].into()), context, @@ -661,12 +662,12 @@ impl Duration { // 14. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand"). let rounding_mode = - get_option::(&round_to, utf16!("roundingMode"), context)?; + get_option::(&round_to, js_str!("roundingMode"), context)?; // 15. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", datetime, undefined). let smallest_unit = get_temporal_unit( &round_to, - utf16!("smallestUnit"), + js_str!("smallestUnit"), TemporalUnitGroup::DateTime, None, context, @@ -725,7 +726,7 @@ impl Duration { let total_of = JsObject::with_null_proto(); // c. Perform ! CreateDataPropertyOrThrow(totalOf, "unit", paramString). total_of.create_data_property_or_throw( - utf16!("unit"), + js_str!("unit"), param_string.clone(), context, )?; @@ -748,7 +749,7 @@ impl Duration { // 10. Let unit be ? GetTemporalUnit(totalOf, "unit", datetime, required). let _unit = get_temporal_unit( &total_of, - utf16!("unit"), + js_str!("unit"), TemporalUnitGroup::DateTime, None, context, @@ -905,70 +906,70 @@ pub(crate) fn to_temporal_partial_duration( // 3. NOTE: The following steps read properties and perform independent validation in alphabetical order. // 4. Let days be ? Get(temporalDurationLike, "days"). - let days = unknown_object.get(utf16!("days"), context)?; + let days = unknown_object.get(js_str!("days"), context)?; if !days.is_undefined() { // 5. If days is not undefined, set result.[[Days]] to ? ToIntegerIfIntegral(days). result.set_days(f64::from(to_integer_if_integral(&days, context)?)); } // 6. Let hours be ? Get(temporalDurationLike, "hours"). - let hours = unknown_object.get(utf16!("hours"), context)?; + let hours = unknown_object.get(js_str!("hours"), context)?; // 7. If hours is not undefined, set result.[[Hours]] to ? ToIntegerIfIntegral(hours). if !hours.is_undefined() { result.set_hours(f64::from(to_integer_if_integral(&hours, context)?)); } // 8. Let microseconds be ? Get(temporalDurationLike, "microseconds"). - let microseconds = unknown_object.get(utf16!("microseconds"), context)?; + let microseconds = unknown_object.get(js_str!("microseconds"), context)?; // 9. If microseconds is not undefined, set result.[[Microseconds]] to ? ToIntegerIfIntegral(microseconds). if !microseconds.is_undefined() { result.set_microseconds(f64::from(to_integer_if_integral(µseconds, context)?)); } // 10. Let milliseconds be ? Get(temporalDurationLike, "milliseconds"). - let milliseconds = unknown_object.get(utf16!("milliseconds"), context)?; + let milliseconds = unknown_object.get(js_str!("milliseconds"), context)?; // 11. If milliseconds is not undefined, set result.[[Milliseconds]] to ? ToIntegerIfIntegral(milliseconds). if !milliseconds.is_undefined() { result.set_milliseconds(f64::from(to_integer_if_integral(&milliseconds, context)?)); } // 12. Let minutes be ? Get(temporalDurationLike, "minutes"). - let minutes = unknown_object.get(utf16!("minutes"), context)?; + let minutes = unknown_object.get(js_str!("minutes"), context)?; // 13. If minutes is not undefined, set result.[[Minutes]] to ? ToIntegerIfIntegral(minutes). if !minutes.is_undefined() { result.set_minutes(f64::from(to_integer_if_integral(&minutes, context)?)); } // 14. Let months be ? Get(temporalDurationLike, "months"). - let months = unknown_object.get(utf16!("months"), context)?; + let months = unknown_object.get(js_str!("months"), context)?; // 15. If months is not undefined, set result.[[Months]] to ? ToIntegerIfIntegral(months). if !months.is_undefined() { result.set_months(f64::from(to_integer_if_integral(&months, context)?)); } // 16. Let nanoseconds be ? Get(temporalDurationLike, "nanoseconds"). - let nanoseconds = unknown_object.get(utf16!("nanoseconds"), context)?; + let nanoseconds = unknown_object.get(js_str!("nanoseconds"), context)?; // 17. If nanoseconds is not undefined, set result.[[Nanoseconds]] to ? ToIntegerIfIntegral(nanoseconds). if !nanoseconds.is_undefined() { result.set_nanoseconds(f64::from(to_integer_if_integral(&nanoseconds, context)?)); } // 18. Let seconds be ? Get(temporalDurationLike, "seconds"). - let seconds = unknown_object.get(utf16!("seconds"), context)?; + let seconds = unknown_object.get(js_str!("seconds"), context)?; // 19. If seconds is not undefined, set result.[[Seconds]] to ? ToIntegerIfIntegral(seconds). if !seconds.is_undefined() { result.set_seconds(f64::from(to_integer_if_integral(&seconds, context)?)); } // 20. Let weeks be ? Get(temporalDurationLike, "weeks"). - let weeks = unknown_object.get(utf16!("weeks"), context)?; + let weeks = unknown_object.get(js_str!("weeks"), context)?; // 21. If weeks is not undefined, set result.[[Weeks]] to ? ToIntegerIfIntegral(weeks). if !weeks.is_undefined() { result.set_weeks(f64::from(to_integer_if_integral(&weeks, context)?)); } // 22. Let years be ? Get(temporalDurationLike, "years"). - let years = unknown_object.get(utf16!("years"), context)?; + let years = unknown_object.get(js_str!("years"), context)?; // 23. If years is not undefined, set result.[[Years]] to ? ToIntegerIfIntegral(years). if !years.is_undefined() { result.set_years(f64::from(to_integer_if_integral(&years, context)?)); diff --git a/core/engine/src/builtins/temporal/instant/mod.rs b/core/engine/src/builtins/temporal/instant/mod.rs index c1259c637a..48335b5db3 100644 --- a/core/engine/src/builtins/temporal/instant/mod.rs +++ b/core/engine/src/builtins/temporal/instant/mod.rs @@ -14,11 +14,12 @@ use crate::{ object::internal_methods::get_prototype_from_constructor, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, Context, JsArgs, JsBigInt, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; use temporal_rs::{ components::Instant as InnerInstant, @@ -64,25 +65,25 @@ impl IntrinsicObject for Instant { Attribute::CONFIGURABLE, ) .accessor( - utf16!("epochSeconds"), + js_str!("epochSeconds"), Some(get_seconds), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("epochMilliseconds"), + js_str!("epochMilliseconds"), Some(get_millis), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("epochMicroseconds"), + js_str!("epochMicroseconds"), Some(get_micros), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("epochNanoseconds"), + js_str!("epochNanoseconds"), Some(get_nanos), None, Attribute::CONFIGURABLE, @@ -285,10 +286,10 @@ impl Instant { // Fetch the necessary options. let options = get_options_object(args.get_or_undefined(1))?; - let mode = get_option::(&options, utf16!("roundingMode"), context)?; - let increment = get_option::(&options, utf16!("roundingIncrement"), context)?; - let smallest_unit = get_option::(&options, utf16!("smallestUnit"), context)?; - let largest_unit = get_option::(&options, utf16!("largestUnit"), context)?; + let mode = get_option::(&options, js_str!("roundingMode"), context)?; + let increment = get_option::(&options, js_str!("roundingIncrement"), context)?; + let smallest_unit = get_option::(&options, js_str!("smallestUnit"), context)?; + let largest_unit = get_option::(&options, js_str!("largestUnit"), context)?; let result = instant .inner .until(&other, mode, increment, smallest_unit, largest_unit)?; @@ -313,10 +314,10 @@ impl Instant { // 3. Return ? DifferenceTemporalInstant(since, instant, other, options). let other = to_temporal_instant(args.get_or_undefined(0))?; let options = get_options_object(args.get_or_undefined(1))?; - let mode = get_option::(&options, utf16!("roundingMode"), context)?; - let increment = get_option::(&options, utf16!("roundingIncrement"), context)?; - let smallest_unit = get_option::(&options, utf16!("smallestUnit"), context)?; - let largest_unit = get_option::(&options, utf16!("largestUnit"), context)?; + let mode = get_option::(&options, js_str!("roundingMode"), context)?; + let increment = get_option::(&options, js_str!("roundingIncrement"), context)?; + let smallest_unit = get_option::(&options, js_str!("smallestUnit"), context)?; + let largest_unit = get_option::(&options, js_str!("largestUnit"), context)?; let result = instant .inner .since(&other, mode, increment, smallest_unit, largest_unit)?; @@ -353,7 +354,7 @@ impl Instant { let new_round_to = JsObject::with_null_proto(); // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString). new_round_to.create_data_property_or_throw( - utf16!("smallestUnit"), + js_str!("smallestUnit"), param_string, context, )?; @@ -370,16 +371,16 @@ impl Instant { // alphabetical order (ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode"). // 7. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo). let rounding_increment = - get_option::(&round_to, utf16!("roundingIncrement"), context)?; + get_option::(&round_to, js_str!("roundingIncrement"), context)?; // 8. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand"). let rounding_mode = - get_option::(&round_to, utf16!("roundingMode"), context)?; + get_option::(&round_to, js_str!("roundingMode"), context)?; // 9. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit"), time, required). let smallest_unit = get_temporal_unit( &round_to, - utf16!("smallestUnit"), + js_str!("smallestUnit"), TemporalUnitGroup::Time, None, context, diff --git a/core/engine/src/builtins/temporal/mod.rs b/core/engine/src/builtins/temporal/mod.rs index be27d83323..adb2688780 100644 --- a/core/engine/src/builtins/temporal/mod.rs +++ b/core/engine/src/builtins/temporal/mod.rs @@ -37,7 +37,7 @@ use crate::{ value::Type, Context, JsBigInt, JsError, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; -use boa_macros::utf16; +use boa_macros::js_str; use boa_profiler::Profiler; use temporal_rs::{ components::{Date as TemporalDate, ZonedDateTime as TemporalZonedDateTime}, @@ -252,7 +252,7 @@ pub(crate) fn to_relative_temporal_object( options: &JsObject, context: &mut Context, ) -> RelativeTemporalObjectResult { - let relative_to = options.get(PropertyKey::from(utf16!("relativeTo")), context)?; + let relative_to = options.get(PropertyKey::from(js_str!("relativeTo")), context)?; let plain_date = match relative_to { JsValue::String(relative_to_str) => Some(relative_to_str.into()), JsValue::Object(relative_to_obj) => Some(relative_to_obj.into()), diff --git a/core/engine/src/builtins/temporal/options.rs b/core/engine/src/builtins/temporal/options.rs index bbdd360178..a13fe2865d 100644 --- a/core/engine/src/builtins/temporal/options.rs +++ b/core/engine/src/builtins/temporal/options.rs @@ -10,7 +10,9 @@ use crate::{ builtins::options::{get_option, ParsableOptionType}, - js_string, Context, JsNativeError, JsObject, JsResult, + js_string, + string::JsStr, + Context, JsNativeError, JsObject, JsResult, }; use temporal_rs::options::{ ArithmeticOverflow, DurationOverflow, InstantDisambiguation, OffsetDisambiguation, @@ -58,7 +60,7 @@ pub(crate) fn get_temporal_rounding_increment( #[inline] pub(crate) fn get_temporal_unit( options: &JsObject, - key: &[u16], + key: JsStr<'_>, unit_group: TemporalUnitGroup, extra_values: Option>, context: &mut Context, diff --git a/core/engine/src/builtins/temporal/plain_date/mod.rs b/core/engine/src/builtins/temporal/plain_date/mod.rs index 43306a7555..aac77d7e01 100644 --- a/core/engine/src/builtins/temporal/plain_date/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date/mod.rs @@ -13,10 +13,11 @@ use crate::{ object::internal_methods::get_prototype_from_constructor, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; use temporal_rs::{ components::{ @@ -125,80 +126,85 @@ impl IntrinsicObject for PlainDate { Attribute::CONFIGURABLE, ) .accessor( - utf16!("calendarId"), + js_string!("calendarId"), Some(get_calendar_id), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("year"), + js_string!("year"), Some(get_year), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("month"), + js_string!("month"), Some(get_month), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("monthCode"), + js_string!("monthCode"), Some(get_month_code), None, Attribute::CONFIGURABLE, ) - .accessor(utf16!("day"), Some(get_day), None, Attribute::CONFIGURABLE) .accessor( - utf16!("dayOfWeek"), + js_string!("day"), + Some(get_day), + None, + Attribute::CONFIGURABLE, + ) + .accessor( + js_string!("dayOfWeek"), Some(get_day_of_week), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("dayOfYear"), + js_string!("dayOfYear"), Some(get_day_of_year), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("weekOfYear"), + js_string!("weekOfYear"), Some(get_week_of_year), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("yearOfWeek"), + js_string!("yearOfWeek"), Some(get_year_of_week), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("daysInWeek"), + js_string!("daysInWeek"), Some(get_days_in_week), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("daysInMonth"), + js_string!("daysInMonth"), Some(get_days_in_month), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("daysInYear"), + js_string!("daysInYear"), Some(get_days_in_year), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("monthsInYear"), + js_string!("monthsInYear"), Some(get_months_in_year), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("inLeapYear"), + js_string!("inLeapYear"), Some(get_in_leap_year), None, Attribute::CONFIGURABLE, @@ -643,7 +649,7 @@ pub(crate) fn to_temporal_date( // c. If item has an [[InitializedTemporalDateTime]] internal slot, then } else if let Some(date_time) = object.downcast_ref::() { // i. Perform ? ToTemporalOverflow(options). - let _o = get_option(&options_obj, utf16!("overflow"), context)? + let _o = get_option(&options_obj, js_str!("overflow"), context)? .unwrap_or(ArithmeticOverflow::Constrain); let date = InnerDate::from_datetime(date_time.inner()); diff --git a/core/engine/src/builtins/temporal/plain_date_time/mod.rs b/core/engine/src/builtins/temporal/plain_date_time/mod.rs index dceb7c452b..462b1caf33 100644 --- a/core/engine/src/builtins/temporal/plain_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date_time/mod.rs @@ -11,10 +11,11 @@ use crate::{ object::internal_methods::get_prototype_from_constructor, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; #[cfg(test)] @@ -152,116 +153,116 @@ impl IntrinsicObject for PlainDateTime { Attribute::CONFIGURABLE, ) .accessor( - utf16!("calendarId"), + js_str!("calendarId"), Some(get_calendar_id), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("year"), + js_str!("year"), Some(get_year), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("month"), + js_str!("month"), Some(get_month), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("monthCode"), + js_str!("monthCode"), Some(get_month_code), None, Attribute::CONFIGURABLE, ) - .accessor(utf16!("day"), Some(get_day), None, Attribute::CONFIGURABLE) + .accessor(js_str!("day"), Some(get_day), None, Attribute::CONFIGURABLE) .accessor( - utf16!("hour"), + js_str!("hour"), Some(get_hour), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("minute"), + js_str!("minute"), Some(get_minute), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("second"), + js_str!("second"), Some(get_second), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("millisecond"), + js_str!("millisecond"), Some(get_millisecond), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("microsecond"), + js_str!("microsecond"), Some(get_microsecond), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("nanosecond"), + js_str!("nanosecond"), Some(get_nanosecond), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("dayOfWeek"), + js_str!("dayOfWeek"), Some(get_day_of_week), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("dayOfYear"), + js_str!("dayOfYear"), Some(get_day_of_year), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("weekOfYear"), + js_str!("weekOfYear"), Some(get_week_of_year), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("yearOfWeek"), + js_str!("yearOfWeek"), Some(get_year_of_week), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("daysInWeek"), + js_str!("daysInWeek"), Some(get_days_in_week), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("daysInMonth"), + js_str!("daysInMonth"), Some(get_days_in_month), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("daysInYear"), + js_str!("daysInYear"), Some(get_days_in_year), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("monthsInYear"), + js_str!("monthsInYear"), Some(get_months_in_year), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("inLeapYear"), + js_str!("inLeapYear"), Some(get_in_leap_year), None, Attribute::CONFIGURABLE, diff --git a/core/engine/src/builtins/temporal/plain_time/mod.rs b/core/engine/src/builtins/temporal/plain_time/mod.rs index 7f5af85725..846b23cc63 100644 --- a/core/engine/src/builtins/temporal/plain_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_time/mod.rs @@ -14,7 +14,7 @@ use crate::{ Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use boa_gc::{Finalize, Trace}; -use boa_macros::utf16; +use boa_macros::js_str; use boa_profiler::Profiler; use temporal_rs::{ components::Time, @@ -71,33 +71,38 @@ impl IntrinsicObject for PlainTime { Self::NAME, Attribute::CONFIGURABLE, ) - .accessor(utf16!("hour"), Some(get_hour), None, Attribute::default()) .accessor( - utf16!("minute"), + js_string!("hour"), + Some(get_hour), + None, + Attribute::default(), + ) + .accessor( + js_string!("minute"), Some(get_minute), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("second"), + js_string!("second"), Some(get_second), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("millisecond"), + js_string!("millisecond"), Some(get_millisecond), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("microsecond"), + js_string!("microsecond"), Some(get_microsecond), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("nanosecond"), + js_string!("nanosecond"), Some(get_nanosecond), None, Attribute::CONFIGURABLE, @@ -345,7 +350,7 @@ impl PlainTime { let new_round_to = JsObject::with_null_proto(); // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString). new_round_to.create_data_property_or_throw( - utf16!("smallestUnit"), + js_str!("smallestUnit"), param_string, context, )?; @@ -361,16 +366,16 @@ impl PlainTime { // 6. NOTE: The following steps read options and perform independent validation in alphabetical order (ToTemporalRoundingIncrement reads "roundingIncrement" and ToTemporalRoundingMode reads "roundingMode"). // 7. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo). let rounding_increment = - get_option::(&round_to, utf16!("roundingIncrement"), context)?; + get_option::(&round_to, js_str!("roundingIncrement"), context)?; // 8. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand"). let rounding_mode = - get_option::(&round_to, utf16!("roundingMode"), context)?; + get_option::(&round_to, js_str!("roundingMode"), context)?; // 9. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", time, required). let smallest_unit = get_temporal_unit( &round_to, - utf16!("smallestUnit"), + js_str!("smallestUnit"), TemporalUnitGroup::Time, None, context, @@ -404,29 +409,29 @@ impl PlainTime { let fields = JsObject::with_object_proto(context.intrinsics()); // 4. Perform ! CreateDataPropertyOrThrow(fields, "isoHour", 𝔽(temporalTime.[[ISOHour]])). - fields.create_data_property_or_throw(utf16!("isoHour"), time.inner.hour(), context)?; + fields.create_data_property_or_throw(js_str!("isoHour"), time.inner.hour(), context)?; // 5. Perform ! CreateDataPropertyOrThrow(fields, "isoMicrosecond", 𝔽(temporalTime.[[ISOMicrosecond]])). fields.create_data_property_or_throw( - utf16!("isoMicrosecond"), + js_str!("isoMicrosecond"), time.inner.microsecond(), context, )?; // 6. Perform ! CreateDataPropertyOrThrow(fields, "isoMillisecond", 𝔽(temporalTime.[[ISOMillisecond]])). fields.create_data_property_or_throw( - utf16!("isoMillisecond"), + js_str!("isoMillisecond"), time.inner.millisecond(), context, )?; // 7. Perform ! CreateDataPropertyOrThrow(fields, "isoMinute", 𝔽(temporalTime.[[ISOMinute]])). - fields.create_data_property_or_throw(utf16!("isoMinute"), time.inner.minute(), context)?; + fields.create_data_property_or_throw(js_str!("isoMinute"), time.inner.minute(), context)?; // 8. Perform ! CreateDataPropertyOrThrow(fields, "isoNanosecond", 𝔽(temporalTime.[[ISONanosecond]])). fields.create_data_property_or_throw( - utf16!("isoNanosecond"), + js_str!("isoNanosecond"), time.inner.nanosecond(), context, )?; // 9. Perform ! CreateDataPropertyOrThrow(fields, "isoSecond", 𝔽(temporalTime.[[ISOSecond]])). - fields.create_data_property_or_throw(utf16!("isoSecond"), time.inner.second(), context)?; + fields.create_data_property_or_throw(js_str!("isoSecond"), time.inner.second(), context)?; // 10. Return fields. Ok(fields.into()) diff --git a/core/engine/src/builtins/temporal/plain_year_month/mod.rs b/core/engine/src/builtins/temporal/plain_year_month/mod.rs index 4fdd69a000..c651534b5d 100644 --- a/core/engine/src/builtins/temporal/plain_year_month/mod.rs +++ b/core/engine/src/builtins/temporal/plain_year_month/mod.rs @@ -7,7 +7,7 @@ use crate::{ object::internal_methods::get_prototype_from_constructor, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use boa_gc::{Finalize, Trace}; @@ -98,49 +98,49 @@ impl IntrinsicObject for PlainYearMonth { Attribute::CONFIGURABLE, ) .accessor( - utf16!("calendarId"), + js_string!("calendarId"), Some(get_calendar_id), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("year"), + js_string!("year"), Some(get_year), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("month"), + js_string!("month"), Some(get_month), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("monthCode"), + js_string!("monthCode"), Some(get_month_code), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("daysInMonth"), + js_string!("daysInMonth"), Some(get_days_in_month), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("daysInYear"), + js_string!("daysInYear"), Some(get_days_in_year), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("monthsInYear"), + js_string!("monthsInYear"), Some(get_months_in_year), None, Attribute::CONFIGURABLE, ) .accessor( - utf16!("inLeapYear"), + js_string!("inLeapYear"), Some(get_in_leap_year), None, Attribute::CONFIGURABLE, diff --git a/core/engine/src/builtins/temporal/tests.rs b/core/engine/src/builtins/temporal/tests.rs index 58dde9b608..ee6095a6ae 100644 --- a/core/engine/src/builtins/temporal/tests.rs +++ b/core/engine/src/builtins/temporal/tests.rs @@ -1,4 +1,6 @@ -use crate::{js_string, run_test_actions, JsValue, TestAction}; +use boa_macros::js_str; + +use crate::{run_test_actions, JsValue, TestAction}; // Temporal Object tests. @@ -8,9 +10,9 @@ fn temporal_object() { run_test_actions([ TestAction::assert_eq( "Object.prototype.toString.call(Temporal)", - js_string!("[object Temporal]"), + js_str!("[object Temporal]"), ), - TestAction::assert_eq("String(Temporal)", js_string!("[object Temporal]")), + TestAction::assert_eq("String(Temporal)", js_str!("[object Temporal]")), TestAction::assert_eq("Object.keys(Temporal).length === 0", true), ]); } @@ -22,7 +24,7 @@ fn now_object() { TestAction::assert_eq("Object.isExtensible(Temporal.Now)", true), TestAction::assert_eq( "Object.prototype.toString.call(Temporal.Now)", - js_string!("[object Temporal.Now]"), + js_str!("[object Temporal.Now]"), ), TestAction::assert_eq( "Object.getPrototypeOf(Temporal.Now) === Object.prototype", diff --git a/core/engine/src/builtins/temporal/time_zone/custom.rs b/core/engine/src/builtins/temporal/time_zone/custom.rs index 9734b3ab6c..57aca91279 100644 --- a/core/engine/src/builtins/temporal/time_zone/custom.rs +++ b/core/engine/src/builtins/temporal/time_zone/custom.rs @@ -1,7 +1,8 @@ //! A custom `TimeZone` object. -use crate::{property::PropertyKey, string::utf16, Context, JsObject, JsValue}; +use crate::{property::PropertyKey, Context, JsObject, JsValue}; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use num_bigint::BigInt; use temporal_rs::{ components::{tz::TzProtocol, Instant}, @@ -18,7 +19,7 @@ impl TzProtocol for JsCustomTimeZone { fn get_offset_nanos_for(&self, context: &mut Context) -> TemporalResult { let method = self .tz - .get(utf16!("getOffsetNanosFor"), context) + .get(js_str!("getOffsetNanosFor"), context) .expect("Method must exist for the custom calendar to be valid."); let result = method @@ -45,7 +46,7 @@ impl TzProtocol for JsCustomTimeZone { let ident = self .tz .__get__( - &PropertyKey::from(utf16!("id")), + &PropertyKey::from(js_str!("id")), JsValue::undefined(), &mut context.into(), ) diff --git a/core/engine/src/builtins/temporal/time_zone/mod.rs b/core/engine/src/builtins/temporal/time_zone/mod.rs index c152364951..826cf94bb5 100644 --- a/core/engine/src/builtins/temporal/time_zone/mod.rs +++ b/core/engine/src/builtins/temporal/time_zone/mod.rs @@ -11,7 +11,7 @@ use crate::{ object::{internal_methods::get_prototype_from_constructor, CONSTRUCTOR}, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use boa_gc::{custom_trace, Finalize, Trace}; @@ -95,7 +95,7 @@ impl IntrinsicObject for TimeZone { realm.intrinsics().constructors().time_zone().prototype(), Attribute::default(), ) - .accessor(utf16!("id"), Some(get_id), None, Attribute::default()) + .accessor(js_string!("id"), Some(get_id), None, Attribute::default()) .build(); } diff --git a/core/engine/src/builtins/typed_array/builtin.rs b/core/engine/src/builtins/typed_array/builtin.rs index 12f9762812..36b25648c5 100644 --- a/core/engine/src/builtins/typed_array/builtin.rs +++ b/core/engine/src/builtins/typed_array/builtin.rs @@ -3,7 +3,7 @@ use std::{ sync::atomic::{self, Ordering}, }; -use boa_macros::utf16; +use boa_macros::{js_str, utf16}; use num_traits::Zero; use super::{ @@ -79,19 +79,19 @@ impl IntrinsicObject for BuiltinTypedArray { Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ) .accessor( - utf16!("buffer"), + js_string!("buffer"), Some(get_buffer), None, Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ) .accessor( - utf16!("byteLength"), + js_string!("byteLength"), Some(get_byte_length), None, Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, ) .accessor( - utf16!("byteOffset"), + js_string!("byteOffset"), Some(get_byte_offset), None, Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, @@ -1214,7 +1214,7 @@ impl BuiltinTypedArray { for k in 0..len { // a. If k > 0, set R to the string-concatenation of R and sep. if k > 0 { - r.extend_from_slice(&sep); + r.extend(sep.iter()); } // b. Let element be ! Get(O, ! ToString(𝔽(k))). @@ -1223,12 +1223,12 @@ impl BuiltinTypedArray { // c. If element is undefined, let next be the empty String; otherwise, let next be ! ToString(element). // d. Set R to the string-concatenation of R and next. if !element.is_undefined() { - r.extend_from_slice(&element.to_string(context)?); + r.extend(element.to_string(context)?.iter()); } } // 9. Return R. - Ok(js_string!(r).into()) + Ok(js_string!(&r[..]).into()) } /// `%TypedArray%.prototype.keys ( )` @@ -2493,7 +2493,7 @@ impl BuiltinTypedArray { if is_fixed_len || !next_element.is_undefined() { let s = next_element .invoke( - utf16!("toLocaleString"), + js_str!("toLocaleString"), &[ args.get_or_undefined(0).clone(), args.get_or_undefined(1).clone(), @@ -2502,11 +2502,11 @@ impl BuiltinTypedArray { )? .to_string(context)?; - r.extend_from_slice(&s); + r.extend(s.iter()); }; } - Ok(js_string!(r).into()) + Ok(js_string!(&r[..]).into()) } /// `%TypedArray%.prototype.values ( )` diff --git a/core/engine/src/builtins/typed_array/mod.rs b/core/engine/src/builtins/typed_array/mod.rs index a2b20fe51e..15f63e0fe3 100644 --- a/core/engine/src/builtins/typed_array/mod.rs +++ b/core/engine/src/builtins/typed_array/mod.rs @@ -29,6 +29,7 @@ use crate::{ Context, JsArgs, JsResult, JsString, }; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; mod builtin; @@ -74,12 +75,12 @@ impl IntrinsicObject for T { Attribute::CONFIGURABLE, ) .property( - js_string!("BYTES_PER_ELEMENT"), + js_str!("BYTES_PER_ELEMENT"), std::mem::size_of::(), Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, ) .static_property( - js_string!("BYTES_PER_ELEMENT"), + js_str!("BYTES_PER_ELEMENT"), std::mem::size_of::(), Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, ) diff --git a/core/engine/src/builtins/typed_array/object.rs b/core/engine/src/builtins/typed_array/object.rs index bb32aa6e05..cdeb7491ed 100644 --- a/core/engine/src/builtins/typed_array/object.rs +++ b/core/engine/src/builtins/typed_array/object.rs @@ -16,7 +16,7 @@ use crate::{ Context, JsNativeError, JsResult, JsString, JsValue, }; use boa_gc::{Finalize, Trace}; -use boa_macros::utf16; +use boa_macros::js_str; use super::{is_valid_integer_index, TypedArrayKind}; @@ -263,7 +263,7 @@ impl TypedArray { /// [spec]: https://tc39.es/ecma262/#sec-canonicalnumericindexstring fn canonical_numeric_index_string(argument: &JsString) -> Option { // 1. If argument is "-0", return -0𝔽. - if argument == utf16!("-0") { + if argument == &js_str!("-0") { return Some(-0.0); } diff --git a/core/engine/src/builtins/uri/mod.rs b/core/engine/src/builtins/uri/mod.rs index 82b711b3f2..3389a36f6d 100644 --- a/core/engine/src/builtins/uri/mod.rs +++ b/core/engine/src/builtins/uri/mod.rs @@ -305,11 +305,11 @@ where loop { // a. If k = strLen, return R. if k == str_len { - return Ok(js_string!(r)); + return Ok(js_string!(&r[..])); } // b. Let C be the code unit at index k within string. - let c = string[k]; + let c = string.get_expect(k); // c. If C is in unescapedSet, then if unescaped_set(c) { @@ -380,11 +380,11 @@ where loop { // a. If k = strLen, return R. if k == str_len { - return Ok(js_string!(r)); + return Ok(js_string!(&r[..])); } // b. Let C be the code unit at index k within string. - let c = string[k]; + let c = string.get_expect(k); // c. If C is not the code unit 0x0025 (PERCENT SIGN), then #[allow(clippy::if_not_else)] @@ -406,9 +406,10 @@ where // iii. If the code units at index (k + 1) and (k + 2) within string do not represent // hexadecimal digits, throw a URIError exception. // iv. Let B be the 8-bit value represented by the two hexadecimal digits at index (k + 1) and (k + 2). - let b = decode_hex_byte(string[k + 1], string[k + 2]).ok_or_else(|| { - JsNativeError::uri().with_message("invalid hexadecimal digit found") - })?; + let b = decode_hex_byte(string.get_expect(k + 1), string.get_expect(k + 2)) + .ok_or_else(|| { + JsNativeError::uri().with_message("invalid hexadecimal digit found") + })?; // v. Set k to k + 2. k += 2; @@ -428,7 +429,7 @@ where } else { // 3. Else, // a. Let S be the substring of string from start to k + 1. - Vec::from(&string[start..=k]) + string.get_expect(start..=k).to_vec() } } else { // viii. Else, @@ -456,7 +457,7 @@ where k += 1; // b. If the code unit at index k within string is not the code unit 0x0025 (PERCENT SIGN), throw a URIError exception. - if string[k] != 0x0025 { + if string.get_expect(k) != 0x0025 { return Err(JsNativeError::uri() .with_message("escape characters must be preceded with a % sign") .into()); @@ -464,9 +465,10 @@ where // c. If the code units at index (k + 1) and (k + 2) within string do not represent hexadecimal digits, throw a URIError exception. // d. Let B be the 8-bit value represented by the two hexadecimal digits at index (k + 1) and (k + 2). - let b = decode_hex_byte(string[k + 1], string[k + 2]).ok_or_else(|| { - JsNativeError::uri().with_message("invalid hexadecimal digit found") - })?; + let b = decode_hex_byte(string.get_expect(k + 1), string.get_expect(k + 2)) + .ok_or_else(|| { + JsNativeError::uri().with_message("invalid hexadecimal digit found") + })?; // e. Set k to k + 2. k += 2; diff --git a/core/engine/src/builtins/weak_map/mod.rs b/core/engine/src/builtins/weak_map/mod.rs index 6b2905b11f..01cd63c0a9 100644 --- a/core/engine/src/builtins/weak_map/mod.rs +++ b/core/engine/src/builtins/weak_map/mod.rs @@ -17,11 +17,12 @@ use crate::{ object::{internal_methods::get_prototype_from_constructor, ErasedVTableObject, JsObject}, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, symbol::JsSymbol, Context, JsArgs, JsNativeError, JsResult, JsString, JsValue, }; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; type NativeWeakMap = boa_gc::WeakMap; @@ -100,7 +101,7 @@ impl BuiltInConstructor for WeakMap { } // 5. Let adder be ? Get(map, "set"). - let adder = map.get(utf16!("set"), context)?; + let adder = map.get(js_str!("set"), context)?; // 6. If IsCallable(adder) is false, throw a TypeError exception. if !adder.is_callable() { diff --git a/core/engine/src/builtins/weak_set/mod.rs b/core/engine/src/builtins/weak_set/mod.rs index 61df3ba0d5..d90a825772 100644 --- a/core/engine/src/builtins/weak_set/mod.rs +++ b/core/engine/src/builtins/weak_set/mod.rs @@ -14,11 +14,12 @@ use crate::{ object::{internal_methods::get_prototype_from_constructor, ErasedVTableObject, JsObject}, property::Attribute, realm::Realm, - string::{common::StaticJsStrings, utf16}, + string::common::StaticJsStrings, symbol::JsSymbol, Context, JsArgs, JsNativeError, JsResult, JsString, JsValue, }; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use boa_profiler::Profiler; type NativeWeakSet = boa_gc::WeakMap; @@ -96,7 +97,7 @@ impl BuiltInConstructor for WeakSet { } // 5. Let adder be ? Get(set, "add"). - let adder = weak_set.get(utf16!("add"), context)?; + let adder = weak_set.get(js_str!("add"), context)?; // 6. If IsCallable(adder) is false, throw a TypeError exception. let adder = adder diff --git a/core/engine/src/bytecompiler/declarations.rs b/core/engine/src/bytecompiler/declarations.rs index 16de09bcff..b8b4fde7a0 100644 --- a/core/engine/src/bytecompiler/declarations.rs +++ b/core/engine/src/bytecompiler/declarations.rs @@ -18,7 +18,7 @@ use boa_ast::{ visitor::NodeRef, Declaration, Script, StatementListItem, }; -use boa_interner::Sym; +use boa_interner::{JStrRef, Sym}; #[cfg(feature = "annex-b")] use boa_ast::operations::annex_b_function_declarations_names; @@ -223,9 +223,11 @@ pub(crate) fn eval_declaration_instantiation_context( let private_identifiers = private_identifiers .into_iter() .map(|ident| { + // TODO: Replace JStrRef with JsStr this would eliminate the to_vec call. + let ident = ident.to_vec(); context .interner() - .get(ident.as_slice()) + .get(JStrRef::Utf16(&ident)) .expect("string should be in interner") }) .collect(); diff --git a/core/engine/src/bytecompiler/expression/mod.rs b/core/engine/src/bytecompiler/expression/mod.rs index 27a226874f..fb229b41c6 100644 --- a/core/engine/src/bytecompiler/expression/mod.rs +++ b/core/engine/src/bytecompiler/expression/mod.rs @@ -4,7 +4,7 @@ mod object_literal; mod unary; mod update; -use super::{Access, Callable, NodeKind, Operand}; +use super::{Access, Callable, NodeKind, Operand, ToJsString}; use crate::{ bytecompiler::{ByteCompiler, Literal}, vm::{GeneratorResumeKind, Opcode}, @@ -22,9 +22,9 @@ use boa_ast::{ impl ByteCompiler<'_> { fn compile_literal(&mut self, lit: &AstLiteral, use_expr: bool) { match lit { - AstLiteral::String(v) => self.emit_push_literal(Literal::String( - self.interner().resolve_expect(*v).into_common(false), - )), + AstLiteral::String(v) => { + self.emit_push_literal(Literal::String(v.to_js_string(self.interner()))); + } AstLiteral::Int(v) => self.emit_push_integer(*v), AstLiteral::Num(v) => self.emit_push_rational(*v), AstLiteral::BigInt(v) => { @@ -58,9 +58,9 @@ impl ByteCompiler<'_> { fn compile_template_literal(&mut self, template_literal: &TemplateLiteral, use_expr: bool) { for element in template_literal.elements() { match element { - TemplateElement::String(s) => self.emit_push_literal(Literal::String( - self.interner().resolve_expect(*s).into_common(false), - )), + TemplateElement::String(s) => { + self.emit_push_literal(Literal::String(s.to_js_string(self.interner()))); + } TemplateElement::Expr(expr) => { self.compile_expr(expr, true); } @@ -268,14 +268,12 @@ impl ByteCompiler<'_> { for (cooked, raw) in template.cookeds().iter().zip(template.raws()) { if let Some(cooked) = cooked { self.emit_push_literal(Literal::String( - self.interner().resolve_expect(*cooked).into_common(false), + cooked.to_js_string(self.interner()), )); } else { self.emit_opcode(Opcode::PushUndefined); } - self.emit_push_literal(Literal::String( - self.interner().resolve_expect(*raw).into_common(false), - )); + self.emit_push_literal(Literal::String(raw.to_js_string(self.interner()))); } self.emit( diff --git a/core/engine/src/bytecompiler/mod.rs b/core/engine/src/bytecompiler/mod.rs index 4b64f6774e..fd30107566 100644 --- a/core/engine/src/bytecompiler/mod.rs +++ b/core/engine/src/bytecompiler/mod.rs @@ -21,7 +21,7 @@ use crate::{ BindingOpcode, CodeBlock, CodeBlockFlags, Constant, GeneratorResumeKind, Handler, InlineCache, Opcode, VaryingOperandKind, }, - JsBigInt, JsString, + JsBigInt, JsStr, JsString, }; use boa_ast::{ declaration::{Binding, LexicalDeclaration, VarDeclaration}, @@ -55,7 +55,15 @@ pub(crate) trait ToJsString { impl ToJsString for Sym { fn to_js_string(&self, interner: &Interner) -> JsString { - js_string!(interner.resolve_expect(*self).utf16()) + // TODO: Identify latin1 encodeable strings during parsing to avoid this check. + let string = interner.resolve_expect(*self).utf16(); + for c in string { + if u8::try_from(*c).is_err() { + return js_string!(string); + } + } + let string = string.iter().map(|c| *c as u8).collect::>(); + js_string!(JsStr::latin1(&string)) } } @@ -392,9 +400,9 @@ impl<'ctx> ByteCompiler<'ctx> { return *index; } - let string = self.interner().resolve_expect(name.sym()).utf16(); let index = self.constants.len() as u32; - self.constants.push(Constant::String(js_string!(string))); + let string = name.to_js_string(self.interner()); + self.constants.push(Constant::String(string)); self.names_map.insert(name, index); index } @@ -744,7 +752,7 @@ impl<'ctx> ByteCompiler<'ctx> { } fn resolve_identifier_expect(&self, identifier: Identifier) -> JsString { - js_string!(self.interner().resolve_expect(identifier.sym()).utf16()) + identifier.to_js_string(self.interner()) } fn access_get(&mut self, access: Access<'_>, use_expr: bool) { diff --git a/core/engine/src/class.rs b/core/engine/src/class.rs index 4939e0cddd..af0838c583 100644 --- a/core/engine/src/class.rs +++ b/core/engine/src/class.rs @@ -10,7 +10,7 @@ //! # property::Attribute, //! # class::{Class, ClassBuilder}, //! # Context, JsResult, JsValue, -//! # JsArgs, Source, JsObject, js_string, +//! # JsArgs, Source, JsObject, js_str, js_string, //! # JsNativeError, JsData, //! # }; //! # use boa_gc::{Finalize, Trace}; @@ -56,7 +56,7 @@ //! let age = args.get_or_undefined(1).to_number(context)?; //! //! // Roughly equivalent to `this.age = Number(age)`. -//! instance.set(js_string!("age"), age, true, context)?; +//! instance.set(js_str!("age"), age, true, context)?; //! //! Ok(()) //! } @@ -70,9 +70,9 @@ //! if let Some(object) = this.as_object() { //! if let Some(animal) = object.downcast_ref::() { //! return Ok(match &*animal { -//! Self::Cat => js_string!("meow"), -//! Self::Dog => js_string!("woof"), -//! Self::Other => js_string!(r"¯\_(ツ)_/¯"), +//! Self::Cat => js_str!("meow"), +//! Self::Dog => js_str!("woof"), +//! Self::Other => js_str!(r"¯\_(ツ)_/¯"), //! }.into()); //! } //! } @@ -96,7 +96,7 @@ //! //! assert_eq!( //! result.as_string().unwrap(), -//! &js_string!("My pet is 3 years old. Right, buddy? - woof!") +//! &js_str!("My pet is 3 years old. Right, buddy? - woof!") //! ); //! } //! ``` diff --git a/core/engine/src/context/intrinsics.rs b/core/engine/src/context/intrinsics.rs index b1056f71bb..a0d8519a7f 100644 --- a/core/engine/src/context/intrinsics.rs +++ b/core/engine/src/context/intrinsics.rs @@ -1,6 +1,7 @@ //! Data structures that contain intrinsic objects and constructors. use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use crate::{ builtins::{iterable::IteratorPrototypes, uri::UriFunctions, Array, OrdinaryObject}, @@ -1394,7 +1395,7 @@ impl ObjectTemplates { let ordinary_object = ObjectTemplate::with_prototype(root_shape, constructors.object().prototype()); let mut array = ObjectTemplate::new(root_shape); - let length_property_key: PropertyKey = js_string!("length").into(); + let length_property_key: PropertyKey = js_str!("length").into(); array.property( length_property_key.clone(), Attribute::WRITABLE | Attribute::PERMANENT | Attribute::NON_ENUMERABLE, @@ -1419,7 +1420,7 @@ impl ObjectTemplates { let mut regexp = regexp_without_proto.clone(); regexp.set_prototype(constructors.regexp().prototype()); - let name_property_key: PropertyKey = js_string!("name").into(); + let name_property_key: PropertyKey = js_str!("name").into(); let mut function = ObjectTemplate::new(root_shape); function.property( length_property_key.clone(), @@ -1478,7 +1479,7 @@ impl ObjectTemplates { // [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, // [[Configurable]]: false }). unmapped_arguments.accessor( - js_string!("callee").into(), + js_str!("callee").into(), true, true, Attribute::NON_ENUMERABLE | Attribute::PERMANENT, @@ -1487,17 +1488,17 @@ impl ObjectTemplates { // 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { // [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). mapped_arguments.property( - js_string!("callee").into(), + js_str!("callee").into(), Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); let mut iterator_result = ordinary_object.clone(); iterator_result.property( - js_string!("value").into(), + js_str!("value").into(), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE, ); iterator_result.property( - js_string!("done").into(), + js_str!("done").into(), Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::ENUMERABLE, ); @@ -1509,11 +1510,11 @@ impl ObjectTemplates { with_resolvers // 4. Perform ! CreateDataPropertyOrThrow(obj, "promise", promiseCapability.[[Promise]]). - .property(js_string!("promise").into(), Attribute::all()) + .property(js_str!("promise").into(), Attribute::all()) // 5. Perform ! CreateDataPropertyOrThrow(obj, "resolve", promiseCapability.[[Resolve]]). - .property(js_string!("resolve").into(), Attribute::all()) + .property(js_str!("resolve").into(), Attribute::all()) // 6. Perform ! CreateDataPropertyOrThrow(obj, "reject", promiseCapability.[[Reject]]). - .property(js_string!("reject").into(), Attribute::all()); + .property(js_str!("reject").into(), Attribute::all()); with_resolvers }; diff --git a/core/engine/src/context/mod.rs b/core/engine/src/context/mod.rs index 32df528b35..99278a173c 100644 --- a/core/engine/src/context/mod.rs +++ b/core/engine/src/context/mod.rs @@ -51,7 +51,7 @@ thread_local! { /// /// ```rust /// use boa_engine::{ -/// js_string, +/// js_str, /// object::ObjectInitializer, /// property::{Attribute, PropertyDescriptor}, /// Context, Source, @@ -73,10 +73,10 @@ thread_local! { /// /// // Create an object that can be used in eval calls. /// let arg = ObjectInitializer::new(&mut context) -/// .property(js_string!("x"), 12, Attribute::READONLY) +/// .property(js_str!("x"), 12, Attribute::READONLY) /// .build(); /// context -/// .register_global_property(js_string!("arg"), arg, Attribute::all()) +/// .register_global_property(js_str!("arg"), arg, Attribute::all()) /// .expect("property shouldn't exist"); /// /// let value = context.eval(Source::from_bytes("test(arg)")).unwrap(); @@ -211,7 +211,7 @@ impl Context { /// # Example /// ``` /// use boa_engine::{ - /// js_string, + /// js_str, /// object::ObjectInitializer, /// property::{Attribute, PropertyDescriptor}, /// Context, @@ -221,19 +221,19 @@ impl Context { /// /// context /// .register_global_property( - /// js_string!("myPrimitiveProperty"), + /// js_str!("myPrimitiveProperty"), /// 10, /// Attribute::all(), /// ) /// .expect("property shouldn't exist"); /// /// let object = ObjectInitializer::new(&mut context) - /// .property(js_string!("x"), 0, Attribute::all()) - /// .property(js_string!("y"), 1, Attribute::all()) + /// .property(js_str!("x"), 0, Attribute::all()) + /// .property(js_str!("y"), 1, Attribute::all()) /// .build(); /// context /// .register_global_property( - /// js_string!("myObjectProperty"), + /// js_str!("myObjectProperty"), /// object, /// Attribute::all(), /// ) diff --git a/core/engine/src/error.rs b/core/engine/src/error.rs index d56b20e68c..4bf3637d62 100644 --- a/core/engine/src/error.rs +++ b/core/engine/src/error.rs @@ -8,10 +8,10 @@ use crate::{ object::JsObject, property::PropertyDescriptor, realm::Realm, - string::utf16, Context, JsString, JsValue, }; use boa_gc::{custom_trace, Finalize, Trace}; +use boa_macros::js_str; use thiserror::Error; /// The error type returned by all operations related @@ -30,11 +30,11 @@ use thiserror::Error; /// # Examples /// /// ```rust -/// # use boa_engine::{JsError, JsNativeError, JsNativeErrorKind, JsValue, js_string}; -/// let cause = JsError::from_opaque(js_string!("error!").into()); +/// # use boa_engine::{JsError, JsNativeError, JsNativeErrorKind, JsValue, js_str}; +/// let cause = JsError::from_opaque(js_str!("error!").into()); /// /// assert!(cause.as_opaque().is_some()); -/// assert_eq!(cause.as_opaque().unwrap(), &JsValue::from(js_string!("error!"))); +/// assert_eq!(cause.as_opaque().unwrap(), &JsValue::from(js_str!("error!"))); /// /// let native_error: JsError = JsNativeError::typ() /// .with_message("invalid type!") @@ -270,7 +270,7 @@ impl JsError { ErrorObject::Syntax => JsNativeErrorKind::Syntax, ErrorObject::Uri => JsNativeErrorKind::Uri, ErrorObject::Aggregate => { - let errors = obj.get(utf16!("errors"), context).map_err(|e| { + let errors = obj.get(js_str!("errors"), context).map_err(|e| { TryNativeError::InaccessibleProperty { property: "errors", source: e, @@ -878,7 +878,7 @@ impl JsNativeError { /// # Examples /// /// ```rust - /// # use boa_engine::{Context, JsError, JsNativeError, js_string}; + /// # use boa_engine::{Context, JsError, JsNativeError, js_str}; /// # use boa_engine::builtins::error::ErrorObject; /// let context = &mut Context::default(); /// @@ -887,8 +887,8 @@ impl JsNativeError { /// /// assert!(error_obj.is::()); /// assert_eq!( - /// error_obj.get(js_string!("message"), context).unwrap(), - /// js_string!("error!").into() + /// error_obj.get(js_str!("message"), context).unwrap(), + /// js_str!("error!").into() /// ) /// ``` /// @@ -941,14 +941,14 @@ impl JsNativeError { JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, tag); o.create_non_enumerable_data_property_or_throw( - js_string!("message"), + js_str!("message"), js_string!(&**message), context, ); if let Some(cause) = cause { o.create_non_enumerable_data_property_or_throw( - js_string!("cause"), + js_str!("cause"), cause.to_opaque(context), context, ); @@ -961,7 +961,7 @@ impl JsNativeError { .collect::>(); let errors = Array::create_array_from_list(errors, context); o.define_property_or_throw( - js_string!("errors"), + js_str!("errors"), PropertyDescriptor::builder() .configurable(true) .enumerable(false) diff --git a/core/engine/src/lib.rs b/core/engine/src/lib.rs index 713906e9bd..5487cee8b6 100644 --- a/core/engine/src/lib.rs +++ b/core/engine/src/lib.rs @@ -77,7 +77,6 @@ compile_error!("Boa requires a lock free `AtomicUsize` in order to work properly."); extern crate self as boa_engine; -extern crate static_assertions as sa; pub use boa_ast as ast; pub use boa_gc as gc; @@ -104,10 +103,11 @@ pub mod symbol; pub mod value; pub mod vm; +pub(crate) mod tagged; + mod host_defined; mod small_map; mod sys; -mod tagged; #[cfg(test)] mod tests; @@ -123,12 +123,12 @@ pub mod prelude { native_function::NativeFunction, object::{JsData, JsObject, NativeObject}, script::Script, - string::JsString, + string::{JsStr, JsString}, symbol::JsSymbol, value::JsValue, }; pub use boa_gc::{Finalize, Trace}; - pub use boa_macros::JsData; + pub use boa_macros::{js_str, JsData}; pub use boa_parser::Source; } @@ -138,6 +138,9 @@ use std::result::Result as StdResult; #[doc(inline)] pub use prelude::*; +#[doc(inline)] +pub use boa_parser::Source; + /// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`) pub type JsResult = StdResult; diff --git a/core/engine/src/module/source.rs b/core/engine/src/module/source.rs index edc1757db8..2062028e09 100644 --- a/core/engine/src/module/source.rs +++ b/core/engine/src/module/source.rs @@ -12,7 +12,7 @@ use boa_ast::{ }; use boa_gc::{Finalize, Gc, GcRefCell, Trace}; use boa_interner::Interner; -use boa_macros::utf16; +use boa_macros::js_str; use indexmap::IndexSet; use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; @@ -506,7 +506,7 @@ impl SourceTextModule { // c. For each element n of starNames, do for n in requested_module.get_exported_names(export_star_set, interner) { // i. If SameValue(n, "default") is false, then - if &n != utf16!("default") { + if n != js_str!("default") { // 1. If exportedNames does not contain n, then // a. Append n to exportedNames. exported_names.insert(n); @@ -583,7 +583,7 @@ impl SourceTextModule { } // 7. If SameValue(exportName, "default") is true, then - if &export_name.clone() == utf16!("default") { + if export_name == &js_str!("default") { // a. Assert: A default export was not explicitly defined by this module. // b. Return null. // c. NOTE: A default export cannot be provided by an export * from "mod" declaration. diff --git a/core/engine/src/object/builtins/jsdate.rs b/core/engine/src/object/builtins/jsdate.rs index 6f6ad4b52f..3dd6c34156 100644 --- a/core/engine/src/object/builtins/jsdate.rs +++ b/core/engine/src/object/builtins/jsdate.rs @@ -18,7 +18,7 @@ use time::{format_description::well_known::Rfc3339, OffsetDateTime}; /// /// ``` /// use boa_engine::{ -/// js_string, object::builtins::JsDate, Context, JsResult, JsValue, +/// js_str, object::builtins::JsDate, Context, JsResult, JsValue, /// }; /// /// fn main() -> JsResult<()> { @@ -31,7 +31,7 @@ use time::{format_description::well_known::Rfc3339, OffsetDateTime}; /// /// assert_eq!( /// date.to_date_string(context)?, -/// JsValue::from(js_string!("Mon Dec 04 1995")) +/// JsValue::from(js_str!("Mon Dec 04 1995")) /// ); /// /// Ok(()) diff --git a/core/engine/src/object/builtins/jsmap.rs b/core/engine/src/object/builtins/jsmap.rs index f462ceb433..4ce123e602 100644 --- a/core/engine/src/object/builtins/jsmap.rs +++ b/core/engine/src/object/builtins/jsmap.rs @@ -4,12 +4,12 @@ use crate::{ builtins::Map, error::JsNativeError, object::{JsFunction, JsMapIterator, JsObject, JsObjectType}, - string::utf16, value::TryFromJs, Context, JsResult, JsValue, }; use boa_gc::{Finalize, Trace}; +use boa_macros::js_str; use std::ops::Deref; /// `JsMap` provides a wrapper for Boa's implementation of the ECMAScript `Map` object. @@ -20,7 +20,7 @@ use std::ops::Deref; /// ``` /// # use boa_engine::{ /// # object::builtins::JsMap, -/// # Context, JsValue, JsResult, js_string +/// # Context, JsValue, JsResult, js_str /// # }; /// # fn main() -> JsResult<()> { /// // Create default `Context` @@ -30,8 +30,8 @@ use std::ops::Deref; /// let map = JsMap::new(context); /// /// // Set key-value pairs for the `JsMap`. -/// map.set(js_string!("Key-1"), js_string!("Value-1"), context)?; -/// map.set(js_string!("Key-2"), 10, context)?; +/// map.set(js_str!("Key-1"), js_str!("Value-1"), context)?; +/// map.set(js_str!("Key-2"), 10, context)?; /// /// assert_eq!(map.get_size(context)?, 2.into()); /// # Ok(()) @@ -42,7 +42,7 @@ use std::ops::Deref; /// ``` /// # use boa_engine::{ /// # object::builtins::{JsArray, JsMap}, -/// # Context, JsValue, JsResult, js_string +/// # Context, JsValue, JsResult, js_str /// # }; /// # fn main() -> JsResult<()> { /// // Create a default `Context` @@ -53,8 +53,8 @@ use std::ops::Deref; /// /// // Create a `[key, value]` pair of JsValues /// let vec_one: Vec = vec![ -/// js_string!("first-key").into(), -/// js_string!("first-value").into() +/// js_str!("first-key").into(), +/// js_str!("first-value").into() /// ]; /// /// // We create an push our `[key, value]` pair onto our array as a `JsArray` @@ -64,8 +64,8 @@ use std::ops::Deref; /// let js_iterable_map = JsMap::from_js_iterable(&js_array.into(), context)?; /// /// assert_eq!( -/// js_iterable_map.get(js_string!("first-key"), context)?, -/// js_string!("first-value").into() +/// js_iterable_map.get(js_str!("first-key"), context)?, +/// js_str!("first-value").into() /// ); /// /// # Ok(()) @@ -102,7 +102,7 @@ impl JsMap { /// ``` /// # use boa_engine::{ /// # object::builtins::{JsArray, JsMap}, - /// # Context, JsResult, JsValue, js_string + /// # Context, JsResult, JsValue, js_str /// # }; /// # fn main() -> JsResult<()> { /// # // Create a default `Context` @@ -112,8 +112,8 @@ impl JsMap { /// /// // Create a `[key, value]` pair of JsValues and add it to the `JsArray` as a `JsArray` /// let vec_one: Vec = vec![ - /// js_string!("first-key").into(), - /// js_string!("first-value").into() + /// js_str!("first-key").into(), + /// js_str!("first-value").into() /// ]; /// js_array.push(JsArray::from_iter(vec_one, context), context)?; /// @@ -129,7 +129,7 @@ impl JsMap { // Let adder be Get(map, "set") per spec. This action should not fail with default map. let adder = map - .get(utf16!("set"), context) + .get(js_str!("set"), context) .expect("creating a map with the default prototype must not fail"); let _completion_record = add_entries_from_iterable(&map, iterable, &adder, context)?; @@ -223,18 +223,18 @@ impl JsMap { /// ``` /// # use boa_engine::{ /// # object::builtins::JsMap, - /// # Context, JsValue, JsResult, js_string + /// # Context, JsValue, JsResult, js_str /// # }; /// # fn main() -> JsResult<()> { /// # let context = &mut Context::default(); /// let js_map = JsMap::new(context); /// - /// js_map.set(js_string!("foo"), js_string!("bar"), context)?; + /// js_map.set(js_str!("foo"), js_str!("bar"), context)?; /// js_map.set(2, 4, context)?; /// /// assert_eq!( - /// js_map.get(js_string!("foo"), context)?, - /// js_string!("bar").into() + /// js_map.get(js_str!("foo"), context)?, + /// js_str!("bar").into() /// ); /// assert_eq!(js_map.get(2, context)?, 4.into()); /// # Ok(()) @@ -259,13 +259,13 @@ impl JsMap { /// ``` /// # use boa_engine::{ /// # object::builtins::JsMap, - /// # Context, JsValue, JsResult, js_string + /// # Context, JsValue, JsResult, js_str /// # }; /// # fn main() -> JsResult<()> { /// # let context = &mut Context::default(); /// let js_map = JsMap::new(context); /// - /// js_map.set(js_string!("foo"), js_string!("bar"), context)?; + /// js_map.set(js_str!("foo"), js_str!("bar"), context)?; /// /// let map_size = js_map.get_size(context)?; /// @@ -285,19 +285,19 @@ impl JsMap { /// ``` /// # use boa_engine::{ /// # object::builtins::JsMap, - /// # Context, JsValue, JsResult, js_string + /// # Context, JsValue, JsResult, js_str /// # }; /// # fn main() -> JsResult<()> { /// # let context = &mut Context::default(); /// let js_map = JsMap::new(context); - /// js_map.set(js_string!("foo"), js_string!("bar"), context)?; - /// js_map.set(js_string!("hello"), js_string!("world"), context)?; + /// js_map.set(js_str!("foo"), js_str!("bar"), context)?; + /// js_map.set(js_str!("hello"), js_str!("world"), context)?; /// - /// js_map.delete(js_string!("foo"), context)?; + /// js_map.delete(js_str!("foo"), context)?; /// /// assert_eq!(js_map.get_size(context)?, 1.into()); /// assert_eq!( - /// js_map.get(js_string!("foo"), context)?, + /// js_map.get(js_str!("foo"), context)?, /// JsValue::undefined() /// ); /// # Ok(()) @@ -317,16 +317,16 @@ impl JsMap { /// ``` /// # use boa_engine::{ /// # object::builtins::JsMap, - /// # Context, JsValue, JsResult, js_string + /// # Context, JsValue, JsResult, js_str /// # }; /// # fn main() -> JsResult<()> { /// # let context = &mut Context::default(); /// let js_map = JsMap::new(context); - /// js_map.set(js_string!("foo"), js_string!("bar"), context)?; + /// js_map.set(js_str!("foo"), js_str!("bar"), context)?; /// - /// let retrieved_value = js_map.get(js_string!("foo"), context)?; + /// let retrieved_value = js_map.get(js_str!("foo"), context)?; /// - /// assert_eq!(retrieved_value, js_string!("bar").into()); + /// assert_eq!(retrieved_value, js_str!("bar").into()); /// # Ok(()) /// # } /// ``` @@ -344,13 +344,13 @@ impl JsMap { /// ``` /// # use boa_engine::{ /// # object::builtins::JsMap, - /// # Context, JsValue, JsResult, js_string + /// # Context, JsValue, JsResult, js_str /// # }; /// # fn main() -> JsResult<()> { /// # let context = &mut Context::default(); /// let js_map = JsMap::new(context); - /// js_map.set(js_string!("foo"), js_string!("bar"), context)?; - /// js_map.set(js_string!("hello"), js_string!("world"), context)?; + /// js_map.set(js_str!("foo"), js_str!("bar"), context)?; + /// js_map.set(js_str!("hello"), js_str!("world"), context)?; /// /// js_map.clear(context)?; /// @@ -370,14 +370,14 @@ impl JsMap { /// ``` /// # use boa_engine::{ /// # object::builtins::JsMap, - /// # Context, JsValue, JsResult, js_string + /// # Context, JsValue, JsResult, js_str /// # }; /// # fn main() -> JsResult<()> { /// # let context = &mut Context::default(); /// let js_map = JsMap::new(context); - /// js_map.set(js_string!("foo"), js_string!("bar"), context)?; + /// js_map.set(js_str!("foo"), js_str!("bar"), context)?; /// - /// let has_key = js_map.has(js_string!("foo"), context)?; + /// let has_key = js_map.has(js_str!("foo"), context)?; /// /// assert_eq!(has_key, true.into()); /// # Ok(()) diff --git a/core/engine/src/object/builtins/jspromise.rs b/core/engine/src/object/builtins/jspromise.rs index 2ee7327eec..cf2358db79 100644 --- a/core/engine/src/object/builtins/jspromise.rs +++ b/core/engine/src/object/builtins/jspromise.rs @@ -25,7 +25,7 @@ use boa_gc::{Finalize, Gc, GcRefCell, Trace}; /// ``` /// # use boa_engine::{ /// # builtins::promise::PromiseState, -/// # js_string, +/// # js_str, /// # object::{builtins::JsPromise, FunctionObjectBuilder}, /// # property::Attribute, /// # Context, JsArgs, JsError, JsValue, NativeFunction, @@ -35,14 +35,14 @@ use boa_gc::{Finalize, Gc, GcRefCell, Trace}; /// let context = &mut Context::default(); /// /// context.register_global_property( -/// js_string!("finally"), +/// js_str!("finally"), /// false, /// Attribute::all(), /// ); /// /// let promise = JsPromise::new( /// |resolvers, context| { -/// let result = js_string!("hello world!").into(); +/// let result = js_str!("hello world!").into(); /// resolvers.resolve.call( /// &JsValue::undefined(), /// &[result], @@ -75,7 +75,7 @@ use boa_gc::{Finalize, Gc, GcRefCell, Trace}; /// .finally( /// NativeFunction::from_fn_ptr(|_, _, context| { /// context.global_object().clone().set( -/// js_string!("finally"), +/// js_str!("finally"), /// JsValue::from(true), /// true, /// context, @@ -90,14 +90,14 @@ use boa_gc::{Finalize, Gc, GcRefCell, Trace}; /// /// assert_eq!( /// promise.state(), -/// PromiseState::Fulfilled(js_string!("hello world!").into()) +/// PromiseState::Fulfilled(js_str!("hello world!").into()) /// ); /// /// assert_eq!( /// context /// .global_object() /// .clone() -/// .get(js_string!("finally"), context)?, +/// .get(js_str!("finally"), context)?, /// JsValue::from(true) /// ); /// diff --git a/core/engine/src/object/builtins/jsproxy.rs b/core/engine/src/object/builtins/jsproxy.rs index 6574114c0c..bb149852e3 100644 --- a/core/engine/src/object/builtins/jsproxy.rs +++ b/core/engine/src/object/builtins/jsproxy.rs @@ -1,16 +1,14 @@ //! A Rust API wrapper for the `Proxy` Builtin ECMAScript Object -use boa_gc::{Finalize, Trace}; - +use super::JsFunction; use crate::{ builtins::Proxy, + js_str, native_function::{NativeFunction, NativeFunctionPointer}, object::{FunctionObjectBuilder, JsObject, JsObjectType}, - string::utf16, value::TryFromJs, Context, JsNativeError, JsResult, JsValue, }; - -use super::JsFunction; +use boa_gc::{Finalize, Trace}; /// `JsProxy` provides a wrapper for Boa's implementation of the ECMAScript `Proxy` object /// @@ -405,7 +403,7 @@ impl JsProxyBuilder { .length(3) .build(); handler - .create_data_property_or_throw(utf16!("apply"), f, context) + .create_data_property_or_throw(js_str!("apply"), f, context) .expect("new object should be writable"); } if let Some(construct) = self.construct { @@ -414,7 +412,7 @@ impl JsProxyBuilder { .length(3) .build(); handler - .create_data_property_or_throw(utf16!("construct"), f, context) + .create_data_property_or_throw(js_str!("construct"), f, context) .expect("new object should be writable"); } if let Some(define_property) = self.define_property { @@ -425,7 +423,7 @@ impl JsProxyBuilder { .length(3) .build(); handler - .create_data_property_or_throw(utf16!("defineProperty"), f, context) + .create_data_property_or_throw(js_str!("defineProperty"), f, context) .expect("new object should be writable"); } if let Some(delete_property) = self.delete_property { @@ -436,7 +434,7 @@ impl JsProxyBuilder { .length(2) .build(); handler - .create_data_property_or_throw(utf16!("deleteProperty"), f, context) + .create_data_property_or_throw(js_str!("deleteProperty"), f, context) .expect("new object should be writable"); } if let Some(get) = self.get { @@ -444,7 +442,7 @@ impl JsProxyBuilder { .length(3) .build(); handler - .create_data_property_or_throw(utf16!("get"), f, context) + .create_data_property_or_throw(js_str!("get"), f, context) .expect("new object should be writable"); } if let Some(get_own_property_descriptor) = self.get_own_property_descriptor { @@ -455,7 +453,7 @@ impl JsProxyBuilder { .length(2) .build(); handler - .create_data_property_or_throw(utf16!("getOwnPropertyDescriptor"), f, context) + .create_data_property_or_throw(js_str!("getOwnPropertyDescriptor"), f, context) .expect("new object should be writable"); } if let Some(get_prototype_of) = self.get_prototype_of { @@ -466,7 +464,7 @@ impl JsProxyBuilder { .length(1) .build(); handler - .create_data_property_or_throw(utf16!("getPrototypeOf"), f, context) + .create_data_property_or_throw(js_str!("getPrototypeOf"), f, context) .expect("new object should be writable"); } if let Some(has) = self.has { @@ -474,7 +472,7 @@ impl JsProxyBuilder { .length(2) .build(); handler - .create_data_property_or_throw(utf16!("has"), f, context) + .create_data_property_or_throw(js_str!("has"), f, context) .expect("new object should be writable"); } if let Some(is_extensible) = self.is_extensible { @@ -485,7 +483,7 @@ impl JsProxyBuilder { .length(1) .build(); handler - .create_data_property_or_throw(utf16!("isExtensible"), f, context) + .create_data_property_or_throw(js_str!("isExtensible"), f, context) .expect("new object should be writable"); } if let Some(own_keys) = self.own_keys { @@ -494,7 +492,7 @@ impl JsProxyBuilder { .length(1) .build(); handler - .create_data_property_or_throw(utf16!("ownKeys"), f, context) + .create_data_property_or_throw(js_str!("ownKeys"), f, context) .expect("new object should be writable"); } if let Some(prevent_extensions) = self.prevent_extensions { @@ -505,7 +503,7 @@ impl JsProxyBuilder { .length(1) .build(); handler - .create_data_property_or_throw(utf16!("preventExtensions"), f, context) + .create_data_property_or_throw(js_str!("preventExtensions"), f, context) .expect("new object should be writable"); } if let Some(set) = self.set { @@ -513,7 +511,7 @@ impl JsProxyBuilder { .length(4) .build(); handler - .create_data_property_or_throw(utf16!("set"), f, context) + .create_data_property_or_throw(js_str!("set"), f, context) .expect("new object should be writable"); } if let Some(set_prototype_of) = self.set_prototype_of { @@ -524,7 +522,7 @@ impl JsProxyBuilder { .length(2) .build(); handler - .create_data_property_or_throw(utf16!("setPrototypeOf"), f, context) + .create_data_property_or_throw(js_str!("setPrototypeOf"), f, context) .expect("new object should be writable"); } diff --git a/core/engine/src/object/internal_methods/string.rs b/core/engine/src/object/internal_methods/string.rs index e2c5809e7e..ce26b9da30 100644 --- a/core/engine/src/object/internal_methods/string.rs +++ b/core/engine/src/object/internal_methods/string.rs @@ -1,5 +1,4 @@ use crate::{ - js_string, object::{JsData, JsObject}, property::{PropertyDescriptor, PropertyKey}, Context, JsResult, JsString, @@ -158,7 +157,7 @@ fn string_get_own_property(obj: &JsObject, key: &PropertyKey) -> Option Debug for JsObject { if self.is_callable() { let name_prop = obj .properties() - .get(&PropertyKey::String(JsString::from("name"))); + .get(&PropertyKey::String(js_string!("name"))); let name = match name_prop { None => JsString::default(), Some(prop) => prop diff --git a/core/engine/src/object/mod.rs b/core/engine/src/object/mod.rs index b992082301..f2fc7ed64f 100644 --- a/core/engine/src/object/mod.rs +++ b/core/engine/src/object/mod.rs @@ -2,6 +2,7 @@ //! //! For the builtin object wrappers, please see [`object::builtins`][builtins] for implementors. +use boa_macros::js_str; pub use jsobject::{RecursionLimiter, Ref, RefMut}; pub use operations::IntegrityLevel; pub use property_map::*; @@ -22,8 +23,8 @@ use crate::{ native_function::{NativeFunction, NativeFunctionObject}, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, - string::{common::StaticJsStrings, utf16}, - Context, JsString, JsSymbol, JsValue, + string::common::StaticJsStrings, + Context, JsStr, JsString, JsSymbol, JsValue, }; use boa_gc::{Finalize, Trace}; @@ -52,10 +53,10 @@ pub use jsobject::*; pub(crate) trait JsObjectType: Into + Into {} /// Const `constructor`, usually set on prototypes as a key to point to their respective constructor object. -pub const CONSTRUCTOR: &[u16] = utf16!("constructor"); +pub const CONSTRUCTOR: JsStr<'_> = js_str!("constructor"); /// Const `prototype`, usually set on constructors as a key to point to their respective prototype object. -pub const PROTOTYPE: &[u16] = utf16!("prototype"); +pub const PROTOTYPE: JsStr<'_> = js_str!("prototype"); /// Common field names. @@ -1017,7 +1018,7 @@ impl<'ctx> ConstructorBuilder<'ctx> { }; constructor.insert(StaticJsStrings::LENGTH, length); - constructor.insert(utf16!("name"), name); + constructor.insert(js_str!("name"), name); if let Some(proto) = self.custom_prototype.take() { constructor.set_prototype(proto); diff --git a/core/engine/src/optimizer/pass/constant_folding.rs b/core/engine/src/optimizer/pass/constant_folding.rs index 0c0aa18b73..c1725ddfa3 100644 --- a/core/engine/src/optimizer/pass/constant_folding.rs +++ b/core/engine/src/optimizer/pass/constant_folding.rs @@ -1,5 +1,6 @@ use crate::{ - builtins::Number, optimizer::PassAction, value::Numeric, Context, JsBigInt, JsString, JsValue, + builtins::Number, bytecompiler::ToJsString, optimizer::PassAction, value::Numeric, Context, + JsBigInt, JsValue, }; use boa_ast::{ expression::{ @@ -12,12 +13,11 @@ use boa_ast::{ }, Expression, }; +use boa_interner::JStrRef; fn literal_to_js_value(literal: &Literal, context: &mut Context) -> JsValue { match literal { - Literal::String(v) => JsValue::new(JsString::from( - context.interner().resolve_expect(*v).utf16(), - )), + Literal::String(v) => JsValue::new(v.to_js_string(context.interner())), Literal::Num(v) => JsValue::new(*v), Literal::Int(v) => JsValue::new(*v), Literal::BigInt(v) => JsValue::new(JsBigInt::new(v.clone())), @@ -32,7 +32,11 @@ fn js_value_to_literal(value: JsValue, context: &mut Context) -> Literal { JsValue::Null => Literal::Null, JsValue::Undefined => Literal::Undefined, JsValue::Boolean(v) => Literal::Bool(v), - JsValue::String(v) => Literal::String(context.interner_mut().get_or_intern(v.as_ref())), + JsValue::String(v) => { + // TODO: Replace JStrRef with JsStr this would eliminate the to_vec call. + let v = v.to_vec(); + Literal::String(context.interner_mut().get_or_intern(JStrRef::Utf16(&v))) + } JsValue::Rational(v) => Literal::Num(v), JsValue::Integer(v) => Literal::Int(v), JsValue::BigInt(v) => Literal::BigInt(Box::new(v.as_inner().clone())), diff --git a/core/engine/src/property/mod.rs b/core/engine/src/property/mod.rs index c18da6c6cf..bb095142f2 100644 --- a/core/engine/src/property/mod.rs +++ b/core/engine/src/property/mod.rs @@ -18,7 +18,9 @@ mod attribute; mod nonmaxu32; -use crate::{js_string, object::shape::slot::SlotAttributes, JsString, JsSymbol, JsValue}; +use crate::{ + js_string, object::shape::slot::SlotAttributes, string::JsStr, JsString, JsSymbol, JsValue, +}; use boa_gc::{Finalize, Trace}; use std::{fmt, iter::FusedIterator}; @@ -664,23 +666,18 @@ where } } -impl From<&[u16]> for PropertyKey { +impl From> for PropertyKey { #[inline] - fn from(string: &[u16]) -> Self { - debug_assert!(parse_u32_index( - String::from_utf16(string) - .expect("should be ascii string") - .bytes() - ) - .is_none()); - Self::String(string.into()) + fn from(string: JsStr<'_>) -> Self { + return parse_u32_index(string.iter()) + .map_or_else(|| Self::String(string.into()), Self::Index); } } impl From for PropertyKey { #[inline] fn from(string: JsString) -> Self { - parse_u32_index(string.as_slice().iter().copied()).map_or(Self::String(string), Self::Index) + return parse_u32_index(string.as_str().iter()).map_or(Self::String(string), Self::Index); } } diff --git a/core/engine/src/string/common.rs b/core/engine/src/string/common.rs index 173ca15126..ba7785d7f6 100644 --- a/core/engine/src/string/common.rs +++ b/core/engine/src/string/common.rs @@ -1,21 +1,21 @@ -use std::hash::BuildHasherDefault; +//! List of commonly used strings in Javascript code. -use crate::tagged::Tagged; +use crate::{tagged::Tagged, JsStr}; use super::JsString; -use boa_macros::utf16; +use boa_macros::js_str; use paste::paste; use rustc_hash::{FxHashMap, FxHasher}; +use std::hash::BuildHasherDefault; macro_rules! well_known_statics { ( $( $(#[$attr:meta])* ($name:ident, $string:literal) ),+$(,)? ) => { $( paste!{ #[doc = "Gets the static `JsString` for `\"" $string "\"`."] - #[allow(unused)] - pub(crate) const $name: JsString = JsString { + pub const $name: JsString = JsString { ptr: Tagged::from_tag( - Self::find_index(utf16!($string)), + Self::find_index($string), ), }; } @@ -26,13 +26,13 @@ macro_rules! well_known_statics { /// List of commonly used strings in Javascript code. /// /// Any strings defined here are used as a static [`JsString`] instead of allocating on the heap. -#[derive(Debug)] -pub(crate) struct StaticJsStrings; +#[derive(Debug, Clone, Copy)] +pub struct StaticJsStrings; impl StaticJsStrings { // useful to search at compile time a certain string in the array - const fn find_index(candidate: &[u16]) -> usize { - const fn const_eq(lhs: &[u16], rhs: &[u16]) -> bool { + const fn find_index(candidate: &str) -> usize { + const fn const_eq(lhs: &[u8], rhs: &[u8]) -> bool { if lhs.len() != rhs.len() { return false; } @@ -46,20 +46,26 @@ impl StaticJsStrings { } true } + + let len = RAW_STATICS.len(); let mut i = 0; - while i < RAW_STATICS.len() { - let s = RAW_STATICS[i]; - if const_eq(s, candidate) { + while i < len { + let Some(s) = RAW_STATICS[i].as_latin1() else { + // All static strings are latin1 encoded + unreachable!() + }; + if const_eq(s, candidate.as_bytes()) { return i; } i += 1; } + panic!("couldn't find the required string on the common string array"); } /// Gets the `JsString` corresponding to `string`, or `None` if the string /// doesn't exist inside the static array. - pub(crate) fn get_string(string: &[u16]) -> Option { + pub(crate) fn get_string(string: &JsStr<'_>) -> Option { if string.len() > MAX_STATIC_LENGTH { return None; } @@ -73,7 +79,7 @@ impl StaticJsStrings { /// Gets the `&[u16]` slice corresponding to the provided index, or `None` if the index /// provided exceeds the size of the static array. - pub(crate) fn get(index: usize) -> Option<&'static [u16]> { + pub(crate) fn get(index: usize) -> Option> { RAW_STATICS.get(index).copied() } @@ -141,11 +147,11 @@ impl StaticJsStrings { (PLURAL_RULES, "PluralRules"), (SEGMENTER, "Segmenter"), (DATE_TIME_FORMAT, "DateTimeFormat"), - (NUMBER_FORMAT, "NumberFormat"), (JSON, "JSON"), (MAP, "Map"), (MATH, "Math"), (NUMBER, "Number"), + (NUMBER_FORMAT, "NumberFormat"), (IS_FINITE, "isFinite"), (IS_NAN, "isNaN"), (PARSE_INT, "parseInt"), @@ -192,10 +198,11 @@ impl StaticJsStrings { } } -static MAX_STATIC_LENGTH: usize = { +const MAX_STATIC_LENGTH: usize = { let mut max = 0; let mut i = 0; while i < RAW_STATICS.len() { + // TOOD: Because `get_index` is not const, we are accessing doc hidden stuff, that may change. let len = RAW_STATICS[i].len(); if len > max { max = len; @@ -207,7 +214,7 @@ static MAX_STATIC_LENGTH: usize = { thread_local! { /// Map from a string inside [`RAW_STATICS`] to its corresponding static index on `RAW_STATICS`. - static RAW_STATICS_CACHE: FxHashMap<&'static [u16], usize> = { + static RAW_STATICS_CACHE: FxHashMap, usize> = { let mut constants = FxHashMap::with_capacity_and_hasher( RAW_STATICS.len(), BuildHasherDefault::::default(), @@ -222,495 +229,722 @@ thread_local! { } /// Array of raw static strings that aren't reference counted. -const RAW_STATICS: &[&[u16]] = &[ - utf16!(""), +const RAW_STATICS: &[JsStr<'_>] = &[ + js_str!(""), // Well known symbols - utf16!("Symbol.asyncIterator"), - utf16!("[Symbol.asyncIterator]"), - utf16!("Symbol.hasInstance"), - utf16!("[Symbol.hasInstance]"), - utf16!("Symbol.isConcatSpreadable"), - utf16!("[Symbol.isConcatSpreadable]"), - utf16!("Symbol.iterator"), - utf16!("[Symbol.iterator]"), - utf16!("Symbol.match"), - utf16!("[Symbol.match]"), - utf16!("Symbol.matchAll"), - utf16!("[Symbol.matchAll]"), - utf16!("Symbol.replace"), - utf16!("[Symbol.replace]"), - utf16!("Symbol.search"), - utf16!("[Symbol.search]"), - utf16!("Symbol.species"), - utf16!("[Symbol.species]"), - utf16!("Symbol.split"), - utf16!("[Symbol.split]"), - utf16!("Symbol.toPrimitive"), - utf16!("[Symbol.toPrimitive]"), - utf16!("Symbol.toStringTag"), - utf16!("[Symbol.toStringTag]"), - utf16!("Symbol.unscopables"), - utf16!("[Symbol.unscopables]"), + js_str!("Symbol.asyncIterator"), + js_str!("[Symbol.asyncIterator]"), + js_str!("Symbol.hasInstance"), + js_str!("[Symbol.hasInstance]"), + js_str!("Symbol.isConcatSpreadable"), + js_str!("[Symbol.isConcatSpreadable]"), + js_str!("Symbol.iterator"), + js_str!("[Symbol.iterator]"), + js_str!("Symbol.match"), + js_str!("[Symbol.match]"), + js_str!("Symbol.matchAll"), + js_str!("[Symbol.matchAll]"), + js_str!("Symbol.replace"), + js_str!("[Symbol.replace]"), + js_str!("Symbol.search"), + js_str!("[Symbol.search]"), + js_str!("Symbol.species"), + js_str!("[Symbol.species]"), + js_str!("Symbol.split"), + js_str!("[Symbol.split]"), + js_str!("Symbol.toPrimitive"), + js_str!("[Symbol.toPrimitive]"), + js_str!("Symbol.toStringTag"), + js_str!("[Symbol.toStringTag]"), + js_str!("Symbol.unscopables"), + js_str!("[Symbol.unscopables]"), + js_str!("get [Symbol.species]"), + js_str!("get [Symbol.toStringTag]"), // Well known builtins - utf16!("Array"), - utf16!("ArrayBuffer"), - utf16!("SharedArrayBuffer"), - utf16!("AsyncFunction"), - utf16!("AsyncGenerator"), - utf16!("AsyncGeneratorFunction"), - utf16!("Atomics"), - utf16!("BigInt"), - utf16!("Boolean"), - utf16!("DataView"), - utf16!("Date"), - utf16!("Error"), - utf16!("AggregateError"), - utf16!("EvalError"), - utf16!("RangeError"), - utf16!("ReferenceError"), - utf16!("SyntaxError"), - utf16!("TypeError"), - utf16!("URIError"), - utf16!("escape"), - utf16!("unescape"), - utf16!("eval"), - utf16!("Function"), - utf16!("Generator"), - utf16!("GeneratorFunction"), - utf16!("Intl"), - utf16!("Collator"), - utf16!("ListFormat"), - utf16!("Locale"), - utf16!("PluralRules"), - utf16!("Segmenter"), - utf16!("DateTimeFormat"), - utf16!("NumberFormat"), - utf16!("JSON"), - utf16!("Map"), - utf16!("Math"), - utf16!("Number"), - utf16!("isFinite"), - utf16!("isNaN"), - utf16!("parseInt"), - utf16!("parseFloat"), - utf16!("Object"), - utf16!("Promise"), - utf16!("Proxy"), - utf16!("Reflect"), - utf16!("RegExp"), - utf16!("Set"), - utf16!("String"), - utf16!("Symbol"), - utf16!("TypedArray"), - utf16!("Int8Array"), - utf16!("Uint8Array"), - utf16!("Uint8ClampedArray"), - utf16!("Int16Array"), - utf16!("Uint16Array"), - utf16!("Int32Array"), - utf16!("Uint32Array"), - utf16!("BigInt64Array"), - utf16!("BigUint64Array"), - utf16!("Float32Array"), - utf16!("Float64Array"), - utf16!("encodeURI"), - utf16!("encodeURIComponent"), - utf16!("decodeURI"), - utf16!("decodeURIComponent"), - utf16!("WeakRef"), - utf16!("WeakMap"), - utf16!("WeakSet"), - utf16!("Temporal"), - utf16!("Temporal.Now"), - utf16!("Temporal.Instant"), - utf16!("Temporal.Duration"), - utf16!("Temporal.Calendar"), - utf16!("Temporal.PlainDate"), - utf16!("Temporal.PlainDateTime"), - utf16!("Temporal.PlainMonthDay"), - utf16!("Temporal.PlainYearMonth"), - utf16!("Temporal.PlainTime"), - utf16!("Temporal.TimeZone"), - utf16!("Temporal.ZonedDateTime"), + js_str!("Array"), + js_str!("ArrayBuffer"), + js_str!("SharedArrayBuffer"), + js_str!("AsyncFunction"), + js_str!("AsyncGenerator"), + js_str!("AsyncGeneratorFunction"), + js_str!("Atomics"), + js_str!("BigInt"), + js_str!("Boolean"), + js_str!("DataView"), + js_str!("Date"), + js_str!("Error"), + js_str!("AggregateError"), + js_str!("EvalError"), + js_str!("RangeError"), + js_str!("ReferenceError"), + js_str!("SyntaxError"), + js_str!("TypeError"), + js_str!("URIError"), + js_str!("escape"), + js_str!("unescape"), + js_str!("eval"), + js_str!("Function"), + js_str!("Generator"), + js_str!("GeneratorFunction"), + js_str!("Intl"), + js_str!("Collator"), + js_str!("ListFormat"), + js_str!("Locale"), + js_str!("PluralRules"), + js_str!("Segmenter"), + js_str!("DateTimeFormat"), + js_str!("JSON"), + js_str!("Map"), + js_str!("Math"), + js_str!("Number"), + js_str!("NumberFormat"), + js_str!("isFinite"), + js_str!("isNaN"), + js_str!("parseInt"), + js_str!("parseFloat"), + js_str!("Object"), + js_str!("Promise"), + js_str!("Proxy"), + js_str!("Reflect"), + js_str!("RegExp"), + js_str!("Set"), + js_str!("String"), + js_str!("Symbol"), + js_str!("TypedArray"), + js_str!("Int8Array"), + js_str!("Uint8Array"), + js_str!("Uint8ClampedArray"), + js_str!("Int16Array"), + js_str!("Uint16Array"), + js_str!("Int32Array"), + js_str!("Uint32Array"), + js_str!("BigInt64Array"), + js_str!("BigUint64Array"), + js_str!("Float32Array"), + js_str!("Float64Array"), + js_str!("encodeURI"), + js_str!("encodeURIComponent"), + js_str!("decodeURI"), + js_str!("decodeURIComponent"), + js_str!("WeakRef"), + js_str!("WeakMap"), + js_str!("WeakSet"), + js_str!("Temporal"), + js_str!("Temporal.Now"), + js_str!("Temporal.Instant"), + js_str!("Temporal.Duration"), + js_str!("Temporal.Calendar"), + js_str!("Temporal.PlainDate"), + js_str!("Temporal.PlainDateTime"), + js_str!("Temporal.PlainMonthDay"), + js_str!("Temporal.PlainYearMonth"), + js_str!("Temporal.PlainTime"), + js_str!("Temporal.TimeZone"), + js_str!("Temporal.ZonedDateTime"), // Misc - utf16!(","), - utf16!(":"), + js_str!(","), + js_str!(":"), // Generic use - utf16!("name"), - utf16!("length"), - utf16!("arguments"), - utf16!("prototype"), - utf16!("constructor"), - utf16!("return"), - utf16!("throw"), - utf16!("global"), - utf16!("globalThis"), + js_str!("name"), + js_str!("length"), + js_str!("arguments"), + js_str!("prototype"), + js_str!("constructor"), + js_str!("return"), + js_str!("throw"), + js_str!("global"), + js_str!("globalThis"), // typeof - utf16!("null"), - utf16!("undefined"), - utf16!("number"), - utf16!("string"), - utf16!("symbol"), - utf16!("bigint"), - utf16!("object"), - utf16!("function"), + js_str!("null"), + js_str!("undefined"), + js_str!("number"), + js_str!("string"), + js_str!("symbol"), + js_str!("bigint"), + js_str!("object"), + js_str!("function"), // Property descriptor - utf16!("value"), - utf16!("get"), - utf16!("set"), - utf16!("writable"), - utf16!("enumerable"), - utf16!("configurable"), + js_str!("value"), + js_str!("get"), + js_str!("set"), + js_str!("writable"), + js_str!("enumerable"), + js_str!("configurable"), // Object object - utf16!("Object"), - utf16!("assign"), - utf16!("create"), - utf16!("toString"), - utf16!("valueOf"), - utf16!("is"), - utf16!("seal"), - utf16!("isSealed"), - utf16!("freeze"), - utf16!("isFrozen"), - utf16!("isExtensible"), - utf16!("hasOwnProperty"), - utf16!("isPrototypeOf"), - utf16!("setPrototypeOf"), - utf16!("getPrototypeOf"), - utf16!("defineProperty"), - utf16!("defineProperties"), - utf16!("deleteProperty"), - utf16!("construct"), - utf16!("hasOwn"), - utf16!("ownKeys"), - utf16!("keys"), - utf16!("values"), - utf16!("entries"), - utf16!("fromEntries"), + js_str!("assign"), + js_str!("create"), + js_str!("toString"), + js_str!("valueOf"), + js_str!("is"), + js_str!("seal"), + js_str!("isSealed"), + js_str!("freeze"), + js_str!("isFrozen"), + js_str!("isExtensible"), + js_str!("hasOwnProperty"), + js_str!("isPrototypeOf"), + js_str!("setPrototypeOf"), + js_str!("getPrototypeOf"), + js_str!("defineProperty"), + js_str!("defineProperties"), + js_str!("deleteProperty"), + js_str!("construct"), + js_str!("hasOwn"), + js_str!("ownKeys"), + js_str!("keys"), + js_str!("values"), + js_str!("entries"), + js_str!("fromEntries"), + js_str!("propertyIsEnumerable"), + js_str!("preventExtensions"), + js_str!("getOwnPropertyDescriptor"), + js_str!("getOwnPropertyDescriptors"), + js_str!("getOwnPropertyNames"), + js_str!("getOwnPropertySymbols"), + js_str!("__defineGetter__"), + js_str!("__defineSetter__"), + js_str!("__lookupGetter__"), + js_str!("__lookupSetter__"), + js_str!("__proto__"), + js_str!("get __proto__"), + js_str!("set __proto__"), // Function object - utf16!("apply"), - utf16!("bind"), - utf16!("call"), + js_str!("apply"), + js_str!("bind"), + js_str!("call"), + js_str!("caller"), + // Arguments object + js_str!("callee"), // Array object - utf16!("at"), - utf16!("from"), - utf16!("isArray"), - utf16!("of"), - utf16!("copyWithin"), - utf16!("every"), - utf16!("fill"), - utf16!("filter"), - utf16!("find"), - utf16!("findIndex"), - utf16!("findLast"), - utf16!("findLastIndex"), - utf16!("flat"), - utf16!("flatMap"), - utf16!("forEach"), - utf16!("includes"), - utf16!("indexOf"), - utf16!("join"), - utf16!("map"), - utf16!("next"), - utf16!("reduce"), - utf16!("reduceRight"), - utf16!("reverse"), - utf16!("shift"), - utf16!("slice"), - utf16!("splice"), - utf16!("some"), - utf16!("sort"), - utf16!("unshift"), - utf16!("push"), - utf16!("pop"), + js_str!("at"), + js_str!("from"), + js_str!("isArray"), + js_str!("of"), + js_str!("copyWithin"), + js_str!("every"), + js_str!("fill"), + js_str!("filter"), + js_str!("find"), + js_str!("findIndex"), + js_str!("findLast"), + js_str!("findLastIndex"), + js_str!("flat"), + js_str!("flatMap"), + js_str!("forEach"), + js_str!("includes"), + js_str!("indexOf"), + js_str!("join"), + js_str!("map"), + js_str!("next"), + js_str!("reduce"), + js_str!("reduceRight"), + js_str!("reverse"), + js_str!("shift"), + js_str!("slice"), + js_str!("splice"), + js_str!("some"), + js_str!("sort"), + js_str!("unshift"), + js_str!("push"), + js_str!("pop"), + js_str!("groupBy"), + js_str!("toReversed"), + js_str!("toSorted"), + js_str!("toSpliced"), + js_str!("with"), // String object - utf16!("charAt"), - utf16!("charCodeAt"), - utf16!("codePointAt"), - utf16!("concat"), - utf16!("endsWith"), - utf16!("fromCharCode"), - utf16!("fromCodePoint"), - utf16!("lastIndexOf"), - utf16!("match"), - utf16!("matchAll"), - utf16!("normalize"), - utf16!("padEnd"), - utf16!("padStart"), - utf16!("raw"), - utf16!("repeat"), - utf16!("replace"), - utf16!("replaceAll"), - utf16!("search"), - utf16!("split"), - utf16!("startsWith"), - utf16!("substr"), - utf16!("substring"), - utf16!("toLocaleString"), - utf16!("toLowerCase"), - utf16!("toUpperCase"), - utf16!("trim"), - utf16!("trimEnd"), - utf16!("trimStart"), + js_str!("charAt"), + js_str!("charCodeAt"), + js_str!("codePointAt"), + js_str!("concat"), + js_str!("endsWith"), + js_str!("fromCharCode"), + js_str!("fromCodePoint"), + js_str!("lastIndexOf"), + js_str!("match"), + js_str!("matchAll"), + js_str!("normalize"), + js_str!("padEnd"), + js_str!("padStart"), + js_str!("raw"), + js_str!("repeat"), + js_str!("replace"), + js_str!("replaceAll"), + js_str!("search"), + js_str!("split"), + js_str!("startsWith"), + js_str!("substr"), + js_str!("substring"), + js_str!("toLocaleString"), + js_str!("toLowerCase"), + js_str!("toUpperCase"), + js_str!("trim"), + js_str!("trimEnd"), + js_str!("trimStart"), + js_str!("isWellFormed"), + js_str!("localeCompare"), + js_str!("toWellFormed"), + js_str!("toLocaleLowerCase"), + js_str!("toLocaleUpperCase"), + js_str!("trimLeft"), + js_str!("trimRight"), + js_str!("anchor"), + js_str!("big"), + js_str!("blink"), + js_str!("bold"), + js_str!("fixed"), + js_str!("fontcolor"), + js_str!("fontsize"), + js_str!("italics"), + js_str!("link"), + js_str!("small"), + js_str!("strike"), + js_str!("sub"), + js_str!("sup"), // Number object - utf16!("Infinity"), - utf16!("NaN"), - utf16!("EPSILON"), - utf16!("MAX_SAFE_INTEGER"), - utf16!("MIN_SAFE_INTEGER"), - utf16!("MAX_VALUE"), - utf16!("MIN_VALUE"), - utf16!("isSafeInteger"), - utf16!("isInteger"), - utf16!("toExponential"), - utf16!("toFixed"), - utf16!("toPrecision"), + js_str!("Infinity"), + js_str!("NaN"), + js_str!("EPSILON"), + js_str!("MAX_SAFE_INTEGER"), + js_str!("MIN_SAFE_INTEGER"), + js_str!("MAX_VALUE"), + js_str!("MIN_VALUE"), + js_str!("NEGATIVE_INFINITY"), + js_str!("POSITIVE_INFINITY"), + js_str!("isSafeInteger"), + js_str!("isInteger"), + js_str!("toExponential"), + js_str!("toFixed"), + js_str!("toPrecision"), // BigInt object - utf16!("asIntN"), - utf16!("asUintN"), + js_str!("asIntN"), + js_str!("asUintN"), // RegExp object - utf16!("exec"), - utf16!("test"), - utf16!("flags"), - utf16!("index"), - utf16!("lastIndex"), - utf16!("hasIndices"), - utf16!("ignoreCase"), - utf16!("multiline"), - utf16!("dotAll"), - utf16!("unicode"), - utf16!("sticky"), - utf16!("source"), - utf16!("get hasIndices"), - utf16!("get global"), - utf16!("get ignoreCase"), - utf16!("get multiline"), - utf16!("get dotAll"), - utf16!("get unicode"), - utf16!("get sticky"), - utf16!("get flags"), - utf16!("get source"), + js_str!("exec"), + js_str!("test"), + js_str!("compile"), + js_str!("flags"), + js_str!("index"), + js_str!("lastIndex"), + js_str!("hasIndices"), + js_str!("ignoreCase"), + js_str!("multiline"), + js_str!("dotAll"), + js_str!("unicode"), + js_str!("sticky"), + js_str!("source"), + js_str!("get hasIndices"), + js_str!("get global"), + js_str!("get ignoreCase"), + js_str!("get multiline"), + js_str!("get dotAll"), + js_str!("get unicode"), + js_str!("get sticky"), + js_str!("get flags"), + js_str!("get source"), // Symbol object - utf16!("for"), - utf16!("keyFor"), - utf16!("description"), - utf16!("asyncIterator"), - utf16!("hasInstance"), - utf16!("species"), - utf16!("unscopables"), - utf16!("iterator"), - utf16!("toStringTag"), - utf16!("toPrimitive"), - utf16!("get description"), + js_str!("for"), + js_str!("keyFor"), + js_str!("description"), + js_str!("asyncIterator"), + js_str!("hasInstance"), + js_str!("species"), + js_str!("unscopables"), + js_str!("iterator"), + js_str!("toStringTag"), + js_str!("toPrimitive"), + js_str!("isConcatSpreadable"), + js_str!("get description"), // Map object - utf16!("clear"), - utf16!("delete"), - utf16!("has"), - utf16!("size"), + js_str!("clear"), + js_str!("delete"), + js_str!("has"), + js_str!("size"), // Set object - utf16!("add"), + js_str!("add"), // Reflect object // Proxy object - utf16!("revocable"), + js_str!("revocable"), // Error objects - utf16!("message"), + js_str!("message"), // Date object - utf16!("toJSON"), - utf16!("getDate"), - utf16!("getDay"), - utf16!("getFullYear"), - utf16!("getHours"), - utf16!("getMilliseconds"), - utf16!("getMinutes"), - utf16!("getMonth"), - utf16!("getSeconds"), - utf16!("getTime"), - utf16!("getYear"), - utf16!("getUTCDate"), - utf16!("getUTCDay"), - utf16!("getUTCFullYear"), - utf16!("getUTCHours"), - utf16!("getUTCMinutes"), - utf16!("getUTCMonth"), - utf16!("getUTCSeconds"), - utf16!("setDate"), - utf16!("setFullYear"), - utf16!("setHours"), - utf16!("setMilliseconds"), - utf16!("setMinutes"), - utf16!("setMonth"), - utf16!("setSeconds"), - utf16!("setYear"), - utf16!("setTime"), - utf16!("setUTCDate"), - utf16!("setUTCFullYear"), - utf16!("setUTCHours"), - utf16!("setUTCMinutes"), - utf16!("setUTCMonth"), - utf16!("setUTCSeconds"), - utf16!("toDateString"), - utf16!("toGMTString"), - utf16!("toISOString"), - utf16!("toTimeString"), - utf16!("toUTCString"), - utf16!("now"), - utf16!("UTC"), + js_str!("toJSON"), + js_str!("getDate"), + js_str!("getDay"), + js_str!("getFullYear"), + js_str!("getHours"), + js_str!("getMilliseconds"), + js_str!("getMinutes"), + js_str!("getMonth"), + js_str!("getSeconds"), + js_str!("getTime"), + js_str!("getYear"), + js_str!("getUTCDate"), + js_str!("getUTCDay"), + js_str!("getUTCFullYear"), + js_str!("getUTCHours"), + js_str!("getUTCMinutes"), + js_str!("getUTCMonth"), + js_str!("getUTCSeconds"), + js_str!("setDate"), + js_str!("setFullYear"), + js_str!("setHours"), + js_str!("setMilliseconds"), + js_str!("setMinutes"), + js_str!("setMonth"), + js_str!("setSeconds"), + js_str!("setYear"), + js_str!("setTime"), + js_str!("setUTCDate"), + js_str!("setUTCFullYear"), + js_str!("setUTCHours"), + js_str!("setUTCMinutes"), + js_str!("setUTCMonth"), + js_str!("setUTCSeconds"), + js_str!("toDateString"), + js_str!("toGMTString"), + js_str!("toISOString"), + js_str!("toTimeString"), + js_str!("toUTCString"), + js_str!("now"), + js_str!("UTC"), + js_str!("getTimezoneOffset"), + js_str!("getUTCMilliseconds"), + js_str!("setUTCMilliseconds"), + js_str!("toLocaleDateString"), + js_str!("toLocaleTimeString"), // JSON object - utf16!("parse"), - utf16!("stringify"), + js_str!("parse"), + js_str!("stringify"), + // Promise object + js_str!("promise"), + js_str!("resolve"), + js_str!("reject"), + js_str!("all"), + js_str!("allSettled"), + js_str!("any"), + js_str!("race"), + js_str!("then"), + js_str!("catch"), + js_str!("finally"), + js_str!("withResolvers"), // Iterator object - utf16!("Array Iterator"), - utf16!("Set Iterator"), - utf16!("String Iterator"), - utf16!("Map Iterator"), - utf16!("For In Iterator"), + js_str!("Array Iterator"), + js_str!("Set Iterator"), + js_str!("String Iterator"), + js_str!("Map Iterator"), + js_str!("For In Iterator"), + js_str!("RegExp String Iterator"), + // Iterator result object + js_str!("done"), // Math object - utf16!("LN10"), - utf16!("LN2"), - utf16!("LOG10E"), - utf16!("LOG2E"), - utf16!("PI"), - utf16!("SQRT1_2"), - utf16!("SQRT2"), - utf16!("abs"), - utf16!("acos"), - utf16!("acosh"), - utf16!("asin"), - utf16!("asinh"), - utf16!("atan"), - utf16!("atanh"), - utf16!("atan2"), - utf16!("cbrt"), - utf16!("ceil"), - utf16!("clz32"), - utf16!("cos"), - utf16!("cosh"), - utf16!("exp"), - utf16!("expm1"), - utf16!("floor"), - utf16!("fround"), - utf16!("hypot"), - utf16!("imul"), - utf16!("log"), - utf16!("log1p"), - utf16!("log10"), - utf16!("log2"), - utf16!("max"), - utf16!("min"), - utf16!("pow"), - utf16!("random"), - utf16!("round"), - utf16!("sign"), - utf16!("sin"), - utf16!("sinh"), - utf16!("sqrt"), - utf16!("tan"), - utf16!("tanh"), - utf16!("trunc"), + js_str!("LN10"), + js_str!("LN2"), + js_str!("LOG10E"), + js_str!("LOG2E"), + js_str!("PI"), + js_str!("SQRT1_2"), + js_str!("SQRT2"), + js_str!("abs"), + js_str!("acos"), + js_str!("acosh"), + js_str!("asin"), + js_str!("asinh"), + js_str!("atan"), + js_str!("atanh"), + js_str!("atan2"), + js_str!("cbrt"), + js_str!("ceil"), + js_str!("clz32"), + js_str!("cos"), + js_str!("cosh"), + js_str!("exp"), + js_str!("expm1"), + js_str!("floor"), + js_str!("fround"), + js_str!("hypot"), + js_str!("imul"), + js_str!("log"), + js_str!("log1p"), + js_str!("log10"), + js_str!("log2"), + js_str!("max"), + js_str!("min"), + js_str!("pow"), + js_str!("random"), + js_str!("round"), + js_str!("sign"), + js_str!("sin"), + js_str!("sinh"), + js_str!("sqrt"), + js_str!("tan"), + js_str!("tanh"), + js_str!("trunc"), // TypedArray object - utf16!("buffer"), - utf16!("byteLength"), - utf16!("byteOffset"), - utf16!("isView"), - utf16!("subarray"), - utf16!("get byteLength"), - utf16!("get buffer"), - utf16!("get byteOffset"), - utf16!("get size"), - utf16!("get length"), + js_str!("BYTES_PER_ELEMENT"), + js_str!("buffer"), + js_str!("byteLength"), + js_str!("byteOffset"), + js_str!("isView"), + js_str!("subarray"), + js_str!("get byteLength"), + js_str!("get buffer"), + js_str!("get byteOffset"), + js_str!("get size"), + js_str!("get length"), // DataView object - utf16!("getBigInt64"), - utf16!("getBigUint64"), - utf16!("getFloat32"), - utf16!("getFloat64"), - utf16!("getInt8"), - utf16!("getInt16"), - utf16!("getInt32"), - utf16!("getUint8"), - utf16!("getUint16"), - utf16!("getUint32"), - utf16!("setBigInt64"), - utf16!("setBigUint64"), - utf16!("setFloat32"), - utf16!("setFloat64"), - utf16!("setInt8"), - utf16!("setInt16"), - utf16!("setInt32"), - utf16!("setUint8"), - utf16!("setUint16"), - utf16!("setUint32"), + js_str!("getBigInt64"), + js_str!("getBigUint64"), + js_str!("getFloat32"), + js_str!("getFloat64"), + js_str!("getInt8"), + js_str!("getInt16"), + js_str!("getInt32"), + js_str!("getUint8"), + js_str!("getUint16"), + js_str!("getUint32"), + js_str!("setBigInt64"), + js_str!("setBigUint64"), + js_str!("setFloat32"), + js_str!("setFloat64"), + js_str!("setInt8"), + js_str!("setInt16"), + js_str!("setInt32"), + js_str!("setUint8"), + js_str!("setUint16"), + js_str!("setUint32"), + // WeakRef object + js_str!("deref"), + // Atomic object + js_str!("and"), + js_str!("compareExchange"), + js_str!("exchange"), + js_str!("isLockFree"), + js_str!("load"), + js_str!("or"), + js_str!("store"), + js_str!("wait"), + js_str!("notify"), + js_str!("xor"), + // Intl object + js_str!("getCanonicalLocales"), + js_str!("get compare"), + js_str!("supportedLocalesOf"), + js_str!("Intl.Collator"), + js_str!("compare"), + js_str!("resolvedOptions"), + js_str!("Intl.ListFormat"), + js_str!("format"), + js_str!("formatToParts"), + js_str!("get baseName"), + js_str!("get calendar"), + js_str!("get caseFirst"), + js_str!("get collation"), + js_str!("get hourCycle"), + js_str!("get numeric"), + js_str!("get numberingSystem"), + js_str!("get language"), + js_str!("get script"), + js_str!("get region"), + js_str!("Intl.Locale"), + js_str!("maximize"), + js_str!("minimize"), + js_str!("baseName"), + js_str!("calendar"), + js_str!("caseFirst"), + js_str!("collation"), + js_str!("hourCycle"), + js_str!("numeric"), + js_str!("numberingSystem"), + js_str!("language"), + js_str!("script"), + js_str!("region"), + js_str!("Intl.Segmenter"), + js_str!("segment"), + js_str!("containing"), + js_str!("Segmenter String Iterator"), + js_str!("Intl.PluralRules"), + js_str!("select"), + // Temporal object + js_str!("get Id"), + js_str!("getOffsetNanosecondsFor"), + js_str!("getOffsetStringFor"), + js_str!("getPlainDateTimeFor"), + js_str!("getInstantFor"), + js_str!("getPossibleInstantFor"), + js_str!("getNextTransition"), + js_str!("getPreviousTransition"), + js_str!("id"), + js_str!("Now"), + js_str!("Calendar"), + js_str!("Duration"), + js_str!("Instant"), + js_str!("PlainDate"), + js_str!("PlainDateTime"), + js_str!("PlainMonthDay"), + js_str!("PlainTime"), + js_str!("PlainYearMonth"), + js_str!("TimeZone"), + js_str!("ZonedDateTime"), + js_str!("timeZoneId"), + js_str!("instant"), + js_str!("plainDateTime"), + js_str!("plainDateTimeISO"), + js_str!("zonedDateTime"), + js_str!("zonedDateTimeISO"), + js_str!("plainDate"), + js_str!("plainDateISO"), + js_str!("get epochSeconds"), + js_str!("get epochMilliseconds"), + js_str!("get epochMicroseconds"), + js_str!("get epochNanoseconds"), + js_str!("epochSeconds"), + js_str!("epochMilliseconds"), + js_str!("epochMicroseconds"), + js_str!("epochNanoseconds"), + js_str!("subtract"), + js_str!("until"), + js_str!("since"), + js_str!("equals"), + js_str!("toZonedDateTime"), + js_str!("toZonedDateTimeISO"), + js_str!("get Years"), + js_str!("get Months"), + js_str!("get Weeks"), + js_str!("get Days"), + js_str!("get Hours"), + js_str!("get Minutes"), + js_str!("get Seconds"), + js_str!("get Milliseconds"), + js_str!("get Microseconds"), + js_str!("get Nanoseconds"), + js_str!("get Sign"), + js_str!("get blank"), + js_str!("years"), + js_str!("months"), + js_str!("weeks"), + js_str!("days"), + js_str!("hours"), + js_str!("minutes"), + js_str!("seconds"), + js_str!("milliseconds"), + js_str!("microseconds"), + js_str!("nanoseconds"), + js_str!("blank"), + js_str!("negated"), + js_str!("total"), + js_str!("get calendarId"), + js_str!("get year"), + js_str!("get month"), + js_str!("get monthCode"), + js_str!("get day"), + js_str!("get dayOfWeek"), + js_str!("get dayOfYear"), + js_str!("get weekOfYear"), + js_str!("get yearOfWeek"), + js_str!("get daysInWeek"), + js_str!("get daysInMonth"), + js_str!("get daysInYear"), + js_str!("get monthsInYear"), + js_str!("get inLeapYear"), + js_str!("calendarId"), + js_str!("year"), + js_str!("month"), + js_str!("monthCode"), + js_str!("day"), + js_str!("dayOfWeek"), + js_str!("dayOfYear"), + js_str!("weekOfYear"), + js_str!("yearOfWeek"), + js_str!("daysInWeek"), + js_str!("daysInMonth"), + js_str!("daysInYear"), + js_str!("monthsInYear"), + js_str!("inLeapYear"), + js_str!("toPlainYearMonth"), + js_str!("toPlainMonthDay"), + js_str!("getISOFields"), + js_str!("getCalendar"), + js_str!("withCalendar"), + js_str!("dateFromFields"), + js_str!("yearMonthFromFields"), + js_str!("monthDayFromFields"), + js_str!("dateAdd"), + js_str!("dateUntil"), + js_str!("era"), + js_str!("eraYear"), + js_str!("fields"), + js_str!("mergeFields"), // Console object - utf16!("console"), - utf16!("assert"), - utf16!("debug"), - utf16!("error"), - utf16!("info"), - utf16!("trace"), - utf16!("warn"), - utf16!("exception"), - utf16!("count"), - utf16!("countReset"), - utf16!("group"), - utf16!("groupCollapsed"), - utf16!("groupEnd"), - utf16!("time"), - utf16!("timeLog"), - utf16!("timeEnd"), - utf16!("dir"), - utf16!("dirxml"), + js_str!("console"), + js_str!("assert"), + js_str!("debug"), + js_str!("error"), + js_str!("info"), + js_str!("trace"), + js_str!("warn"), + js_str!("exception"), + js_str!("count"), + js_str!("countReset"), + js_str!("group"), + js_str!("groupCollapsed"), + js_str!("groupEnd"), + js_str!("time"), + js_str!("timeLog"), + js_str!("timeEnd"), + js_str!("dir"), + js_str!("dirxml"), // Minified name - utf16!("a"), - utf16!("b"), - utf16!("c"), - utf16!("d"), - utf16!("e"), - utf16!("f"), - utf16!("g"), - utf16!("h"), - utf16!("i"), - utf16!("j"), - utf16!("k"), - utf16!("l"), - utf16!("m"), - utf16!("n"), - utf16!("o"), - utf16!("p"), - utf16!("q"), - utf16!("r"), - utf16!("s"), - utf16!("t"), - utf16!("u"), - utf16!("v"), - utf16!("w"), - utf16!("x"), - utf16!("y"), - utf16!("z"), - utf16!("A"), - utf16!("B"), - utf16!("C"), - utf16!("D"), - utf16!("E"), - utf16!("F"), - utf16!("G"), - utf16!("H"), - utf16!("I"), - utf16!("J"), - utf16!("K"), - utf16!("L"), - utf16!("M"), - utf16!("N"), - utf16!("O"), - utf16!("P"), - utf16!("Q"), - utf16!("R"), - utf16!("S"), - utf16!("T"), - utf16!("U"), - utf16!("V"), - utf16!("W"), - utf16!("X"), - utf16!("Y"), - utf16!("Z"), - utf16!("_"), - utf16!("$"), + js_str!("a"), + js_str!("c"), + js_str!("d"), + js_str!("e"), + js_str!("f"), + js_str!("g"), + js_str!("h"), + js_str!("i"), + js_str!("j"), + js_str!("k"), + js_str!("l"), + js_str!("m"), + js_str!("n"), + js_str!("o"), + js_str!("p"), + js_str!("q"), + js_str!("r"), + js_str!("s"), + js_str!("t"), + js_str!("u"), + js_str!("v"), + js_str!("w"), + js_str!("x"), + js_str!("y"), + js_str!("z"), + js_str!("A"), + js_str!("C"), + js_str!("D"), + js_str!("E"), + js_str!("F"), + js_str!("G"), + js_str!("H"), + js_str!("I"), + js_str!("J"), + js_str!("K"), + js_str!("L"), + js_str!("M"), + js_str!("N"), + js_str!("O"), + js_str!("P"), + js_str!("Q"), + js_str!("R"), + js_str!("S"), + js_str!("T"), + js_str!("U"), + js_str!("V"), + js_str!("W"), + js_str!("X"), + js_str!("Y"), + js_str!("Z"), + js_str!("_"), + js_str!("$"), ]; diff --git a/core/engine/src/string/iter.rs b/core/engine/src/string/iter.rs new file mode 100644 index 0000000000..03dedcb6b8 --- /dev/null +++ b/core/engine/src/string/iter.rs @@ -0,0 +1,95 @@ +use std::iter::FusedIterator; + +use crate::JsStr; + +use super::JsStrVariant; + +#[derive(Debug, Clone)] +enum IterInner<'a> { + U8(std::iter::Copied>), + U16(std::iter::Copied>), +} + +/// Iterator over a [`JsStr`]. +#[derive(Debug, Clone)] +pub struct Iter<'a> { + inner: IterInner<'a>, +} + +impl<'a> Iter<'a> { + pub(crate) fn new(s: JsStr<'a>) -> Self { + let inner = match s.variant() { + JsStrVariant::Latin1(s) => IterInner::U8(s.iter().copied()), + JsStrVariant::Utf16(s) => IterInner::U16(s.iter().copied()), + }; + Iter { inner } + } +} + +impl Iterator for Iter<'_> { + type Item = u16; + + fn next(&mut self) -> Option { + match &mut self.inner { + IterInner::U8(iter) => iter.map(u16::from).next(), + IterInner::U16(iter) => iter.next(), + } + } +} + +impl FusedIterator for Iter<'_> {} + +impl ExactSizeIterator for Iter<'_> { + fn len(&self) -> usize { + match &self.inner { + IterInner::U8(v) => v.len(), + IterInner::U16(v) => v.len(), + } + } +} + +#[derive(Debug, Clone)] +enum WindowsInner<'a> { + U8(std::slice::Windows<'a, u8>), + U16(std::slice::Windows<'a, u16>), +} + +/// An iterator over overlapping subslices of length size. +/// +/// This struct is created by the `windows` method. +#[derive(Debug, Clone)] +pub struct Windows<'a> { + inner: WindowsInner<'a>, +} + +impl<'a> Windows<'a> { + pub(crate) fn new(string: JsStr<'a>, size: usize) -> Self { + let inner = match string.variant() { + JsStrVariant::Latin1(v) => WindowsInner::U8(v.windows(size)), + JsStrVariant::Utf16(v) => WindowsInner::U16(v.windows(size)), + }; + Self { inner } + } +} + +impl<'a> Iterator for Windows<'a> { + type Item = JsStr<'a>; + + fn next(&mut self) -> Option { + match &mut self.inner { + WindowsInner::U8(iter) => iter.next().map(JsStr::latin1), + WindowsInner::U16(iter) => iter.next().map(JsStr::utf16), + } + } +} + +impl FusedIterator for Windows<'_> {} + +impl ExactSizeIterator for Windows<'_> { + fn len(&self) -> usize { + match &self.inner { + WindowsInner::U8(v) => v.len(), + WindowsInner::U16(v) => v.len(), + } + } +} diff --git a/core/engine/src/string/mod.rs b/core/engine/src/string/mod.rs index ccbbebed2d..16f2172731 100644 --- a/core/engine/src/string/mod.rs +++ b/core/engine/src/string/mod.rs @@ -1,10 +1,10 @@ -//! A UTF-16–encoded, reference counted, immutable string. +//! A Latin1 or UTF-16 encoded, reference counted, immutable string. //! //! This module contains the [`JsString`] type, the [`js_string`][crate::js_string] macro and the -//! [`utf16`] macro. +//! [`js_str`][crate::js_str] macro. //! //! The [`js_string`][crate::js_string] macro is used when you need to create a new [`JsString`], -//! and the [`utf16`] macro is used for const conversions of string literals to UTF-16. +//! and the [`js_str`][crate::js_str] macro is used for const conversions of string literals to [`JsStr`]. // Required per unsafe code standards to ensure every unsafe usage is properly documented. // - `unsafe_op_in_unsafe_fn` will be warn-by-default in edition 2024: @@ -21,32 +21,32 @@ // the same names from the unstable functions of the `std::ptr` module. #![allow(unstable_name_collisions)] -pub(crate) mod common; +pub mod common; +mod iter; +mod str; +use self::{common::StaticJsStrings, iter::Windows, str::JsSliceIndex}; +#[doc(inline)] +pub use crate::string::{ + iter::Iter, + str::{JsStr, JsStrVariant}, +}; use crate::{ builtins::string::is_trimmable_whitespace, tagged::{Tagged, UnwrappedTagged}, - JsBigInt, }; use boa_gc::{Finalize, Trace}; -pub use boa_macros::utf16; - use std::{ alloc::{alloc, dealloc, Layout}, - borrow::Borrow, cell::Cell, convert::Infallible, hash::{Hash, Hasher}, iter::Peekable, - ops::{Deref, Index}, process::abort, ptr::{self, addr_of, addr_of_mut, NonNull}, - slice::SliceIndex, str::FromStr, }; -use self::common::StaticJsStrings; - fn alloc_overflow() -> ! { panic!("detected overflow during string allocation") } @@ -59,7 +59,6 @@ fn alloc_overflow() -> ! { /// /// ``` /// use boa_engine::js_string; -/// use boa_engine::string::utf16; /// /// let empty_str = js_string!(); /// assert!(empty_str.is_empty()); @@ -71,9 +70,8 @@ fn alloc_overflow() -> ! { /// /// ``` /// # use boa_engine::js_string; -/// # use boa_engine::string::utf16; /// let hw = js_string!("Hello, world!"); -/// assert_eq!(&hw, utf16!("Hello, world!")); +/// assert_eq!(&hw, "Hello, world!"); /// ``` /// /// Any `&[u16]` slice is a valid `JsString`, including unpaired surrogates: @@ -87,30 +85,30 @@ fn alloc_overflow() -> ! { /// the concatenation of every slice: /// /// ``` -/// # use boa_engine::js_string; -/// # use boa_engine::string::utf16; -/// const NAME: &[u16] = utf16!("human! "); +/// # use boa_engine::{js_string, js_str, JsStr}; +/// const NAME: JsStr<'_> = js_str!("human! "); /// let greeting = js_string!("Hello, "); -/// let msg = js_string!(&greeting, &NAME, utf16!("Nice to meet you!")); +/// let msg = js_string!(&greeting, NAME, js_str!("Nice to meet you!")); /// -/// assert_eq!(&msg, utf16!("Hello, human! Nice to meet you!")); +/// assert_eq!(&msg, "Hello, human! Nice to meet you!"); /// ``` #[macro_export] +#[allow(clippy::module_name_repetitions)] macro_rules! js_string { () => { - $crate::JsString::default() + $crate::string::JsString::default() }; ($s:literal) => { - $crate::JsString::from($crate::string::utf16!($s)) + $crate::string::JsString::from($crate::js_str!($s)) }; ($s:expr) => { - $crate::JsString::from($s) + $crate::string::JsString::from($s) }; ( $x:expr, $y:expr ) => { - $crate::JsString::concat($x, $y) + $crate::string::JsString::concat($crate::string::JsStr::from($x), $crate::string::JsStr::from($y)) }; ( $( $s:expr ),+ ) => { - $crate::JsString::concat_array(&[ $( $s ),+ ]) + $crate::string::JsString::concat_array(&[ $( $crate::string::JsStr::from($s) ),+ ]) }; } @@ -177,8 +175,10 @@ impl CodePoint { /// The raw representation of a [`JsString`] in the heap. #[repr(C)] struct RawJsString { - /// The UTF-16 length. - len: usize, + /// Contains the flags and Latin1/UTF-16 length. + /// + /// The latin1 flag is stored in the bottom bit. + flags_and_len: usize, /// The number of references to the string. /// @@ -189,63 +189,142 @@ struct RawJsString { data: [u16; 0], } +impl RawJsString { + const LATIN1_BITFLAG: usize = 1 << 0; + const BITFLAG_COUNT: usize = 1; + + const fn is_latin1(&self) -> bool { + (self.flags_and_len & Self::LATIN1_BITFLAG) != 0 + } + + const fn len(&self) -> usize { + self.flags_and_len >> Self::BITFLAG_COUNT + } + + const fn encode_flags_and_len(len: usize, latin1: bool) -> usize { + (len << Self::BITFLAG_COUNT) | (latin1 as usize) + } +} + const DATA_OFFSET: usize = std::mem::size_of::(); -/// A UTF-16–encoded, reference counted, immutable string. +/// A Latin1 or UTF-16–encoded, reference counted, immutable string. /// /// This is pretty similar to a [Rc][std::rc::Rc]\<[\[u16\]][slice]\>, but without the /// length metadata associated with the `Rc` fat pointer. Instead, the length of every string is /// stored on the heap, along with its reference counter and its data. /// +/// The string can be latin1 (stored as a byte for space efficiency) or U16 encoding. +/// /// We define some commonly used string constants in an interner. For these strings, we don't allocate /// memory on the heap to reduce the overhead of memory allocation and reference counting. -/// -/// # Deref -/// -/// [`JsString`] implements [Deref], inheriting all of -/// \[u16\]'s methods. #[derive(Trace, Finalize)] // Safety: `JsString` does not contain any objects which needs to be traced, so this is safe. #[boa_gc(unsafe_empty_trace)] +#[allow(clippy::module_name_repetitions)] pub struct JsString { ptr: Tagged, } // JsString should always be pointer sized. -sa::assert_eq_size!(JsString, *const ()); +static_assertions::assert_eq_size!(JsString, *const ()); + +impl<'a> From<&'a JsString> for JsStr<'a> { + fn from(value: &'a JsString) -> Self { + value.as_str() + } +} + +impl<'a> IntoIterator for &'a JsString { + type IntoIter = Iter<'a>; + + type Item = u16; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} impl JsString { + /// Create an iterator over the [`JsString`]. + #[inline] + #[must_use] + pub fn iter(&self) -> Iter<'_> { + Iter::new(self.as_str()) + } + + /// Create an iterator over overlapping subslices of length size. + #[inline] + #[must_use] + pub fn windows(&self, size: usize) -> Windows<'_> { + Windows::new(self.as_str(), size) + } + /// Obtains the underlying [`&[u16]`][slice] slice of a [`JsString`] #[must_use] - pub fn as_slice(&self) -> &[u16] { - self + pub fn as_str(&self) -> JsStr<'_> { + match self.ptr.unwrap() { + UnwrappedTagged::Ptr(h) => { + // SAFETY: + // - The `RawJsString` type has all the necessary information to reconstruct a valid + // slice (length and starting pointer). + // + // - We aligned `h.data` on allocation, and the block is of size `h.len`, so this + // should only generate valid reads. + // + // - The lifetime of `&Self::Target` is shorter than the lifetime of `self`, as seen + // by its signature, so this doesn't outlive `self`. + unsafe { + let h = h.as_ptr(); + + if (*h).is_latin1() { + JsStr::latin1(std::slice::from_raw_parts( + addr_of!((*h).data).cast(), + (*h).len(), + )) + } else { + JsStr::utf16(std::slice::from_raw_parts( + addr_of!((*h).data).cast(), + (*h).len(), + )) + } + } + } + UnwrappedTagged::Tag(index) => { + // SAFETY: all static strings are valid indices on `STATIC_JS_STRINGS`, so `get` should always + // return `Some`. + unsafe { StaticJsStrings::get(index).unwrap_unchecked() } + } + } } /// Creates a new [`JsString`] from the concatenation of `x` and `y`. #[must_use] - pub fn concat(x: &[u16], y: &[u16]) -> Self { + pub fn concat(x: JsStr<'_>, y: JsStr<'_>) -> Self { Self::concat_array(&[x, y]) } /// Creates a new [`JsString`] from the concatenation of every element of /// `strings`. #[must_use] - pub fn concat_array(strings: &[&[u16]]) -> Self { + pub fn concat_array(strings: &[JsStr<'_>]) -> Self { + let mut latin1_encoding = true; let mut full_count = 0usize; - for &string in strings { + for string in strings { let Some(sum) = full_count.checked_add(string.len()) else { alloc_overflow() }; + if !string.is_latin1() { + latin1_encoding = false; + } full_count = sum; } - let ptr = Self::allocate_inner(full_count); + let ptr = Self::allocate_inner(full_count, latin1_encoding); let string = { // SAFETY: `allocate_inner` guarantees that `ptr` is a valid pointer. - let mut data = unsafe { addr_of_mut!((*ptr.as_ptr()).data).cast() }; - for string in strings { - let count = string.len(); + let mut data = unsafe { addr_of_mut!((*ptr.as_ptr()).data).cast::() }; + for &string in strings { // SAFETY: // The sum of all `count` for each `string` equals `full_count`, and since we're // iteratively writing each of them to `data`, `copy_non_overlapping` always stays @@ -257,8 +336,30 @@ impl JsString { // `allocate_inner` must return a valid pointer to newly allocated memory, meaning // `ptr` and all `string`s should never overlap. unsafe { - ptr::copy_nonoverlapping(string.as_ptr(), data, count); - data = data.add(count); + // NOTE: The aligment is checked when we allocate the array. + #[allow(clippy::cast_ptr_alignment)] + match (latin1_encoding, string.variant()) { + (true, JsStrVariant::Latin1(s)) => { + let count = s.len(); + ptr::copy_nonoverlapping(s.as_ptr(), data.cast::(), count); + data = data.cast::().add(count).cast::(); + } + (false, JsStrVariant::Latin1(s)) => { + let count = s.len(); + for (i, byte) in s.iter().enumerate() { + *data.cast::().add(i) = u16::from(*byte); + } + data = data.cast::().add(count).cast::(); + } + (false, JsStrVariant::Utf16(s)) => { + let count = s.len(); + ptr::copy_nonoverlapping(s.as_ptr(), data.cast::(), count); + data = data.cast::().add(count).cast::(); + } + (true, JsStrVariant::Utf16(_)) => { + unreachable!("Already checked that it's latin1 encoding") + } + } } } Self { @@ -267,7 +368,7 @@ impl JsString { } }; - StaticJsStrings::get_string(&string[..]).unwrap_or(string) + StaticJsStrings::get_string(&string.as_str()).unwrap_or(string) } /// Decodes a [`JsString`] into a [`String`], replacing invalid data with its escaped representation @@ -278,9 +379,15 @@ impl JsString { } /// Decodes a [`JsString`] into a [`String`], returning + /// + /// # Errors + /// /// [`FromUtf16Error`][std::string::FromUtf16Error] if it contains any invalid data. pub fn to_std_string(&self) -> Result { - String::from_utf16(self) + match self.as_str().variant() { + JsStrVariant::Latin1(v) => Ok(v.iter().copied().map(char::from).collect()), + JsStrVariant::Utf16(v) => String::from_utf16(v), + } } /// Decodes a [`JsString`] into an iterator of [`Result`], returning surrogates as @@ -350,12 +457,12 @@ impl JsString { } } - js_string!(text) + js_string!(&text[..]) } /// Gets an iterator of all the Unicode codepoints of a [`JsString`]. pub fn code_points(&self) -> impl Iterator + Clone + '_ { - char::decode_utf16(self.iter().copied()).map(|res| match res { + char::decode_utf16(self.iter()).map(|res| match res { Ok(c) => CodePoint::Unicode(c), Err(e) => CodePoint::UnpairedSurrogate(e.unpaired_surrogate()), }) @@ -370,7 +477,8 @@ impl JsString { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-stringindexof - pub(crate) fn index_of(&self, search_value: &[u16], from_index: usize) -> Option { + #[must_use] + pub fn index_of(&self, search_value: JsStr<'_>, from_index: usize) -> Option { // 1. Assert: Type(string) is String. // 2. Assert: Type(searchValue) is String. // 3. Assert: fromIndex is a non-negative integer. @@ -410,7 +518,12 @@ impl JsString { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-codepointat - pub(crate) fn code_point_at(&self, position: usize) -> CodePoint { + /// + /// # Panics + /// + /// If `position` is smaller than size of string. + #[must_use] + pub fn code_point_at(&self, position: usize) -> CodePoint { // 1. Let size be the length of string. let size = self.len(); @@ -418,28 +531,35 @@ impl JsString { // position >= 0 ensured by position: usize assert!(position < size); - // 3. Let first be the code unit at index position within string. - // 4. Let cp be the code point whose numeric value is that of first. - // 5. If first is not a leading surrogate or trailing surrogate, then - // a. Return the Record { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: false }. - // 6. If first is a trailing surrogate or position + 1 = size, then - // a. Return the Record { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }. - // 7. Let second be the code unit at index position + 1 within string. - // 8. If second is not a trailing surrogate, then - // a. Return the Record { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }. - // 9. Set cp to ! UTF16SurrogatePairToCodePoint(first, second). - - // We can skip the checks and instead use the `char::decode_utf16` function to take care of that for us. - let code_point = self - .get(position..=position + 1) - .unwrap_or(&self[position..=position]); - - match char::decode_utf16(code_point.iter().copied()) - .next() - .expect("code_point always has a value") - { - Ok(c) => CodePoint::Unicode(c), - Err(e) => CodePoint::UnpairedSurrogate(e.unpaired_surrogate()), + match self.as_str().variant() { + JsStrVariant::Latin1(v) => { + let code_point = v.get(position).expect("Already checked the size"); + CodePoint::Unicode(*code_point as char) + } + // 3. Let first be the code unit at index position within string. + // 4. Let cp be the code point whose numeric value is that of first. + // 5. If first is not a leading surrogate or trailing surrogate, then + // a. Return the Record { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: false }. + // 6. If first is a trailing surrogate or position + 1 = size, then + // a. Return the Record { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }. + // 7. Let second be the code unit at index position + 1 within string. + // 8. If second is not a trailing surrogate, then + // a. Return the Record { [[CodePoint]]: cp, [[CodeUnitCount]]: 1, [[IsUnpairedSurrogate]]: true }. + // 9. Set cp to ! UTF16SurrogatePairToCodePoint(first, second). + JsStrVariant::Utf16(v) => { + // We can skip the checks and instead use the `char::decode_utf16` function to take care of that for us. + let code_point = v + .get(position..=position + 1) + .unwrap_or(&v[position..=position]); + + match char::decode_utf16(code_point.iter().copied()) + .next() + .expect("code_point always has a value") + { + Ok(c) => CodePoint::Unicode(c), + Err(e) => CodePoint::UnpairedSurrogate(e.unpaired_surrogate()), + } + } } } @@ -449,7 +569,7 @@ impl JsString { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-stringtonumber - pub(crate) fn to_number(&self) -> f64 { + pub fn to_number(&self) -> f64 { // 1. Let text be ! StringToCodePoints(str). // 2. Let literal be ParseText(text, StringNumericLiteral). let Ok(string) = self.to_std_string() else { @@ -504,29 +624,13 @@ impl JsString { fast_float::parse(string).unwrap_or(f64::NAN) } - /// Abstract operation `StringToBigInt ( str )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-stringtobigint - pub(crate) fn to_big_int(&self) -> Option { - // 1. Let text be ! StringToCodePoints(str). - // 2. Let literal be ParseText(text, StringIntegerLiteral). - // 3. If literal is a List of errors, return undefined. - // 4. Let mv be the MV of literal. - // 5. Assert: mv is an integer. - // 6. Return ℤ(mv). - JsBigInt::from_string(self.to_std_string().ok().as_ref()?) - } - /// Allocates a new [`RawJsString`] with an internal capacity of `str_len` chars. /// /// # Panics /// /// Panics if `try_allocate_inner` returns `Err`. - fn allocate_inner(str_len: usize) -> NonNull { - match Self::try_allocate_inner(str_len) { + fn allocate_inner(str_len: usize, latin1: bool) -> NonNull { + match Self::try_allocate_inner(str_len, latin1) { Ok(v) => v, Err(None) => alloc_overflow(), Err(Some(layout)) => std::alloc::handle_alloc_error(layout), @@ -541,11 +645,18 @@ impl JsString { /// /// Returns `Err(None)` on integer overflows `usize::MAX`. /// Returns `Err(Some(Layout))` on allocation error. - fn try_allocate_inner(str_len: usize) -> Result, Option> { - let (layout, offset) = Layout::array::(str_len) - .and_then(|arr| Layout::new::().extend(arr)) - .map(|(layout, offset)| (layout.pad_to_align(), offset)) - .map_err(|_| None)?; + fn try_allocate_inner( + str_len: usize, + latin1: bool, + ) -> Result, Option> { + let (layout, offset) = if latin1 { + Layout::array::(str_len) + } else { + Layout::array::(str_len) + } + .and_then(|arr| Layout::new::().extend(arr)) + .map(|(layout, offset)| (layout.pad_to_align(), offset)) + .map_err(|_| None)?; debug_assert_eq!(offset, DATA_OFFSET); @@ -566,7 +677,7 @@ impl JsString { unsafe { // Write the first part, the `RawJsString`. inner.as_ptr().write(RawJsString { - len: str_len, + flags_and_len: RawJsString::encode_flags_and_len(str_len, latin1), refcount: Cell::new(1), data: [0; 0], }); @@ -593,12 +704,13 @@ impl JsString { } /// Creates a new [`JsString`] from `data`, without checking if the string is in the interner. - fn from_slice_skip_interning(string: &[u16]) -> Self { + fn from_slice_skip_interning(string: JsStr<'_>) -> Self { let count = string.len(); - let ptr = Self::allocate_inner(count); + let ptr = Self::allocate_inner(count, string.is_latin1()); // SAFETY: `allocate_inner` guarantees that `ptr` is a valid pointer. - let data = unsafe { addr_of_mut!((*ptr.as_ptr()).data) }; + let data = unsafe { addr_of_mut!((*ptr.as_ptr()).data).cast::() }; + // SAFETY: // - We read `count = data.len()` elements from `data`, which is within the bounds of the slice. // - `allocate_inner` must allocate at least `count` elements, which allows us to safely @@ -608,24 +720,125 @@ impl JsString { // - `allocate_inner` must return a valid pointer to newly allocated memory, meaning `ptr` // and `data` should never overlap. unsafe { - ptr::copy_nonoverlapping(string.as_ptr(), data.cast(), count); + // NOTE: The aligment is checked when we allocate the array. + #[allow(clippy::cast_ptr_alignment)] + match string.variant() { + JsStrVariant::Latin1(s) => { + ptr::copy_nonoverlapping(s.as_ptr(), data.cast::(), count); + } + JsStrVariant::Utf16(s) => { + ptr::copy_nonoverlapping(s.as_ptr(), data.cast::(), count); + } + } } Self { // Safety: `allocate_inner` guarantees `ptr` is a valid heap pointer. ptr: Tagged::from_non_null(ptr), } } -} -impl AsRef<[u16]> for JsString { - fn as_ref(&self) -> &[u16] { - self + /// Creates a new [`JsString`] from `data`. + fn from_slice(string: JsStr<'_>) -> Self { + if let Some(s) = StaticJsStrings::get_string(&string) { + return s; + } + Self::from_slice_skip_interning(string) + } + + /// Get the length of the [`JsString`]. + #[inline] + #[must_use] + pub fn len(&self) -> usize { + match self.ptr.unwrap() { + UnwrappedTagged::Ptr(h) => { + // SAFETY: + // - The `RawJsString` type has all the necessary information to reconstruct a valid + // slice (length and starting pointer). + // + // - We aligned `h.data` on allocation, and the block is of size `h.len`, so this + // should only generate valid reads. + // + // - The lifetime of `&Self::Target` is shorter than the lifetime of `self`, as seen + // by its signature, so this doesn't outlive `self`. + unsafe { + let h = h.as_ptr(); + (*h).len() + } + } + UnwrappedTagged::Tag(index) => { + // SAFETY: all static strings are valid indices on `STATIC_JS_STRINGS`, so `get` should always + // return `Some`. + unsafe { StaticJsStrings::get(index).unwrap_unchecked().len() } + } + } + } + + /// Return true if the [`JsString`] is emtpy. + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Convert the [`JsString`] into a [`Vec`]. + #[must_use] + pub fn to_vec(&self) -> Vec { + self.as_str().to_vec() + } + + /// Check if the [`JsString`] contains a byte. + #[must_use] + pub fn contains(&self, element: u8) -> bool { + match self.as_str().variant() { + JsStrVariant::Latin1(v) => v.contains(&element), + JsStrVariant::Utf16(v) => v.contains(&u16::from(element)), + } } -} -impl Borrow<[u16]> for JsString { - fn borrow(&self) -> &[u16] { - self + /// Trim whitespace from the start and end of the [`JsString`]. + #[must_use] + pub fn trim(&self) -> JsStr<'_> { + self.as_str().trim() + } + + /// Trim whitespace from the start of the [`JsString`]. + #[must_use] + pub fn trim_start(&self) -> JsStr<'_> { + self.as_str().trim_start() + } + + /// Trim whitespace from the end of the [`JsString`]. + #[must_use] + pub fn trim_end(&self) -> JsStr<'_> { + self.as_str().trim_end() + } + + /// Check if the [`JsString`] is static. + #[must_use] + pub fn is_static(&self) -> bool { + self.ptr.is_tagged() + } + + /// Get the element a the given index, [`None`] otherwise. + #[must_use] + pub fn get<'a, I>(&'a self, index: I) -> Option + where + I: JsSliceIndex<'a>, + { + I::get(self.as_str(), index) + } + + /// Get the element a the given index. + /// + /// # Panics + /// + /// If the index is out of bounds. + #[must_use] + pub fn get_expect<'a, I>(&'a self, index: I) -> I::Value + where + I: JsSliceIndex<'a>, + { + self.get(index).expect("Index out of bounds") } } @@ -668,12 +881,21 @@ impl Drop for JsString { // All the checks for the validity of the layout have already been made on `alloc_inner`, // so we can skip the unwrap. let layout = unsafe { - Layout::for_value(inner) - .extend(Layout::array::(inner.len).unwrap_unchecked()) - .unwrap_unchecked() - .0 - .pad_to_align() + if inner.is_latin1() { + Layout::for_value(inner) + .extend(Layout::array::(inner.len()).unwrap_unchecked()) + .unwrap_unchecked() + .0 + .pad_to_align() + } else { + Layout::for_value(inner) + .extend(Layout::array::(inner.len()).unwrap_unchecked()) + .unwrap_unchecked() + .0 + .pad_to_align() + } }; + // Safety: // If refcount is 0 and we call drop, that means this is the last `JsString` which // points to this memory allocation, so deallocating it is safe. @@ -684,72 +906,64 @@ impl Drop for JsString { } } -impl std::fmt::Debug for JsString { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::char::decode_utf16(self.as_slice().to_owned()) - .map(|r| { - r.map_or_else( - |err| format!("<0x{:04x}>", err.unpaired_surrogate()), - String::from, - ) - }) - .collect::() - .fmt(f) +impl ToStringEscaped for JsString { + #[inline] + fn to_string_escaped(&self) -> String { + match self.as_str().variant() { + JsStrVariant::Latin1(v) => v.iter().copied().map(char::from).collect(), + JsStrVariant::Utf16(v) => v.to_string_escaped(), + } } } -impl Deref for JsString { - type Target = [u16]; - - fn deref(&self) -> &Self::Target { - match self.ptr.unwrap() { - UnwrappedTagged::Ptr(h) => { - // SAFETY: - // - The `RawJsString` type has all the necessary information to reconstruct a valid - // slice (length and starting pointer). - // - // - We aligned `h.data` on allocation, and the block is of size `h.len`, so this - // should only generate valid reads. - // - // - The lifetime of `&Self::Target` is shorter than the lifetime of `self`, as seen - // by its signature, so this doesn't outlive `self`. - unsafe { - let h = h.as_ptr(); - std::slice::from_raw_parts(addr_of!((*h).data).cast(), (*h).len) - } - } - UnwrappedTagged::Tag(index) => { - // SAFETY: all static strings are valid indices on `STATIC_JS_STRINGS`, so `get` should always - // return `Some`. - unsafe { StaticJsStrings::get(index).unwrap_unchecked() } - } - } +impl std::fmt::Debug for JsString { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.to_std_string_escaped().fmt(f) } } impl Eq for JsString {} impl From<&[u16]> for JsString { + #[inline] fn from(s: &[u16]) -> Self { - StaticJsStrings::get_string(s).unwrap_or_else(|| Self::from_slice_skip_interning(s)) - } -} - -impl From> for JsString { - fn from(vec: Vec) -> Self { - Self::from(&vec[..]) + JsString::from_slice(JsStr::utf16(s)) } } impl From<&str> for JsString { #[inline] fn from(s: &str) -> Self { + // TODO: Check for latin1 encoding + if s.is_ascii() { + let js_str = JsStr::latin1(s.as_bytes()); + return StaticJsStrings::get_string(&js_str) + .unwrap_or_else(|| JsString::from_slice_skip_interning(js_str)); + } let s = s.encode_utf16().collect::>(); + JsString::from_slice_skip_interning(JsStr::utf16(&s[..])) + } +} - Self::from(&s[..]) +impl From> for JsString { + fn from(value: JsStr<'_>) -> Self { + StaticJsStrings::get_string(&value) + .unwrap_or_else(|| JsString::from_slice_skip_interning(value)) } } +impl From<&[JsString]> for JsString { + fn from(value: &[JsString]) -> Self { + Self::concat_array( + &value + .iter() + .map(Self::as_str) + .map(Into::into) + .collect::>()[..], + ) + } +} impl From for JsString { #[inline] fn from(s: String) -> Self { @@ -765,34 +979,39 @@ impl From<&[u16; N]> for JsString { impl Hash for JsString { fn hash(&self, state: &mut H) { - self[..].hash(state); + self.as_str().hash(state); } } -impl> Index for JsString { - type Output = I::Output; - - #[inline] - fn index(&self, index: I) -> &Self::Output { - Index::index(&**self, index) +impl PartialOrd for JsStr<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) } } impl Ord for JsString { fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self[..].cmp(other) + self.as_str().cmp(&other.as_str()) } } impl PartialEq for JsString { fn eq(&self, other: &Self) -> bool { - self[..] == other[..] + self.as_str() == other.as_str() } } impl PartialEq for [u16] { fn eq(&self, other: &JsString) -> bool { - self == &**other + if self.len() != other.len() { + return false; + } + for (x, y) in self.iter().copied().zip(other.iter()) { + if x != y { + return false; + } + } + true } } @@ -804,7 +1023,7 @@ impl PartialEq for [u16; N] { impl PartialEq<[u16]> for JsString { fn eq(&self, other: &[u16]) -> bool { - &**self == other + other == self } } @@ -838,6 +1057,18 @@ impl PartialEq for str { } } +impl PartialEq> for JsString { + fn eq(&self, other: &JsStr<'_>) -> bool { + self.as_str() == *other + } +} + +impl PartialEq for JsStr<'_> { + fn eq(&self, other: &JsString) -> bool { + other == self + } +} + impl PartialOrd for JsString { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -852,45 +1083,6 @@ impl FromStr for JsString { } } -/// Utility trait that adds trimming functionality to every `UTF-16` string. -pub(crate) trait Utf16Trim { - /// Trims both leading and trailing space from `self`. - fn trim(&self) -> &Self { - self.trim_start().trim_end() - } - - /// Trims all leading space from `self`. - fn trim_start(&self) -> &Self; - - /// Trims all trailing space from `self`. - fn trim_end(&self) -> &Self; -} - -impl Utf16Trim for [u16] { - fn trim_start(&self) -> &Self { - if let Some(left) = self - .iter() - .copied() - .position(|r| !char::from_u32(u32::from(r)).is_some_and(is_trimmable_whitespace)) - { - &self[left..] - } else { - &[] - } - } - fn trim_end(&self) -> &Self { - if let Some(right) = self - .iter() - .copied() - .rposition(|r| !char::from_u32(u32::from(r)).is_some_and(is_trimmable_whitespace)) - { - &self[..=right] - } else { - &[] - } - } -} - /// Utility trait that adds a `UTF-16` escaped representation to every [`[u16]`][slice]. pub(crate) trait ToStringEscaped { /// Decodes `self` as an `UTF-16` encoded string, escaping any unpaired surrogates by its @@ -912,10 +1104,13 @@ impl ToStringEscaped for [u16] { #[allow(clippy::redundant_clone)] #[cfg(test)] mod tests { - use crate::tagged::UnwrappedTagged; + use std::hash::{BuildHasher, BuildHasherDefault, Hash}; + + use crate::{string::common::StaticJsStrings, tagged::UnwrappedTagged, JsStr}; - use super::utf16; use super::JsString; + use boa_macros::{js_str, utf16}; + use rustc_hash::FxHasher; impl JsString { /// Gets the number of `JsString`s which point to this allocation. @@ -931,10 +1126,14 @@ mod tests { } } + fn hash_value(value: &T) -> u64 { + BuildHasherDefault::::default().hash_one(value) + } + #[test] fn empty() { let s = js_string!(); - assert_eq!(*s, "".encode_utf16().collect::>()); + assert_eq!(&s, utf16!("")); } #[test] @@ -1005,29 +1204,24 @@ mod tests { #[test] fn as_str() { - const HELLO: &str = "Hello"; + const HELLO: &[u16] = utf16!("Hello"); let x = js_string!(HELLO); - assert_eq!(*x, HELLO.encode_utf16().collect::>()); + assert_eq!(&x, HELLO); } #[test] fn hash() { - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; - - const HELLOWORLD: &[u16] = utf16!("Hello World!"); + const HELLOWORLD: JsStr<'_> = js_str!("Hello World!"); let x = js_string!(HELLOWORLD); - assert_eq!(&*x, HELLOWORLD); + assert_eq!(x.as_str(), HELLOWORLD); - let mut hasher = DefaultHasher::new(); - HELLOWORLD.hash(&mut hasher); - let s_hash = hasher.finish(); + assert!(HELLOWORLD.is_latin1()); + assert!(x.as_str().is_latin1()); - let mut hasher = DefaultHasher::new(); - x.hash(&mut hasher); - let x_hash = hasher.finish(); + let s_hash = hash_value(&HELLOWORLD); + let x_hash = hash_value(&x); assert_eq!(s_hash, x_hash); } @@ -1040,7 +1234,7 @@ mod tests { let x = js_string!("hello"); let z = js_string!("world"); - let xy = js_string!(&x, Y); + let xy = js_string!(&x, &JsString::from(Y)); assert_eq!(&xy, utf16!("hello, ")); assert_eq!(xy.refcount(), Some(1)); @@ -1048,8 +1242,45 @@ mod tests { assert_eq!(&xyz, utf16!("hello, world")); assert_eq!(xyz.refcount(), Some(1)); - let xyzw = js_string!(&xyz, W); + let xyzw = js_string!(&xyz, &JsString::from(W)); assert_eq!(&xyzw, utf16!("hello, world!")); assert_eq!(xyzw.refcount(), Some(1)); } + + #[test] + fn trim_start_non_ascii_to_ascii() { + let s = "\u{2029}abc"; + let x = js_string!(s); + + let y = js_string!(x.trim_start()); + + assert_eq!(&y, s.trim_start()); + } + + #[test] + fn conversion_to_known_static_js_string() { + const JS_STR_U8: &JsStr<'_> = &js_str!("length"); + const JS_STR_U16: &JsStr<'_> = &JsStr::utf16(utf16!("length")); + + assert!(JS_STR_U8.is_latin1()); + assert!(!JS_STR_U16.is_latin1()); + + assert_eq!(JS_STR_U8, JS_STR_U8); + assert_eq!(JS_STR_U16, JS_STR_U16); + + assert_eq!(JS_STR_U8, JS_STR_U16); + assert_eq!(JS_STR_U16, JS_STR_U8); + + assert_eq!(hash_value(JS_STR_U8), hash_value(JS_STR_U16)); + + let string = StaticJsStrings::get_string(JS_STR_U8); + + assert!(string.is_some()); + assert!(string.unwrap().as_str().is_latin1()); + + let string = StaticJsStrings::get_string(JS_STR_U16); + + assert!(string.is_some()); + assert!(string.unwrap().as_str().is_latin1()); + } } diff --git a/core/engine/src/string/str.rs b/core/engine/src/string/str.rs new file mode 100644 index 0000000000..6ce3c4cabd --- /dev/null +++ b/core/engine/src/string/str.rs @@ -0,0 +1,358 @@ +use crate::{ + builtins::string::{is_trimmable_whitespace, is_trimmable_whitespace_latin1}, + string::Iter, +}; +use std::{ + hash::{Hash, Hasher}, + slice::SliceIndex, +}; + +use super::iter::Windows; + +// Modified port of +#[inline] +pub(crate) const fn trim_latin1_start(mut bytes: &[u8]) -> &[u8] { + // Note: A pattern matching based approach (instead of indexing) allows + // making the function const. + while let [first, rest @ ..] = bytes { + if is_trimmable_whitespace_latin1(*first) { + bytes = rest; + } else { + break; + } + } + bytes +} + +// Modified port of +#[inline] +pub(crate) const fn trim_latin1_end(mut bytes: &[u8]) -> &[u8] { + // Note: A pattern matching based approach (instead of indexing) allows + // making the function const. + while let [rest @ .., last] = bytes { + if is_trimmable_whitespace_latin1(*last) { + bytes = rest; + } else { + break; + } + } + bytes +} + +/// Inner representation of a [`JsStr`]. +#[derive(Debug, Clone, Copy)] +pub enum JsStrVariant<'a> { + /// Latin1 string representation. + Latin1(&'a [u8]), + + /// U16 string representation. + Utf16(&'a [u16]), +} + +/// This is equivalent to Rust's `&str`. +#[derive(Debug, Clone, Copy)] +pub struct JsStr<'a> { + inner: JsStrVariant<'a>, +} + +impl<'a> JsStr<'a> { + /// This represents an empty string. + pub const EMPTY: Self = Self::latin1("".as_bytes()); + + /// Creates a [`JsStr`] from codepoints that can fit in a `u8`. + #[inline] + #[must_use] + pub const fn latin1(value: &'a [u8]) -> Self { + Self { + inner: JsStrVariant::Latin1(value), + } + } + + /// Creates a [`JsStr`] from utf16 encoded string. + #[inline] + #[must_use] + pub const fn utf16(value: &'a [u16]) -> Self { + Self { + inner: JsStrVariant::Utf16(value), + } + } + + /// Get the length of the [`JsStr`]. + #[inline] + #[must_use] + pub const fn len(&self) -> usize { + match self.inner { + JsStrVariant::Latin1(v) => v.len(), + JsStrVariant::Utf16(v) => v.len(), + } + } + + /// Return the inner [`JsStrVariant`] varient of the [`JsStr`]. + #[inline] + #[must_use] + pub fn variant(self) -> JsStrVariant<'a> { + self.inner + } + + /// Check if the [`JsStr`] is latin1 encoded. + #[inline] + #[must_use] + pub fn is_latin1(&self) -> bool { + matches!(self.inner, JsStrVariant::Latin1(_)) + } + + /// Returns [`u8`] slice if the [`JsStr`] is latin1 encoded, otherwise [`None`]. + #[inline] + #[must_use] + pub const fn as_latin1(&self) -> Option<&[u8]> { + if let JsStrVariant::Latin1(slice) = self.inner { + return Some(slice); + } + + None + } + + /// Iterate over the codepoints of the string. + #[inline] + #[must_use] + pub fn iter(self) -> Iter<'a> { + Iter::new(self) + } + + /// Iterate over the codepoints of the string. + #[inline] + #[must_use] + pub fn windows(self, size: usize) -> Windows<'a> { + Windows::new(self, size) + } + + /// Check if the [`JsStr`] is empty. + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Trims both leading and trailing space. + #[inline] + #[must_use] + pub fn trim(self) -> JsStr<'a> { + self.trim_start().trim_end() + } + + /// Trims all leading space. + #[inline] + #[must_use] + pub fn trim_start(self) -> Self { + match self.variant() { + JsStrVariant::Latin1(s) => Self::latin1(trim_latin1_start(s)), + JsStrVariant::Utf16(s) => { + let value = if let Some(left) = s.iter().copied().position(|r| { + !char::from_u32(u32::from(r)).is_some_and(is_trimmable_whitespace) + }) { + &s[left..] + } else { + return Self::EMPTY; + }; + + JsStr { + inner: JsStrVariant::Utf16(value), + } + } + } + } + + /// Trims all trailing space. + #[inline] + #[must_use] + pub fn trim_end(self) -> Self { + match self.variant() { + JsStrVariant::Latin1(s) => Self::latin1(trim_latin1_end(s)), + JsStrVariant::Utf16(s) => { + let value = if let Some(right) = s.iter().copied().rposition(|r| { + !char::from_u32(u32::from(r)).is_some_and(is_trimmable_whitespace) + }) { + &s[..=right] + } else { + return Self::EMPTY; + }; + + JsStr { + inner: JsStrVariant::Utf16(value), + } + } + } + } + + /// Returns an element or subslice depending on the type of index, otherwise [`None`]. + #[inline] + #[must_use] + pub fn get(self, index: I) -> Option + where + I: JsSliceIndex<'a>, + { + I::get(self, index) + } + + /// Convert the [`JsStr`] into a [`Vec`]. + pub fn to_vec(&self) -> Vec { + match self.variant() { + JsStrVariant::Latin1(v) => v.iter().copied().map(u16::from).collect(), + JsStrVariant::Utf16(v) => v.to_vec(), + } + } + + /// Returns true if needle is a prefix of the [`JsStr`]. + #[must_use] + pub fn starts_with(&self, needle: JsStr<'_>) -> bool { + let n = needle.len(); + self.len() >= n && needle == self.get(..n).expect("already checked size") + } + /// Returns `true` if `needle` is a suffix of the [`JsStr`]. + #[must_use] + pub fn ends_with(&self, needle: JsStr<'_>) -> bool { + let (m, n) = (self.len(), needle.len()); + m >= n && needle == self.get(m - n..).expect("already checked size") + } +} + +impl Hash for JsStr<'_> { + fn hash(&self, state: &mut H) { + // NOTE: The hash function has been inlined to ensure that a hash of latin1 and U16 + // encoded strings remains the same if they have the same characters + match self.variant() { + JsStrVariant::Latin1(s) => { + state.write_usize(s.len()); + for elem in s { + state.write_u16(u16::from(*elem)); + } + } + JsStrVariant::Utf16(s) => { + state.write_usize(s.len()); + for elem in s { + state.write_u16(*elem); + } + } + } + } +} + +impl Ord for JsStr<'_> { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match (self.variant(), other.variant()) { + (JsStrVariant::Latin1(x), JsStrVariant::Latin1(y)) => x.cmp(y), + (JsStrVariant::Utf16(x), JsStrVariant::Utf16(y)) => x.cmp(y), + _ => self.iter().cmp(other.iter()), + } + } +} + +impl Eq for JsStr<'_> {} + +impl PartialEq for JsStr<'_> { + fn eq(&self, other: &Self) -> bool { + match (self.variant(), other.variant()) { + (JsStrVariant::Latin1(lhs), JsStrVariant::Latin1(rhs)) => return lhs == rhs, + (JsStrVariant::Utf16(lhs), JsStrVariant::Utf16(rhs)) => return lhs == rhs, + _ => {} + } + if self.len() != other.len() { + return false; + } + for (x, y) in self.iter().zip(other.iter()) { + if x != y { + return false; + } + } + true + } +} + +impl<'a> PartialEq> for [u16] { + fn eq(&self, other: &JsStr<'a>) -> bool { + if self.len() != other.len() { + return false; + } + for (x, y) in self.iter().copied().zip(other.iter()) { + if x != y { + return false; + } + } + true + } +} + +pub trait JsSliceIndex<'a>: SliceIndex<[u8]> + SliceIndex<[u16]> { + type Value; + + fn get(_: JsStr<'a>, index: Self) -> Option; +} + +impl<'a> JsSliceIndex<'a> for usize { + type Value = u16; + + #[inline] + fn get(value: JsStr<'a>, index: Self) -> Option { + match value.variant() { + JsStrVariant::Latin1(v) => v.get(index).copied().map(u16::from), + JsStrVariant::Utf16(v) => v.get(index).copied(), + } + } +} + +impl<'a> JsSliceIndex<'a> for std::ops::Range { + type Value = JsStr<'a>; + + #[inline] + fn get(value: JsStr<'a>, index: Self) -> Option { + match value.variant() { + JsStrVariant::Latin1(v) => v.get(index).map(JsStr::latin1), + JsStrVariant::Utf16(v) => v.get(index).map(JsStr::utf16), + } + } +} + +impl<'a> JsSliceIndex<'a> for std::ops::RangeInclusive { + type Value = JsStr<'a>; + + #[inline] + fn get(value: JsStr<'a>, index: Self) -> Option { + match value.variant() { + JsStrVariant::Latin1(v) => v.get(index).map(JsStr::latin1), + JsStrVariant::Utf16(v) => v.get(index).map(JsStr::utf16), + } + } +} + +impl<'a> JsSliceIndex<'a> for std::ops::RangeFrom { + type Value = JsStr<'a>; + + #[inline] + fn get(value: JsStr<'a>, index: Self) -> Option { + match value.variant() { + JsStrVariant::Latin1(v) => v.get(index).map(JsStr::latin1), + JsStrVariant::Utf16(v) => v.get(index).map(JsStr::utf16), + } + } +} + +impl<'a> JsSliceIndex<'a> for std::ops::RangeTo { + type Value = JsStr<'a>; + + #[inline] + fn get(value: JsStr<'a>, index: Self) -> Option { + match value.variant() { + JsStrVariant::Latin1(v) => v.get(index).map(JsStr::latin1), + JsStrVariant::Utf16(v) => v.get(index).map(JsStr::utf16), + } + } +} + +impl<'a> JsSliceIndex<'a> for std::ops::RangeFull { + type Value = JsStr<'a>; + + #[inline] + fn get(value: JsStr<'a>, _index: Self) -> Option { + Some(value) + } +} diff --git a/core/engine/src/symbol.rs b/core/engine/src/symbol.rs index 06c6590ec0..11b04dd9e6 100644 --- a/core/engine/src/symbol.rs +++ b/core/engine/src/symbol.rs @@ -23,12 +23,12 @@ use crate::{ js_string, - string::{common::StaticJsStrings, utf16}, + string::{common::StaticJsStrings, JsString}, tagged::{Tagged, UnwrappedTagged}, - JsData, JsString, }; use boa_gc::{Finalize, Trace}; +use boa_macros::{js_str, JsData}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::{ @@ -135,6 +135,7 @@ struct Inner { // Safety: JsSymbol does not contain any objects which needs to be traced, // so this is safe. #[boa_gc(unsafe_empty_trace)] +#[allow(clippy::module_name_repetitions)] pub struct JsSymbol { repr: Tagged, } @@ -167,7 +168,7 @@ impl JsSymbol { let hash = get_id()?; let arc = Arc::new(Inner { hash, - description: description.map(|s| Box::from(&*s)), + description: description.map(|s| s.iter().collect::>().into_boxed_slice()), }); Some(Self { @@ -209,7 +210,7 @@ impl JsSymbol { return wk.fn_name(); } self.description() - .map(|s| js_string!(utf16!("["), &*s, utf16!("]"))) + .map(|s| js_string!(js_str!("["), &s, js_str!("]"))) .unwrap_or_default() } @@ -243,7 +244,7 @@ impl JsSymbol { pub fn descriptive_string(&self) -> JsString { self.description().as_ref().map_or_else( || js_string!("Symbol()"), - |desc| js_string!(utf16!("Symbol("), desc, utf16!(")")), + |desc| js_string!(js_str!("Symbol("), desc, js_str!(")")), ) } diff --git a/core/engine/src/tests/control_flow/loops.rs b/core/engine/src/tests/control_flow/loops.rs index 52cebb6243..d5c0254e89 100644 --- a/core/engine/src/tests/control_flow/loops.rs +++ b/core/engine/src/tests/control_flow/loops.rs @@ -1,4 +1,5 @@ -use crate::{js_string, run_test_actions, JsNativeErrorKind, TestAction}; +use crate::{run_test_actions, JsNativeErrorKind, TestAction}; +use boa_macros::js_str; use indoc::indoc; #[test] @@ -87,7 +88,7 @@ fn for_loop() { b } "#}, - js_string!("hello"), + js_str!("hello"), ), TestAction::assert_eq( indoc! {r#" @@ -192,7 +193,7 @@ fn try_break_finally_edge_cases() { a + b "#; - run_test_actions([TestAction::assert_eq(scenario, js_string!("foobar"))]); + run_test_actions([TestAction::assert_eq(scenario, js_str!("foobar"))]); } #[test] @@ -228,7 +229,7 @@ fn try_break_labels() { } "#; - run_test_actions([TestAction::assert_eq(scenario, js_string!("finally! :)"))]); + run_test_actions([TestAction::assert_eq(scenario, js_str!("finally! :)"))]); } #[test] @@ -268,10 +269,10 @@ fn break_nested_labels_loops_and_try() { run_test_actions([ TestAction::run(scenario), - TestAction::assert_eq("nestedLabels(true)", js_string!("foobar broke-foo")), + TestAction::assert_eq("nestedLabels(true)", js_str!("foobar broke-foo")), TestAction::assert_eq( "nestedLabels(false)", - js_string!("foobar broke-bar broke-spacer broke-foo"), + js_str!("foobar broke-bar broke-spacer broke-foo"), ), ]); } @@ -376,7 +377,7 @@ fn break_environment_gauntlet() { } "#; - run_test_actions([TestAction::assert_eq(scenario, js_string!("5601try_block"))]); + run_test_actions([TestAction::assert_eq(scenario, js_str!("5601try_block"))]); } #[test] @@ -707,7 +708,7 @@ fn for_loop_break_label() { } str "#}, - js_string!("01"), + js_str!("01"), )]); } @@ -822,7 +823,7 @@ fn for_in_break_label() { } str "#}, - js_string!("0"), + js_str!("0"), )]); } @@ -843,6 +844,6 @@ fn for_in_continue_label() { } str "#}, - js_string!("00"), + js_str!("00"), )]); } diff --git a/core/engine/src/tests/control_flow/mod.rs b/core/engine/src/tests/control_flow/mod.rs index 270c917f24..a326cae893 100644 --- a/core/engine/src/tests/control_flow/mod.rs +++ b/core/engine/src/tests/control_flow/mod.rs @@ -1,7 +1,8 @@ +use boa_macros::js_str; use indoc::indoc; mod loops; -use crate::{js_string, run_test_actions, JsNativeErrorKind, TestAction}; +use crate::{run_test_actions, JsNativeErrorKind, TestAction}; #[test] fn test_invalid_break() { @@ -381,7 +382,7 @@ fn string_switch() { a; "#}, - js_string!("world"), + js_str!("world"), )]); } @@ -418,13 +419,13 @@ fn bigger_switch_example() { return b; } "#}), - TestAction::assert_eq("f(0)", js_string!("Mon")), - TestAction::assert_eq("f(1)", js_string!("Tue")), - TestAction::assert_eq("f(2)", js_string!("Wed")), - TestAction::assert_eq("f(3)", js_string!("Thurs")), - TestAction::assert_eq("f(4)", js_string!("Fri")), - TestAction::assert_eq("f(5)", js_string!("Sat")), - TestAction::assert_eq("f(6)", js_string!("Sun")), + TestAction::assert_eq("f(0)", js_str!("Mon")), + TestAction::assert_eq("f(1)", js_str!("Tue")), + TestAction::assert_eq("f(2)", js_str!("Wed")), + TestAction::assert_eq("f(3)", js_str!("Thurs")), + TestAction::assert_eq("f(4)", js_str!("Fri")), + TestAction::assert_eq("f(5)", js_str!("Sat")), + TestAction::assert_eq("f(6)", js_str!("Sun")), ]); } @@ -440,7 +441,7 @@ fn break_labelled_if_statement() { } result "#}, - js_string!("foo"), + js_str!("foo"), )]); } @@ -458,6 +459,6 @@ fn break_labelled_try_statement() { } result "#}, - js_string!("foo"), + js_str!("foo"), )]); } diff --git a/core/engine/src/tests/env.rs b/core/engine/src/tests/env.rs index 372641cbf4..c15bb16e89 100644 --- a/core/engine/src/tests/env.rs +++ b/core/engine/src/tests/env.rs @@ -1,6 +1,7 @@ +use boa_macros::js_str; use indoc::indoc; -use crate::{js_string, run_test_actions, JsNativeErrorKind, TestAction}; +use crate::{run_test_actions, JsNativeErrorKind, TestAction}; #[test] // https://github.com/boa-dev/boa/issues/2317 @@ -15,7 +16,7 @@ fn fun_block_eval_2317() { return y + x; })("arg"); "#}, - js_string!("arginner"), + js_str!("arginner"), ), TestAction::assert_eq( indoc! {r#" @@ -26,7 +27,7 @@ fn fun_block_eval_2317() { return y + x; })(); "#}, - js_string!("defaultinner"), + js_str!("defaultinner"), ), ]); } diff --git a/core/engine/src/tests/function.rs b/core/engine/src/tests/function.rs index 4a359fff7d..6c70c5ec4d 100644 --- a/core/engine/src/tests/function.rs +++ b/core/engine/src/tests/function.rs @@ -1,4 +1,5 @@ -use crate::{js_string, run_test_actions, JsNativeErrorKind, JsValue, TestAction}; +use crate::{run_test_actions, JsNativeErrorKind, JsValue, TestAction}; +use boa_macros::js_str; use indoc::indoc; #[test] @@ -24,7 +25,7 @@ fn property_accessor_member_expression_dot_notation_on_function() { function asd () {}; asd.name; "#}, - js_string!("asd"), + js_str!("asd"), )]); } @@ -35,7 +36,7 @@ fn property_accessor_member_expression_bracket_notation_on_function() { function asd () {}; asd['name']; "#}, - js_string!("asd"), + js_str!("asd"), )]); } @@ -62,7 +63,7 @@ fn early_return() { } outer_fnct() "#}, - js_string!("outer"), + js_str!("outer"), ), ]); } @@ -78,8 +79,8 @@ fn should_set_this_value() { var bar = new Foo(); "#}), - TestAction::assert_eq("bar.a", js_string!("a")), - TestAction::assert_eq("bar.b", js_string!("b")), + TestAction::assert_eq("bar.a", js_str!("a")), + TestAction::assert_eq("bar.b", js_str!("b")), ]); } diff --git a/core/engine/src/tests/mod.rs b/core/engine/src/tests/mod.rs index 4c1e0d658f..705375cd77 100644 --- a/core/engine/src/tests/mod.rs +++ b/core/engine/src/tests/mod.rs @@ -1,3 +1,4 @@ +use boa_macros::js_str; use indoc::indoc; mod control_flow; @@ -8,7 +9,7 @@ mod operators; mod promise; mod spread; -use crate::{js_string, run_test_actions, JsNativeErrorKind, JsValue, TestAction}; +use crate::{run_test_actions, JsNativeErrorKind, JsValue, TestAction}; #[test] fn length_correct_value_on_string_literal() { @@ -355,7 +356,7 @@ fn multiline_str_concat() { 'world'; a "#}, - js_string!("hello world"), + js_str!("hello world"), )]); } @@ -479,7 +480,7 @@ fn template_literal() { let a = 10; `result: ${a} and ${a+10}`; "#}, - js_string!("result: 10 and 20"), + js_str!("result: 10 and 20"), )]); } diff --git a/core/engine/src/tests/operators.rs b/core/engine/src/tests/operators.rs index 553d8135e6..a414687ecb 100644 --- a/core/engine/src/tests/operators.rs +++ b/core/engine/src/tests/operators.rs @@ -1,11 +1,12 @@ -use crate::{js_string, run_test_actions, JsNativeErrorKind, JsValue, TestAction}; +use crate::{run_test_actions, JsNativeErrorKind, JsValue, TestAction}; +use boa_macros::js_str; use indoc::indoc; #[test] fn property_accessor_member_expression_dot_notation_on_string_literal() { run_test_actions([TestAction::assert_eq( "typeof 'asd'.matchAll", - js_string!("function"), + js_str!("function"), )]); } @@ -13,7 +14,7 @@ fn property_accessor_member_expression_dot_notation_on_string_literal() { fn property_accessor_member_expression_bracket_notation_on_string_literal() { run_test_actions([TestAction::assert_eq( "typeof 'asd'['matchAll']", - js_string!("function"), + js_str!("function"), )]); } @@ -197,15 +198,15 @@ fn unary_operations_on_this() { #[test] fn typeofs() { run_test_actions([ - TestAction::assert_eq("typeof String()", js_string!("string")), - TestAction::assert_eq("typeof 5", js_string!("number")), - TestAction::assert_eq("typeof 0.5", js_string!("number")), - TestAction::assert_eq("typeof undefined", js_string!("undefined")), - TestAction::assert_eq("typeof true", js_string!("boolean")), - TestAction::assert_eq("typeof null", js_string!("object")), - TestAction::assert_eq("typeof {}", js_string!("object")), - TestAction::assert_eq("typeof Symbol()", js_string!("symbol")), - TestAction::assert_eq("typeof function(){}", js_string!("function")), + TestAction::assert_eq("typeof String()", js_str!("string")), + TestAction::assert_eq("typeof 5", js_str!("number")), + TestAction::assert_eq("typeof 0.5", js_str!("number")), + TestAction::assert_eq("typeof undefined", js_str!("undefined")), + TestAction::assert_eq("typeof true", js_str!("boolean")), + TestAction::assert_eq("typeof null", js_str!("object")), + TestAction::assert_eq("typeof {}", js_str!("object")), + TestAction::assert_eq("typeof Symbol()", js_str!("symbol")), + TestAction::assert_eq("typeof function(){}", js_str!("function")), ]); } @@ -248,7 +249,7 @@ fn unary_void() { const b = void test() + ''; a + b "#}, - js_string!("42undefined"), + js_str!("42undefined"), ), ]); } @@ -494,10 +495,7 @@ fn logical_assignment() { #[test] fn conditional_op() { - run_test_actions([TestAction::assert_eq( - "1 === 2 ? 'a' : 'b'", - js_string!("b"), - )]); + run_test_actions([TestAction::assert_eq("1 === 2 ? 'a' : 'b'", js_str!("b"))]); } #[test] diff --git a/core/engine/src/tests/spread.rs b/core/engine/src/tests/spread.rs index ff0d9ddecd..b272df56fb 100644 --- a/core/engine/src/tests/spread.rs +++ b/core/engine/src/tests/spread.rs @@ -1,4 +1,5 @@ -use crate::{js_string, run_test_actions, JsNativeErrorKind, JsValue, TestAction}; +use crate::{run_test_actions, JsNativeErrorKind, JsValue, TestAction}; +use boa_macros::js_str; use indoc::indoc; #[test] @@ -26,7 +27,7 @@ fn spread_with_arguments() { var result = foo(...a); "#}), TestAction::assert_eq("result[0]", 1), - TestAction::assert_eq("result[1]", js_string!("test")), + TestAction::assert_eq("result[1]", js_str!("test")), TestAction::assert_eq("result[2]", 3), TestAction::assert_eq("result[3]", 4), ]); @@ -131,7 +132,7 @@ fn spread_with_new() { } f('message').m; "#}, - js_string!("message"), + js_str!("message"), )]); } @@ -147,6 +148,6 @@ fn spread_with_call() { } g('message'); "#}, - js_string!("message"), + js_str!("message"), )]); } diff --git a/core/engine/src/value/conversions/mod.rs b/core/engine/src/value/conversions/mod.rs index a23f048d08..b247db10f9 100644 --- a/core/engine/src/value/conversions/mod.rs +++ b/core/engine/src/value/conversions/mod.rs @@ -1,6 +1,6 @@ //! Conversions from JavaScript values into Rust values, and the other way around. -use crate::js_string; +use crate::{js_string, string::JsStr}; use super::{JsBigInt, JsObject, JsString, JsSymbol, JsValue, Profiler}; @@ -10,6 +10,14 @@ pub(super) mod try_from_js; pub(super) mod convert; +impl From> for JsValue { + fn from(value: JsStr<'_>) -> Self { + let _timer = Profiler::global().start_event("From>", "value"); + + Self::String(value.into()) + } +} + impl From for JsValue { fn from(value: JsString) -> Self { let _timer = Profiler::global().start_event("From", "value"); diff --git a/core/engine/src/value/conversions/serde_json.rs b/core/engine/src/value/conversions/serde_json.rs index 7b79e780c2..104d928e7c 100644 --- a/core/engine/src/value/conversions/serde_json.rs +++ b/core/engine/src/value/conversions/serde_json.rs @@ -176,12 +176,13 @@ impl JsValue { #[cfg(test)] mod tests { + use boa_macros::js_str; use indoc::indoc; use serde_json::json; use crate::object::JsArray; - use crate::{js_string, run_test_actions, TestAction}; - use crate::{string::utf16, JsValue}; + use crate::JsValue; + use crate::{run_test_actions, TestAction}; #[test] fn json_conversions() { @@ -210,23 +211,23 @@ mod tests { let value = JsValue::from_json(&json, ctx).unwrap(); let obj = value.as_object().unwrap(); assert_eq!( - obj.get(utf16!("name"), ctx).unwrap(), - js_string!("John Doe").into() + obj.get(js_str!("name"), ctx).unwrap(), + js_str!("John Doe").into() ); - assert_eq!(obj.get(utf16!("age"), ctx).unwrap(), 43_i32.into()); - assert_eq!(obj.get(utf16!("minor"), ctx).unwrap(), false.into()); - assert_eq!(obj.get(utf16!("adult"), ctx).unwrap(), true.into()); + assert_eq!(obj.get(js_str!("age"), ctx).unwrap(), 43_i32.into()); + assert_eq!(obj.get(js_str!("minor"), ctx).unwrap(), false.into()); + assert_eq!(obj.get(js_str!("adult"), ctx).unwrap(), true.into()); { - let extra = obj.get(utf16!("extra"), ctx).unwrap(); + let extra = obj.get(js_str!("extra"), ctx).unwrap(); let extra = extra.as_object().unwrap(); - assert!(extra.get(utf16!("address"), ctx).unwrap().is_null()); + assert!(extra.get(js_str!("address"), ctx).unwrap().is_null()); } { - let phones = obj.get(utf16!("phones"), ctx).unwrap(); + let phones = obj.get(js_str!("phones"), ctx).unwrap(); let phones = phones.as_object().unwrap(); let arr = JsArray::from_object(phones.clone()).unwrap(); - assert_eq!(arr.at(0, ctx).unwrap(), js_string!("+44 1234567").into()); + assert_eq!(arr.at(0, ctx).unwrap(), js_str!("+44 1234567").into()); assert_eq!(arr.at(1, ctx).unwrap(), JsValue::from(-45_i32)); assert!(arr.at(2, ctx).unwrap().is_object()); assert_eq!(arr.at(3, ctx).unwrap(), true.into()); diff --git a/core/engine/src/value/display.rs b/core/engine/src/value/display.rs index 97e478b3b7..cdf463ea1a 100644 --- a/core/engine/src/value/display.rs +++ b/core/engine/src/value/display.rs @@ -1,16 +1,14 @@ -use std::borrow::Cow; - +use super::{fmt, Display, HashSet, JsValue}; use crate::{ builtins::{ error::ErrorObject, map::ordered_map::OrderedMap, promise::PromiseState, set::ordered_set::OrderedSet, Array, Promise, }, + js_str, property::PropertyDescriptor, - string::utf16, JsError, JsString, }; - -use super::{fmt, Display, HashSet, JsValue}; +use std::borrow::Cow; /// This object is used for displaying a `Value`. #[derive(Debug, Clone, Copy)] @@ -120,7 +118,7 @@ pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children } else if v_bor.is::() { let len = v_bor .properties() - .get(&utf16!("length").into()) + .get(&js_str!("length").into()) .expect("array object must have 'length' property") // FIXME: handle accessor descriptors .expect_value() @@ -196,7 +194,7 @@ pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children } else if v_bor.is::() { drop(v_bor); let name: Cow<'static, str> = v - .get_property(&utf16!("name").into()) + .get_property(&js_str!("name").into()) .as_ref() .and_then(PropertyDescriptor::value) .map_or_else( @@ -211,7 +209,7 @@ pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children }, ); let message = v - .get_property(&utf16!("message").into()) + .get_property(&js_str!("message").into()) .as_ref() .and_then(PropertyDescriptor::value) .map(|v| { diff --git a/core/engine/src/value/equality.rs b/core/engine/src/value/equality.rs index 4f3c2846ea..d14a95d1af 100644 --- a/core/engine/src/value/equality.rs +++ b/core/engine/src/value/equality.rs @@ -65,14 +65,14 @@ impl JsValue { // a. Let n be ! StringToBigInt(y). // b. If n is NaN, return false. // c. Return the result of the comparison x == n. - (Self::BigInt(ref a), Self::String(ref b)) => { - b.to_big_int().as_ref().map_or(false, |b| a == b) - } + (Self::BigInt(ref a), Self::String(ref b)) => JsBigInt::from_js_string(b) + .as_ref() + .map_or(false, |b| a == b), // 7. If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x. - (Self::String(ref a), Self::BigInt(ref b)) => { - a.to_big_int().as_ref().map_or(false, |a| a == b) - } + (Self::String(ref a), Self::BigInt(ref b)) => JsBigInt::from_js_string(a) + .as_ref() + .map_or(false, |a| a == b), // 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y. (Self::Boolean(x), _) => return other.equals(&Self::new(i32::from(*x)), context), diff --git a/core/engine/src/value/mod.rs b/core/engine/src/value/mod.rs index 6678e294d6..bae1f87add 100644 --- a/core/engine/src/value/mod.rs +++ b/core/engine/src/value/mod.rs @@ -8,6 +8,7 @@ use std::{ ops::Sub, }; +use boa_macros::js_str; use num_bigint::BigInt; use num_integer::Integer; use num_traits::{ToPrimitive, Zero}; @@ -393,9 +394,9 @@ impl JsValue { // 1. Assert: preferredType is number. // 2. Let hint be "number". let hint = match preferred_type { - PreferredType::Default => js_string!("default"), - PreferredType::String => js_string!("string"), - PreferredType::Number => js_string!("number"), + PreferredType::Default => js_str!("default"), + PreferredType::String => js_str!("string"), + PreferredType::Number => js_str!("number"), } .into(); @@ -440,7 +441,7 @@ impl JsValue { Self::Undefined => Err(JsNativeError::typ() .with_message("cannot convert undefined to a BigInt") .into()), - Self::String(ref string) => string.to_big_int().map_or_else( + Self::String(ref string) => JsBigInt::from_js_string(string).map_or_else( || { Err(JsNativeError::syntax() .with_message(format!( @@ -495,8 +496,8 @@ impl JsValue { /// This function is equivalent to `String(value)` in JavaScript. pub fn to_string(&self, context: &mut Context) -> JsResult { match self { - Self::Null => Ok("null".into()), - Self::Undefined => Ok("undefined".into()), + Self::Null => Ok(js_string!("null")), + Self::Undefined => Ok(js_string!("undefined")), Self::Boolean(boolean) => Ok(boolean.to_string().into()), Self::Rational(rational) => Ok(Number::to_js_string(*rational)), Self::Integer(integer) => Ok(integer.to_string().into()), @@ -518,7 +519,6 @@ impl JsValue { /// /// See: pub fn to_object(&self, context: &mut Context) -> JsResult { - // TODO: add fast paths with object template match self { Self::Undefined | Self::Null => Err(JsNativeError::typ() .with_message("cannot convert 'null' or 'undefined' to object") @@ -991,21 +991,22 @@ impl JsValue { #[must_use] pub fn js_type_of(&self) -> JsString { match *self { - Self::Rational(_) | Self::Integer(_) => js_string!("number"), - Self::String(_) => js_string!("string"), - Self::Boolean(_) => js_string!("boolean"), - Self::Symbol(_) => js_string!("symbol"), - Self::Null => js_string!("object"), - Self::Undefined => js_string!("undefined"), - Self::BigInt(_) => js_string!("bigint"), + Self::Rational(_) | Self::Integer(_) => js_str!("number"), + Self::String(_) => js_str!("string"), + Self::Boolean(_) => js_str!("boolean"), + Self::Symbol(_) => js_str!("symbol"), + Self::Null => js_str!("object"), + Self::Undefined => js_str!("undefined"), + Self::BigInt(_) => js_str!("bigint"), Self::Object(ref object) => { if object.is_callable() { - js_string!("function") + js_str!("function") } else { - js_string!("object") + js_str!("object") } } } + .into() } /// Abstract operation `IsArray ( argument )` diff --git a/core/engine/src/value/operations.rs b/core/engine/src/value/operations.rs index e7a403296a..c26a0b00ba 100644 --- a/core/engine/src/value/operations.rs +++ b/core/engine/src/value/operations.rs @@ -537,11 +537,9 @@ impl JsValue { match (px, py) { (Self::String(ref x), Self::String(ref y)) => (x < y).into(), - (Self::BigInt(ref x), Self::String(ref y)) => y - .to_big_int() + (Self::BigInt(ref x), Self::String(ref y)) => JsBigInt::from_js_string(y) .map_or(AbstractRelation::Undefined, |y| (*x < y).into()), - (Self::String(ref x), Self::BigInt(ref y)) => x - .to_big_int() + (Self::String(ref x), Self::BigInt(ref y)) => JsBigInt::from_js_string(x) .map_or(AbstractRelation::Undefined, |x| (x < *y).into()), (px, py) => match (px.to_numeric(context)?, py.to_numeric(context)?) { (Numeric::Number(x), Numeric::Number(y)) => Number::less_than(x, y), diff --git a/core/engine/src/value/tests.rs b/core/engine/src/value/tests.rs index 74c2c7ed5e..4d8fffc169 100644 --- a/core/engine/src/value/tests.rs +++ b/core/engine/src/value/tests.rs @@ -1,4 +1,3 @@ -use boa_macros::utf16; use indoc::indoc; use super::*; @@ -28,9 +27,9 @@ fn get_set_field() { run_test_actions([TestAction::assert_context(|ctx| { let obj = &JsObject::with_object_proto(ctx.intrinsics()); // Create string and convert it to a Value - let s = JsValue::new(js_string!("bar")); - obj.set(js_string!("foo"), s, false, ctx).unwrap(); - obj.get(js_string!("foo"), ctx).unwrap() == JsValue::new(js_string!("bar")) + let s = JsValue::new(js_str!("bar")); + obj.set(js_str!("foo"), s, false, ctx).unwrap(); + obj.get(js_str!("foo"), ctx).unwrap() == JsValue::new(js_str!("bar")) })]); } @@ -241,7 +240,7 @@ fn add_number_and_number() { fn add_number_and_string() { run_test_actions([TestAction::assert_eq( "1 + \" + 2 = 3\"", - js_string!("1 + 2 = 3"), + js_str!("1 + 2 = 3"), )]); } @@ -249,7 +248,7 @@ fn add_number_and_string() { fn add_string_and_string() { run_test_actions([TestAction::assert_eq( "\"Hello\" + \", world\"", - js_string!("Hello, world"), + js_str!("Hello, world"), )]); } @@ -262,7 +261,7 @@ fn add_number_object_and_number() { fn add_number_object_and_string_object() { run_test_actions([TestAction::assert_eq( "new Number(10) + new String(\"0\")", - js_string!("100"), + js_str!("100"), )]); } @@ -445,7 +444,7 @@ fn to_integer_or_infinity() { IntegerOrInfinity::Integer(11) ); assert_eq!( - JsValue::new(js_string!("12")) + JsValue::new(js_str!("12")) .to_integer_or_infinity(ctx) .unwrap(), IntegerOrInfinity::Integer(12) @@ -465,8 +464,8 @@ fn test_accessors() { let a = { get b() { return "c" }, set b(value) { arr = arr.concat([value]) }} ; a.b = "a"; "#}), - TestAction::assert_eq("a.b", js_string!("c")), - TestAction::assert_eq("arr[0]", js_string!("a")), + TestAction::assert_eq("a.b", js_str!("c")), + TestAction::assert_eq("arr[0]", js_str!("a")), ]); } @@ -683,16 +682,13 @@ fn to_int32() { #[test] fn to_string() { run_test_actions([TestAction::inspect_context(|ctx| { - assert_eq!(&JsValue::null().to_string(ctx).unwrap(), utf16!("null")); + assert_eq!(&JsValue::null().to_string(ctx).unwrap(), "null"); + assert_eq!(&JsValue::undefined().to_string(ctx).unwrap(), "undefined"); + assert_eq!(&JsValue::new(55).to_string(ctx).unwrap(), "55"); + assert_eq!(&JsValue::new(55.0).to_string(ctx).unwrap(), "55"); assert_eq!( - &JsValue::undefined().to_string(ctx).unwrap(), - utf16!("undefined") - ); - assert_eq!(&JsValue::new(55).to_string(ctx).unwrap(), utf16!("55")); - assert_eq!(&JsValue::new(55.0).to_string(ctx).unwrap(), utf16!("55")); - assert_eq!( - &JsValue::new(js_string!("hello")).to_string(ctx).unwrap(), - utf16!("hello") + JsValue::new(js_str!("hello")).to_string(ctx).unwrap(), + js_str!("hello") ); })]); } @@ -704,7 +700,7 @@ fn to_bigint() { assert!(JsValue::undefined().to_bigint(ctx).is_err()); assert!(JsValue::new(55).to_bigint(ctx).is_err()); assert!(JsValue::new(10.0).to_bigint(ctx).is_err()); - assert!(JsValue::new(js_string!("100")).to_bigint(ctx).is_ok()); + assert!(JsValue::new(js_str!("100")).to_bigint(ctx).is_ok()); })]); } @@ -737,7 +733,7 @@ mod cyclic_conversions { let a = [b, b]; JSON.stringify(a) "#}, - js_string!("[[],[]]"), + js_str!("[[],[]]"), )]); } diff --git a/core/engine/src/vm/flowgraph/mod.rs b/core/engine/src/vm/flowgraph/mod.rs index 273053d15b..0c931d5afe 100644 --- a/core/engine/src/vm/flowgraph/mod.rs +++ b/core/engine/src/vm/flowgraph/mod.rs @@ -1,13 +1,13 @@ //! This module is responsible for generating the vm instruction flowgraph. use crate::vm::CodeBlock; -use boa_macros::utf16; mod color; mod edge; mod graph; mod node; +use boa_macros::js_str; pub use color::*; pub use edge::*; pub use graph::*; @@ -20,7 +20,7 @@ impl CodeBlock { #[allow(clippy::match_same_arms)] pub fn to_graph(&self, graph: &mut SubGraph) { // Have to remove any invalid graph chars like `<` or `>`. - let name = if self.name() == utf16!("
") { + let name = if self.name() == &js_str!("
") { "__main__".to_string() } else { self.name().to_std_string_escaped() diff --git a/core/engine/src/vm/opcode/await/mod.rs b/core/engine/src/vm/opcode/await/mod.rs index e9fc03f79c..580bf18071 100644 --- a/core/engine/src/vm/opcode/await/mod.rs +++ b/core/engine/src/vm/opcode/await/mod.rs @@ -1,4 +1,5 @@ use boa_gc::{Gc, GcRefCell}; +use boa_macros::js_str; use crate::{ builtins::{ @@ -82,7 +83,7 @@ impl Operation for Await { captures.clone(), ), ) - .name("") + .name(js_str!("")) .length(1) .build(); @@ -122,7 +123,7 @@ impl Operation for Await { captures, ), ) - .name("") + .name(js_str!("")) .length(1) .build(); diff --git a/core/engine/src/vm/opcode/concat/mod.rs b/core/engine/src/vm/opcode/concat/mod.rs index d6d33a215c..2bcc3c5505 100644 --- a/core/engine/src/vm/opcode/concat/mod.rs +++ b/core/engine/src/vm/opcode/concat/mod.rs @@ -20,8 +20,9 @@ impl ConcatToString { let s = JsString::concat_array( &strings .iter() - .map(JsString::as_slice) - .collect::>(), + .map(JsString::as_str) + .map(Into::into) + .collect::>(), ); context.vm.push(s); Ok(CompletionType::Normal) diff --git a/core/engine/src/vm/opcode/define/class/getter.rs b/core/engine/src/vm/opcode/define/class/getter.rs index 7eda9a7f14..174576778c 100644 --- a/core/engine/src/vm/opcode/define/class/getter.rs +++ b/core/engine/src/vm/opcode/define/class/getter.rs @@ -1,9 +1,11 @@ +use boa_macros::js_str; + use crate::{ builtins::function::{set_function_name, OrdinaryFunction}, object::internal_methods::InternalMethodContext, property::PropertyDescriptor, vm::{opcode::Operation, CompletionType}, - Context, JsResult, JsString, + Context, JsResult, }; /// `DefineClassStaticGetterByName` implements the Opcode Operation for `Opcode::DefineClassStaticGetterByName` @@ -28,7 +30,7 @@ impl DefineClassStaticGetterByName { let function_object = function .as_object() .expect("method must be function object"); - set_function_name(function_object, &key, Some(JsString::from("get")), context); + set_function_name(function_object, &key, Some(js_str!("get")), context); function_object .downcast_mut::() .expect("method must be function object") @@ -96,7 +98,7 @@ impl DefineClassGetterByName { let function_object = function .as_object() .expect("method must be function object"); - set_function_name(function_object, &key, Some(JsString::from("get")), context); + set_function_name(function_object, &key, Some(js_str!("get")), context); function_object .downcast_mut::() .expect("method must be function object") @@ -166,7 +168,7 @@ impl Operation for DefineClassStaticGetterByValue { let function_object = function .as_object() .expect("method must be function object"); - set_function_name(function_object, &key, Some(JsString::from("get")), context); + set_function_name(function_object, &key, Some(js_str!("get")), context); function_object .downcast_mut::() .expect("method must be function object") @@ -216,7 +218,7 @@ impl Operation for DefineClassGetterByValue { let function_object = function .as_object() .expect("method must be function object"); - set_function_name(function_object, &key, Some(JsString::from("get")), context); + set_function_name(function_object, &key, Some(js_str!("get")), context); function_object .downcast_mut::() .expect("method must be function object") diff --git a/core/engine/src/vm/opcode/define/class/setter.rs b/core/engine/src/vm/opcode/define/class/setter.rs index 1a8cdbfe4d..8d2272b2f4 100644 --- a/core/engine/src/vm/opcode/define/class/setter.rs +++ b/core/engine/src/vm/opcode/define/class/setter.rs @@ -1,9 +1,11 @@ +use boa_macros::js_str; + use crate::{ builtins::function::{set_function_name, OrdinaryFunction}, object::internal_methods::InternalMethodContext, property::PropertyDescriptor, vm::{opcode::Operation, CompletionType}, - Context, JsResult, JsString, + Context, JsResult, }; /// `DefineClassStaticSetterByName` implements the Opcode Operation for `Opcode::DefineClassStaticSetterByName` @@ -28,7 +30,7 @@ impl DefineClassStaticSetterByName { let function_object = function .as_object() .expect("method must be function object"); - set_function_name(function_object, &key, Some(JsString::from("set")), context); + set_function_name(function_object, &key, Some(js_str!("set")), context); function_object .downcast_mut::() .expect("method must be function object") @@ -97,7 +99,7 @@ impl DefineClassSetterByName { let function_object = function .as_object() .expect("method must be function object"); - set_function_name(function_object, &key, Some(JsString::from("set")), context); + set_function_name(function_object, &key, Some(js_str!("set")), context); function_object .downcast_mut::() .expect("method must be function object") @@ -169,7 +171,7 @@ impl Operation for DefineClassStaticSetterByValue { let function_object = function .as_object() .expect("method must be function object"); - set_function_name(function_object, &key, Some(JsString::from("set")), context); + set_function_name(function_object, &key, Some(js_str!("set")), context); function_object .downcast_mut::() .expect("method must be function object") @@ -220,7 +222,7 @@ impl Operation for DefineClassSetterByValue { let function_object = function .as_object() .expect("method must be function object"); - set_function_name(function_object, &key, Some(JsString::from("set")), context); + set_function_name(function_object, &key, Some(js_str!("set")), context); function_object .downcast_mut::() .expect("method must be function object") diff --git a/core/engine/src/vm/opcode/generator/mod.rs b/core/engine/src/vm/opcode/generator/mod.rs index 53197a3bdc..fb6f98fa90 100644 --- a/core/engine/src/vm/opcode/generator/mod.rs +++ b/core/engine/src/vm/opcode/generator/mod.rs @@ -8,8 +8,8 @@ use crate::{ generator::{GeneratorContext, GeneratorState}, }, error::JsNativeError, + js_str, object::PROTOTYPE, - string::utf16, vm::{ call_frame::GeneratorResumeKind, opcode::{Operation, ReThrow}, @@ -244,7 +244,7 @@ impl Operation for GeneratorDelegateNext { GeneratorResumeKind::Throw => { let throw = iterator_record .iterator() - .get_method(utf16!("throw"), context)?; + .get_method(js_str!("throw"), context)?; if let Some(throw) = throw { let result = throw.call( &iterator_record.iterator().clone().into(), @@ -265,7 +265,7 @@ impl Operation for GeneratorDelegateNext { GeneratorResumeKind::Return => { let r#return = iterator_record .iterator() - .get_method(utf16!("return"), context)?; + .get_method(js_str!("return"), context)?; if let Some(r#return) = r#return { let result = r#return.call( &iterator_record.iterator().clone().into(), diff --git a/core/engine/src/vm/opcode/iteration/for_in.rs b/core/engine/src/vm/opcode/iteration/for_in.rs index 0baa43e8dd..ee6998f9a6 100644 --- a/core/engine/src/vm/opcode/iteration/for_in.rs +++ b/core/engine/src/vm/opcode/iteration/for_in.rs @@ -1,6 +1,6 @@ use crate::{ builtins::{iterable::IteratorRecord, object::for_in_iterator::ForInIterator}, - js_string, + js_str, vm::{opcode::Operation, CompletionType}, Context, JsResult, JsValue, }; @@ -23,7 +23,7 @@ impl Operation for CreateForInIterator { let object = object.to_object(context)?; let iterator = ForInIterator::create_for_in_iterator(JsValue::new(object), context); let next_method = iterator - .get(js_string!("next"), context) + .get(js_str!("next"), context) .expect("ForInIterator must have a `next` method"); context diff --git a/core/engine/src/vm/opcode/iteration/iterator.rs b/core/engine/src/vm/opcode/iteration/iterator.rs index cf19d7be3b..c43afd192d 100644 --- a/core/engine/src/vm/opcode/iteration/iterator.rs +++ b/core/engine/src/vm/opcode/iteration/iterator.rs @@ -1,8 +1,6 @@ -use std::matches; - use crate::{ builtins::{iterable::create_iter_result_object, Array}, - js_string, + js_str, vm::{opcode::Operation, CompletionType, GeneratorResumeKind}, Context, JsResult, }; @@ -245,10 +243,7 @@ impl Operation for IteratorReturn { return Ok(CompletionType::Normal); } - let Some(ret) = record - .iterator() - .get_method(js_string!("return"), context)? - else { + let Some(ret) = record.iterator().get_method(js_str!("return"), context)? else { context.vm.push(false); return Ok(CompletionType::Normal); }; diff --git a/core/engine/src/vm/opcode/push/class/private.rs b/core/engine/src/vm/opcode/push/class/private.rs index a33ff5a47f..032c330000 100644 --- a/core/engine/src/vm/opcode/push/class/private.rs +++ b/core/engine/src/vm/opcode/push/class/private.rs @@ -1,9 +1,8 @@ use crate::{ builtins::function::OrdinaryFunction, - js_string, + js_str, js_string, object::{internal_methods::InternalMethodContext, PrivateElement}, property::PropertyDescriptor, - string::utf16, vm::{opcode::Operation, CompletionType}, Context, JsResult, }; @@ -22,7 +21,7 @@ impl PushClassPrivateMethod { let method = context.vm.pop(); let method_object = method.as_callable().expect("method must be callable"); - let name_string = js_string!(utf16!("#"), &name); + let name_string = js_string!(js_str!("#"), &name); let desc = PropertyDescriptor::builder() .value(name_string) .writable(false) @@ -31,7 +30,7 @@ impl PushClassPrivateMethod { .build(); method_object .__define_own_property__( - &utf16!("name").into(), + &js_str!("name").into(), desc, &mut InternalMethodContext::new(context), ) diff --git a/core/engine/src/vm/opcode/set/private.rs b/core/engine/src/vm/opcode/set/private.rs index 98e2b77112..dd0c8631f1 100644 --- a/core/engine/src/vm/opcode/set/private.rs +++ b/core/engine/src/vm/opcode/set/private.rs @@ -1,8 +1,7 @@ use crate::{ - js_string, + js_str, js_string, object::PrivateElement, property::PropertyDescriptor, - string::utf16, vm::{opcode::Operation, CompletionType}, Context, JsResult, }; @@ -114,7 +113,7 @@ impl SetPrivateMethod { let value = context.vm.pop(); let value = value.as_callable().expect("method must be callable"); - let name_string = js_string!(utf16!("#"), &name); + let name_string = js_string!(js_str!("#"), &name); let desc = PropertyDescriptor::builder() .value(name_string) .writable(false) diff --git a/core/engine/src/vm/opcode/set/property.rs b/core/engine/src/vm/opcode/set/property.rs index c1233b395c..0725668106 100644 --- a/core/engine/src/vm/opcode/set/property.rs +++ b/core/engine/src/vm/opcode/set/property.rs @@ -1,9 +1,11 @@ +use boa_macros::js_str; + use crate::{ builtins::function::set_function_name, object::{internal_methods::InternalMethodContext, shape::slot::SlotAttributes}, property::{PropertyDescriptor, PropertyKey}, vm::{opcode::Operation, CompletionType}, - Context, JsNativeError, JsResult, JsString, JsValue, + Context, JsNativeError, JsResult, JsValue, }; /// `SetPropertyByName` implements the Opcode Operation for `Opcode::SetPropertyByName` @@ -384,8 +386,8 @@ impl Operation for SetFunctionName { }; let prefix = match prefix { - 1 => Some(JsString::from("get")), - 2 => Some(JsString::from("set")), + 1 => Some(js_str!("get")), + 2 => Some(js_str!("set")), _ => None, }; diff --git a/core/engine/src/vm/opcode/templates/mod.rs b/core/engine/src/vm/opcode/templates/mod.rs index c33e831d19..affb3a723e 100644 --- a/core/engine/src/vm/opcode/templates/mod.rs +++ b/core/engine/src/vm/opcode/templates/mod.rs @@ -1,11 +1,11 @@ use crate::{ builtins::array::Array, + js_str, object::IntegrityLevel, property::PropertyDescriptor, vm::{opcode::Operation, CompletionType}, Context, JsResult, }; -use boa_macros::utf16; /// `TemplateLookup` implements the Opcode Operation for `Opcode::TemplateLookup` /// @@ -79,7 +79,7 @@ impl TemplateCreate { .expect("should never fail per spec"); template .define_property_or_throw( - utf16!("raw"), + js_str!("raw"), PropertyDescriptor::builder() .value(raw_obj) .writable(false) diff --git a/core/engine/src/vm/tests.rs b/core/engine/src/vm/tests.rs index 8925200682..e59a6597bf 100644 --- a/core/engine/src/vm/tests.rs +++ b/core/engine/src/vm/tests.rs @@ -2,6 +2,7 @@ use crate::{ js_string, property::Attribute, run_test_actions, Context, JsNativeErrorKind, JsValue, TestAction, }; +use boa_macros::js_str; use boa_parser::Source; use indoc::indoc; @@ -12,7 +13,7 @@ fn typeof_string() { const a = "hello"; typeof a; "#}, - js_string!("string"), + js_str!("string"), )]); } @@ -23,7 +24,7 @@ fn typeof_number() { let a = 1234; typeof a; "#}, - js_string!("number"), + js_str!("number"), )]); } @@ -54,7 +55,7 @@ fn try_catch_finally_from_init() { } finally { } "#}, - js_string!("h"), + js_str!("h"), )]); } @@ -89,7 +90,7 @@ fn use_last_expr_try_block() { "Bye!" } "#}, - js_string!("Hello!"), + js_str!("Hello!"), )]); } @@ -106,7 +107,7 @@ fn use_last_expr_catch_block() { "Hello!"; } "#}, - js_string!("Hello!"), + js_str!("Hello!"), )]); } @@ -137,7 +138,7 @@ fn finally_block_binding_env() { } buf "#}, - js_string!("Hey hey people"), + js_str!("Hey hey people"), )]); } @@ -154,7 +155,7 @@ fn run_super_method_in_object() { Object.setPrototypeOf(obj, proto); obj.v(); "#}, - js_string!("super"), + js_str!("super"), )]); } @@ -178,7 +179,7 @@ fn get_reference_by_super() { obj.method(); fromA + fromB "#}, - js_string!("ab"), + js_str!("ab"), )]); } @@ -238,7 +239,7 @@ fn order_of_execution_in_assigment_with_comma_expressions() { (f(1), a)[(f(2), 0)][(f(3), 0)] = (f(4), 123); result "#}, - js_string!("1234"), + js_str!("1234"), )]); } @@ -325,7 +326,7 @@ fn arguments_object_constructor_valid_index() { new F(); typeof args "#}, - js_string!("object"), + js_str!("object"), )]); } @@ -387,8 +388,8 @@ fn super_construction_with_paramater_expression() { } } "#}), - TestAction::assert_eq("new Student().name", js_string!("unknown")), - TestAction::assert_eq("new Student('Jack').name", js_string!("Jack")), + TestAction::assert_eq("new Student().name", js_str!("unknown")), + TestAction::assert_eq("new Student('Jack').name", js_str!("Jack")), ]); } diff --git a/core/macros/src/lib.rs b/core/macros/src/lib.rs index 600b566bdb..027e6c0478 100644 --- a/core/macros/src/lib.rs +++ b/core/macros/src/lib.rs @@ -181,6 +181,33 @@ pub fn utf16(input: TokenStream) -> TokenStream { .into() } +/// Convert a utf8 string literal to a `JsString` +#[proc_macro] +pub fn js_str(input: TokenStream) -> TokenStream { + let literal = parse_macro_input!(input as LitStr); + let utf8 = literal.value(); + + let mut is_latin1 = true; + let utf16 = utf8 + .encode_utf16() + .inspect(|x| { + if *x > u8::MAX.into() { + is_latin1 = false; + } + }) + .collect::>(); + if is_latin1 { + quote! { + ::boa_engine::string::JsStr::latin1(#utf8.as_bytes()) + } + } else { + quote! { + ::boa_engine::string::JsStr::utf16([#(#utf16),*].as_slice()) + } + } + .into() +} + decl_derive! { [Trace, attributes(boa_gc, unsafe_ignore_trace)] => /// Derive the `Trace` trait. diff --git a/core/runtime/src/console/mod.rs b/core/runtime/src/console/mod.rs index df89049687..afb3e2e3bf 100644 --- a/core/runtime/src/console/mod.rs +++ b/core/runtime/src/console/mod.rs @@ -15,15 +15,13 @@ mod tests; use boa_engine::{ - js_string, + js_str, js_string, native_function::NativeFunction, object::{JsObject, ObjectInitializer}, - string::utf16, value::{JsValue, Numeric}, - Context, JsArgs, JsData, JsResult, JsString, + Context, JsArgs, JsData, JsResult, JsStr, JsString, }; use boa_gc::{Finalize, Trace}; -// use boa_profiler::Profiler; use rustc_hash::FxHashMap; use std::{cell::RefCell, rc::Rc, time::SystemTime}; @@ -132,7 +130,7 @@ pub struct Console { impl Console { /// Name of the built-in `console` property. - pub const NAME: &'static str = "console"; + pub const NAME: JsStr<'static> = js_str!("console"); /// Initializes the `console` built-in object. #[allow(clippy::too_many_lines)] @@ -280,8 +278,8 @@ impl Console { } else if !args[0].is_string() { args.insert(0, JsValue::new(message)); } else { - let value: Vec = args[0].display().to_string().encode_utf16().collect(); - let concat = js_string!(&message, utf16!(": "), &value); + let value = JsString::from(args[0].display().to_string()); + let concat = js_string!(message.as_str(), js_str!(": "), &value); args[0] = JsValue::new(concat); } diff --git a/docs/boa_object.md b/docs/boa_object.md index e5e5a92f67..b3fa7098a3 100644 --- a/docs/boa_object.md +++ b/docs/boa_object.md @@ -313,3 +313,34 @@ function x() { } x(); // RuntimeLimit: Maximum recursion limit 100 exceeded ``` + +## Module `$boa.string` + +This module contains helpful functions for getting information about a strings. + +### Function `$boa.string.storage(str)` + +Returns the string's inner storage type, if it's a well known string that is stored in the `STATIC_STRINGS` array in boa, +then `"static"` is returned, `"heap"` otherwise. + +```JavaScript +$boa.string.storage("push") // "static" +$boa.string.storage("specialFunction") // "heap" +``` + +### Function `$boa.string.encoding(str)` + +Returns the string's inner encoding of the string. + +```JavaScript +$boa.string.encoding("Greeting") // "latin1" +$boa.string.encoding("挨拶") // "utf16" +``` + +### Function `$boa.string.summary(str)` + +Returns an object with a short summary of the of the given string. + +```JavaScript +$boa.string.summary("Greeting") // { storage: "heap", encoding: "latin1" } +``` diff --git a/examples/src/bin/classes.rs b/examples/src/bin/classes.rs index 943a27410c..44da13ce8b 100644 --- a/examples/src/bin/classes.rs +++ b/examples/src/bin/classes.rs @@ -2,12 +2,11 @@ use boa_engine::{ class::{Class, ClassBuilder}, error::JsNativeError, - js_string, + js_str, js_string, native_function::NativeFunction, property::Attribute, Context, JsArgs, JsData, JsResult, JsString, JsValue, Source, }; - use boa_gc::{Finalize, Trace}; use boa_runtime::Console; @@ -121,13 +120,13 @@ impl Class for Person { // We add an `"inheritedProperty"` property to the prototype of `Person` with // a value of `10` and default attribute flags `READONLY`, `NON_ENUMERABLE` and `PERMANENT`. - class.property(js_string!("inheritedProperty"), 10, Attribute::default()); + class.property(js_str!("inheritedProperty"), 10, Attribute::default()); // Finally, we add a `"staticProperty"` property to `Person` with a value // of `"Im a static property"` and attribute flags `WRITABLE`, `ENUMERABLE` and `PERMANENT`. class.static_property( - js_string!("staticProperty"), - js_string!("Im a static property"), + js_str!("staticProperty"), + js_str!("Im a static property"), Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::PERMANENT, ); @@ -140,7 +139,7 @@ fn add_runtime(context: &mut Context) { // We first add the `console` object, to be able to call `console.log()`. let console = Console::init(context); context - .register_global_property(js_string!(Console::NAME), console, Attribute::all()) + .register_global_property(Console::NAME, console, Attribute::all()) .expect("the console builtin shouldn't exist"); // Then we need to register the global class `Person` inside `context`. diff --git a/examples/src/bin/closures.rs b/examples/src/bin/closures.rs index 9740882763..efc414061e 100644 --- a/examples/src/bin/closures.rs +++ b/examples/src/bin/closures.rs @@ -4,11 +4,10 @@ use std::cell::{Cell, RefCell}; use boa_engine::{ - js_string, + js_str, js_string, native_function::NativeFunction, object::{builtins::JsArray, FunctionObjectBuilder, JsObject}, property::{Attribute, PropertyDescriptor}, - string::utf16, Context, JsError, JsNativeError, JsString, JsValue, Source, }; use boa_gc::{Finalize, GcRefCell, Trace}; @@ -24,7 +23,7 @@ fn main() -> Result<(), JsError> { // We register a global closure function that has the name 'closure' with length 0. context .register_global_callable( - js_string!("closure"), + JsString::from("closure"), 0, NativeFunction::from_copy_closure(move |_, _, _| { println!("Called `closure`"); @@ -53,9 +52,9 @@ fn main() -> Result<(), JsError> { // We create a new `JsObject` with some data let object = JsObject::with_object_proto(context.intrinsics()); object.define_property_or_throw( - js_string!("name"), + js_str!("name"), PropertyDescriptor::builder() - .value(js_string!("Boa dev")) + .value(js_str!("Boa dev")) .writable(false) .enumerable(false) .configurable(false), @@ -78,18 +77,18 @@ fn main() -> Result<(), JsError> { let BigStruct { greeting, object } = &mut *captures; println!("Called `createMessage`"); // We obtain the `name` property of `captures.object` - let name = object.get(js_string!("name"), context)?; + let name = object.get(js_str!("name"), context)?; // We create a new message from our captured variable. let message = js_string!( - utf16!("message from `"), + js_str!("message from `"), &name.to_string(context)?, - utf16!("`: "), - greeting + js_str!("`: "), + &*greeting ); // We can also mutate the moved data inside the closure. - captures.greeting = js_string!(greeting, utf16!(" Hello!")); + captures.greeting = js_string!(&*greeting, js_str!(" Hello!")); println!("{}", message.to_std_string_escaped()); println!(); @@ -102,7 +101,7 @@ fn main() -> Result<(), JsError> { ), ) // And here we assign `createMessage` to the `name` property of the closure. - .name("createMessage") + .name(js_str!("createMessage")) // By default all `FunctionBuilder`s set the `length` property to `0` and // the `constructable` property to `false`. .build(); @@ -112,7 +111,7 @@ fn main() -> Result<(), JsError> { .register_global_property( // We set the key to access the function the same as its name for // consistency, but it may be different if needed. - js_string!("createMessage"), + js_str!("createMessage"), // We pass `js_function` as a property value. js_function, // We assign to the "createMessage" property the desired attributes. @@ -145,7 +144,7 @@ fn main() -> Result<(), JsError> { // We register a global closure that is not `Copy`. context .register_global_callable( - js_string!("enumerate"), + js_str!("enumerate").into(), 0, // Note that it is required to use `unsafe` code, since the compiler cannot verify that the // types captured by the closure are not traceable. diff --git a/examples/src/bin/futures.rs b/examples/src/bin/futures.rs index d677a761af..3a5a3487f9 100644 --- a/examples/src/bin/futures.rs +++ b/examples/src/bin/futures.rs @@ -8,7 +8,7 @@ use std::{ use boa_engine::{ context::ContextBuilder, job::{FutureJob, JobQueue, NativeJob}, - js_string, + js_str, native_function::NativeFunction, property::Attribute, Context, JsArgs, JsResult, JsValue, Source, @@ -137,13 +137,13 @@ fn add_runtime(context: &mut Context) { // First add the `console` object, to be able to call `console.log()`. let console = Console::init(context); context - .register_global_property(js_string!(Console::NAME), console, Attribute::all()) + .register_global_property(Console::NAME, console, Attribute::all()) .expect("the console builtin shouldn't exist"); // Then, bind the defined async function to the ECMAScript function "delay". context .register_global_builtin_callable( - js_string!("delay"), + js_str!("delay").into(), 1, NativeFunction::from_async_fn(delay), ) diff --git a/examples/src/bin/host_defined.rs b/examples/src/bin/host_defined.rs index c6b12d3661..475e21cfab 100644 --- a/examples/src/bin/host_defined.rs +++ b/examples/src/bin/host_defined.rs @@ -1,7 +1,7 @@ // This example goes into the details on how to store user defined structs/state that is shared. use boa_engine::{ - js_string, native_function::NativeFunction, Context, JsArgs, JsData, JsError, JsNativeError, + native_function::NativeFunction, Context, JsArgs, JsData, JsError, JsNativeError, JsString, JsValue, Source, }; use boa_gc::{Finalize, Trace}; @@ -90,7 +90,7 @@ fn main() -> Result<(), JsError> { // // The funtion lives in the context's realm and has access to the host-defined field. context.register_global_builtin_callable( - js_string!("setRealmValue"), + JsString::from("setRealmValue"), 1, NativeFunction::from_fn_ptr(|_, args, context| { let value: usize = args.get_or_undefined(0).try_js_into(context)?; @@ -112,7 +112,7 @@ fn main() -> Result<(), JsError> { )?; context.register_global_builtin_callable( - js_string!("getRealmValue"), + JsString::from("getRealmValue"), 0, NativeFunction::from_fn_ptr(|_, _, context| { let mut host_defined = context.realm().host_defined_mut(); diff --git a/examples/src/bin/jsarray.rs b/examples/src/bin/jsarray.rs index c870831bb0..3f09b0b757 100644 --- a/examples/src/bin/jsarray.rs +++ b/examples/src/bin/jsarray.rs @@ -1,10 +1,9 @@ // This example shows how to manipulate a Javascript array using Rust code. use boa_engine::{ - js_string, + js_str, native_function::NativeFunction, object::{builtins::JsArray, FunctionObjectBuilder}, - string::utf16, Context, JsResult, JsValue, }; @@ -17,16 +16,13 @@ fn main() -> JsResult<()> { assert!(array.is_empty(context)?); - array.push(js_string!("Hello, world"), context)?; // [ "Hello, world" ] + array.push(js_str!("Hello, world"), context)?; // [ "Hello, world" ] array.push(true, context)?; // [ "Hello, world", true ] assert!(!array.is_empty(context)?); assert_eq!(array.pop(context)?, JsValue::new(true)); // [ "Hello, world" ] - assert_eq!( - array.pop(context)?, - JsValue::new(js_string!("Hello, world")) - ); // [ ] + assert_eq!(array.pop(context)?, JsValue::new(js_str!("Hello, world"))); // [ ] assert_eq!(array.pop(context)?, JsValue::undefined()); // [ ] array.push(1, context)?; // [ 1 ] @@ -58,12 +54,12 @@ fn main() -> JsResult<()> { // Join the array with an optional separator (default ","). let joined_array = array.join(None, context)?; - assert_eq!(&joined_array, utf16!("14,13,12,11,10")); + assert_eq!(&joined_array, "14,13,12,11,10"); array.fill(false, Some(1), Some(4), context)?; let joined_array = array.join(Some("::".into()), context)?; - assert_eq!(&joined_array, utf16!("14::false::false::false::10")); + assert_eq!(&joined_array, "14::false::false::false::10"); let filter_callback = FunctionObjectBuilder::new( context.realm(), @@ -97,7 +93,7 @@ fn main() -> JsResult<()> { .concat(&[another_array.into()], context)? // [ 100, 196, 1, 2, 3, 4, 5 ] .slice(Some(1), Some(5), context)?; // [ 196, 1, 2, 3 ] - assert_eq!(&chained_array.join(None, context)?, utf16!("196,1,2,3")); + assert_eq!(&chained_array.join(None, context)?, "196,1,2,3"); let reduce_callback = FunctionObjectBuilder::new( context.realm(), @@ -117,7 +113,7 @@ fn main() -> JsResult<()> { context .global_object() - .set(js_string!("myArray"), array, true, context)?; + .set(js_str!("myArray"), array, true, context)?; Ok(()) } diff --git a/examples/src/bin/jsarraybuffer.rs b/examples/src/bin/jsarraybuffer.rs index 5b321a5006..c5e6e44a4b 100644 --- a/examples/src/bin/jsarraybuffer.rs +++ b/examples/src/bin/jsarraybuffer.rs @@ -1,7 +1,7 @@ // This example shows how to manipulate a Javascript array using Rust code. use boa_engine::{ - js_string, + js_str, object::builtins::{JsArrayBuffer, JsDataView, JsUint32Array, JsUint8Array}, property::Attribute, Context, JsResult, JsValue, @@ -54,7 +54,7 @@ fn main() -> JsResult<()> { // We can also register it as a global property context .register_global_property( - js_string!("myArrayBuffer"), + js_str!("myArrayBuffer"), array_buffer, Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, ) diff --git a/examples/src/bin/jsmap.rs b/examples/src/bin/jsmap.rs index 3ac1b06c8a..3925749786 100644 --- a/examples/src/bin/jsmap.rs +++ b/examples/src/bin/jsmap.rs @@ -1,6 +1,6 @@ use boa_engine::{ js_string, - object::{builtins::JsArray, builtins::JsMap}, + object::builtins::{JsArray, JsMap}, Context, JsResult, JsValue, }; diff --git a/examples/src/bin/jstypedarray.rs b/examples/src/bin/jstypedarray.rs index 8b6faabfd8..72009def83 100644 --- a/examples/src/bin/jstypedarray.rs +++ b/examples/src/bin/jstypedarray.rs @@ -1,13 +1,13 @@ // This example shows how to manipulate a Javascript array using Rust code. use boa_engine::{ - js_string, + js_str, native_function::NativeFunction, object::{ builtins::{JsArray, JsArrayBuffer, JsUint8Array}, FunctionObjectBuilder, }, - property::{Attribute, PropertyKey}, + property::Attribute, Context, JsNativeError, JsResult, JsValue, }; use boa_gc::{Gc, GcRefCell}; @@ -168,7 +168,7 @@ fn main() -> JsResult<()> { .buffer(context)? .as_object() .unwrap() - .get(PropertyKey::String(js_string!("byteLength")), context) + .get(js_str!("byteLength"), context) .unwrap(), JsValue::new(8) ); @@ -195,23 +195,23 @@ fn main() -> JsResult<()> { // toLocaleString // let array = JsUint32Array::from_iter(vec![500, 8123, 12], context)?; - // let locales: Option = Some(js_string!("de-DE").into()); + // let locales: Option = Some(js_str!("de-DE").into()); // let options = Some(context.eval(Source::from_bytes( // r##"let options = { style: "currency", currency: "EUR" }; options;"##, // ))?); // assert_eq!( // array.to_locale_string(locales, options, context)?, - // js_string!("500,00 €,8.123,00 €,12,00 €").into() + // js_str!("500,00 €,8.123,00 €,12,00 €").into() // ); // toStringTag let array = JsUint8Array::from_iter(vec![1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, 8u8], context)?; let tag = array.to_string_tag(context)?.to_string(context)?; - assert_eq!(tag, js_string!("Uint8Array")); + assert_eq!(tag, js_str!("Uint8Array")); context .register_global_property( - js_string!("myUint8Array"), + js_str!("myUint8Array"), array, Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, ) diff --git a/examples/src/bin/module_fetch.rs b/examples/src/bin/module_fetch.rs index 9e1adb57ca..0f2de3b846 100644 --- a/examples/src/bin/module_fetch.rs +++ b/examples/src/bin/module_fetch.rs @@ -7,9 +7,8 @@ use std::{ use boa_engine::{ builtins::promise::PromiseState, job::{FutureJob, JobQueue, NativeJob}, - js_string, + js_str, module::ModuleLoader, - string::utf16, Context, JsNativeError, JsResult, JsString, JsValue, Module, }; use boa_parser::Source; @@ -149,9 +148,7 @@ fn main() -> JsResult<()> { } } - let default = module - .namespace(context) - .get(js_string!("default"), context)?; + let default = module.namespace(context).get(js_str!("default"), context)?; // `default` should contain the result of our calculations. let default = default @@ -163,14 +160,14 @@ fn main() -> JsResult<()> { .get(0, context)? .as_string() .ok_or_else(|| JsNativeError::typ().with_message("array element was not a string"))?, - utf16!("aGVsbG8=") + &js_str!("aGVsbG8=") ); assert_eq!( default .get(1, context)? .as_string() .ok_or_else(|| JsNativeError::typ().with_message("array element was not a string"))?, - utf16!("d29ybGQ=") + &js_str!("d29ybGQ=") ); Ok(()) diff --git a/examples/src/bin/modulehandler.rs b/examples/src/bin/modulehandler.rs index d77198934a..87660f524d 100644 --- a/examples/src/bin/modulehandler.rs +++ b/examples/src/bin/modulehandler.rs @@ -2,7 +2,7 @@ // the require/module.exports pattern use boa_engine::{ - js_string, native_function::NativeFunction, prelude::JsObject, property::Attribute, Context, + js_str, native_function::NativeFunction, prelude::JsObject, property::Attribute, Context, JsArgs, JsNativeError, JsResult, JsValue, Source, }; use boa_runtime::Console; @@ -13,7 +13,7 @@ fn add_runtime(context: &mut Context) { // We first add the `console` object, to be able to call `console.log()`. let console = Console::init(context); context - .register_global_property(js_string!(Console::NAME), console, Attribute::all()) + .register_global_property(Console::NAME, console, Attribute::all()) .expect("the console builtin shouldn't exist"); } @@ -28,23 +28,19 @@ fn main() -> Result<(), Box> { add_runtime(&mut ctx); // Adding custom implementation that mimics 'require' - ctx.register_global_callable( - js_string!("require"), - 0, - NativeFunction::from_fn_ptr(require), - )?; + ctx.register_global_callable("require".into(), 0, NativeFunction::from_fn_ptr(require))?; // Adding custom object that mimics 'module.exports' let moduleobj = JsObject::default(); moduleobj.set( - js_string!("exports"), - JsValue::from(js_string!(" ")), + js_str!("exports"), + JsValue::from(js_str!(" ")), false, &mut ctx, )?; ctx.register_global_property( - js_string!("module"), + js_str!("module"), JsValue::from(moduleobj), Attribute::default(), )?; @@ -72,9 +68,9 @@ fn require(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult Result<(), Box> { // We can access the full namespace of the module with all its exports. let namespace = module.namespace(context); - let result = namespace.get(js_string!("result"), context)?; + let result = namespace.get(js_str!("result"), context)?; println!("result = {}", result.display()); - assert_eq!( - namespace.get(js_string!("result"), context)?, - JsValue::from(5) - ); + assert_eq!(namespace.get(js_str!("result"), context)?, JsValue::from(5)); let mix = namespace - .get(js_string!("mix"), context)? + .get(js_str!("mix"), context)? .as_callable() .cloned() .ok_or_else(|| JsNativeError::typ().with_message("mix export wasn't a function!"))?; diff --git a/examples/src/bin/synthetic.rs b/examples/src/bin/synthetic.rs index ffab2c6ba4..80511e9e04 100644 --- a/examples/src/bin/synthetic.rs +++ b/examples/src/bin/synthetic.rs @@ -9,7 +9,8 @@ use boa_engine::builtins::promise::PromiseState; use boa_engine::module::{SimpleModuleLoader, SyntheticModuleInitializer}; use boa_engine::object::FunctionObjectBuilder; use boa_engine::{ - js_string, Context, JsArgs, JsError, JsNativeError, JsValue, Module, NativeFunction, Source, + js_str, js_string, Context, JsArgs, JsError, JsNativeError, JsValue, Module, NativeFunction, + Source, }; fn main() -> Result<(), Box> { @@ -77,17 +78,14 @@ fn main() -> Result<(), Box> { // We can access the full namespace of the module with all its exports. let namespace = module.namespace(context); - let result = namespace.get(js_string!("result"), context)?; + let result = namespace.get(js_str!("result"), context)?; println!("result = {}", result.display()); - assert_eq!( - namespace.get(js_string!("result"), context)?, - JsValue::from(5) - ); + assert_eq!(namespace.get(js_str!("result"), context)?, JsValue::from(5)); let mix = namespace - .get(js_string!("mix"), context)? + .get(js_str!("mix"), context)? .as_callable() .cloned() .ok_or_else(|| JsNativeError::typ().with_message("mix export wasn't a function!"))?; @@ -111,7 +109,7 @@ fn create_operations_module(context: &mut Context) -> Module { }), ) .length(2) - .name(js_string!("sum")) + .name("sum") .build(); let sub = FunctionObjectBuilder::new( context.realm(), @@ -120,7 +118,7 @@ fn create_operations_module(context: &mut Context) -> Module { }), ) .length(2) - .name(js_string!("sub")) + .name("sub") .build(); let mult = FunctionObjectBuilder::new( context.realm(), @@ -129,7 +127,7 @@ fn create_operations_module(context: &mut Context) -> Module { }), ) .length(2) - .name(js_string!("mult")) + .name("mult") .build(); let div = FunctionObjectBuilder::new( context.realm(), @@ -138,7 +136,7 @@ fn create_operations_module(context: &mut Context) -> Module { }), ) .length(2) - .name(js_string!("div")) + .name("div") .build(); let sqrt = FunctionObjectBuilder::new( context.realm(), @@ -148,7 +146,7 @@ fn create_operations_module(context: &mut Context) -> Module { }), ) .length(1) - .name(js_string!("sqrt")) + .name("sqrt") .build(); Module::synthetic( diff --git a/tests/tester/src/exec/js262.rs b/tests/tester/src/exec/js262.rs index 62cedb9be5..6e44571f14 100644 --- a/tests/tester/src/exec/js262.rs +++ b/tests/tester/src/exec/js262.rs @@ -256,7 +256,7 @@ fn agent_obj(handles: WorkerHandles, context: &mut Context) -> JsObject { return Ok(JsValue::null()); }; - Ok(js_string!(msg).into()) + Ok(js_string!(&msg[..]).into()) }) }; diff --git a/tests/tester/src/exec/mod.rs b/tests/tester/src/exec/mod.rs index a8c7f177e3..e5ce0553cf 100644 --- a/tests/tester/src/exec/mod.rs +++ b/tests/tester/src/exec/mod.rs @@ -8,7 +8,7 @@ use crate::{ }; use boa_engine::{ builtins::promise::PromiseState, - js_string, + js_str, js_string, module::{Module, SimpleModuleLoader}, native_function::NativeFunction, object::FunctionObjectBuilder, @@ -535,11 +535,7 @@ impl Test { if console { let console = boa_runtime::Console::init(&mut context); context - .register_global_property( - js_string!(boa_runtime::Console::NAME), - console, - Attribute::all(), - ) + .register_global_property(boa_runtime::Console::NAME, console, Attribute::all()) .expect("the console builtin shouldn't exist"); } @@ -601,10 +597,10 @@ fn is_error_type(error: &JsError, target_type: ErrorType, context: &mut Context) .as_opaque() .expect("try_native cannot fail if e is not opaque") .as_object() - .and_then(|o| o.get(js_string!("constructor"), context).ok()) + .and_then(|o| o.get(js_str!("constructor"), context).ok()) .as_ref() .and_then(JsValue::as_object) - .and_then(|o| o.get(js_string!("name"), context).ok()) + .and_then(|o| o.get(js_str!("name"), context).ok()) .as_ref() .and_then(JsValue::as_string) .is_some_and(|s| s == target_type.as_str()); @@ -647,7 +643,7 @@ fn register_print_fn(context: &mut Context, async_result: AsyncResult) { context .register_global_property( - js_string!("print"), + js_str!("print"), js_function, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, )