1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
use itertools::Itertools;
use crate::{
builtins::{BuiltInBuilder, IntrinsicObject},
context::intrinsics::Intrinsics,
js_string,
object::ObjectData,
realm::Realm,
Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use super::{create_segment_data_object, SegmentIterator};
#[derive(Debug, Trace, Finalize)]
pub struct Segments {
segmenter: JsObject,
string: JsString,
}
impl IntrinsicObject for Segments {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event("%SegmentsPrototype%", "init");
BuiltInBuilder::with_intrinsic::<Self>(realm)
.static_method(Self::containing, "containing", 1)
.static_method(
Self::iterator,
(JsSymbol::iterator(), js_string!("[Symbol.iterator]")),
0,
)
.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
intrinsics.objects().segments_prototype()
}
}
impl Segments {
/// [`CreateSegmentsObject ( segmenter, string )`][spec]
///
/// [spec]: https://tc39.es/ecma402/#sec-createsegmentsobject
pub(crate) fn create(
segmenter: JsObject,
string: JsString,
context: &mut Context<'_>,
) -> JsObject {
// 1. Let internalSlotsList be « [[SegmentsSegmenter]], [[SegmentsString]] ».
// 2. Let segments be OrdinaryObjectCreate(%SegmentsPrototype%, internalSlotsList).
// 3. Set segments.[[SegmentsSegmenter]] to segmenter.
// 4. Set segments.[[SegmentsString]] to string.
// 5. Return segments.
JsObject::from_proto_and_data(
context.intrinsics().objects().segments_prototype(),
ObjectData::segments(Self { segmenter, string }),
)
}
/// [`%SegmentsPrototype%.containing ( index )`][spec]
///
/// [spec]: https://tc39.es/ecma402/#sec-%segmentsprototype%.containing
fn containing(
this: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. Let segments be the this value.
// 2. Perform ? RequireInternalSlot(segments, [[SegmentsSegmenter]]).
let segments = this.as_object().map(JsObject::borrow).ok_or_else(|| {
JsNativeError::typ()
.with_message("`containing` can only be called on a `Segments` object")
})?;
let segments = segments.as_segments().ok_or_else(|| {
JsNativeError::typ()
.with_message("`containing` can only be called on a `Segments` object")
})?;
// 3. Let segmenter be segments.[[SegmentsSegmenter]].
let segmenter = segments.segmenter.borrow();
let segmenter = segmenter
.as_segmenter()
.expect("segments object should contain a segmenter");
// 4. Let string be segments.[[SegmentsString]].
// 5. Let len be the length of string.
let len = segments.string.len() as i64;
// 6. Let n be ? ToIntegerOrInfinity(index).
let Some(n) = args
.get_or_undefined(0)
.to_integer_or_infinity(context)?
.as_integer()
// 7. If n < 0 or n ≥ len, return undefined.
.filter(|i| (0..len).contains(i))
.map(|n| n as usize) else {
return Ok(JsValue::undefined());
};
// 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);
std::iter::from_fn(|| segments.next().map(|i| (i, segments.is_word_like())))
.tuple_windows()
.find(|((i, _), (j, _))| (*i..*j).contains(&n))
.map(|((i, _), (j, word))| ((i..j), word))
.expect("string should have at least a length of 1, and `n` must be in range")
};
// 10. Return ! CreateSegmentDataObject(segmenter, string, startIndex, endIndex).
Ok(
create_segment_data_object(segments.string.clone(), range, is_word_like, context)
.into(),
)
}
/// [`%SegmentsPrototype% [ @@iterator ] ( )`][spec]
///
/// [spec]: https://tc39.es/ecma402/#sec-%segmentsprototype%-@@iterator
fn iterator(this: &JsValue, _: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
// 1. Let segments be the this value.
// 2. Perform ? RequireInternalSlot(segments, [[SegmentsSegmenter]]).
let segments = this.as_object().map(JsObject::borrow).ok_or_else(|| {
JsNativeError::typ()
.with_message("`containing` can only be called on a `Segments` object")
})?;
let segments = segments.as_segments().ok_or_else(|| {
JsNativeError::typ()
.with_message("`containing` can only be called on a `Segments` object")
})?;
// 3. Let segmenter be segments.[[SegmentsSegmenter]].
// 4. Let string be segments.[[SegmentsString]].
// 5. Return ! CreateSegmentIterator(segmenter, string).
Ok(
SegmentIterator::create(segments.segmenter.clone(), segments.string.clone(), context)
.into(),
)
}
}