Skip to content

Commit

Permalink
Implement prototype of Intl built-in (#1622)
Browse files Browse the repository at this point in the history
<!---
Thank you for contributing to Boa! Please fill out the template below, and remove or add any
information as you feel neccesary.
--->

This pull request is related to #1180.

It changes the following:

- Creates the `Intl` global
- Adds the `Intl.getCanonicalLocales` method

At the moment it does not actually use ICU4X behind the scenes; `Intl.getCanonicalLocales` simply acts as if all the locales passed are canonical locales. This will not be the case in the final PR.


Co-authored-by: RageKnify <RageKnify@gmail.com>
  • Loading branch information
hle0 and RageKnify committed Oct 24, 2021
1 parent 6262bd9 commit da29677
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 0 deletions.
141 changes: 141 additions & 0 deletions boa/src/builtins/intl/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//! This module implements the global `Intl` object.
//!
//! `Intl` is a built-in object that has properties and methods for i18n. It's not a function object.
//!
//! More information:
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma402/#intl-object

use indexmap::IndexSet;

use crate::{
builtins::{Array, BuiltIn, JsArgs},
object::ObjectInitializer,
property::Attribute,
symbol::WellKnownSymbols,
BoaProfiler, Context, JsResult, JsString, JsValue,
};

/// JavaScript `Intl` object.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct Intl;

impl BuiltIn for Intl {
const NAME: &'static str = "Intl";

const ATTRIBUTE: Attribute = Attribute::WRITABLE
.union(Attribute::NON_ENUMERABLE)
.union(Attribute::CONFIGURABLE);

fn init(context: &mut Context) -> JsValue {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");

let string_tag = WellKnownSymbols::to_string_tag();
let object = ObjectInitializer::new(context)
.function(Self::get_canonical_locales, "getCanonicalLocales", 1)
.property(
string_tag,
Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.build();

object.into()
}
}

impl Intl {
fn canonicalize_locale(locale: &str) -> JsString {
JsString::new(locale)
}

fn canonicalize_locale_list(
args: &[JsValue],
context: &mut Context,
) -> JsResult<Vec<JsString>> {
// https://tc39.es/ecma402/#sec-canonicalizelocalelist
// 1. If locales is undefined, then
let locales = args.get_or_undefined(0);
if locales.is_undefined() {
// a. Return a new empty List.
return Ok(Vec::new());
}

let locales = &args[0];

// 2. Let seen be a new empty List.
let mut seen = IndexSet::new();

// 3. If Type(locales) is String or Type(locales) is Object and locales has an [[InitializedLocale]] internal slot, then
// TODO: check if Type(locales) is object and handle the internal slots
let o = if locales.is_string() {
// a. Let O be CreateArrayFromList(« locales »).
Array::create_array_from_list([locales.clone()], context)
} else {
// 4. Else,
// a. Let O be ? ToObject(locales).
locales.to_object(context)?
};

// 5. Let len be ? ToLength(? Get(O, "length")).
let len = o.length_of_array_like(context)?;

// 6 Let k be 0.
// 7. Repeat, while k < len,
for k in 0..len {
// a. Let Pk be ToString(k).
// b. Let kPresent be ? HasProperty(O, Pk).
let k_present = o.has_property(k, context)?;
// c. If kPresent is true, then
if k_present {
// i. Let kValue be ? Get(O, Pk).
let k_value = o.get(k, context)?;
// ii. If Type(kValue) is not String or Object, throw a TypeError exception.
if !(k_value.is_object() || k_value.is_string()) {
return Err(context
.throw_type_error("locale should be a String or Object")
.unwrap_err());
}
// iii. If Type(kValue) is Object and kValue has an [[InitializedLocale]] internal slot, then
// TODO: handle checks for InitializedLocale internal slot (there should be an if statement here)
// 1. Let tag be kValue.[[Locale]].
// iv. Else,
// 1. Let tag be ? ToString(kValue).
let tag = k_value.to_string(context)?;
// v. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
// TODO: implement `IsStructurallyValidLanguageTag`

// vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag).
seen.insert(Self::canonicalize_locale(&tag));
// vii. If canonicalizedTag is not an element of seen, append canonicalizedTag as the last element of seen.
}
// d. Increase k by 1.
}

// 8. Return seen.
Ok(seen.into_iter().collect::<Vec<JsString>>())
}

/// Returns an array containing the canonical locale names.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN docs][mdn]
///
/// [spec]: https://tc39.es/ecma402/#sec-intl.getcanonicallocales
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/getCanonicalLocales
pub(crate) fn get_canonical_locales(
_: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let ll be ? CanonicalizeLocaleList(locales).
let ll = Self::canonicalize_locale_list(args, context)?;
// 2. Return CreateArrayFromList(ll).
Ok(JsValue::Object(Array::create_array_from_list(
ll.into_iter().map(|x| x.into()),
context,
)))
}
}
3 changes: 3 additions & 0 deletions boa/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod error;
pub mod function;
pub mod global_this;
pub mod infinity;
pub mod intl;
pub mod intrinsics;
pub mod iterable;
pub mod json;
Expand Down Expand Up @@ -39,6 +40,7 @@ pub(crate) use self::{
function::BuiltInFunctionObject,
global_this::GlobalThis,
infinity::Infinity,
intl::Intl,
json::Json,
map::map_iterator::MapIterator,
map::Map,
Expand Down Expand Up @@ -124,6 +126,7 @@ pub fn init(context: &mut Context) {
BuiltInFunctionObject,
BuiltInObjectObject,
Math,
Intl,
Json,
Array,
Proxy,
Expand Down

0 comments on commit da29677

Please sign in to comment.