///|
/// A string-keyed dictionary with the semantics of a plain JavaScript object
/// used as a map, which is how PEG.js stores label environments and rule
/// tables:
///
/// * assigning `__proto__` is a no-op for non-object values, so that key is
/// never stored (and lookups of it fail);
/// * `Object.keys` order lists array-index keys ("0", "1", ...) first in
/// ascending numeric order, then other keys in insertion order.
priv struct JsDict[V] {
map : Map[String, V]
}
///|
fn[V] JsDict::new() -> JsDict[V] {
{ map: {}, }
}
///|
fn[V] JsDict::clone(self : JsDict[V]) -> JsDict[V] {
{ map: self.map.copy(), }
}
///|
fn[V] JsDict::set(self : JsDict[V], key : String, value : V) -> Unit {
if key != "__proto__" {
self.map[key] = value
}
}
///|
fn[V] JsDict::get(self : JsDict[V], key : String) -> V? {
self.map.get(key)
}
///|
/// Parses `key` as a JavaScript array index (canonical decimal < 2^32 - 1).
fn array_index(key : String) -> Int64? {
if key == "" ||
key.length() > 10 ||
(key.length() > 1 && key.code_unit_at(0) == '0') {
return None
}
let mut n = 0L
for i in 0.. '9'.to_int() {
return None
}
n = n * 10L + (c - '0'.to_int()).to_int64()
}
if n < 4294967295L {
Some(n)
} else {
None
}
}
///|
/// Keys in `Object.keys` order.
fn[V] JsDict::keys(self : JsDict[V]) -> Array[String] {
let indices : Array[(Int64, String)] = []
let others : Array[String] = []
for k, _ in self.map {
match array_index(k) {
Some(n) => indices.push((n, k))
None => others.push(k)
}
}
indices.sort_by_key(p => p.0)
indices.map(p => p.1) + others
}