Rust编写的JavaScript引擎,该项目是一个试验性质的项目。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

368 lines
11 KiB

//! This module implements the global `Symbol` object.
//!
//! The data type symbol is a primitive data type.
//! The `Symbol()` function returns a value of type symbol, has static properties that expose
//! several members of built-in objects, has static methods that expose the global symbol registry,
//! and resembles a built-in object class, but is incomplete as a constructor because it does not
//! support the syntax "`new Symbol()`".
//!
//! Every symbol value returned from `Symbol()` is unique.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-symbol-value
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
use crate::{js_string, string::utf16, JsString};
use boa_gc::{empty_trace, Finalize, Trace};
use std::{
cell::Cell,
hash::{Hash, Hasher},
rc::Rc,
};
/// A structure that contains the JavaScript well known symbols.
///
/// # Examples
/// ```
Create new lazy Error type (#2283) This is an experiment that tries to migrate the codebase from eager `Error` objects to lazy ones. In short words, this redefines `JsResult = Result<JsValue, JsError>`, where `JsError` is a brand new type that stores only the essential part of an error type, and only transforms those errors to `JsObject`s on demand (when having to pass them as arguments to functions or store them inside async/generators). This change is pretty big, because it unblocks a LOT of code from having to take a `&mut Context` on each call. It also paves the road for possibly making `JsError` a proper variant of `JsValue`, which can be a pretty big optimization for try/catch. A downside of this is that it exposes some brand new error types to our public API. However, we can now implement `Error` on `JsError`, making our `JsResult` type a bit more inline with Rust's best practices. ~Will mark this as draft, since it's missing some documentation and a lot of examples, but~ it's pretty much feature complete. As always, any comments about the design are very much appreciated! Note: Since there are a lot of changes which are essentially just rewriting `context.throw` to `JsNativeError::%type%`, I'll leave an "index" of the most important changes here: - [boa_engine/src/error.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-f15f2715655440626eefda5c46193d29856f4949ad37380c129a8debc6b82f26) - [boa_engine/src/builtins/error/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-3eb1e4b4b5c7210eb98192a5277f5a239148423c6b970c4ae05d1b267f8f1084) - [boa_tester/src/exec/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-fc3d7ad7b5e64574258c9febbe56171f3309b74e0c8da35238a76002f3ee34d9)
2 years ago
/// # use boa_engine::symbol::WellKnownSymbols;
///
/// let iterator = WellKnownSymbols::iterator();
/// assert_eq!(iterator.description().unwrap().to_std_string_escaped(), "Symbol.iterator");
/// ```
/// This is equivalent to `let iterator = Symbol.iterator` in JavaScript.
#[derive(Debug, Clone)]
pub struct WellKnownSymbols {
async_iterator: JsSymbol,
has_instance: JsSymbol,
is_concat_spreadable: JsSymbol,
iterator: JsSymbol,
r#match: JsSymbol,
match_all: JsSymbol,
replace: JsSymbol,
search: JsSymbol,
species: JsSymbol,
split: JsSymbol,
to_primitive: JsSymbol,
to_string_tag: JsSymbol,
unscopables: JsSymbol,
}
/// Reserved number of symbols.
///
/// This is where the well known symbol live
/// and internal engine symbols.
const RESERVED_SYMBOL_HASHES: u64 = 128;
thread_local! {
/// Cached well known symbols
static WELL_KNOW_SYMBOLS: WellKnownSymbols = WellKnownSymbols::new();
/// Symbol hash.
///
/// For now this is an incremented u64 number.
static SYMBOL_HASH_COUNT: Cell<u64> = Cell::new(RESERVED_SYMBOL_HASHES);
}
impl WellKnownSymbols {
/// Create the well known symbols.
fn new() -> Self {
let mut count = 0;
let async_iterator = JsSymbol::with_hash(count, Some("Symbol.asyncIterator".into()));
count += 1;
let has_instance = JsSymbol::with_hash(count, Some("Symbol.hasInstance".into()));
count += 1;
let is_concat_spreadable =
JsSymbol::with_hash(count, Some("Symbol.isConcatSpreadable".into()));
count += 1;
let iterator = JsSymbol::with_hash(count, Some("Symbol.iterator".into()));
count += 1;
let match_ = JsSymbol::with_hash(count, Some("Symbol.match".into()));
count += 1;
let match_all = JsSymbol::with_hash(count, Some("Symbol.matchAll".into()));
count += 1;
let replace = JsSymbol::with_hash(count, Some("Symbol.replace".into()));
count += 1;
let search = JsSymbol::with_hash(count, Some("Symbol.search".into()));
count += 1;
let species = JsSymbol::with_hash(count, Some("Symbol.species".into()));
count += 1;
let split = JsSymbol::with_hash(count, Some("Symbol.split".into()));
count += 1;
let to_primitive = JsSymbol::with_hash(count, Some("Symbol.toPrimitive".into()));
count += 1;
let to_string_tag = JsSymbol::with_hash(count, Some("Symbol.toStringTag".into()));
count += 1;
let unscopables = JsSymbol::with_hash(count, Some("Symbol.unscopables".into()));
Self {
async_iterator,
has_instance,
is_concat_spreadable,
iterator,
r#match: match_,
match_all,
replace,
search,
species,
split,
to_primitive,
to_string_tag,
unscopables,
}
}
/// The `Symbol.asyncIterator` well known symbol.
///
/// A method that returns the default `AsyncIterator` for an object.
/// Called by the semantics of the `for-await-of` statement.
#[inline]
#[must_use]
pub fn async_iterator() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.async_iterator.clone())
}
/// The `Symbol.hasInstance` well known symbol.
///
/// A method that determines if a `constructor` object
/// recognizes an object as one of the `constructor`'s instances.
/// Called by the semantics of the instanceof operator.
#[inline]
#[must_use]
pub fn has_instance() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.has_instance.clone())
}
/// The `Symbol.isConcatSpreadable` well known symbol.
///
/// A Boolean valued property that if `true` indicates that
/// an object should be flattened to its array elements
/// by `Array.prototype.concat`.
#[inline]
#[must_use]
pub fn is_concat_spreadable() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.is_concat_spreadable.clone())
}
/// The `Symbol.iterator` well known symbol.
///
/// A method that returns the default Iterator for an object.
/// Called by the semantics of the `for-of` statement.
#[inline]
#[must_use]
pub fn iterator() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.iterator.clone())
}
/// The `Symbol.match` well known symbol.
///
/// A regular expression method that matches the regular expression
/// against a string. Called by the `String.prototype.match` method.
#[inline]
#[must_use]
pub fn r#match() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.r#match.clone())
}
/// The `Symbol.matchAll` well known symbol.
///
/// A regular expression method that returns an iterator, that yields
/// matches of the regular expression against a string.
/// Called by the `String.prototype.matchAll` method.
#[inline]
#[must_use]
pub fn match_all() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.match_all.clone())
}
/// The `Symbol.replace` well known symbol.
///
/// A regular expression method that replaces matched substrings
/// of a string. Called by the `String.prototype.replace` method.
#[inline]
#[must_use]
pub fn replace() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.replace.clone())
}
/// The `Symbol.search` well known symbol.
///
/// A regular expression method that returns the index within a
/// string that matches the regular expression.
/// Called by the `String.prototype.search` method.
#[inline]
#[must_use]
pub fn search() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.search.clone())
}
/// The `Symbol.species` well known symbol.
///
/// A function valued property that is the `constructor` function
/// that is used to create derived objects.
#[inline]
#[must_use]
pub fn species() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.species.clone())
}
/// The `Symbol.split` well known symbol.
///
/// A regular expression method that splits a string at the indices
/// that match the regular expression.
/// Called by the `String.prototype.split` method.
#[inline]
#[must_use]
pub fn split() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.split.clone())
}
/// The `Symbol.toPrimitive` well known symbol.
///
/// A method that converts an object to a corresponding primitive value.
/// Called by the `ToPrimitive` (`Value::to_primitive`) abstract operation.
#[inline]
#[must_use]
pub fn to_primitive() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.to_primitive.clone())
}
/// The `Symbol.toStringTag` well known symbol.
///
/// A String valued property that is used in the creation of the default
/// string description of an object.
/// Accessed by the built-in method `Object.prototype.toString`.
#[inline]
#[must_use]
pub fn to_string_tag() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.to_string_tag.clone())
}
/// The `Symbol.unscopables` well known symbol.
///
/// An object valued property whose own and inherited property names are property
/// names that are excluded from the `with` environment bindings of the associated object.
#[inline]
#[must_use]
pub fn unscopables() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.unscopables.clone())
}
}
/// The inner representation of a JavaScript symbol.
#[derive(Debug, Clone)]
struct Inner {
hash: u64,
description: Option<JsString>,
}
/// This represents a JavaScript symbol primitive.
#[derive(Debug, Clone, Finalize)]
pub struct JsSymbol {
inner: Rc<Inner>,
}
// Safety: JsSymbol does not contain any objects which needs to be traced,
// so this is safe.
unsafe impl Trace for JsSymbol {
empty_trace!();
}
impl JsSymbol {
/// Create a new symbol.
#[inline]
#[must_use]
pub fn new(description: Option<JsString>) -> Self {
let hash = SYMBOL_HASH_COUNT.with(|count| {
let hash = count.get();
count.set(hash + 1);
hash
});
Self {
inner: Rc::new(Inner { hash, description }),
}
}
/// Create a new symbol with a specified hash and description.
#[inline]
fn with_hash(hash: u64, description: Option<JsString>) -> Self {
Self {
inner: Rc::new(Inner { hash, description }),
}
}
/// Returns the `Symbol`s description.
#[inline]
#[must_use]
pub fn description(&self) -> Option<JsString> {
self.inner.description.clone()
}
/// Returns the `Symbol`s hash.
///
/// The hash is guaranteed to be unique.
#[inline]
#[must_use]
pub fn hash(&self) -> u64 {
self.inner.hash
}
/// Abstract operation `SymbolDescriptiveString ( sym )`
///
/// More info:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-symboldescriptivestring
#[must_use]
pub fn descriptive_string(&self) -> JsString {
self.inner.description.as_ref().map_or_else(
|| js_string!("Symbol()"),
|desc| js_string!(utf16!("Symbol("), desc, utf16!(")")),
)
}
}
impl std::fmt::Display for JsSymbol {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.inner.description {
Some(desc) => write!(f, "Symbol({})", desc.to_std_string_escaped()),
None => write!(f, "Symbol()"),
}
}
}
impl Eq for JsSymbol {}
impl PartialEq for JsSymbol {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.inner.hash == other.inner.hash
}
}
impl PartialOrd for JsSymbol {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.inner.hash.partial_cmp(&other.inner.hash)
}
}
impl Ord for JsSymbol {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.inner.hash.cmp(&other.inner.hash)
}
}
impl Hash for JsSymbol {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.inner.hash.hash(state);
}
}