summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/intl/LanguageTag.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin/intl/LanguageTag.cpp')
-rw-r--r--js/src/builtin/intl/LanguageTag.cpp193
1 files changed, 193 insertions, 0 deletions
diff --git a/js/src/builtin/intl/LanguageTag.cpp b/js/src/builtin/intl/LanguageTag.cpp
new file mode 100644
index 0000000000..3372f5d99a
--- /dev/null
+++ b/js/src/builtin/intl/LanguageTag.cpp
@@ -0,0 +1,193 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "builtin/intl/LanguageTag.h"
+
+#include "mozilla/intl/Locale.h"
+#include "mozilla/Span.h"
+
+#include "builtin/intl/StringAsciiChars.h"
+#include "gc/Tracer.h"
+#include "vm/JSContext.h"
+
+namespace js {
+namespace intl {
+
+[[nodiscard]] bool ParseLocale(JSContext* cx, Handle<JSLinearString*> str,
+ mozilla::intl::Locale& result) {
+ if (StringIsAscii(str)) {
+ intl::StringAsciiChars chars(str);
+ if (!chars.init(cx)) {
+ return false;
+ }
+
+ if (mozilla::intl::LocaleParser::TryParse(chars, result).isOk()) {
+ return true;
+ }
+ }
+
+ if (UniqueChars localeChars = QuoteString(cx, str, '"')) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_INVALID_LANGUAGE_TAG, localeChars.get());
+ }
+ return false;
+}
+
+bool ParseStandaloneLanguageTag(Handle<JSLinearString*> str,
+ mozilla::intl::LanguageSubtag& result) {
+ // Tell the analysis the |IsStructurallyValidLanguageTag| function can't GC.
+ JS::AutoSuppressGCAnalysis nogc;
+
+ if (str->hasLatin1Chars()) {
+ if (!mozilla::intl::IsStructurallyValidLanguageTag<Latin1Char>(
+ str->latin1Range(nogc))) {
+ return false;
+ }
+ result.Set<Latin1Char>(str->latin1Range(nogc));
+ } else {
+ if (!mozilla::intl::IsStructurallyValidLanguageTag<char16_t>(
+ str->twoByteRange(nogc))) {
+ return false;
+ }
+ result.Set<char16_t>(str->twoByteRange(nogc));
+ }
+ return true;
+}
+
+bool ParseStandaloneScriptTag(Handle<JSLinearString*> str,
+ mozilla::intl::ScriptSubtag& result) {
+ // Tell the analysis the |IsStructurallyValidScriptTag| function can't GC.
+ JS::AutoSuppressGCAnalysis nogc;
+
+ if (str->hasLatin1Chars()) {
+ if (!mozilla::intl::IsStructurallyValidScriptTag<Latin1Char>(
+ str->latin1Range(nogc))) {
+ return false;
+ }
+ result.Set<Latin1Char>(str->latin1Range(nogc));
+ } else {
+ if (!mozilla::intl::IsStructurallyValidScriptTag<char16_t>(
+ str->twoByteRange(nogc))) {
+ return false;
+ }
+ result.Set<char16_t>(str->twoByteRange(nogc));
+ }
+ return true;
+}
+
+bool ParseStandaloneRegionTag(Handle<JSLinearString*> str,
+ mozilla::intl::RegionSubtag& result) {
+ // Tell the analysis the |IsStructurallyValidRegionTag| function can't GC.
+ JS::AutoSuppressGCAnalysis nogc;
+
+ if (str->hasLatin1Chars()) {
+ if (!mozilla::intl::IsStructurallyValidRegionTag<Latin1Char>(
+ str->latin1Range(nogc))) {
+ return false;
+ }
+ result.Set<Latin1Char>(str->latin1Range(nogc));
+ } else {
+ if (!mozilla::intl::IsStructurallyValidRegionTag<char16_t>(
+ str->twoByteRange(nogc))) {
+ return false;
+ }
+ result.Set<char16_t>(str->twoByteRange(nogc));
+ }
+ return true;
+}
+
+template <typename CharT>
+static bool IsAsciiLowercaseAlpha(mozilla::Span<const CharT> span) {
+ // Tell the analysis the |std::all_of| function can't GC.
+ JS::AutoSuppressGCAnalysis nogc;
+
+ const CharT* ptr = span.data();
+ size_t length = span.size();
+ return std::all_of(ptr, ptr + length, mozilla::IsAsciiLowercaseAlpha<CharT>);
+}
+
+static bool IsAsciiLowercaseAlpha(JSLinearString* str) {
+ JS::AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars()) {
+ return IsAsciiLowercaseAlpha<Latin1Char>(str->latin1Range(nogc));
+ }
+ return IsAsciiLowercaseAlpha<char16_t>(str->twoByteRange(nogc));
+}
+
+template <typename CharT>
+static bool IsAsciiAlpha(mozilla::Span<const CharT> span) {
+ // Tell the analysis the |std::all_of| function can't GC.
+ JS::AutoSuppressGCAnalysis nogc;
+
+ const CharT* ptr = span.data();
+ size_t length = span.size();
+ return std::all_of(ptr, ptr + length, mozilla::IsAsciiAlpha<CharT>);
+}
+
+static bool IsAsciiAlpha(JSLinearString* str) {
+ JS::AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars()) {
+ return IsAsciiAlpha<Latin1Char>(str->latin1Range(nogc));
+ }
+ return IsAsciiAlpha<char16_t>(str->twoByteRange(nogc));
+}
+
+JS::Result<JSString*> ParseStandaloneISO639LanguageTag(
+ JSContext* cx, Handle<JSLinearString*> str) {
+ // ISO-639 language codes contain either two or three characters.
+ size_t length = str->length();
+ if (length != 2 && length != 3) {
+ return nullptr;
+ }
+
+ // We can directly the return the input below if it's in the correct case.
+ bool isLowerCase = IsAsciiLowercaseAlpha(str);
+ if (!isLowerCase) {
+ // Must be an ASCII alpha string.
+ if (!IsAsciiAlpha(str)) {
+ return nullptr;
+ }
+ }
+
+ mozilla::intl::LanguageSubtag languageTag;
+ if (str->hasLatin1Chars()) {
+ JS::AutoCheckCannotGC nogc;
+ languageTag.Set<Latin1Char>(str->latin1Range(nogc));
+ } else {
+ JS::AutoCheckCannotGC nogc;
+ languageTag.Set<char16_t>(str->twoByteRange(nogc));
+ }
+
+ if (!isLowerCase) {
+ // The language subtag is canonicalized to lower case.
+ languageTag.ToLowerCase();
+ }
+
+ // Reject the input if the canonical tag contains more than just a single
+ // language subtag.
+ if (mozilla::intl::Locale::ComplexLanguageMapping(languageTag)) {
+ return nullptr;
+ }
+
+ // Take care to replace deprecated subtags with their preferred values.
+ JSString* result;
+ if (mozilla::intl::Locale::LanguageMapping(languageTag) || !isLowerCase) {
+ result = NewStringCopy<CanGC>(cx, languageTag.Span());
+ } else {
+ result = str;
+ }
+ if (!result) {
+ return cx->alreadyReportedOOM();
+ }
+ return result;
+}
+
+void js::intl::UnicodeExtensionKeyword::trace(JSTracer* trc) {
+ TraceRoot(trc, &type_, "UnicodeExtensionKeyword::type");
+}
+
+} // namespace intl
+} // namespace js