Skip to content

Commit f67d0c7

Browse files
authored
Merge pull request #6 from sebastianjacmatt/Instant/round
Instant.prototype.round implementation. almost all tests pass, code still kinda sucks
2 parents 811cfb0 + ae54b4d commit f67d0c7

File tree

4 files changed

+97
-70
lines changed

4 files changed

+97
-70
lines changed

nova_vm/src/builtin_strings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ return
356356
reverse
357357
revocable
358358
#[cfg(any(feature = "math", feature = "temporal"))]round
359+
#[cfg(feature = "temporal")]roundingMode
359360
#[cfg(feature = "temporal")]roundingIncrement
360361
seal
361362
#[cfg(feature = "regexp")]search

nova_vm/src/ecmascript/builtins/temporal.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ pub mod instant;
88
pub mod options;
99
pub mod plain_time;
1010

11-
use temporal_rs::options::{DifferenceSettings, RoundingMode, Unit, UnitGroup};
11+
use temporal_rs::options::{DifferenceSettings, RoundingIncrement, RoundingMode, Unit, UnitGroup};
1212

1313
use crate::{
1414
ecmascript::{
1515
builders::ordinary_object_builder::OrdinaryObjectBuilder,
1616
builtins::temporal::{
17-
instant::instant_prototype::{DefaultOption, get_temporal_unit_valued_option},
17+
instant::instant_prototype::get_temporal_unit_valued_option,
1818
options::{get_rounding_increment_option, get_rounding_mode_option},
1919
},
2020
execution::{Agent, JsResult, Realm},
@@ -81,6 +81,8 @@ impl Temporal {
8181
trivially_bindable!(DifferenceSettings);
8282
trivially_bindable!(UnitGroup);
8383
trivially_bindable!(Unit);
84+
trivially_bindable!(RoundingMode);
85+
trivially_bindable!(RoundingIncrement);
8486

8587
/// [13.42 GetDifferenceSettings ( operation, options, unitGroup, disallowedUnits, fallbackSmallestUnit, smallestLargestDefaultUnit )](https://tc39.es/proposal-temporal/#sec-temporal-getdifferencesettings)
8688
/// The abstract operation GetDifferenceSettings takes arguments operation (since or until),
@@ -111,7 +113,6 @@ pub(crate) fn get_difference_settings<'gc, const IS_UNTIL: bool>(
111113
agent,
112114
options.get(agent),
113115
BUILTIN_STRING_MEMORY.largestUnit.to_property_key(),
114-
DefaultOption::Unset,
115116
gc.reborrow(),
116117
)
117118
.unbind()?
@@ -135,7 +136,6 @@ pub(crate) fn get_difference_settings<'gc, const IS_UNTIL: bool>(
135136
agent,
136137
options.get(agent),
137138
BUILTIN_STRING_MEMORY.smallestUnit.to_property_key(),
138-
DefaultOption::Unset,
139139
gc.reborrow(),
140140
)
141141
.unbind()?
@@ -157,8 +157,8 @@ pub(crate) fn get_difference_settings<'gc, const IS_UNTIL: bool>(
157157
// 17. If maximum is not unset, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false).
158158
// 18. Return the Record { [[SmallestUnit]]: smallestUnit, [[LargestUnit]]: largestUnit, [[RoundingMode]]: roundingMode, [[RoundingIncrement]]: roundingIncrement, }.
159159
let mut diff_settings = temporal_rs::options::DifferenceSettings::default();
160-
diff_settings.largest_unit = Some(largest_unit);
161-
diff_settings.smallest_unit = Some(smallest_unit);
160+
diff_settings.largest_unit = largest_unit;
161+
diff_settings.smallest_unit = smallest_unit;
162162
diff_settings.rounding_mode = Some(rounding_mode);
163163
diff_settings.increment = Some(rounding_increment);
164164
Ok(diff_settings)

nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ use crate::{
1616
require_internal_slot_temporal_instant, to_temporal_instant,
1717
},
1818
options::{
19-
get_options_object, get_rounding_increment_option, get_rounding_mode_option,
19+
get_option, get_options_object, get_rounding_increment_option,
20+
get_rounding_mode_option,
2021
},
2122
},
2223
},
@@ -27,7 +28,7 @@ use crate::{
2728
types::{BUILTIN_STRING_MEMORY, BigInt, IntoValue, Object, PropertyKey, String, Value},
2829
},
2930
engine::{
30-
context::{Bindable, GcScope, NoGcScope, trivially_bindable},
31+
context::{Bindable, GcScope, NoGcScope},
3132
rootable::Scopable,
3233
},
3334
heap::WellKnownSymbolIndexes,
@@ -295,7 +296,7 @@ impl TemporalInstantPrototype {
295296
}
296297

297298
// 4. If roundTo is a String, then
298-
let round_to = if let Value::String(round_to) = round_to.unbind() {
299+
let round_to = if round_to.unbind().is_string() {
299300
// a. Let paramString be roundTo.
300301
let param_string = round_to;
301302
// b. Set roundTo to OrdinaryObjectCreate(null).
@@ -316,17 +317,21 @@ impl TemporalInstantPrototype {
316317
.unbind()?
317318
.bind(gc.nogc())
318319
};
320+
319321
let round_to = round_to.scope(agent, gc.nogc());
322+
320323
// 6. NOTE: The following steps read options and perform independent validation in
321324
// alphabetical order (GetRoundingIncrementOption reads "roundingIncrement" and
322325
// GetRoundingModeOption reads "roundingMode").
323326
let mut options = RoundingOptions::default();
327+
324328
// 7. Let roundingIncrement be ? GetRoundingIncrementOption(roundTo).
325329
let rounding_increment =
326330
get_rounding_increment_option(agent, round_to.get(agent), gc.reborrow())
327331
.unbind()?
328332
.bind(gc.nogc());
329333
options.increment = Some(rounding_increment);
334+
330335
// 8. Let roundingMode be ? GetRoundingModeOption(roundTo, half-expand).
331336
let rounding_mode = get_rounding_mode_option(
332337
agent,
@@ -337,17 +342,18 @@ impl TemporalInstantPrototype {
337342
.unbind()?
338343
.bind(gc.nogc());
339344
options.rounding_mode = Some(rounding_mode);
345+
340346
// 9. Let smallestUnit be ? GetTemporalUnitValuedOption(roundTo, "smallestUnit", required).
341347
let smallest_unit = get_temporal_unit_valued_option(
342348
agent,
343349
round_to.get(agent),
344350
BUILTIN_STRING_MEMORY.smallestUnit.into(),
345-
DefaultOption::Required,
346351
gc.reborrow(),
347352
)
348353
.unbind()?
349354
.bind(gc.nogc());
350-
options.smallest_unit = Some(smallest_unit);
355+
options.smallest_unit = smallest_unit;
356+
351357
// 10. Perform ? ValidateTemporalUnitValue(smallestUnit, time).
352358
// 11. If smallestUnit is hour, then
353359
// a. Let maximum be HoursPerDay.
@@ -518,7 +524,7 @@ pub(crate) fn to_integer_with_truncation<'gc>(
518524
// 2. If number is NaN, +∞𝔽 or -∞𝔽, throw a RangeError exception.
519525
if number.is_nan(agent) || number.is_pos_infinity(agent) || number.is_neg_infinity(agent) {
520526
return Err(agent.throw_exception_with_static_message(
521-
ExceptionType::TypeError,
527+
ExceptionType::RangeError,
522528
"Number cannot be NaN, positive infinity, or negative infinity",
523529
gc.into_nogc(),
524530
));
@@ -528,31 +534,22 @@ pub(crate) fn to_integer_with_truncation<'gc>(
528534
Ok(number.into_f64(agent).trunc())
529535
}
530536

531-
trivially_bindable!(Unit);
532-
533537
/// ### [13.17 GetTemporalUnitValuedOption ( options, key, default )] (https://tc39.es/proposal-temporal/#sec-temporal-gettemporalunitvaluedoption)
534538
///
535539
/// The abstract operation GetTemporalUnitValuedOption takes arguments options (an Object), key (a
536540
/// property key), and default (required or unset) and returns either a normal completion
537541
/// containing either a Temporal unit, unset, or auto, or a throw completion. It attempts to read a
538542
/// Temporal unit from the specified property of options.
539543
pub(crate) fn get_temporal_unit_valued_option<'gc>(
540-
_agent: &mut Agent,
544+
agent: &mut Agent,
541545
options: Object,
542546
key: PropertyKey,
543-
default: DefaultOption,
544547
gc: GcScope<'gc, '_>,
545-
) -> JsResult<'gc, Unit> {
546-
let _options = options.bind(gc.nogc());
547-
let _default = default.bind(gc.nogc());
548-
let _key = key.bind(gc.nogc());
549-
todo!()
550-
}
548+
) -> JsResult<'gc, Option<Unit>> {
549+
let options = options.bind(gc.nogc());
550+
let key = key.bind(gc.nogc());
551551

552-
#[allow(dead_code)]
553-
pub(crate) enum DefaultOption {
554-
Required,
555-
Unset,
556-
}
552+
let opt = get_option::<Unit>(agent, options.unbind(), key.unbind(), gc)?;
557553

558-
trivially_bindable!(DefaultOption);
554+
Ok(opt)
555+
}

nova_vm/src/ecmascript/builtins/temporal/options.rs

Lines changed: 71 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,38 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
use std::num::NonZeroU32;
5+
use std::{num::NonZeroU32, str::FromStr};
66

7-
use temporal_rs::options::{RoundingIncrement, RoundingMode};
7+
use temporal_rs::options::{RoundingIncrement, RoundingMode, Unit};
88

99
use crate::{
1010
ecmascript::{
11-
abstract_operations::{
12-
operations_on_objects::get,
13-
type_conversion::{to_boolean, to_string},
14-
},
11+
abstract_operations::{operations_on_objects::get, type_conversion::to_string},
1512
builtins::{
1613
ordinary::ordinary_object_create_with_intrinsics,
1714
temporal::instant::instant_prototype::to_integer_with_truncation,
1815
},
1916
execution::{Agent, JsResult, agent::ExceptionType},
20-
types::{BUILTIN_STRING_MEMORY, Object, PropertyKey, String, Value},
17+
types::{BUILTIN_STRING_MEMORY, Object, PropertyKey, Value},
2118
},
22-
engine::context::{Bindable, GcScope, NoGcScope, trivially_bindable},
19+
engine::context::{Bindable, GcScope, NoGcScope},
2320
};
2421

25-
pub(crate) enum OptionType {
26-
Boolean(bool),
27-
String(String<'static>),
22+
pub trait OptionType: Sized {
23+
fn from_string(s: &str) -> Result<Self, std::string::String>;
24+
}
25+
26+
impl OptionType for RoundingMode {
27+
fn from_string(s: &str) -> Result<Self, std::string::String> {
28+
RoundingMode::from_str(s).map_err(|e| e.to_string())
29+
}
2830
}
2931

30-
trivially_bindable!(OptionType);
31-
trivially_bindable!(RoundingMode);
32-
trivially_bindable!(RoundingIncrement);
32+
impl OptionType for Unit {
33+
fn from_string(s: &str) -> Result<Self, std::string::String> {
34+
Unit::from_str(s).map_err(|e| e.to_string())
35+
}
36+
}
3337

3438
/// ### [14.5.2.1 GetOptionsObject ( options )](https://tc39.es/proposal-temporal/#sec-getoptionsobject)
3539
///
@@ -52,9 +56,11 @@ pub(crate) fn get_options_object<'gc>(
5256
))
5357
}
5458
// 2. If options is an Object, then
55-
Value::Object(obj) => {
59+
value if value.is_object() => {
5660
// a. Return options.
57-
Ok(obj.into())
61+
// TODO: remove unwrap; Although safe because value is an object
62+
let obj = Object::try_from(value).unwrap();
63+
Ok(obj)
5864
}
5965
// 3. Throw a TypeError exception.
6066
_ => Err(agent.throw_exception_with_static_message(
@@ -74,13 +80,15 @@ pub(crate) fn get_options_object<'gc>(
7480
/// specified property of options, converts it to the required type, checks whether it is allowed
7581
/// by values if values is not empty, and substitutes default if the value is undefined. It
7682
/// performs the following steps when called:
77-
pub(crate) fn get_option<'gc>(
83+
pub(crate) fn get_option<'gc, T>(
7884
agent: &mut Agent,
7985
options: Object,
8086
property: PropertyKey,
81-
typee: OptionType,
8287
mut gc: GcScope<'gc, '_>,
83-
) -> JsResult<'gc, OptionType> {
88+
) -> JsResult<'gc, Option<T>>
89+
where
90+
T: OptionType,
91+
{
8492
let options = options.bind(gc.nogc());
8593
let property = property.bind(gc.nogc());
8694
// 1. Let value be ? Get(options, property).
@@ -91,27 +99,40 @@ pub(crate) fn get_option<'gc>(
9199
if value.is_undefined() {
92100
// a. If default is required, throw a RangeError exception.
93101
// b. Return default.
94-
todo!()
95-
}
96-
match typee {
97-
// 3. If type is boolean, then
98-
OptionType::Boolean(_) => {
99-
// a. Set value to ToBoolean(value).
100-
let value = to_boolean(agent, value);
101-
}
102-
// 4. Else,
103-
OptionType::String(_) => {
104-
// a. Assert: type is string.
105-
// b. Set value to ? ToString(value).
106-
let value = to_string(agent, value.unbind(), gc.reborrow())
107-
.unbind()?
108-
.bind(gc.nogc());
109-
}
102+
return Ok(None);
110103
}
111104

105+
// 3. If type is boolean, then
106+
// a. Set value to ToBoolean(value).
107+
// 4. Else,
108+
// a. Assert: type is string.
109+
// b. Set value to ? ToString(value).
112110
// 5. If values is not empty and values does not contain value, throw a RangeError exception.
111+
112+
// TODO: Currently only works for temporal_rs::Unit, and temporal_rs::RoundingMode.
113+
//
114+
// Should be extended to work with
115+
// 1. ecmascript::types::String
116+
// 2. bool
117+
// 3. Potentially other temporal_rs types.
118+
119+
let js_str = to_string(agent, value.unbind(), gc.reborrow())
120+
.unbind()?
121+
.bind(gc.nogc());
122+
123+
// TODO: Fix this code.. None case is unreachable but code sucks rn..
124+
let rust_str = js_str.as_str(agent).unwrap();
125+
126+
let parsed = T::from_string(rust_str).map_err(|msg| {
127+
agent.throw_exception_with_static_message(
128+
ExceptionType::RangeError,
129+
Box::leak(msg.into_boxed_str()),
130+
gc.into_nogc(),
131+
)
132+
})?;
133+
113134
// 6. Return value.
114-
todo!()
135+
Ok(Some(parsed))
115136
}
116137

117138
/// ### [14.5.2.3 GetRoundingModeOption ( options, fallback)](https://tc39.es/proposal-temporal/#sec-temporal-getroundingmodeoption)
@@ -121,18 +142,27 @@ pub(crate) fn get_option<'gc>(
121142
// completion. It fetches and validates the "roundingMode" property from options, returning
122143
// fallback as a default if absent. It performs the following steps when called:
123144
pub(crate) fn get_rounding_mode_option<'gc>(
124-
_agent: &mut Agent,
145+
agent: &mut Agent,
125146
options: Object,
126147
fallback: RoundingMode,
127148
gc: GcScope<'gc, '_>,
128149
) -> JsResult<'gc, RoundingMode> {
129-
let _options = options.bind(gc.nogc());
130-
let _fallback = fallback.bind(gc.nogc());
150+
let options = options.bind(gc.nogc());
151+
let fallback = fallback.bind(gc.nogc());
152+
131153
// 1. Let allowedStrings be the List of Strings from the "String Identifier" column of Table 28.
132154
// 2. Let stringFallback be the value from the "String Identifier" column of the row with fallback in its "Rounding Mode" column.
133155
// 3. Let stringValue be ? GetOption(options, "roundingMode", string, allowedStrings, stringFallback).
134156
// 4. Return the value from the "Rounding Mode" column of the row with stringValue in its "String Identifier" column.
135-
todo!()
157+
match get_option::<RoundingMode>(
158+
agent,
159+
options.unbind(),
160+
BUILTIN_STRING_MEMORY.roundingMode.into(),
161+
gc,
162+
)? {
163+
Some(mode) => Ok(mode),
164+
None => Ok(fallback),
165+
}
136166
}
137167

138168
/// ### [14.5.2.4 GetRoundingIncrementOption ( options )](https://tc39.es/proposal-temporal/#sec-temporal-getroundingincrementoption)
@@ -165,15 +195,14 @@ pub(crate) fn get_rounding_increment_option<'gc>(
165195
.bind(gc.nogc());
166196

167197
// 4. If integerIncrement < 1 or integerIncrement > 10**9, throw a RangeError exception.
168-
if integer_increment < 1.0 || integer_increment > 1_000_000_000.0 {
198+
if !(1.0..=1_000_000_000.0).contains(&integer_increment) {
169199
return Err(agent.throw_exception_with_static_message(
170200
ExceptionType::RangeError,
171201
"roundingIncrement must be between 1 and 10**9",
172202
gc.into_nogc(),
173203
));
174204
}
175205

176-
// Convert safely and return integerIncrement
177206
// NOTE: `as u32` is safe here since we validated it’s in range.
178207
let integer_increment_u32 = integer_increment as u32;
179208
let increment =

0 commit comments

Comments
 (0)