Page MenuHomePhorge

intl.c
No OneTemporary

Size
15 KB
Referenced Files
None
Subscribers
None
#include <compat.h> // IWYU pragma: keep
#include <stdbool.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "ant.h"
#include "errors.h"
#include "runtime.h"
#include "internal.h"
#include "descriptors.h"
#include "modules/intl.h"
#include "modules/symbol.h"
static ant_value_t g_intl_collator_proto = 0;
static ant_value_t g_intl_numberformat_proto = 0;
static ant_value_t g_intl_datetimeformat_proto = 0;
static ant_value_t g_intl_segmenter_proto = 0;
typedef struct {
int hour12;
int minute;
int second;
const char *day_period;
} intl_dtf_fields_t;
static ant_value_t intl_create_instance(ant_t *js, ant_value_t fallback_proto) {
ant_value_t obj = js_mkobj(js);
ant_value_t proto = js_instance_proto_from_new_target(js, fallback_proto);
if (is_object_type(proto)) js_set_proto_init(obj, proto);
return obj;
}
// TODO: docs/exec-plans/tech-debt.md
static inline bool intl_ascii_is_alpha(char c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}
static inline bool intl_ascii_is_digit(char c) {
return c >= '0' && c <= '9';
}
static inline bool intl_ascii_is_alnum(char c) {
return intl_ascii_is_alpha(c) || intl_ascii_is_digit(c);
}
static inline char intl_ascii_lower(char c) {
return (c >= 'A' && c <= 'Z') ? (char)(c + ('a' - 'A')) : c;
}
static bool intl_ascii_all(const char *s, size_t len, bool (*pred)(char)) {
if (!s || len == 0) return false;
for (size_t i = 0; i < len; i++) if (!pred(s[i])) return false;
return true;
}
static bool intl_is_valid_language_tag(const char *tag, size_t len) {
if (!tag || len == 0) return false;
size_t start = 0;
size_t end = 0;
while (end < len && tag[end] != '-') end++;
size_t first_len = end - start;
if (first_len < 2 || first_len > 8) return false;
if (!intl_ascii_all(tag, first_len, intl_ascii_is_alpha)) return false;
bool need_extension_subtag = false;
bool in_private_use = false;
bool saw_private_use_subtag = false;
while (end < len) {
start = end + 1;
if (start >= len) return false;
end = start;
while (end < len && tag[end] != '-') end++;
size_t subtag_len = end - start;
if (subtag_len == 0 || subtag_len > 8) return false;
const char *subtag = tag + start;
if (!intl_ascii_all(subtag, subtag_len, intl_ascii_is_alnum)) return false;
if (in_private_use) {
saw_private_use_subtag = true;
continue;
}
if (need_extension_subtag) {
if (subtag_len < 2) return false;
need_extension_subtag = false;
continue;
}
if (subtag_len == 1) {
char singleton = intl_ascii_lower(subtag[0]);
if (singleton == 'x') in_private_use = true;
else need_extension_subtag = true;
}
}
if (need_extension_subtag) return false;
if (in_private_use && !saw_private_use_subtag) return false;
return true;
}
static ant_value_t intl_resolve_locale(ant_t *js, ant_value_t input) {
if (vtype(input) == T_ARR) input = js_get(js, input, "0");
if (vtype(input) == T_UNDEF) return js_mkstr(js, "en-US", 5);
ant_value_t locale = js_tostring_val(js, input);
if (is_err(locale)) return locale;
size_t len = 0;
const char *tag = js_getstr(js, locale, &len);
if (!intl_is_valid_language_tag(tag, len))
return js_mkerr_typed(js, JS_ERR_RANGE, "Invalid language tag");
return locale;
}
static ant_value_t intl_get_option_string(ant_t *js, ant_value_t options, const char *key, const char *fallback) {
if (vtype(options) != T_OBJ) return js_mkstr(js, fallback, strlen(fallback));
ant_value_t value = js_get(js, options, key);
if (vtype(value) == T_UNDEF) return js_mkstr(js, fallback, strlen(fallback));
ant_value_t str = js_tostring_val(js, value);
if (is_err(str)) return str;
size_t len = 0;
const char *ptr = js_getstr(js, str, &len);
if (!ptr || len == 0) return js_mkstr(js, fallback, strlen(fallback));
return str;
}
static ant_value_t intl_collator_compare(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t left = js_tostring_val(js, nargs > 0 ? args[0] : js_mkstr(js, "", 0));
if (is_err(left)) return left;
ant_value_t right = js_tostring_val(js, nargs > 1 ? args[1] : js_mkstr(js, "", 0));
if (is_err(right)) return right;
const char *left_str = js_getstr(js, left, NULL);
const char *right_str = js_getstr(js, right, NULL);
int result = strcoll(left_str ? left_str : "", right_str ? right_str : "");
if (result < 0) return js_mknum(-1);
if (result > 0) return js_mknum(1);
return js_mknum(0);
}
static ant_value_t intl_collator_resolved_options(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t obj = js_mkobj(js);
ant_value_t this_obj = js_getthis(js);
ant_value_t locale = is_object_type(this_obj)
? js_get(js, this_obj, "locale")
: js_mkundef();
if (vtype(locale) != T_STR) locale = js_mkstr(js, "en-US", 5);
js_set(js, obj, "locale", locale);
return obj;
}
static ant_value_t intl_numberformat_format(ant_t *js, ant_value_t *args, int nargs) {
double number = nargs > 0 ? js_to_number(js, args[0]) : 0.0;
ant_value_t raw_val = js_tostring_val(js, js_mknum(number));
if (is_err(raw_val)) return raw_val;
size_t raw_len = 0;
const char *raw = js_getstr(js, raw_val, &raw_len);
if (!raw || raw_len == 0) return js_mkstr(js, "0", 1);
if (
!isfinite(number) ||
memchr(raw, 'e', raw_len) ||
memchr(raw, 'E', raw_len)
) return raw_val;
const char *dot = memchr(raw, '.', raw_len);
size_t int_len = dot ? (size_t)(dot - raw) : raw_len;
size_t start = raw[0] == '-' ? 1 : 0;
size_t frac_len = dot ? (raw_len - int_len) : 0;
char buf[128];
size_t pos = 0;
if (start) buf[pos++] = '-';
for (size_t i = start; i < int_len; i++) {
buf[pos++] = raw[i];
size_t remaining = int_len - 1 - i;
if (remaining > 0 && remaining % 3 == 0) buf[pos++] = ',';
}
if (dot && frac_len > 0) {
memcpy(buf + pos, dot, frac_len);
pos += frac_len;
}
buf[pos] = '\0';
return js_mkstr(js, buf, pos);
}
static ant_value_t intl_numberformat_resolved_options(ant_t *js, ant_value_t *args, int nargs) {
return intl_collator_resolved_options(js, args, nargs);
}
static void intl_dtf_extract_fields(ant_t *js, ant_value_t *args, int nargs, intl_dtf_fields_t *out) {
time_t t = time(NULL);
if (nargs >= 1) t = (time_t)(js_to_number(js, args[0]) / 1000.0);
struct tm local;
#ifdef _WIN32
localtime_s(&local, &t);
#else
localtime_r(&t, &local);
#endif
out->hour12 = local.tm_hour % 12;
if (out->hour12 == 0) out->hour12 = 12;
out->minute = local.tm_min;
out->second = local.tm_sec;
out->day_period = local.tm_hour < 12 ? "AM" : "PM";
}
static ant_value_t intl_dtf_format(ant_t *js, ant_value_t *args, int nargs) {
intl_dtf_fields_t fields;
intl_dtf_extract_fields(js, args, nargs, &fields);
char buf[64];
snprintf(
buf, sizeof(buf), "%d:%02d:%02d %s",
fields.hour12, fields.minute, fields.second, fields.day_period
);
return js_mkstr(js, buf, strlen(buf));
}
static ant_value_t intl_dtf_resolved_options(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t obj = js_mkobj(js);
ant_value_t this_obj = js_getthis(js);
ant_value_t locale = is_object_type(this_obj) ? js_get(js, this_obj, "locale") : js_mkundef();
ant_value_t time_zone = is_object_type(this_obj) ? js_get(js, this_obj, "timeZone") : js_mkundef();
if (vtype(locale) != T_STR) locale = js_mkstr(js, "en-US", 5);
if (vtype(time_zone) != T_STR) time_zone = js_mkstr(js, "UTC", 3);
js_set(js, obj, "locale", locale);
js_set(js, obj, "timeZone", time_zone);
return obj;
}
static ant_value_t intl_dtf_make_part(ant_t *js, const char *type, const char *value) {
ant_value_t obj = js_mkobj(js);
js_set(js, obj, "type", js_mkstr(js, type, strlen(type)));
js_set(js, obj, "value", js_mkstr(js, value, strlen(value)));
return obj;
}
static ant_value_t intl_dtf_format_to_parts(ant_t *js, ant_value_t *args, int nargs) {
intl_dtf_fields_t fields;
intl_dtf_extract_fields(js, args, nargs, &fields);
char hour[8];
char minute[8];
char second[8];
snprintf(hour, sizeof(hour), "%d", fields.hour12);
snprintf(minute, sizeof(minute), "%02d", fields.minute);
snprintf(second, sizeof(second), "%02d", fields.second);
ant_value_t parts = js_mkarr(js);
js_arr_push(js, parts, intl_dtf_make_part(js, "hour", hour));
js_arr_push(js, parts, intl_dtf_make_part(js, "literal", ":"));
js_arr_push(js, parts, intl_dtf_make_part(js, "minute", minute));
js_arr_push(js, parts, intl_dtf_make_part(js, "literal", ":"));
js_arr_push(js, parts, intl_dtf_make_part(js, "second", second));
js_arr_push(js, parts, intl_dtf_make_part(js, "literal", " "));
js_arr_push(js, parts, intl_dtf_make_part(js, "dayPeriod", fields.day_period));
return parts;
}
static size_t intl_utf8_segment_len(const char *input, size_t remaining) {
if (remaining == 0) return 0;
const unsigned char *s = (const unsigned char *)input;
unsigned char c = s[0];
size_t len = 1;
if ((c & 0x80) == 0) return 1;
if ((c & 0xe0) == 0xc0) len = 2;
else if ((c & 0xf0) == 0xe0) len = 3;
else if ((c & 0xf8) == 0xf0) len = 4;
if (len > remaining) return 1;
for (size_t i = 1; i < len; i++) if ((s[i] & 0xc0) != 0x80) return 1;
return len;
}
static bool intl_ascii_is_word_byte(const char *segment, size_t len) {
if (len != 1) return true;
unsigned char c = (unsigned char)segment[0];
return
(c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
c == '_';
}
static const char *intl_segmenter_granularity(ant_t *js, ant_value_t segmenter, size_t *len) {
ant_value_t granularity = js_get(js, segmenter, "granularity");
if (vtype(granularity) != T_STR) {
if (len) *len = 8;
return "grapheme";
}
return js_getstr(js, granularity, len);
}
static ant_value_t intl_segmenter_segment(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t input = nargs > 0 ? js_tostring_val(js, args[0]) : js_mkstr(js, "", 0);
if (is_err(input)) return input;
size_t input_len = 0;
char *input_str = js_getstr(js, input, &input_len);
ant_value_t segments = js_mkarr(js);
ant_value_t this_obj = js_getthis(js);
size_t granularity_len = 0;
const char *granularity = intl_segmenter_granularity(js, this_obj, &granularity_len);
bool word_granularity = granularity_len == 4 && memcmp(granularity, "word", 4) == 0;
for (size_t offset = 0; offset < input_len;) {
size_t segment_len = intl_utf8_segment_len(input_str + offset, input_len - offset);
ant_value_t record = js_mkobj(js);
js_set(js, record, "segment", js_mkstr(js, input_str + offset, segment_len));
js_set(js, record, "index", js_mknum((double)offset));
js_set(js, record, "input", input);
if (word_granularity) js_set(
js, record, "isWordLike",
js_bool(intl_ascii_is_word_byte(input_str + offset, segment_len))
);
js_arr_push(js, segments, record);
offset += segment_len;
}
return segments;
}
static ant_value_t intl_segmenter_resolved_options(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t obj = js_mkobj(js);
ant_value_t this_obj = js_getthis(js);
size_t granularity_len = 0;
const char *granularity = intl_segmenter_granularity(js, this_obj, &granularity_len);
ant_value_t locale = is_object_type(this_obj) ? js_get(js, this_obj, "locale") : js_mkundef();
if (vtype(locale) != T_STR) locale = js_mkstr(js, "en-US", 5);
js_set(js, obj, "locale", locale);
js_set(js, obj, "granularity", js_mkstr(js, granularity, granularity_len));
return obj;
}
static ant_value_t intl_collator_constructor(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t locale = intl_resolve_locale(js, nargs > 0 ? args[0] : js_mkundef());
if (is_err(locale)) return locale;
ant_value_t obj = intl_create_instance(js, g_intl_collator_proto);
js_set(js, obj, "locale", locale);
return obj;
}
static ant_value_t intl_numberformat_constructor(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t locale = intl_resolve_locale(js, nargs > 0 ? args[0] : js_mkundef());
if (is_err(locale)) return locale;
ant_value_t obj = intl_create_instance(js, g_intl_numberformat_proto);
js_set(js, obj, "locale", locale);
return obj;
}
static ant_value_t intl_dtf_constructor(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t locale = intl_resolve_locale(js, nargs > 0 ? args[0] : js_mkundef());
if (is_err(locale)) return locale;
ant_value_t time_zone = intl_get_option_string(
js, nargs > 1 ? args[1] : js_mkundef(),
"timeZone", "UTC"
);
if (is_err(time_zone)) return time_zone;
ant_value_t obj = intl_create_instance(js, g_intl_datetimeformat_proto);
js_set(js, obj, "locale", locale);
js_set(js, obj, "timeZone", time_zone);
return obj;
}
static ant_value_t intl_segmenter_constructor(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t locale = intl_resolve_locale(js, nargs > 0 ? args[0] : js_mkundef());
if (is_err(locale)) return locale;
ant_value_t granularity = intl_get_option_string(
js, nargs > 1 ? args[1] : js_mkundef(),
"granularity", "grapheme"
);
if (is_err(granularity)) return granularity;
ant_value_t obj = intl_create_instance(js, g_intl_segmenter_proto);
js_set(js, obj, "locale", locale);
js_set(js, obj, "granularity", granularity);
return obj;
}
void init_intl_module(void) {
ant_t *js = rt->js;
ant_value_t global = js_glob(js);
ant_value_t intl = js_mkobj(js);
ant_value_t object_proto = js->sym.object_proto;
if (is_object_type(object_proto)) js_set_proto_init(intl, object_proto);
js_set_sym(js, intl, get_toStringTag_sym(), js_mkstr(js, "Intl", 4));
g_intl_collator_proto = js_mkobj(js);
js_set(js, g_intl_collator_proto, "compare", js_mkfun(intl_collator_compare));
js_set(js, g_intl_collator_proto, "resolvedOptions", js_mkfun(intl_collator_resolved_options));
js_set_sym(js, g_intl_collator_proto, get_toStringTag_sym(), js_mkstr(js, "Intl.Collator", 13));
ant_value_t collator_ctor = js_make_ctor(js, intl_collator_constructor, g_intl_collator_proto, "Collator", 8);
js_set(js, intl, "Collator", collator_ctor);
g_intl_numberformat_proto = js_mkobj(js);
js_set(js, g_intl_numberformat_proto, "format", js_mkfun(intl_numberformat_format));
js_set(js, g_intl_numberformat_proto, "resolvedOptions", js_mkfun(intl_numberformat_resolved_options));
js_set_sym(js, g_intl_numberformat_proto, get_toStringTag_sym(), js_mkstr(js, "Intl.NumberFormat", 17));
ant_value_t numberformat_ctor = js_make_ctor(js, intl_numberformat_constructor, g_intl_numberformat_proto, "NumberFormat", 12);
js_set(js, intl, "NumberFormat", numberformat_ctor);
g_intl_datetimeformat_proto = js_mkobj(js);
js_set(js, g_intl_datetimeformat_proto, "format", js_mkfun(intl_dtf_format));
js_set(js, g_intl_datetimeformat_proto, "resolvedOptions", js_mkfun(intl_dtf_resolved_options));
js_set(js, g_intl_datetimeformat_proto, "formatToParts", js_mkfun(intl_dtf_format_to_parts));
js_set_sym(js, g_intl_datetimeformat_proto, get_toStringTag_sym(), js_mkstr(js, "Intl.DateTimeFormat", 19));
ant_value_t dtf_ctor = js_make_ctor(js, intl_dtf_constructor, g_intl_datetimeformat_proto, "DateTimeFormat", 14);
js_set(js, intl, "DateTimeFormat", dtf_ctor);
g_intl_segmenter_proto = js_mkobj(js);
js_set(js, g_intl_segmenter_proto, "segment", js_mkfun(intl_segmenter_segment));
js_set(js, g_intl_segmenter_proto, "resolvedOptions", js_mkfun(intl_segmenter_resolved_options));
js_set_sym(js, g_intl_segmenter_proto, get_toStringTag_sym(), js_mkstr(js, "Intl.Segmenter", 14));
ant_value_t segmenter_ctor = js_make_ctor(js, intl_segmenter_constructor, g_intl_segmenter_proto, "Segmenter", 9);
js_set(js, intl, "Segmenter", segmenter_ctor);
js_set(js, global, "Intl", intl);
js_set_descriptor(js, global, "Intl", 4, JS_DESC_W | JS_DESC_C);
}

File Metadata

Mime Type
text/x-c
Expires
Sat, May 2, 2:12 AM (1 d, 19 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
541692
Default Alt Text
intl.c (15 KB)

Event Timeline