AbstractOperations.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. /*
  2. * Copyright (c) 2021-2022, Idan Horowitz <idan.horowitz@serenityos.org>
  3. * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
  4. * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
  5. * Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
  6. *
  7. * SPDX-License-Identifier: BSD-2-Clause
  8. */
  9. #include <LibCrypto/BigFraction/BigFraction.h>
  10. #include <LibJS/Runtime/PropertyKey.h>
  11. #include <LibJS/Runtime/Temporal/AbstractOperations.h>
  12. #include <LibJS/Runtime/Temporal/Duration.h>
  13. #include <LibJS/Runtime/Temporal/ISO8601.h>
  14. namespace JS::Temporal {
  15. // https://tc39.es/proposal-temporal/#table-temporal-units
  16. struct TemporalUnit {
  17. Unit value;
  18. StringView singular_property_name;
  19. StringView plural_property_name;
  20. UnitCategory category;
  21. RoundingIncrement maximum_duration_rounding_increment;
  22. };
  23. static auto temporal_units = to_array<TemporalUnit>({
  24. { Unit::Year, "year"sv, "years"sv, UnitCategory::Date, Unset {} },
  25. { Unit::Month, "month"sv, "months"sv, UnitCategory::Date, Unset {} },
  26. { Unit::Week, "week"sv, "weeks"sv, UnitCategory::Date, Unset {} },
  27. { Unit::Day, "day"sv, "days"sv, UnitCategory::Date, Unset {} },
  28. { Unit::Hour, "hour"sv, "hours"sv, UnitCategory::Time, 24 },
  29. { Unit::Minute, "minute"sv, "minutes"sv, UnitCategory::Time, 60 },
  30. { Unit::Second, "second"sv, "seconds"sv, UnitCategory::Time, 60 },
  31. { Unit::Millisecond, "millisecond"sv, "milliseconds"sv, UnitCategory::Time, 1000 },
  32. { Unit::Microsecond, "microsecond"sv, "microseconds"sv, UnitCategory::Time, 1000 },
  33. { Unit::Nanosecond, "nanosecond"sv, "nanoseconds"sv, UnitCategory::Time, 1000 },
  34. });
  35. StringView temporal_unit_to_string(Unit unit)
  36. {
  37. return temporal_units[to_underlying(unit)].singular_property_name;
  38. }
  39. // 13.18 GetTemporalRelativeToOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalrelativetooption
  40. ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM& vm, Object const& options)
  41. {
  42. // 1. Let value be ? Get(options, "relativeTo").
  43. auto value = TRY(options.get(vm.names.relativeTo));
  44. // 2. If value is undefined, return the Record { [[PlainRelativeTo]]: undefined, [[ZonedRelativeTo]]: undefined }.
  45. if (value.is_undefined())
  46. return RelativeTo { .plain_relative_to = {}, .zoned_relative_to = {} };
  47. // FIXME: Implement the remaining steps of this AO when we have implemented PlainRelativeTo and ZonedRelativeTo.
  48. return RelativeTo { .plain_relative_to = {}, .zoned_relative_to = {} };
  49. }
  50. // 13.20 IsCalendarUnit ( unit ), https://tc39.es/proposal-temporal/#sec-temporal-iscalendarunit
  51. bool is_calendar_unit(Unit unit)
  52. {
  53. // 1. If unit is year, return true.
  54. if (unit == Unit::Year)
  55. return true;
  56. // 2. If unit is month, return true.
  57. if (unit == Unit::Month)
  58. return true;
  59. // 3. If unit is week, return true.
  60. if (unit == Unit::Week)
  61. return true;
  62. // 4. Return false.
  63. return false;
  64. }
  65. // 13.21 TemporalUnitCategory ( unit ), https://tc39.es/proposal-temporal/#sec-temporal-temporalunitcategory
  66. UnitCategory temporal_unit_category(Unit unit)
  67. {
  68. // 1. Return the value from the "Category" column of the row of Table 21 in which unit is in the "Value" column.
  69. return temporal_units[to_underlying(unit)].category;
  70. }
  71. // 13.35 ParseTemporalDurationString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldurationstring
  72. ThrowCompletionOr<GC::Ref<Duration>> parse_temporal_duration_string(VM& vm, StringView iso_string)
  73. {
  74. // 1. Let duration be ParseText(StringToCodePoints(isoString), TemporalDurationString).
  75. auto parse_result = parse_iso8601(Production::TemporalDurationString, iso_string);
  76. // 2. If duration is a List of errors, throw a RangeError exception.
  77. if (!parse_result.has_value())
  78. return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidDurationString, iso_string);
  79. // 3. Let sign be the source text matched by the ASCIISign Parse Node contained within duration, or an empty sequence
  80. // of code points if not present.
  81. auto sign = parse_result->sign;
  82. // 4. If duration contains a DurationYearsPart Parse Node, then
  83. // a. Let yearsNode be that DurationYearsPart Parse Node contained within duration.
  84. // b. Let years be the source text matched by the DecimalDigits Parse Node contained within yearsNode.
  85. // 5. Else,
  86. // a. Let years be an empty sequence of code points.
  87. auto years = parse_result->duration_years.value_or({});
  88. // 6. If duration contains a DurationMonthsPart Parse Node, then
  89. // a. Let monthsNode be the DurationMonthsPart Parse Node contained within duration.
  90. // b. Let months be the source text matched by the DecimalDigits Parse Node contained within monthsNode.
  91. // 7. Else,
  92. // a. Let months be an empty sequence of code points.
  93. auto months = parse_result->duration_months.value_or({});
  94. // 8. If duration contains a DurationWeeksPart Parse Node, then
  95. // a. Let weeksNode be the DurationWeeksPart Parse Node contained within duration.
  96. // b. Let weeks be the source text matched by the DecimalDigits Parse Node contained within weeksNode.
  97. // 9. Else,
  98. // a. Let weeks be an empty sequence of code points.
  99. auto weeks = parse_result->duration_weeks.value_or({});
  100. // 10. If duration contains a DurationDaysPart Parse Node, then
  101. // a. Let daysNode be the DurationDaysPart Parse Node contained within duration.
  102. // b. Let days be the source text matched by the DecimalDigits Parse Node contained within daysNode.
  103. // 11. Else,
  104. // a. Let days be an empty sequence of code points.
  105. auto days = parse_result->duration_days.value_or({});
  106. // 12. If duration contains a DurationHoursPart Parse Node, then
  107. // a. Let hoursNode be the DurationHoursPart Parse Node contained within duration.
  108. // b. Let hours be the source text matched by the DecimalDigits Parse Node contained within hoursNode.
  109. // c. Let fHours be the source text matched by the TemporalDecimalFraction Parse Node contained within
  110. // hoursNode, or an empty sequence of code points if not present.
  111. // 13. Else,
  112. // a. Let hours be an empty sequence of code points.
  113. // b. Let fHours be an empty sequence of code points.
  114. auto hours = parse_result->duration_hours.value_or({});
  115. auto fractional_hours = parse_result->duration_hours_fraction.value_or({});
  116. // 14. If duration contains a DurationMinutesPart Parse Node, then
  117. // a. Let minutesNode be the DurationMinutesPart Parse Node contained within duration.
  118. // b. Let minutes be the source text matched by the DecimalDigits Parse Node contained within minutesNode.
  119. // c. Let fMinutes be the source text matched by the TemporalDecimalFraction Parse Node contained within
  120. // minutesNode, or an empty sequence of code points if not present.
  121. // 15. Else,
  122. // a. Let minutes be an empty sequence of code points.
  123. // b. Let fMinutes be an empty sequence of code points.
  124. auto minutes = parse_result->duration_minutes.value_or({});
  125. auto fractional_minutes = parse_result->duration_minutes_fraction.value_or({});
  126. // 16. If duration contains a DurationSecondsPart Parse Node, then
  127. // a. Let secondsNode be the DurationSecondsPart Parse Node contained within duration.
  128. // b. Let seconds be the source text matched by the DecimalDigits Parse Node contained within secondsNode.
  129. // c. Let fSeconds be the source text matched by the TemporalDecimalFraction Parse Node contained within
  130. // secondsNode, or an empty sequence of code points if not present.
  131. // 17. Else,
  132. // a. Let seconds be an empty sequence of code points.
  133. // b. Let fSeconds be an empty sequence of code points.
  134. auto seconds = parse_result->duration_seconds.value_or({});
  135. auto fractional_seconds = parse_result->duration_seconds_fraction.value_or({});
  136. // 18. Let yearsMV be ? ToIntegerWithTruncation(CodePointsToString(years)).
  137. auto years_value = TRY(to_integer_with_truncation(vm, years, ErrorType::TemporalInvalidDurationString, iso_string));
  138. // 19. Let monthsMV be ? ToIntegerWithTruncation(CodePointsToString(months)).
  139. auto months_value = TRY(to_integer_with_truncation(vm, months, ErrorType::TemporalInvalidDurationString, iso_string));
  140. // 20. Let weeksMV be ? ToIntegerWithTruncation(CodePointsToString(weeks)).
  141. auto weeks_value = TRY(to_integer_with_truncation(vm, weeks, ErrorType::TemporalInvalidDurationString, iso_string));
  142. // 21. Let daysMV be ? ToIntegerWithTruncation(CodePointsToString(days)).
  143. auto days_value = TRY(to_integer_with_truncation(vm, days, ErrorType::TemporalInvalidDurationString, iso_string));
  144. // 22. Let hoursMV be ? ToIntegerWithTruncation(CodePointsToString(hours)).
  145. auto hours_value = TRY(to_integer_with_truncation(vm, hours, ErrorType::TemporalInvalidDurationString, iso_string));
  146. Crypto::BigFraction minutes_value;
  147. Crypto::BigFraction seconds_value;
  148. Crypto::BigFraction milliseconds_value;
  149. auto remainder_one = [](Crypto::BigFraction const& value) {
  150. // FIXME: We should add a generic remainder() method to BigFraction, or a method equivalent to modf(). But for
  151. // now, since we know we are only dividing by powers of 10, we can implement a very situationally specific
  152. // method to extract the fractional part of the BigFraction.
  153. auto res = value.numerator().divided_by(value.denominator());
  154. return Crypto::BigFraction { move(res.remainder), value.denominator() };
  155. };
  156. // 23. If fHours is not empty, then
  157. if (!fractional_hours.is_empty()) {
  158. // a. Assert: minutes, fMinutes, seconds, and fSeconds are empty.
  159. VERIFY(minutes.is_empty());
  160. VERIFY(fractional_minutes.is_empty());
  161. VERIFY(seconds.is_empty());
  162. VERIFY(fractional_seconds.is_empty());
  163. // b. Let fHoursDigits be the substring of CodePointsToString(fHours) from 1.
  164. auto fractional_hours_digits = fractional_hours.substring_view(1);
  165. // c. Let fHoursScale be the length of fHoursDigits.
  166. auto fractional_hours_scale = fractional_hours_digits.length();
  167. // d. Let minutesMV be ? ToIntegerWithTruncation(fHoursDigits) / 10**fHoursScale × 60.
  168. auto minutes_integer = TRY(to_integer_with_truncation(vm, fractional_hours_digits, ErrorType::TemporalInvalidDurationString, iso_string));
  169. minutes_value = Crypto::BigFraction { minutes_integer } / Crypto::BigFraction { pow(10.0, fractional_hours_scale) } * Crypto::BigFraction { 60.0 };
  170. }
  171. // 24. Else,
  172. else {
  173. // a. Let minutesMV be ? ToIntegerWithTruncation(CodePointsToString(minutes)).
  174. auto minutes_integer = TRY(to_integer_with_truncation(vm, minutes, ErrorType::TemporalInvalidDurationString, iso_string));
  175. minutes_value = Crypto::BigFraction { minutes_integer };
  176. }
  177. // 25. If fMinutes is not empty, then
  178. if (!fractional_minutes.is_empty()) {
  179. // a. Assert: seconds and fSeconds are empty.
  180. VERIFY(seconds.is_empty());
  181. VERIFY(fractional_seconds.is_empty());
  182. // b. Let fMinutesDigits be the substring of CodePointsToString(fMinutes) from 1.
  183. auto fractional_minutes_digits = fractional_minutes.substring_view(1);
  184. // c. Let fMinutesScale be the length of fMinutesDigits.
  185. auto fractional_minutes_scale = fractional_minutes_digits.length();
  186. // d. Let secondsMV be ? ToIntegerWithTruncation(fMinutesDigits) / 10**fMinutesScale × 60.
  187. auto seconds_integer = TRY(to_integer_with_truncation(vm, fractional_minutes_digits, ErrorType::TemporalInvalidDurationString, iso_string));
  188. seconds_value = Crypto::BigFraction { seconds_integer } / Crypto::BigFraction { pow(10.0, fractional_minutes_scale) } * Crypto::BigFraction { 60.0 };
  189. }
  190. // 26. Else if seconds is not empty, then
  191. else if (!seconds.is_empty()) {
  192. // a. Let secondsMV be ? ToIntegerWithTruncation(CodePointsToString(seconds)).
  193. auto seconds_integer = TRY(to_integer_with_truncation(vm, seconds, ErrorType::TemporalInvalidDurationString, iso_string));
  194. seconds_value = Crypto::BigFraction { seconds_integer };
  195. }
  196. // 27. Else,
  197. else {
  198. // a. Let secondsMV be remainder(minutesMV, 1) × 60.
  199. seconds_value = remainder_one(minutes_value) * Crypto::BigFraction { 60.0 };
  200. }
  201. // 28. If fSeconds is not empty, then
  202. if (!fractional_seconds.is_empty()) {
  203. // a. Let fSecondsDigits be the substring of CodePointsToString(fSeconds) from 1.
  204. auto fractional_seconds_digits = fractional_seconds.substring_view(1);
  205. // b. Let fSecondsScale be the length of fSecondsDigits.
  206. auto fractional_seconds_scale = fractional_seconds_digits.length();
  207. // c. Let millisecondsMV be ? ToIntegerWithTruncation(fSecondsDigits) / 10**fSecondsScale × 1000.
  208. auto milliseconds_integer = TRY(to_integer_with_truncation(vm, fractional_seconds_digits, ErrorType::TemporalInvalidDurationString, iso_string));
  209. milliseconds_value = Crypto::BigFraction { milliseconds_integer } / Crypto::BigFraction { pow(10.0, fractional_seconds_scale) } * Crypto::BigFraction { 1000.0 };
  210. }
  211. // 29. Else,
  212. else {
  213. // a. Let millisecondsMV be remainder(secondsMV, 1) × 1000.
  214. milliseconds_value = remainder_one(seconds_value) * Crypto::BigFraction { 1000.0 };
  215. }
  216. // 30. Let microsecondsMV be remainder(millisecondsMV, 1) × 1000.
  217. auto microseconds_value = remainder_one(milliseconds_value) * Crypto::BigFraction { 1000.0 };
  218. // 31. Let nanosecondsMV be remainder(microsecondsMV, 1) × 1000.
  219. auto nanoseconds_value = remainder_one(microseconds_value) * Crypto::BigFraction { 1000.0 };
  220. // 32. If sign contains the code point U+002D (HYPHEN-MINUS), then
  221. // a. Let factor be -1.
  222. // 33. Else,
  223. // a. Let factor be 1.
  224. i8 factor = sign == '-' ? -1 : 1;
  225. // 34. Set yearsMV to yearsMV × factor.
  226. years_value *= factor;
  227. // 35. Set monthsMV to monthsMV × factor.
  228. months_value *= factor;
  229. // 36. Set weeksMV to weeksMV × factor.
  230. weeks_value *= factor;
  231. // 37. Set daysMV to daysMV × factor.
  232. days_value *= factor;
  233. // 38. Set hoursMV to hoursMV × factor.
  234. hours_value *= factor;
  235. // 39. Set minutesMV to floor(minutesMV) × factor.
  236. auto factored_minutes_value = floor(minutes_value.to_double()) * factor;
  237. // 40. Set secondsMV to floor(secondsMV) × factor.
  238. auto factored_seconds_value = floor(seconds_value.to_double()) * factor;
  239. // 41. Set millisecondsMV to floor(millisecondsMV) × factor.
  240. auto factored_milliseconds_value = floor(milliseconds_value.to_double()) * factor;
  241. // 42. Set microsecondsMV to floor(microsecondsMV) × factor.
  242. auto factored_microseconds_value = floor(microseconds_value.to_double()) * factor;
  243. // 43. Set nanosecondsMV to floor(nanosecondsMV) × factor.
  244. auto factored_nanoseconds_value = floor(nanoseconds_value.to_double()) * factor;
  245. // 44. Return ? CreateTemporalDuration(yearsMV, monthsMV, weeksMV, daysMV, hoursMV, minutesMV, secondsMV, millisecondsMV, microsecondsMV, nanosecondsMV).
  246. return TRY(create_temporal_duration(vm, years_value, months_value, weeks_value, days_value, hours_value, factored_minutes_value, factored_seconds_value, factored_milliseconds_value, factored_microseconds_value, factored_nanoseconds_value));
  247. }
  248. // 14.4.1.1 GetOptionsObject ( options ), https://tc39.es/proposal-temporal/#sec-getoptionsobject
  249. ThrowCompletionOr<GC::Ref<Object>> get_options_object(VM& vm, Value options)
  250. {
  251. auto& realm = *vm.current_realm();
  252. // 1. If options is undefined, then
  253. if (options.is_undefined()) {
  254. // a. Return OrdinaryObjectCreate(null).
  255. return Object::create(realm, nullptr);
  256. }
  257. // 2. If options is an Object, then
  258. if (options.is_object()) {
  259. // a. Return options.
  260. return options.as_object();
  261. }
  262. // 3. Throw a TypeError exception.
  263. return vm.throw_completion<TypeError>(ErrorType::NotAnObject, "Options");
  264. }
  265. // 14.4.1.2 GetOption ( options, property, type, values, default ), https://tc39.es/proposal-temporal/#sec-getoption
  266. ThrowCompletionOr<Value> get_option(VM& vm, Object const& options, PropertyKey const& property, OptionType type, ReadonlySpan<StringView> values, OptionDefault const& default_)
  267. {
  268. VERIFY(property.is_string());
  269. // 1. Let value be ? Get(options, property).
  270. auto value = TRY(options.get(property));
  271. // 2. If value is undefined, then
  272. if (value.is_undefined()) {
  273. // a. If default is REQUIRED, throw a RangeError exception.
  274. if (default_.has<DefaultRequired>())
  275. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, "undefined"sv, property.as_string());
  276. // b. Return default.
  277. return default_.visit(
  278. [](DefaultRequired) -> Value { VERIFY_NOT_REACHED(); },
  279. [](Empty) -> Value { return js_undefined(); },
  280. [](bool default_) -> Value { return Value { default_ }; },
  281. [](double default_) -> Value { return Value { default_ }; },
  282. [&](StringView default_) -> Value { return PrimitiveString::create(vm, default_); });
  283. }
  284. // 3. If type is BOOLEAN, then
  285. if (type == OptionType::Boolean) {
  286. // a. Set value to ToBoolean(value).
  287. value = Value { value.to_boolean() };
  288. }
  289. // 4. Else,
  290. else {
  291. // a. Assert: type is STRING.
  292. VERIFY(type == OptionType::String);
  293. // b. Set value to ? ToString(value).
  294. value = TRY(value.to_primitive_string(vm));
  295. }
  296. // 5. If values is not EMPTY and values does not contain value, throw a RangeError exception.
  297. if (!values.is_empty()) {
  298. // NOTE: Every location in the spec that invokes GetOption with type=boolean also has values=undefined.
  299. VERIFY(value.is_string());
  300. if (auto value_string = value.as_string().utf8_string(); !values.contains_slow(value_string))
  301. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, value_string, property.as_string());
  302. }
  303. // 6. Return value.
  304. return value;
  305. }
  306. }