Compare commits

..

No commits in common. "master" and "improve-date-parsing" have entirely different histories.

46 changed files with 5674 additions and 7925 deletions

View file

@ -2,13 +2,6 @@
## Master branch ## Master branch
## v0.4.2 - 2024-03-23
* Update to QuickJS `2024-01-13`
* Update dependencies
## v0.4.1 - 2023-02-05
* Fixed use after free in `set_global` [#105](https://github.com/theduke/quickjs-rs/issues/105) * Fixed use after free in `set_global` [#105](https://github.com/theduke/quickjs-rs/issues/105)
* `add_callback` can now take `JsValue` arguments [#109](https://github.com/theduke/quickjs-rs/issues/109) * `add_callback` can now take `JsValue` arguments [#109](https://github.com/theduke/quickjs-rs/issues/109)
* Enable chrono feature by default * Enable chrono feature by default

View file

@ -1,39 +1,36 @@
[package] [package]
edition = "2021" edition = "2018"
name = "quick-js-dtp" name = "quick-js"
description = "QuickJS Javascript engine wrapper (with improved date parser)" description = "QuickJS Javascript engine wrapper"
version = "0.4.2" version = "0.4.2-alpha.0"
readme = "README.md" readme = "README.md"
repository = "https://github.com/Theta-Dev/quickjs-rs" documentation = "https://docs.rs/quick-js"
repository = "https://github.com/theduke/quickjs-rs"
license = "MIT" license = "MIT"
authors = [ authors = ["Christoph Herzog <chris@theduke.at>"]
"Christoph Herzog <chris@theduke.at>",
"ThetaDev <t.testboy@gmail.com>",
]
keywords = ["quickjs", "javascript", "js", "engine", "interpreter"] keywords = ["quickjs", "javascript", "js", "engine", "interpreter"]
[lib]
name = "quick_js"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["chrono", "bigint", "log"] features = [ "chrono", "bigint", "log" ]
[features] [features]
default = ["chrono"] default = ["chrono"]
patch-bigint = ["libquickjs-dtp-sys/patch-bigint"] patched = ["libquickjs-sys/patched"]
patch-dateparser = ["libquickjs-dtp-sys/patch-dateparser"] bigint = ["num-bigint", "num-traits", "libquickjs-sys/patched"]
bigint = ["num-bigint", "num-traits", "libquickjs-dtp-sys/patch-bigint"]
[dependencies] [dependencies]
libquickjs-dtp-sys = { version = "0.10.0", path = "./libquickjs-sys" } libquickjs-sys = { version = ">= 0.9.0, < 0.10.0", path = "./libquickjs-sys" }
chrono = { version = "0.4.7", optional = true } chrono = { version = "0.4.7", optional = true }
num-bigint = { version = "0.4.0", optional = true } num-bigint = { version = "0.2.2", optional = true }
num-traits = { version = "0.2.0", optional = true } num-traits = { version = "0.2.0", optional = true }
log = { version = "0.4.8", optional = true } log = { version = "0.4.8", optional = true }
once_cell = "1.2.0" once_cell = "1.2.0"
[dev-dependencies] [dev-dependencies]
rstest = { version = "0.18.0", default-features = false } rstest = "0.15.0"
[workspace] [workspace]
members = ["libquickjs-sys"] members = [
"libquickjs-sys",
]

View file

@ -1,8 +1,8 @@
# quick-js-dtp # quickjs-rs
[![Crates.io](https://img.shields.io/crates/v/quick-js-dtp.svg?maxAge=3600)](https://crates.io/crates/quick-js-dtp) [![Crates.io](https://img.shields.io/crates/v/quick-js.svg?maxAge=3600)](https://crates.io/crates/quick-js)
[![docs.rs](https://docs.rs/quick-js-dtp/badge.svg)](https://docs.rs/quick-js-dtp) [![docs.rs](https://docs.rs/quick-js/badge.svg)](https://docs.rs/quick-js)
[![CI](https://github.com/Theta-Dev/quickjs-rs/actions/workflows/main.yml/badge.svg)](https://github.com/Theta-Dev/quickjs-rs/actions/workflows/main.yml) [![Build Status](https://github.com/theduke/quickjs-rs/workflows/CI/badge.svg)
A Rust wrapper for [QuickJS](https://bellard.org/quickjs/). A Rust wrapper for [QuickJS](https://bellard.org/quickjs/).
@ -11,15 +11,11 @@ It is fast and supports the full ES2020 specification.
This crate allows you to easily run and integrate with Javascript code from Rust. This crate allows you to easily run and integrate with Javascript code from Rust.
This is a fork of the original [quick-js](https://crates.io/crates/quick-js)
crate which includes a fully featured date parser, capable of parsing dates like
`Sat, 01-Jan-2000 00:00:00 PST`.
## Quickstart ## Quickstart
```toml ```toml
[dependencies] [dependencies]
quick-js-dtp = "0.4.1" quick-js = "0.4.1"
``` ```
```rust ```rust
@ -55,13 +51,10 @@ The crate supports the following features:
* `log`: allows forwarding `console.log` messages to the `log` crate. * `log`: allows forwarding `console.log` messages to the `log` crate.
Note: must be enabled with `ContextBuilder::console(quick_js::console::LogConsole);` Note: must be enabled with `ContextBuilder::console(quick_js::console::LogConsole);`
* `patch-bigint` * `patched`
Enabled automatically for some other features, like `bigint`. Enabled automatically for some other features, like `bigint`.
You should not need to enable this manually. You should not need to enable this manually.
Applies QuickJS patches that can be found in `libquickjs-sys/embed/patches` directory.
* `patch-dateparser`
Enables the improved JS date parser that supports additional date formats like
`Sat, 01-Jan-2000 00:00:00 PST`
## Installation ## Installation

View file

@ -4,7 +4,7 @@ pub fn main() {
let context = Context::new().unwrap(); let context = Context::new().unwrap();
let value = context.eval("1 + 2").unwrap(); let value = context.eval("1 + 2").unwrap();
println!("js: 1 + 2 = {value:?}"); println!("js: 1 + 2 = {:?}", value);
context context
.add_callback("myCallback", |a: i32, b: i32| a + b * b) .add_callback("myCallback", |a: i32, b: i32| a + b * b)
@ -18,5 +18,5 @@ pub fn main() {
"#, "#,
) )
.unwrap(); .unwrap();
println!("js: callback = {value:?}"); println!("js: callback = {:?}", value);
} }

View file

@ -1,6 +1,6 @@
embed_dir := "./libquickjs-sys/embed/quickjs" embed_dir := "./libquickjs-sys/embed/quickjs"
DOWNLOAD_URL := "https://bellard.org/quickjs/quickjs-2024-01-13.tar.xz" DOWNLOAD_URL := "https://bellard.org/quickjs/quickjs-2021-03-27.tar.xz"
FEATURES := "--all-features" FEATURES := "--all-features"
download-new: download-new:
@ -55,4 +55,5 @@ valgrind:
echo "Checking for memory leaks..." echo "Checking for memory leaks..."
cargo clean cargo clean
cargo build --tests --all-features cargo build --tests --all-features
find target/debug/deps -maxdepth 1 -type f -executable -not -name "*.so" | xargs valgrind --leak-check=full --error-exitcode=1 --gen-suppressions=yes --show-error-list=yes find target/debug/deps -maxdepth 1 -type f -executable | xargs valgrind --leak-check=full --error-exitcode=1 --gen-suppressions=yes --show-error-list=yes

View file

@ -1,29 +1,26 @@
[package] [package]
edition = "2021" edition = "2018"
name = "libquickjs-dtp-sys" name = "libquickjs-sys"
description = "QuickJS Javascript Engine FFI bindings (with improved date parser)" description = "QuickJS Javascript Engine FFI bindings"
version = "0.10.0" version = "0.9.0"
readme = "README.md" readme = "README.md"
repository = "https://github.com/Theta-Dev/quickjs-rs" documentation = "https://docs.rs/quickjs-sys"
repository = "https://github.com/theduke/quickjs-rs"
license = "MIT" license = "MIT"
authors = ["Christoph Herzog <chris@theduke.at>", "ThetaDev <t.testboy@gmail.com>"] authors = ["Christoph Herzog <chris@theduke.at>"]
categories = ["external-ffi-bindings"] categories = ["external-ffi-bindings"]
keywords = ["quickjs"] keywords = ["quickjs"]
build = "build.rs" build = "build.rs"
[lib]
name = "libquickjs_sys"
[features] [features]
bundled = ["cc", "copy_dir"] bundled = ["cc", "copy_dir"]
patch-bigint = ["bundled"] patched = ["bundled"]
patch-dateparser = ["bundled"]
default = ["bundled"] default = ["bundled"]
system = ["bindgen"] system = ["bindgen"]
[build-dependencies] [build-dependencies]
bindgen = { version = "0.69.0", optional = true } bindgen = { version = "0.57.0", optional = true }
cc = { version = "1.0.66", optional = true } cc = { version = "1.0.66", optional = true }
copy_dir = { version = "0.1.2", optional = true } copy_dir = { version = "0.1.2", optional = true }

View file

@ -1,17 +1,13 @@
# libquickjs-dtp-sys # libquickjs-sys
FFI Bindings for [quickjs](https://bellard.org/quickjs/), a Javascript engine. FFI Bindings for [quickjs](https://bellard.org/quickjs/), a Javascript engine.
This is a fork of the original [libquickjs-sys](https://crates.io/crates/libquickjs-sys) See the [quick](https://crates.io/crates/quickjs) crate for a high-level
crate which includes a fully featured date parser, capable of parsing dates like
`Sat, 01-Jan-2000 00:00:00 PST`.
See the [quick-js-dtp](https://crates.io/crates/quick-js-dtp) crate for a high-level
wrapper. wrapper.
*Version 0.10.0* *Version 0.9.0*
**Embedded VERSION: 2024-01-13** **Embedded VERSION: 2021-03-27**
## Embedded vs system ## Embedded vs system

View file

@ -55,11 +55,8 @@ fn main() {
copy_dir::copy_dir(embed_path.join("quickjs"), &code_dir) copy_dir::copy_dir(embed_path.join("quickjs"), &code_dir)
.expect("Could not copy quickjs directory"); .expect("Could not copy quickjs directory");
#[cfg(feature = "patch-bigint")] #[cfg(feature = "patched")]
apply_patch(&code_dir, "js-tobigint64-overflow.patch"); apply_patches(&code_dir);
#[cfg(feature = "patch-dateparser")]
apply_patch(&code_dir, "dateparser.patch");
std::fs::copy( std::fs::copy(
embed_path.join("static-functions.c"), embed_path.join("static-functions.c"),
@ -118,25 +115,27 @@ fn main() {
.expect("Could not copy bindings.rs"); .expect("Could not copy bindings.rs");
} }
#[cfg(any(feature = "patch-bigint", feature = "patch-dateparser"))] #[cfg(feature = "patched")]
fn apply_patch(code_dir: &PathBuf, name: &str) { fn apply_patches(code_dir: &PathBuf) {
eprintln!("Applying {name}"); use std::fs;
let patch_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
.join("embed")
.join("patches")
.join(name);
assert!(patch_path.exists(), "Could not open patch {name}");
let status = std::process::Command::new("patch") eprintln!("Applying patches...");
.current_dir(code_dir) let embed_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("embed");
.arg("-i") let patches_path = embed_path.join("patches");
.arg(&patch_path) for patch in fs::read_dir(patches_path).expect("Could not open patches directory") {
.spawn() let patch = patch.expect("Could not open patch");
.expect("Could not apply patches") eprintln!("Applying {:?}...", patch.file_name());
.wait() let status = std::process::Command::new("patch")
.expect("Could not apply patches"); .current_dir(&code_dir)
assert!( .arg("-i")
status.success(), .arg(patch.path())
"Patch command returned non-zero exit code" .spawn()
); .expect("Could not apply patches")
.wait()
.expect("Could not apply patches");
assert!(
status.success(),
"Patch command returned non-zero exit code"
);
}
} }

File diff suppressed because it is too large Load diff

View file

@ -1,543 +0,0 @@
diff --git a/libquickjs-sys/embed/quickjs/quickjs.c b/libquickjs-sys/embed/quickjs/quickjs.c
index 4e58a98..93cf853 100644
--- a/libquickjs-sys/embed/quickjs/quickjs.c
+++ b/libquickjs-sys/embed/quickjs/quickjs.c
@@ -1459,6 +1459,10 @@ static inline int is_digit(int c) {
return c >= '0' && c <= '9';
}
+static inline int is_space_like(char c) {
+ return c == ' ' || c == ',' || c == ':' || c == '-' || c == '/';
+}
+
typedef struct JSClassShortDef {
JSAtom class_name;
JSClassFinalizer *finalizer;
@@ -49343,6 +49347,23 @@ static int const month_days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
static char const month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
static char const day_names[] = "SunMonTueWedThuFriSat";
+static const struct KnownZone {
+ char tzName[4];
+ int tzOffset;
+} known_zones[] = {
+ { "UTC", 0 },
+ { "UT", 0 },
+ { "GMT", 0 },
+ { "EST", -300 },
+ { "EDT", -240 },
+ { "CST", -360 },
+ { "CDT", -300 },
+ { "MST", -420 },
+ { "MDT", -360 },
+ { "PST", -480 },
+ { "PDT", -420 }
+};
+
static __exception int get_date_fields(JSContext *ctx, JSValueConst obj,
double fields[9], int is_local, int force)
{
@@ -49707,14 +49728,44 @@ static JSValue js_Date_UTC(JSContext *ctx, JSValueConst this_val,
return JS_NewFloat64(ctx, set_date_fields(fields, 0));
}
-static void string_skip_spaces(JSString *sp, int *pp) {
- while (*pp < sp->len && string_get(sp, *pp) == ' ')
+static void string_skip_spaces_and_comments(JSString *sp, int *pp) {
+ int nesting = 0;
+ while (*pp < sp->len) {
+ char ch = string_get(sp, *pp);
+ int nxt = *pp + 1;
+
+ // interpret - before a number as a sign rather than a comment char
+ if (ch == '-' && nesting == 0 && nxt < sp->len && is_digit(string_get(sp, nxt))) {
+ break;
+ }
+ if (!is_space_like(ch)) {
+ if (ch == '(') {
+ nesting++;
+ } else if (ch == ')' && nesting > 0) {
+ nesting--;
+ } else if (nesting == 0) {
+ break;
+ }
+ }
+
*pp += 1;
+ }
}
-static void string_skip_non_spaces(JSString *sp, int *pp) {
- while (*pp < sp->len && string_get(sp, *pp) != ' ')
- *pp += 1;
+static inline BOOL char_eq_ignorecase(char c1, char c2) {
+ if ((c1 >= 'A' && c1 <= 'Z') || (c1 >= 'a' && c1 <= 'z'))
+ return (c1 | 0x20) == (c2 | 0x20);
+ return c1 == c2;
+}
+
+static BOOL string_eq_ignorecase(JSString *s1, int p, const char *s2, int len) {
+ if (s1->len - p < len) return FALSE;
+
+ for (int i=0; i<len; i++) {
+ if (!char_eq_ignorecase(string_get(s1, p + i), s2[i]))
+ return FALSE;
+ }
+ return TRUE;
}
/* parse a numeric field with an optional sign if accept_sign is TRUE */
@@ -49752,11 +49803,7 @@ static int string_get_signed_digits(JSString *sp, int *pp, int64_t *pval) {
p++;
res = string_get_digits(sp, &p, pval);
- if (res == 0 && sgn == '-') {
- if (*pval == 0)
- return -1; // reject negative zero
- *pval = -*pval;
- }
+ if (res == 0 && sgn == '-') *pval = -*pval;
*pp = p;
return res;
}
@@ -49805,6 +49852,34 @@ static int string_get_milliseconds(JSString *sp, int *pp, int64_t *pval) {
return 0;
}
+static int string_get_num_timezone(JSString *sp, int *pp, int64_t *pval)
+{
+ int p = *pp;
+
+ int64_t o;
+ if (string_get_signed_digits(sp, &p, &o))
+ return -1;
+
+ if (o < -9959 || o > 9959) {
+ return -1;
+ }
+
+ int sgn = (o < 0) ? -1 : 1;
+ o = abs((int32_t) o);
+
+ if (string_get(sp, p) != ':') {
+ *pval = ((o / 100) * 60 + (o % 100)) * sgn;
+ } else {
+ p++;
+ int64_t o2;
+ if (string_get_digits(sp, &p, &o2))
+ return -1;
+ *pval = (o * 60 + o2) * sgn;
+ }
+
+ *pp = p;
+ return 0;
+}
static int find_abbrev(JSString *sp, int p, const char *list, int count) {
int n, i;
@@ -49822,27 +49897,30 @@ static int find_abbrev(JSString *sp, int p, const char *list, int count) {
return -1;
}
-static int string_get_month(JSString *sp, int *pp, int64_t *pval) {
- int n;
-
- string_skip_spaces(sp, pp);
- n = find_abbrev(sp, *pp, month_names, 12);
- if (n < 0)
- return -1;
-
- *pval = n;
- *pp += 3;
- return 0;
-}
-
static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
- // parse(s)
+ // This parses a date in the form:
+ // Tuesday, 09-Nov-99 23:12:40 GMT
+ // or
+ // Sat, 01-Jan-2000 08:00:00 GMT
+ // or
+ // Sat, 01 Jan 2000 08:00:00 GMT
+ // or
+ // 01 Jan 99 22:00 +0100 (exceptions in rfc822/rfc2822)
+ // ### non RFC formats, added for Javascript:
+ // [Wednesday] January 09 1999 23:12:40 GMT
+ // [Wednesday] January 09 23:12:40 GMT 1999
+ //
+ // We ignore the weekday.
+ //
+ // Date parser adapted from KJS' implementation
+ // Source: https://invent.kde.org/frameworks/kjs/-/blob/fd9252ec4b270ebb4a299129099bce97f00dac0e/src/kjs/date_object.cpp#L1153
+
JSValue s, rv;
int64_t fields[] = { 0, 1, 1, 0, 0, 0, 0 };
double fields1[7];
- int64_t tz, hh, mm;
+ int64_t tz = 0, hh, mm;
double d;
int p, i, c, sgn, l;
JSString *sp;
@@ -49856,7 +49934,18 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
sp = JS_VALUE_GET_STRING(s);
p = 0;
- if (p < sp->len && (((c = string_get(sp, p)) >= '0' && c <= '9') || c == '+' || c == '-')) {
+ string_skip_spaces_and_comments(sp, &p);
+
+ int end_of_digits = p;
+ if (string_get(sp, end_of_digits) == '+' || string_get(sp, end_of_digits) == '-') {
+ p++;
+ }
+ while (end_of_digits < sp->len && is_digit(string_get(sp, end_of_digits))) {
+ end_of_digits++;
+ }
+
+ if ((end_of_digits - p) > 0 &&
+ (string_get(sp, end_of_digits) == '-' || string_get(sp, end_of_digits) == 'T')) {
/* ISO format */
/* year field can be negative */
if (string_get_signed_digits(sp, &p, &fields[0]))
@@ -49866,23 +49955,28 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
if (p >= sp->len)
break;
switch(i) {
- case 1:
- case 2:
+ case 1: // Year
+ case 2: // Month
c = '-';
break;
- case 3:
+ case 3: // Day
c = 'T';
break;
- case 4:
- case 5:
+ case 4: // Hour
+ case 5: // Minute
c = ':';
break;
- case 6:
+ case 6: // Second
c = '.';
break;
}
- if (string_get(sp, p) != c)
- break;
+ if (string_get(sp, p) != c) {
+ // 2000T08:00Z
+ if (i < 3 && string_get(sp, p) == 'T') {
+ i = 3;
+ }
+ else break;
+ }
p++;
if (i == 6) {
if (string_get_milliseconds(sp, &p, &fields[i]))
@@ -49892,6 +49986,9 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
goto done;
}
}
+ // Hour only is invalid
+ if (i == 4) goto done;
+
/* no time: UTC by default */
is_local = (i > 3);
fields[1] -= 1;
@@ -49929,68 +50026,250 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val,
goto done;
}
} else {
- /* toString or toUTCString format */
- /* skip the day of the week */
- string_skip_non_spaces(sp, &p);
- string_skip_spaces(sp, &p);
- if (p >= sp->len)
+ // Check contents of first words if not number
+ int64_t month = -1;
+ int word_start = p;
+ char c = string_get(sp, p);
+
+ while (!is_digit(c)) {
+ if (c == ' ' || c == '(') {
+ if (p - word_start >= 3) {
+ month = find_abbrev(sp, word_start, month_names, 12);
+ }
+ string_skip_spaces_and_comments(sp, &p);
+ word_start = p;
+ } else {
+ p++;
+ }
+
+ c = string_get(sp, p);
+ }
+
+ // Missing delimiter between month and day (like "January29")?
+ if (month == -1 && word_start != p) {
+ month = find_abbrev(sp, word_start, month_names, 12);
+ }
+
+ string_skip_spaces_and_comments(sp, &p);
+ if (sp->len <= p)
goto done;
- c = string_get(sp, p);
- if (c >= '0' && c <= '9') {
- /* day of month first */
- if (string_get_digits(sp, &p, &fields[2]))
+
+ // '09-Nov-99 23:12:40 GMT'
+ int64_t day = 0;
+ if (string_get_digits(sp, &p, &day))
+ goto done;
+
+ int64_t year = -1;
+ if (day > 31) {
+ if (string_get(sp, p) != '/')
goto done;
- if (string_get_month(sp, &p, &fields[1]))
+ // looks like a YYYY/MM/DD date
+ p++;
+ if (sp->len <= p)
goto done;
- } else {
- /* month first */
- if (string_get_month(sp, &p, &fields[1]))
+
+ year = day;
+ if (string_get_digits(sp, &p, &month))
goto done;
- string_skip_spaces(sp, &p);
- if (string_get_digits(sp, &p, &fields[2]))
+ month--;
+
+ if (string_get(sp, p) != '/')
goto done;
- }
- /* year */
- string_skip_spaces(sp, &p);
- if (string_get_signed_digits(sp, &p, &fields[0]))
- goto done;
+ p++;
+ if (sp->len <= p)
+ goto done;
+
+ if (string_get_digits(sp, &p, &day))
+ goto done;
+ } else if (string_get(sp, p) == '/' && month == -1) {
+ p++;
+ // This looks like a MM/DD/YYYY date, not an RFC date.
+ month = day - 1; // 0-based
+ if (string_get_digits(sp, &p, &day))
+ goto done;
+
+ if (!is_space_like(string_get(sp, p)))
+ goto done;
+ p++;
+ if (sp->len <= p)
+ goto done;
+ } else {
+ if (string_get(sp, p) == '-') {
+ p++;
+ }
+ string_skip_spaces_and_comments(sp, &p);
- /* hour, min, seconds */
- string_skip_spaces(sp, &p);
- for(i = 0; i < 3; i++) {
- if (i == 1 || i == 2) {
- if (p >= sp->len)
+ // Jan,2000,08:00:00 UT
+ if (month == -1) {
+ month = find_abbrev(sp, p, month_names, 12);
+ if (month == -1)
goto done;
- if (string_get(sp, p) != ':')
+
+ while (p < sp->len && !is_space_like(string_get(sp, p))) {
+ p++;
+ }
+ if (sp->len <= p)
goto done;
p++;
}
- if (string_get_digits(sp, &p, &fields[3 + i]))
- goto done;
}
- // XXX: parse optional milliseconds?
- /* parse the time zone offset if present: [+-]HHmm */
- is_local = FALSE;
- tz = 0;
- for (tz = 0; p < sp->len; p++) {
- sgn = string_get(sp, p);
- if (sgn == '+' || sgn == '-') {
+ string_skip_spaces_and_comments(sp, &p);
+
+ if (year < 0) {
+ // Year following, e.g. 01 Jan 2000 08:00:00 UT
+ // Time following, e.g. Jan 01 08:00:00 UT 2000
+ if (sp->len <= p + 2 || string_get(sp, p + 2) != ':') {
+ if (string_get_digits(sp, &p, &year))
+ goto done;
+ }
+ }
+
+ // Don't fail if the time is missing.
+ int64_t hour = 0;
+ int64_t minute = 0;
+ int64_t second = 0;
+
+ if (sp->len > p) {
+ // ' 23:12:40 GMT'
+ if (string_get(sp, p) == ':') {
+ // There was no year; the number was the hour.
+ year = -1;
+ } else if (is_space_like(string_get(sp, p))) {
p++;
- if (string_get_fixed_width_digits(sp, &p, 2, &hh))
+ string_skip_spaces_and_comments(sp, &p);
+ }
+
+ // Read a number? If not, this might be a timezone name.
+ if (!string_get_digits(sp, &p, &hour)) {
+ if (hour < 0 || hour > 23) {
goto done;
- if (string_get_fixed_width_digits(sp, &p, 2, &mm))
+ }
+
+ if (sp->len <= p)
goto done;
- tz = hh * 60 + mm;
- if (sgn == '-')
- tz = -tz;
- break;
+
+ // ':12:40 GMT'
+ if (string_get(sp, p) != ':') {
+ goto done;
+ }
+ p++;
+
+ if (string_get_digits(sp, &p, &minute))
+ goto done;
+
+ // ':40 GMT'
+ // seconds are optional in rfc822 + rfc2822
+ if (string_get(sp, p) == ':') {
+ p++;
+
+ if (string_get_digits(sp, &p, &second))
+ goto done;
+
+ // disallow trailing colon seconds
+ if (string_get(sp, p) == ':') {
+ goto done;
+ }
+ } else if (string_get(sp, p) != ' ') {
+ goto done;
+ }
+
+ string_skip_spaces_and_comments(sp, &p);
+
+ if (string_eq_ignorecase(sp, p, "AM", 2)) {
+ if (hour > 12) {
+ goto done;
+ }
+ if (hour == 12) {
+ hour = 0;
+ }
+ p += 2;
+ string_skip_spaces_and_comments(sp, &p);
+ } else if (string_eq_ignorecase(sp, p, "PM", 2)) {
+ if (hour > 12) {
+ goto done;
+ }
+ if (hour != 12) {
+ hour += 12;
+ }
+ p += 2;
+ string_skip_spaces_and_comments(sp, &p);
+ }
}
}
+
+ // Don't fail if the time zone is missing.
+ // Some websites omit the time zone
+ if (sp->len > p) {
+ if (string_get(sp, p) == '+' || string_get(sp, p) == '-') {
+ if (string_get_num_timezone(sp, &p, &tz))
+ goto done;
+ is_local = FALSE;
+ } else {
+ for (int i = 0; i < sizeof(known_zones) / sizeof(struct KnownZone); i++) {
+ if (string_eq_ignorecase(sp, p, known_zones[i].tzName, strlen(known_zones[i].tzName))) {
+ tz = known_zones[i].tzOffset;
+ p += strlen(known_zones[i].tzName);
+ is_local = FALSE;
+
+ // TZ offset (GMT+0)
+ if (string_get(sp, p) == '+' || string_get(sp, p) == '-') {
+ int64_t o;
+ if (string_get_num_timezone(sp, &p, &o))
+ goto done;
+
+ tz += o;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ string_skip_spaces_and_comments(sp, &p);
+
+ if (sp->len > p && year == -1) {
+ if (string_get_digits(sp, &p, &year))
+ goto done;
+ }
+
+ string_skip_spaces_and_comments(sp, &p);
+
+ // Trailing garbage
+ if (sp->len > p) {
+ goto done;
+ }
+
+ // Y2K: Handle 2 digit years.
+ if (year >= 0 && year < 100) {
+ if (year < 50) {
+ year += 2000;
+ } else {
+ year += 1900;
+ }
+ }
+
+ fields[0] = year;
+ fields[1] = month;
+ fields[2] = day;
+ fields[3] = hour;
+ fields[4] = minute;
+ fields[5] = second;
}
+
+ // Validate fields
+ if (fields[1] < 0 || fields[1] > 11 ||
+ fields[2] < 1 || fields[2] > 31 ||
+ fields[3] < 0 || fields[3] > 24 ||
+ fields[4] < 0 || fields[4] > 59 ||
+ fields[5] < 0 || fields[5] > 59 ||
+ fields[6] < 0 || fields[6] > 999 ||
+ fields[3] * 3600 * 1000 + fields[4] * 60000 + fields[5] * 1000 + fields[6] > 24 * 3600 * 1000
+ ) goto done;
+
for(i = 0; i < 7; i++)
fields1[i] = fields[i];
- d = set_date_fields(fields1, is_local) - tz * 60000;
+ d = set_date_fields(fields1, is_local) - (tz * 60000);
rv = JS_NewFloat64(ctx, d);
done:

View file

@ -1,30 +1,3 @@
2024-01-13:
- top-level-await support in modules
- allow 'await' in the REPL
- added Array.prototype.{with,toReversed,toSpliced,toSorted} and
TypedArray.prototype.{with,toReversed,toSorted}
- added String.prototype.isWellFormed and String.prototype.toWellFormed
- added Object.groupBy and Map.groupBy
- added Promise.withResolvers
- class static block
- 'in' operator support for private fields
- optional chaining fixes
- added RegExp 'd' flag
- fixed RegExp zero length match logic
- fixed RegExp case insensitive flag
- added os.getpid() and os.now()
- added cosmopolitan build
- misc bug fixes
2023-12-09:
- added Object.hasOwn, {String|Array|TypedArray}.prototype.at,
{Array|TypedArray}.prototype.findLast{Index}
- BigInt support is enabled even if CONFIG_BIGNUM disabled
- updated to Unicode 15.0.0
- misc bug fixes
2021-03-27: 2021-03-27:
- faster Array.prototype.push and Array.prototype.unshift - faster Array.prototype.push and Array.prototype.unshift

View file

@ -33,20 +33,6 @@ CONFIG_LTO=y
#CONFIG_WERROR=y #CONFIG_WERROR=y
# force 32 bit build for some utilities # force 32 bit build for some utilities
#CONFIG_M32=y #CONFIG_M32=y
# cosmopolitan build (see https://github.com/jart/cosmopolitan)
#CONFIG_COSMO=y
# installation directory
PREFIX?=/usr/local
# use the gprof profiler
#CONFIG_PROFILE=y
# use address sanitizer
#CONFIG_ASAN=y
# include the code for BigFloat/BigDecimal, math mode and faster large integers
CONFIG_BIGNUM=y
OBJDIR=.obj
ifdef CONFIG_DARWIN ifdef CONFIG_DARWIN
# use clang instead of gcc # use clang instead of gcc
@ -54,22 +40,33 @@ CONFIG_CLANG=y
CONFIG_DEFAULT_AR=y CONFIG_DEFAULT_AR=y
endif endif
# installation directory
prefix=/usr/local
# use the gprof profiler
#CONFIG_PROFILE=y
# use address sanitizer
#CONFIG_ASAN=y
# include the code for BigInt/BigFloat/BigDecimal and math mode
CONFIG_BIGNUM=y
OBJDIR=.obj
ifdef CONFIG_WIN32 ifdef CONFIG_WIN32
ifdef CONFIG_M32 ifdef CONFIG_M32
CROSS_PREFIX?=i686-w64-mingw32- CROSS_PREFIX=i686-w64-mingw32-
else else
CROSS_PREFIX?=x86_64-w64-mingw32- CROSS_PREFIX=x86_64-w64-mingw32-
endif endif
EXE=.exe EXE=.exe
else else
CROSS_PREFIX?= CROSS_PREFIX=
EXE= EXE=
endif endif
ifdef CONFIG_CLANG ifdef CONFIG_CLANG
HOST_CC=clang HOST_CC=clang
CC=$(CROSS_PREFIX)clang CC=$(CROSS_PREFIX)clang
CFLAGS+=-g -Wall -MMD -MF $(OBJDIR)/$(@F).d CFLAGS=-g -Wall -MMD -MF $(OBJDIR)/$(@F).d
CFLAGS += -Wextra CFLAGS += -Wextra
CFLAGS += -Wno-sign-compare CFLAGS += -Wno-sign-compare
CFLAGS += -Wno-missing-field-initializers CFLAGS += -Wno-missing-field-initializers
@ -87,18 +84,10 @@ ifdef CONFIG_CLANG
AR=$(CROSS_PREFIX)ar AR=$(CROSS_PREFIX)ar
endif endif
endif endif
else ifdef CONFIG_COSMO
CONFIG_LTO=
HOST_CC=gcc
CC=cosmocc
# cosmocc does not correct support -MF
CFLAGS=-g -Wall #-MMD -MF $(OBJDIR)/$(@F).d
CFLAGS += -Wno-array-bounds -Wno-format-truncation
AR=cosmoar
else else
HOST_CC=gcc HOST_CC=gcc
CC=$(CROSS_PREFIX)gcc CC=$(CROSS_PREFIX)gcc
CFLAGS+=-g -Wall -MMD -MF $(OBJDIR)/$(@F).d CFLAGS=-g -Wall -MMD -MF $(OBJDIR)/$(@F).d
CFLAGS += -Wno-array-bounds -Wno-format-truncation CFLAGS += -Wno-array-bounds -Wno-format-truncation
ifdef CONFIG_LTO ifdef CONFIG_LTO
AR=$(CROSS_PREFIX)gcc-ar AR=$(CROSS_PREFIX)gcc-ar
@ -107,7 +96,6 @@ else
endif endif
endif endif
STRIP=$(CROSS_PREFIX)strip STRIP=$(CROSS_PREFIX)strip
CFLAGS+=-fwrapv # ensure that signed overflows behave as expected
ifdef CONFIG_WERROR ifdef CONFIG_WERROR
CFLAGS+=-Werror CFLAGS+=-Werror
endif endif
@ -124,11 +112,7 @@ CFLAGS_DEBUG=$(CFLAGS) -O0
CFLAGS_SMALL=$(CFLAGS) -Os CFLAGS_SMALL=$(CFLAGS) -Os
CFLAGS_OPT=$(CFLAGS) -O2 CFLAGS_OPT=$(CFLAGS) -O2
CFLAGS_NOLTO:=$(CFLAGS_OPT) CFLAGS_NOLTO:=$(CFLAGS_OPT)
ifdef CONFIG_COSMO LDFLAGS=-g
LDFLAGS+=-s # better to strip by default
else
LDFLAGS+=-g
endif
ifdef CONFIG_LTO ifdef CONFIG_LTO
CFLAGS_SMALL+=-flto CFLAGS_SMALL+=-flto
CFLAGS_OPT+=-flto CFLAGS_OPT+=-flto
@ -148,12 +132,6 @@ else
LDEXPORT=-rdynamic LDEXPORT=-rdynamic
endif endif
ifndef CONFIG_COSMO
ifndef CONFIG_DARWIN
CONFIG_SHARED_LIBS=y # building shared libraries is supported
endif
endif
PROGS=qjs$(EXE) qjsc$(EXE) run-test262 PROGS=qjs$(EXE) qjsc$(EXE) run-test262
ifneq ($(CROSS_PREFIX),) ifneq ($(CROSS_PREFIX),)
QJSC_CC=gcc QJSC_CC=gcc
@ -176,21 +154,23 @@ endif
# examples # examples
ifeq ($(CROSS_PREFIX),) ifeq ($(CROSS_PREFIX),)
PROGS+=examples/hello ifdef CONFIG_ASAN
ifndef CONFIG_ASAN PROGS+=
PROGS+=examples/hello_module else
PROGS+=examples/hello examples/hello_module examples/test_fib
ifndef CONFIG_DARWIN
PROGS+=examples/fib.so examples/point.so
endif endif
ifdef CONFIG_SHARED_LIBS
PROGS+=examples/test_fib examples/fib.so examples/point.so
endif endif
endif endif
all: $(OBJDIR) $(OBJDIR)/quickjs.check.o $(OBJDIR)/qjs.check.o $(PROGS) all: $(OBJDIR) $(OBJDIR)/quickjs.check.o $(OBJDIR)/qjs.check.o $(PROGS)
QJS_LIB_OBJS=$(OBJDIR)/quickjs.o $(OBJDIR)/libregexp.o $(OBJDIR)/libunicode.o $(OBJDIR)/cutils.o $(OBJDIR)/quickjs-libc.o $(OBJDIR)/libbf.o QJS_LIB_OBJS=$(OBJDIR)/quickjs.o $(OBJDIR)/libregexp.o $(OBJDIR)/libunicode.o $(OBJDIR)/cutils.o $(OBJDIR)/quickjs-libc.o
QJS_OBJS=$(OBJDIR)/qjs.o $(OBJDIR)/repl.o $(QJS_LIB_OBJS) QJS_OBJS=$(OBJDIR)/qjs.o $(OBJDIR)/repl.o $(QJS_LIB_OBJS)
ifdef CONFIG_BIGNUM ifdef CONFIG_BIGNUM
QJS_LIB_OBJS+=$(OBJDIR)/libbf.o
QJS_OBJS+=$(OBJDIR)/qjscalc.o QJS_OBJS+=$(OBJDIR)/qjscalc.o
endif endif
@ -221,11 +201,11 @@ $(QJSC): $(OBJDIR)/qjsc.host.o \
endif #CROSS_PREFIX endif #CROSS_PREFIX
QJSC_DEFINES:=-DCONFIG_CC=\"$(QJSC_CC)\" -DCONFIG_PREFIX=\"$(PREFIX)\" QJSC_DEFINES:=-DCONFIG_CC=\"$(QJSC_CC)\" -DCONFIG_PREFIX=\"$(prefix)\"
ifdef CONFIG_LTO ifdef CONFIG_LTO
QJSC_DEFINES+=-DCONFIG_LTO QJSC_DEFINES+=-DCONFIG_LTO
endif endif
QJSC_HOST_DEFINES:=-DCONFIG_CC=\"$(HOST_CC)\" -DCONFIG_PREFIX=\"$(PREFIX)\" QJSC_HOST_DEFINES:=-DCONFIG_CC=\"$(HOST_CC)\" -DCONFIG_PREFIX=\"$(prefix)\"
$(OBJDIR)/qjsc.o: CFLAGS+=$(QJSC_DEFINES) $(OBJDIR)/qjsc.o: CFLAGS+=$(QJSC_DEFINES)
$(OBJDIR)/qjsc.host.o: CFLAGS+=$(QJSC_HOST_DEFINES) $(OBJDIR)/qjsc.host.o: CFLAGS+=$(QJSC_HOST_DEFINES)
@ -318,17 +298,17 @@ clean:
rm -rf run-test262-debug run-test262-32 rm -rf run-test262-debug run-test262-32
install: all install: all
mkdir -p "$(DESTDIR)$(PREFIX)/bin" mkdir -p "$(DESTDIR)$(prefix)/bin"
$(STRIP) qjs qjsc $(STRIP) qjs qjsc
install -m755 qjs qjsc "$(DESTDIR)$(PREFIX)/bin" install -m755 qjs qjsc "$(DESTDIR)$(prefix)/bin"
ln -sf qjs "$(DESTDIR)$(PREFIX)/bin/qjscalc" ln -sf qjs "$(DESTDIR)$(prefix)/bin/qjscalc"
mkdir -p "$(DESTDIR)$(PREFIX)/lib/quickjs" mkdir -p "$(DESTDIR)$(prefix)/lib/quickjs"
install -m644 libquickjs.a "$(DESTDIR)$(PREFIX)/lib/quickjs" install -m644 libquickjs.a "$(DESTDIR)$(prefix)/lib/quickjs"
ifdef CONFIG_LTO ifdef CONFIG_LTO
install -m644 libquickjs.lto.a "$(DESTDIR)$(PREFIX)/lib/quickjs" install -m644 libquickjs.lto.a "$(DESTDIR)$(prefix)/lib/quickjs"
endif endif
mkdir -p "$(DESTDIR)$(PREFIX)/include/quickjs" mkdir -p "$(DESTDIR)$(prefix)/include/quickjs"
install -m644 quickjs.h quickjs-libc.h "$(DESTDIR)$(PREFIX)/include/quickjs" install -m644 quickjs.h quickjs-libc.h "$(DESTDIR)$(prefix)/include/quickjs"
############################################################################### ###############################################################################
# examples # examples
@ -337,7 +317,10 @@ endif
HELLO_SRCS=examples/hello.js HELLO_SRCS=examples/hello.js
HELLO_OPTS=-fno-string-normalize -fno-map -fno-promise -fno-typedarray \ HELLO_OPTS=-fno-string-normalize -fno-map -fno-promise -fno-typedarray \
-fno-typedarray -fno-regexp -fno-json -fno-eval -fno-proxy \ -fno-typedarray -fno-regexp -fno-json -fno-eval -fno-proxy \
-fno-date -fno-module-loader -fno-bigint -fno-date -fno-module-loader
ifdef CONFIG_BIGNUM
HELLO_OPTS+=-fno-bigint
endif
hello.c: $(QJSC) $(HELLO_SRCS) hello.c: $(QJSC) $(HELLO_SRCS)
$(QJSC) -e $(HELLO_OPTS) -o $@ $(HELLO_SRCS) $(QJSC) -e $(HELLO_OPTS) -o $@ $(HELLO_SRCS)
@ -394,7 +377,7 @@ doc/%.html: doc/%.html.pre
############################################################################### ###############################################################################
# tests # tests
ifdef CONFIG_SHARED_LIBS ifndef CONFIG_DARWIN
test: tests/bjson.so examples/point.so test: tests/bjson.so examples/point.so
endif endif
ifdef CONFIG_M32 ifdef CONFIG_M32
@ -408,7 +391,7 @@ test: qjs
./qjs tests/test_loop.js ./qjs tests/test_loop.js
./qjs tests/test_std.js ./qjs tests/test_std.js
./qjs tests/test_worker.js ./qjs tests/test_worker.js
ifdef CONFIG_SHARED_LIBS ifndef CONFIG_DARWIN
ifdef CONFIG_BIGNUM ifdef CONFIG_BIGNUM
./qjs --bignum tests/test_bjson.js ./qjs --bignum tests/test_bjson.js
else else

View file

@ -1,3 +1,6 @@
Bugs:
- modules: better error handling with cyclic module references
Misc ideas: Misc ideas:
- use custom printf to avoid compatibility issues with floating point numbers - use custom printf to avoid compatibility issues with floating point numbers
- consistent naming for preprocessor defines - consistent naming for preprocessor defines
@ -63,5 +66,5 @@ Optimization ideas:
Test262o: 0/11262 errors, 463 excluded Test262o: 0/11262 errors, 463 excluded
Test262o commit: 7da91bceb9ce7613f87db47ddd1292a2dda58b42 (es5-tests branch) Test262o commit: 7da91bceb9ce7613f87db47ddd1292a2dda58b42 (es5-tests branch)
Result: 10/76947 errors, 1497 excluded, 8117 skipped Result: 35/75280 errors, 909 excluded, 585 skipped
Test262 commit: 6cbb6da9473c56d95358d8e679c5a6d2b4574efb Test262 commit: 31126581e7290f9233c29cefd93f66c6ac78f1c9

View file

@ -1 +1 @@
2024-01-13 2021-03-27

View file

@ -49,9 +49,6 @@
#define countof(x) (sizeof(x) / sizeof((x)[0])) #define countof(x) (sizeof(x) / sizeof((x)[0]))
#endif #endif
/* return the pointer of type 'type *' containing 'ptr' as field 'member' */
#define container_of(ptr, type, member) ((type *)((uint8_t *)(ptr) - offsetof(type, member)))
typedef int BOOL; typedef int BOOL;
#ifndef FALSE #ifndef FALSE

View file

@ -37,12 +37,10 @@
/* enable it to check the multiplication result */ /* enable it to check the multiplication result */
//#define USE_MUL_CHECK //#define USE_MUL_CHECK
#ifdef CONFIG_BIGNUM
/* enable it to use FFT/NTT multiplication */ /* enable it to use FFT/NTT multiplication */
#define USE_FFT_MUL #define USE_FFT_MUL
/* enable decimal floating point support */ /* enable decimal floating point support */
#define USE_BF_DEC #define USE_BF_DEC
#endif
//#define inline __attribute__((always_inline)) //#define inline __attribute__((always_inline))
@ -166,21 +164,6 @@ static inline slimb_t sat_add(slimb_t a, slimb_t b)
return r; return r;
} }
static inline __maybe_unused limb_t shrd(limb_t low, limb_t high, long shift)
{
if (shift != 0)
low = (low >> shift) | (high << (LIMB_BITS - shift));
return low;
}
static inline __maybe_unused limb_t shld(limb_t a1, limb_t a0, long shift)
{
if (shift != 0)
return (a1 << shift) | (a0 >> (LIMB_BITS - shift));
else
return a1;
}
#define malloc(s) malloc_is_forbidden(s) #define malloc(s) malloc_is_forbidden(s)
#define free(p) free_is_forbidden(p) #define free(p) free_is_forbidden(p)
#define realloc(p, s) realloc_is_forbidden(p, s) #define realloc(p, s) realloc_is_forbidden(p, s)
@ -253,7 +236,7 @@ int bf_set_ui(bf_t *r, uint64_t a)
a1 = a >> 32; a1 = a >> 32;
shift = clz(a1); shift = clz(a1);
r->tab[0] = a0 << shift; r->tab[0] = a0 << shift;
r->tab[1] = shld(a1, a0, shift); r->tab[1] = (a1 << shift) | (a0 >> (LIMB_BITS - shift));
r->expn = 2 * LIMB_BITS - shift; r->expn = 2 * LIMB_BITS - shift;
} }
#endif #endif
@ -1602,9 +1585,7 @@ int bf_mul(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec,
r = &tmp; r = &tmp;
} }
if (bf_resize(r, a_len + b_len)) { if (bf_resize(r, a_len + b_len)) {
#ifdef USE_FFT_MUL
fail: fail:
#endif
bf_set_nan(r); bf_set_nan(r);
ret = BF_ST_MEM_ERROR; ret = BF_ST_MEM_ERROR;
goto done; goto done;
@ -2301,14 +2282,11 @@ static int bf_pow_ui_ui(bf_t *r, limb_t a1, limb_t b,
bf_t a; bf_t a;
int ret; int ret;
#ifdef USE_BF_DEC
if (a1 == 10 && b <= LIMB_DIGITS) { if (a1 == 10 && b <= LIMB_DIGITS) {
/* use precomputed powers. We do not round at this point /* use precomputed powers. We do not round at this point
because we expect the caller to do it */ because we expect the caller to do it */
ret = bf_set_ui(r, mp_pow_dec[b]); ret = bf_set_ui(r, mp_pow_dec[b]);
} else } else {
#endif
{
bf_init(r->ctx, &a); bf_init(r->ctx, &a);
ret = bf_set_ui(&a, a1); ret = bf_set_ui(&a, a1);
ret |= bf_pow_ui(r, &a, b, prec, flags); ret |= bf_pow_ui(r, &a, b, prec, flags);
@ -5414,6 +5392,21 @@ int bf_acos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags)
#endif /* LIMB_BITS != 64 */ #endif /* LIMB_BITS != 64 */
static inline __maybe_unused limb_t shrd(limb_t low, limb_t high, long shift)
{
if (shift != 0)
low = (low >> shift) | (high << (LIMB_BITS - shift));
return low;
}
static inline __maybe_unused limb_t shld(limb_t a1, limb_t a0, long shift)
{
if (shift != 0)
return (a1 << shift) | (a0 >> (LIMB_BITS - shift));
else
return a1;
}
#if LIMB_DIGITS == 19 #if LIMB_DIGITS == 19
/* WARNING: hardcoded for b = 1e19. It is assumed that: /* WARNING: hardcoded for b = 1e19. It is assumed that:

View file

@ -27,7 +27,7 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#if defined(__SIZEOF_INT128__) && (INTPTR_MAX >= INT64_MAX) #if INTPTR_MAX >= INT64_MAX
#define LIMB_LOG2_BITS 6 #define LIMB_LOG2_BITS 6
#else #else
#define LIMB_LOG2_BITS 5 #define LIMB_LOG2_BITS 5

View file

@ -50,7 +50,8 @@ DEF(range32, 3) /* variable length */
DEF(lookahead, 5) DEF(lookahead, 5)
DEF(negative_lookahead, 5) DEF(negative_lookahead, 5)
DEF(push_char_pos, 1) /* push the character position on the stack */ DEF(push_char_pos, 1) /* push the character position on the stack */
DEF(check_advance, 1) /* pop one stack element and check that it is different from the character position */ DEF(bne_char_pos, 5) /* pop one stack element and jump if equal to the character
position */
DEF(prev, 1) /* go to the previous char */ DEF(prev, 1) /* go to the previous char */
DEF(simple_greedy_quant, 17) DEF(simple_greedy_quant, 17)

View file

@ -34,6 +34,9 @@
/* /*
TODO: TODO:
- Add full unicode canonicalize rules for character ranges (not
really useful but needed for exact "ignorecase" compatibility).
- Add a lock step execution mode (=linear time execution guaranteed) - Add a lock step execution mode (=linear time execution guaranteed)
when the regular expression is "simple" i.e. no backreference nor when the regular expression is "simple" i.e. no backreference nor
complicated lookahead. The opcodes are designed for this execution complicated lookahead. The opcodes are designed for this execution
@ -117,6 +120,33 @@ static int dbuf_insert(DynBuf *s, int pos, int len)
return 0; return 0;
} }
/* canonicalize with the specific JS regexp rules */
static uint32_t lre_canonicalize(uint32_t c, BOOL is_utf16)
{
uint32_t res[LRE_CC_RES_LEN_MAX];
int len;
if (is_utf16) {
if (likely(c < 128)) {
if (c >= 'A' && c <= 'Z')
c = c - 'A' + 'a';
} else {
lre_case_conv(res, c, 2);
c = res[0];
}
} else {
if (likely(c < 128)) {
if (c >= 'a' && c <= 'z')
c = c - 'a' + 'A';
} else {
/* legacy regexp: to upper case if single char >= 128 */
len = lre_case_conv(res, c, FALSE);
if (len == 1 && res[0] >= 128)
c = res[0];
}
}
return c;
}
static const uint16_t char_range_d[] = { static const uint16_t char_range_d[] = {
1, 1,
0x0030, 0x0039 + 1, 0x0030, 0x0039 + 1,
@ -215,6 +245,31 @@ static int cr_init_char_range(REParseState *s, CharRange *cr, uint32_t c)
return -1; return -1;
} }
static int cr_canonicalize(CharRange *cr)
{
CharRange a;
uint32_t pt[2];
int i, ret;
cr_init(&a, cr->mem_opaque, lre_realloc);
pt[0] = 'a';
pt[1] = 'z' + 1;
ret = cr_op(&a, cr->points, cr->len, pt, 2, CR_OP_INTER);
if (ret)
goto fail;
/* convert to upper case */
/* XXX: the generic unicode case would be much more complicated
and not really useful */
for(i = 0; i < a.len; i++) {
a.points[i] += 'A' - 'a';
}
/* Note: for simplicity we keep the lower case ranges */
ret = cr_union1(cr, a.points, a.len);
fail:
cr_free(&a);
return ret;
}
#ifdef DUMP_REOP #ifdef DUMP_REOP
static __maybe_unused void lre_dump_bytecode(const uint8_t *buf, static __maybe_unused void lre_dump_bytecode(const uint8_t *buf,
int buf_len) int buf_len)
@ -280,6 +335,7 @@ static __maybe_unused void lre_dump_bytecode(const uint8_t *buf,
case REOP_loop: case REOP_loop:
case REOP_lookahead: case REOP_lookahead:
case REOP_negative_lookahead: case REOP_negative_lookahead:
case REOP_bne_char_pos:
val = get_u32(buf + pos + 1); val = get_u32(buf + pos + 1);
val += (pos + 5); val += (pos + 5);
printf(" %u", val); printf(" %u", val);
@ -866,7 +922,7 @@ static int re_parse_char_class(REParseState *s, const uint8_t **pp)
} }
} }
if (s->ignore_case) { if (s->ignore_case) {
if (cr_regexp_canonicalize(cr, s->is_utf16)) if (cr_canonicalize(cr))
goto memory_error; goto memory_error;
} }
if (invert) { if (invert) {
@ -887,17 +943,22 @@ static int re_parse_char_class(REParseState *s, const uint8_t **pp)
} }
/* Return: /* Return:
- true if the opcodes may not advance the char pointer 1 if the opcodes in bc_buf[] always advance the character pointer.
- false if the opcodes always advance the char pointer 0 if the character pointer may not be advanced.
-1 if the code may depend on side effects of its previous execution (backreference)
*/ */
static BOOL re_need_check_advance(const uint8_t *bc_buf, int bc_buf_len) static int re_check_advance(const uint8_t *bc_buf, int bc_buf_len)
{ {
int pos, opcode, len; int pos, opcode, ret, len, i;
uint32_t val; uint32_t val, last;
BOOL ret; BOOL has_back_reference;
uint8_t capture_bitmap[CAPTURE_COUNT_MAX];
ret = TRUE; ret = -2; /* not known yet */
pos = 0; pos = 0;
has_back_reference = FALSE;
memset(capture_bitmap, 0, sizeof(capture_bitmap));
while (pos < bc_buf_len) { while (pos < bc_buf_len) {
opcode = bc_buf[pos]; opcode = bc_buf[pos];
len = reopcode_info[opcode].size; len = reopcode_info[opcode].size;
@ -915,7 +976,8 @@ static BOOL re_need_check_advance(const uint8_t *bc_buf, int bc_buf_len)
case REOP_dot: case REOP_dot:
case REOP_any: case REOP_any:
simple_char: simple_char:
ret = FALSE; if (ret == -2)
ret = 1;
break; break;
case REOP_line_start: case REOP_line_start:
case REOP_line_end: case REOP_line_end:
@ -929,16 +991,41 @@ static BOOL re_need_check_advance(const uint8_t *bc_buf, int bc_buf_len)
break; break;
case REOP_save_start: case REOP_save_start:
case REOP_save_end: case REOP_save_end:
val = bc_buf[pos + 1];
capture_bitmap[val] |= 1;
break;
case REOP_save_reset: case REOP_save_reset:
{
val = bc_buf[pos + 1];
last = bc_buf[pos + 2];
while (val < last)
capture_bitmap[val++] |= 1;
}
break;
case REOP_back_reference: case REOP_back_reference:
case REOP_backward_back_reference: case REOP_backward_back_reference:
val = bc_buf[pos + 1];
capture_bitmap[val] |= 2;
has_back_reference = TRUE;
break; break;
default: default:
/* safe behvior: we cannot predict the outcome */ /* safe behvior: we cannot predict the outcome */
return TRUE; if (ret == -2)
ret = 0;
break;
} }
pos += len; pos += len;
} }
if (has_back_reference) {
/* check if there is back reference which references a capture
made in the some code */
for(i = 0; i < CAPTURE_COUNT_MAX; i++) {
if (capture_bitmap[i] == 3)
return -1;
}
}
if (ret == -2)
ret = 0;
return ret; return ret;
} }
@ -984,10 +1071,11 @@ static int re_is_simple_quantifier(const uint8_t *bc_buf, int bc_buf_len)
} }
/* '*pp' is the first char after '<' */ /* '*pp' is the first char after '<' */
static int re_parse_group_name(char *buf, int buf_size, const uint8_t **pp) static int re_parse_group_name(char *buf, int buf_size,
const uint8_t **pp, BOOL is_utf16)
{ {
const uint8_t *p, *p1; const uint8_t *p;
uint32_t c, d; uint32_t c;
char *q; char *q;
p = *pp; p = *pp;
@ -998,18 +1086,11 @@ static int re_parse_group_name(char *buf, int buf_size, const uint8_t **pp)
p++; p++;
if (*p != 'u') if (*p != 'u')
return -1; return -1;
c = lre_parse_escape(&p, 2); // accept surrogate pairs c = lre_parse_escape(&p, is_utf16 * 2);
} else if (c == '>') { } else if (c == '>') {
break; break;
} else if (c >= 128) { } else if (c >= 128) {
c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p); c = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p);
if (c >= 0xD800 && c <= 0xDBFF) {
d = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &p1);
if (d >= 0xDC00 && d <= 0xDFFF) {
c = 0x10000 + 0x400 * (c - 0xD800) + (d - 0xDC00);
p = p1;
}
}
} else { } else {
p++; p++;
} }
@ -1059,7 +1140,8 @@ static int re_parse_captures(REParseState *s, int *phas_named_captures,
/* potential named capture */ /* potential named capture */
if (capture_name) { if (capture_name) {
p += 3; p += 3;
if (re_parse_group_name(name, sizeof(name), &p) == 0) { if (re_parse_group_name(name, sizeof(name), &p,
s->is_utf16) == 0) {
if (!strcmp(name, capture_name)) if (!strcmp(name, capture_name))
return capture_index; return capture_index;
} }
@ -1232,7 +1314,7 @@ static int re_parse_term(REParseState *s, BOOL is_backward_dir)
} else if (p[2] == '<') { } else if (p[2] == '<') {
p += 3; p += 3;
if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf), if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf),
&p)) { &p, s->is_utf16)) {
return re_parse_error(s, "invalid group name"); return re_parse_error(s, "invalid group name");
} }
if (find_group_name(s, s->u.tmp_buf) > 0) { if (find_group_name(s, s->u.tmp_buf) > 0) {
@ -1296,7 +1378,7 @@ static int re_parse_term(REParseState *s, BOOL is_backward_dir)
} }
p1 += 3; p1 += 3;
if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf), if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf),
&p1)) { &p1, s->is_utf16)) {
if (s->is_utf16 || re_has_named_captures(s)) if (s->is_utf16 || re_has_named_captures(s))
return re_parse_error(s, "invalid group name"); return re_parse_error(s, "invalid group name");
else else
@ -1509,12 +1591,8 @@ static int re_parse_term(REParseState *s, BOOL is_backward_dir)
if (dbuf_error(&s->byte_code)) if (dbuf_error(&s->byte_code))
goto out_of_memory; goto out_of_memory;
/* the spec tells that if there is no advance when add_zero_advance_check = (re_check_advance(s->byte_code.buf + last_atom_start,
running the atom after the first quant_min times, s->byte_code.size - last_atom_start) == 0);
then there is no match. We remove this test when we
are sure the atom always advances the position. */
add_zero_advance_check = re_need_check_advance(s->byte_code.buf + last_atom_start,
s->byte_code.size - last_atom_start);
} else { } else {
add_zero_advance_check = FALSE; add_zero_advance_check = FALSE;
} }
@ -1534,34 +1612,38 @@ static int re_parse_term(REParseState *s, BOOL is_backward_dir)
} }
if (quant_max == 0) { if (quant_max == 0) {
s->byte_code.size = last_atom_start; s->byte_code.size = last_atom_start;
} else if (quant_max == 1 || quant_max == INT32_MAX) { } else if (quant_max == 1) {
BOOL has_goto = (quant_max == INT32_MAX); if (dbuf_insert(&s->byte_code, last_atom_start, 5))
goto out_of_memory;
s->byte_code.buf[last_atom_start] = REOP_split_goto_first +
greedy;
put_u32(s->byte_code.buf + last_atom_start + 1, len);
} else if (quant_max == INT32_MAX) {
if (dbuf_insert(&s->byte_code, last_atom_start, 5 + add_zero_advance_check)) if (dbuf_insert(&s->byte_code, last_atom_start, 5 + add_zero_advance_check))
goto out_of_memory; goto out_of_memory;
s->byte_code.buf[last_atom_start] = REOP_split_goto_first + s->byte_code.buf[last_atom_start] = REOP_split_goto_first +
greedy; greedy;
put_u32(s->byte_code.buf + last_atom_start + 1, put_u32(s->byte_code.buf + last_atom_start + 1,
len + 5 * has_goto + add_zero_advance_check * 2); len + 5 + add_zero_advance_check);
if (add_zero_advance_check) { if (add_zero_advance_check) {
/* avoid infinite loop by stoping the
recursion if no advance was made in the
atom (only works if the atom has no
side effect) */
s->byte_code.buf[last_atom_start + 1 + 4] = REOP_push_char_pos; s->byte_code.buf[last_atom_start + 1 + 4] = REOP_push_char_pos;
re_emit_op(s, REOP_check_advance); re_emit_goto(s, REOP_bne_char_pos, last_atom_start);
} } else {
if (has_goto)
re_emit_goto(s, REOP_goto, last_atom_start); re_emit_goto(s, REOP_goto, last_atom_start);
}
} else { } else {
if (dbuf_insert(&s->byte_code, last_atom_start, 10 + add_zero_advance_check)) if (dbuf_insert(&s->byte_code, last_atom_start, 10))
goto out_of_memory; goto out_of_memory;
pos = last_atom_start; pos = last_atom_start;
s->byte_code.buf[pos++] = REOP_push_i32; s->byte_code.buf[pos++] = REOP_push_i32;
put_u32(s->byte_code.buf + pos, quant_max); put_u32(s->byte_code.buf + pos, quant_max);
pos += 4; pos += 4;
s->byte_code.buf[pos++] = REOP_split_goto_first + greedy; s->byte_code.buf[pos++] = REOP_split_goto_first + greedy;
put_u32(s->byte_code.buf + pos, len + 5 + add_zero_advance_check * 2); put_u32(s->byte_code.buf + pos, len + 5);
pos += 4;
if (add_zero_advance_check) {
s->byte_code.buf[pos++] = REOP_push_char_pos;
re_emit_op(s, REOP_check_advance);
}
re_emit_goto(s, REOP_loop, last_atom_start + 5); re_emit_goto(s, REOP_loop, last_atom_start + 5);
re_emit_op(s, REOP_drop); re_emit_op(s, REOP_drop);
} }
@ -1585,25 +1667,22 @@ static int re_parse_term(REParseState *s, BOOL is_backward_dir)
if (quant_max == INT32_MAX) { if (quant_max == INT32_MAX) {
pos = s->byte_code.size; pos = s->byte_code.size;
re_emit_op_u32(s, REOP_split_goto_first + greedy, re_emit_op_u32(s, REOP_split_goto_first + greedy,
len + 5 + add_zero_advance_check * 2); len + 5 + add_zero_advance_check);
if (add_zero_advance_check) if (add_zero_advance_check)
re_emit_op(s, REOP_push_char_pos); re_emit_op(s, REOP_push_char_pos);
/* copy the atom */ /* copy the atom */
dbuf_put_self(&s->byte_code, last_atom_start, len); dbuf_put_self(&s->byte_code, last_atom_start, len);
if (add_zero_advance_check) if (add_zero_advance_check)
re_emit_op(s, REOP_check_advance); re_emit_goto(s, REOP_bne_char_pos, pos);
re_emit_goto(s, REOP_goto, pos); else
re_emit_goto(s, REOP_goto, pos);
} else if (quant_max > quant_min) { } else if (quant_max > quant_min) {
re_emit_op_u32(s, REOP_push_i32, quant_max - quant_min); re_emit_op_u32(s, REOP_push_i32, quant_max - quant_min);
pos = s->byte_code.size; pos = s->byte_code.size;
re_emit_op_u32(s, REOP_split_goto_first + greedy, re_emit_op_u32(s, REOP_split_goto_first + greedy, len + 5);
len + 5 + add_zero_advance_check * 2);
if (add_zero_advance_check)
re_emit_op(s, REOP_push_char_pos);
/* copy the atom */ /* copy the atom */
dbuf_put_self(&s->byte_code, last_atom_start, len); dbuf_put_self(&s->byte_code, last_atom_start, len);
if (add_zero_advance_check)
re_emit_op(s, REOP_check_advance);
re_emit_goto(s, REOP_loop, pos); re_emit_goto(s, REOP_loop, pos);
re_emit_op(s, REOP_drop); re_emit_op(s, REOP_drop);
} }
@ -1717,7 +1796,7 @@ static int compute_stack_size(const uint8_t *bc_buf, int bc_buf_len)
} }
break; break;
case REOP_drop: case REOP_drop:
case REOP_check_advance: case REOP_bne_char_pos:
assert(stack_size > 0); assert(stack_size > 0);
stack_size--; stack_size--;
break; break;
@ -2213,9 +2292,11 @@ static intptr_t lre_exec_backtrack(REExecContext *s, uint8_t **capture,
case REOP_push_char_pos: case REOP_push_char_pos:
stack[stack_len++] = (uintptr_t)cptr; stack[stack_len++] = (uintptr_t)cptr;
break; break;
case REOP_check_advance: case REOP_bne_char_pos:
if (stack[--stack_len] == (uintptr_t)cptr) val = get_u32(pc);
goto no_match; pc += 4;
if (stack[--stack_len] != (uintptr_t)cptr)
pc += (int)val;
break; break;
case REOP_word_boundary: case REOP_word_boundary:
case REOP_not_word_boundary: case REOP_not_word_boundary:

View file

@ -36,7 +36,6 @@
#define LRE_FLAG_DOTALL (1 << 3) #define LRE_FLAG_DOTALL (1 << 3)
#define LRE_FLAG_UTF16 (1 << 4) #define LRE_FLAG_UTF16 (1 << 4)
#define LRE_FLAG_STICKY (1 << 5) #define LRE_FLAG_STICKY (1 << 5)
#define LRE_FLAG_INDICES (1 << 6) /* Unused by libregexp, just recorded. */
#define LRE_FLAG_NAMED_GROUPS (1 << 7) /* named groups are present in the regexp */ #define LRE_FLAG_NAMED_GROUPS (1 << 7) /* named groups are present in the regexp */

File diff suppressed because it is too large Load diff

View file

@ -43,111 +43,11 @@ enum {
RUN_TYPE_UF_D1_EXT, RUN_TYPE_UF_D1_EXT,
RUN_TYPE_U_EXT, RUN_TYPE_U_EXT,
RUN_TYPE_LF_EXT, RUN_TYPE_LF_EXT,
RUN_TYPE_UF_EXT2, RUN_TYPE_U_EXT2,
RUN_TYPE_LF_EXT2, RUN_TYPE_L_EXT2,
RUN_TYPE_UF_EXT3, RUN_TYPE_U_EXT3,
}; };
static int lre_case_conv1(uint32_t c, int conv_type)
{
uint32_t res[LRE_CC_RES_LEN_MAX];
lre_case_conv(res, c, conv_type);
return res[0];
}
/* case conversion using the table entry 'idx' with value 'v' */
static int lre_case_conv_entry(uint32_t *res, uint32_t c, int conv_type, uint32_t idx, uint32_t v)
{
uint32_t code, data, type, a, is_lower;
is_lower = (conv_type != 0);
type = (v >> (32 - 17 - 7 - 4)) & 0xf;
data = ((v & 0xf) << 8) | case_conv_table2[idx];
code = v >> (32 - 17);
switch(type) {
case RUN_TYPE_U:
case RUN_TYPE_L:
case RUN_TYPE_UF:
case RUN_TYPE_LF:
if (conv_type == (type & 1) ||
(type >= RUN_TYPE_UF && conv_type == 2)) {
c = c - code + (case_conv_table1[data] >> (32 - 17));
}
break;
case RUN_TYPE_UL:
a = c - code;
if ((a & 1) != (1 - is_lower))
break;
c = (a ^ 1) + code;
break;
case RUN_TYPE_LSU:
a = c - code;
if (a == 1) {
c += 2 * is_lower - 1;
} else if (a == (1 - is_lower) * 2) {
c += (2 * is_lower - 1) * 2;
}
break;
case RUN_TYPE_U2L_399_EXT2:
if (!is_lower) {
res[0] = c - code + case_conv_ext[data >> 6];
res[1] = 0x399;
return 2;
} else {
c = c - code + case_conv_ext[data & 0x3f];
}
break;
case RUN_TYPE_UF_D20:
if (conv_type == 1)
break;
c = data + (conv_type == 2) * 0x20;
break;
case RUN_TYPE_UF_D1_EXT:
if (conv_type == 1)
break;
c = case_conv_ext[data] + (conv_type == 2);
break;
case RUN_TYPE_U_EXT:
case RUN_TYPE_LF_EXT:
if (is_lower != (type - RUN_TYPE_U_EXT))
break;
c = case_conv_ext[data];
break;
case RUN_TYPE_LF_EXT2:
if (!is_lower)
break;
res[0] = c - code + case_conv_ext[data >> 6];
res[1] = case_conv_ext[data & 0x3f];
return 2;
case RUN_TYPE_UF_EXT2:
if (conv_type == 1)
break;
res[0] = c - code + case_conv_ext[data >> 6];
res[1] = case_conv_ext[data & 0x3f];
if (conv_type == 2) {
/* convert to lower */
res[0] = lre_case_conv1(res[0], 1);
res[1] = lre_case_conv1(res[1], 1);
}
return 2;
default:
case RUN_TYPE_UF_EXT3:
if (conv_type == 1)
break;
res[0] = case_conv_ext[data >> 8];
res[1] = case_conv_ext[(data >> 4) & 0xf];
res[2] = case_conv_ext[data & 0xf];
if (conv_type == 2) {
/* convert to lower */
res[0] = lre_case_conv1(res[0], 1);
res[1] = lre_case_conv1(res[1], 1);
res[2] = lre_case_conv1(res[2], 1);
}
return 3;
}
res[0] = c;
return 1;
}
/* conv_type: /* conv_type:
0 = to upper 0 = to upper
1 = to lower 1 = to lower
@ -166,9 +66,10 @@ int lre_case_conv(uint32_t *res, uint32_t c, int conv_type)
} }
} }
} else { } else {
uint32_t v, code, len; uint32_t v, code, data, type, len, a, is_lower;
int idx, idx_min, idx_max; int idx, idx_min, idx_max;
is_lower = (conv_type != 0);
idx_min = 0; idx_min = 0;
idx_max = countof(case_conv_table1) - 1; idx_max = countof(case_conv_table1) - 1;
while (idx_min <= idx_max) { while (idx_min <= idx_max) {
@ -181,7 +82,74 @@ int lre_case_conv(uint32_t *res, uint32_t c, int conv_type)
} else if (c >= code + len) { } else if (c >= code + len) {
idx_min = idx + 1; idx_min = idx + 1;
} else { } else {
return lre_case_conv_entry(res, c, conv_type, idx, v); type = (v >> (32 - 17 - 7 - 4)) & 0xf;
data = ((v & 0xf) << 8) | case_conv_table2[idx];
switch(type) {
case RUN_TYPE_U:
case RUN_TYPE_L:
case RUN_TYPE_UF:
case RUN_TYPE_LF:
if (conv_type == (type & 1) ||
(type >= RUN_TYPE_UF && conv_type == 2)) {
c = c - code + (case_conv_table1[data] >> (32 - 17));
}
break;
case RUN_TYPE_UL:
a = c - code;
if ((a & 1) != (1 - is_lower))
break;
c = (a ^ 1) + code;
break;
case RUN_TYPE_LSU:
a = c - code;
if (a == 1) {
c += 2 * is_lower - 1;
} else if (a == (1 - is_lower) * 2) {
c += (2 * is_lower - 1) * 2;
}
break;
case RUN_TYPE_U2L_399_EXT2:
if (!is_lower) {
res[0] = c - code + case_conv_ext[data >> 6];
res[1] = 0x399;
return 2;
} else {
c = c - code + case_conv_ext[data & 0x3f];
}
break;
case RUN_TYPE_UF_D20:
if (conv_type == 1)
break;
c = data + (conv_type == 2) * 0x20;
break;
case RUN_TYPE_UF_D1_EXT:
if (conv_type == 1)
break;
c = case_conv_ext[data] + (conv_type == 2);
break;
case RUN_TYPE_U_EXT:
case RUN_TYPE_LF_EXT:
if (is_lower != (type - RUN_TYPE_U_EXT))
break;
c = case_conv_ext[data];
break;
case RUN_TYPE_U_EXT2:
case RUN_TYPE_L_EXT2:
if (conv_type != (type - RUN_TYPE_U_EXT2))
break;
res[0] = c - code + case_conv_ext[data >> 6];
res[1] = case_conv_ext[data & 0x3f];
return 2;
default:
case RUN_TYPE_U_EXT3:
if (conv_type != 0)
break;
res[0] = case_conv_ext[data >> 8];
res[1] = case_conv_ext[(data >> 4) & 0xf];
res[2] = case_conv_ext[data & 0xf];
return 3;
}
break;
} }
} }
} }
@ -189,77 +157,6 @@ int lre_case_conv(uint32_t *res, uint32_t c, int conv_type)
return 1; return 1;
} }
static int lre_case_folding_entry(uint32_t c, uint32_t idx, uint32_t v, BOOL is_unicode)
{
uint32_t res[LRE_CC_RES_LEN_MAX];
int len;
if (is_unicode) {
len = lre_case_conv_entry(res, c, 2, idx, v);
if (len == 1) {
c = res[0];
} else {
/* handle the few specific multi-character cases (see
unicode_gen.c:dump_case_folding_special_cases()) */
if (c == 0xfb06) {
c = 0xfb05;
} else if (c == 0x01fd3) {
c = 0x390;
} else if (c == 0x01fe3) {
c = 0x3b0;
}
}
} else {
if (likely(c < 128)) {
if (c >= 'a' && c <= 'z')
c = c - 'a' + 'A';
} else {
/* legacy regexp: to upper case if single char >= 128 */
len = lre_case_conv_entry(res, c, FALSE, idx, v);
if (len == 1 && res[0] >= 128)
c = res[0];
}
}
return c;
}
/* JS regexp specific rules for case folding */
int lre_canonicalize(uint32_t c, BOOL is_unicode)
{
if (c < 128) {
/* fast case */
if (is_unicode) {
if (c >= 'A' && c <= 'Z') {
c = c - 'A' + 'a';
}
} else {
if (c >= 'a' && c <= 'z') {
c = c - 'a' + 'A';
}
}
} else {
uint32_t v, code, len;
int idx, idx_min, idx_max;
idx_min = 0;
idx_max = countof(case_conv_table1) - 1;
while (idx_min <= idx_max) {
idx = (unsigned)(idx_max + idx_min) / 2;
v = case_conv_table1[idx];
code = v >> (32 - 17);
len = (v >> (32 - 17 - 7)) & 0x7f;
if (c < code) {
idx_max = idx - 1;
} else if (c >= code + len) {
idx_min = idx + 1;
} else {
return lre_case_folding_entry(c, idx, v, is_unicode);
}
}
}
return c;
}
static uint32_t get_le24(const uint8_t *ptr) static uint32_t get_le24(const uint8_t *ptr)
{ {
#if defined(__x86__) || defined(__x86_64__) #if defined(__x86__) || defined(__x86_64__)
@ -1282,11 +1179,11 @@ static int unicode_case1(CharRange *cr, int case_mask)
#define MR(x) (1 << RUN_TYPE_ ## x) #define MR(x) (1 << RUN_TYPE_ ## x)
const uint32_t tab_run_mask[3] = { const uint32_t tab_run_mask[3] = {
MR(U) | MR(UF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(UF_D20) | MR(U) | MR(UF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(UF_D20) |
MR(UF_D1_EXT) | MR(U_EXT) | MR(UF_EXT2) | MR(UF_EXT3), MR(UF_D1_EXT) | MR(U_EXT) | MR(U_EXT2) | MR(U_EXT3),
MR(L) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2), MR(L) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(L_EXT2),
MR(UF) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2) | MR(UF_D20) | MR(UF_D1_EXT) | MR(LF_EXT) | MR(UF_EXT2) | MR(UF_EXT3), MR(UF) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(UF_D20) | MR(UF_D1_EXT) | MR(LF_EXT),
}; };
#undef MR #undef MR
uint32_t mask, v, code, type, len, i, idx; uint32_t mask, v, code, type, len, i, idx;
@ -1339,136 +1236,7 @@ static int unicode_case1(CharRange *cr, int case_mask)
} }
return 0; return 0;
} }
static int point_cmp(const void *p1, const void *p2, void *arg)
{
uint32_t v1 = *(uint32_t *)p1;
uint32_t v2 = *(uint32_t *)p2;
return (v1 > v2) - (v1 < v2);
}
static void cr_sort_and_remove_overlap(CharRange *cr)
{
uint32_t start, end, start1, end1, i, j;
/* the resulting ranges are not necessarily sorted and may overlap */
rqsort(cr->points, cr->len / 2, sizeof(cr->points[0]) * 2, point_cmp, NULL);
j = 0;
for(i = 0; i < cr->len; ) {
start = cr->points[i];
end = cr->points[i + 1];
i += 2;
while (i < cr->len) {
start1 = cr->points[i];
end1 = cr->points[i + 1];
if (start1 > end) {
/* |------|
* |-------| */
break;
} else if (end1 <= end) {
/* |------|
* |--| */
i += 2;
} else {
/* |------|
* |-------| */
end = end1;
i += 2;
}
}
cr->points[j] = start;
cr->points[j + 1] = end;
j += 2;
}
cr->len = j;
}
/* canonicalize a character set using the JS regex case folding rules
(see lre_canonicalize()) */
int cr_regexp_canonicalize(CharRange *cr, BOOL is_unicode)
{
CharRange cr_inter, cr_mask, cr_result, cr_sub;
uint32_t v, code, len, i, idx, start, end, c, d_start, d_end, d;
cr_init(&cr_mask, cr->mem_opaque, cr->realloc_func);
cr_init(&cr_inter, cr->mem_opaque, cr->realloc_func);
cr_init(&cr_result, cr->mem_opaque, cr->realloc_func);
cr_init(&cr_sub, cr->mem_opaque, cr->realloc_func);
if (unicode_case1(&cr_mask, is_unicode ? CASE_F : CASE_U))
goto fail;
if (cr_op(&cr_inter, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER))
goto fail;
if (cr_invert(&cr_mask))
goto fail;
if (cr_op(&cr_sub, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER))
goto fail;
/* cr_inter = cr & cr_mask */
/* cr_sub = cr & ~cr_mask */
/* use the case conversion table to compute the result */
d_start = -1;
d_end = -1;
idx = 0;
v = case_conv_table1[idx];
code = v >> (32 - 17);
len = (v >> (32 - 17 - 7)) & 0x7f;
for(i = 0; i < cr_inter.len; i += 2) {
start = cr_inter.points[i];
end = cr_inter.points[i + 1];
for(c = start; c < end; c++) {
for(;;) {
if (c >= code && c < code + len)
break;
idx++;
assert(idx < countof(case_conv_table1));
v = case_conv_table1[idx];
code = v >> (32 - 17);
len = (v >> (32 - 17 - 7)) & 0x7f;
}
d = lre_case_folding_entry(c, idx, v, is_unicode);
/* try to merge with the current interval */
if (d_start == -1) {
d_start = d;
d_end = d + 1;
} else if (d_end == d) {
d_end++;
} else {
cr_add_interval(&cr_result, d_start, d_end);
d_start = d;
d_end = d + 1;
}
}
}
if (d_start != -1) {
if (cr_add_interval(&cr_result, d_start, d_end))
goto fail;
}
/* the resulting ranges are not necessarily sorted and may overlap */
cr_sort_and_remove_overlap(&cr_result);
/* or with the character not affected by the case folding */
cr->len = 0;
if (cr_op(cr, cr_result.points, cr_result.len, cr_sub.points, cr_sub.len, CR_OP_UNION))
goto fail;
cr_free(&cr_inter);
cr_free(&cr_mask);
cr_free(&cr_result);
cr_free(&cr_sub);
return 0;
fail:
cr_free(&cr_inter);
cr_free(&cr_mask);
cr_free(&cr_result);
cr_free(&cr_sub);
return -1;
}
typedef enum { typedef enum {
POP_GC, POP_GC,
POP_PROP, POP_PROP,

View file

@ -41,7 +41,6 @@ typedef enum {
} UnicodeNormalizationEnum; } UnicodeNormalizationEnum;
int lre_case_conv(uint32_t *res, uint32_t c, int conv_type); int lre_case_conv(uint32_t *res, uint32_t c, int conv_type);
int lre_canonicalize(uint32_t c, BOOL is_unicode);
LRE_BOOL lre_is_cased(uint32_t c); LRE_BOOL lre_is_cased(uint32_t c);
LRE_BOOL lre_is_case_ignorable(uint32_t c); LRE_BOOL lre_is_case_ignorable(uint32_t c);
@ -102,8 +101,6 @@ int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len,
int cr_invert(CharRange *cr); int cr_invert(CharRange *cr);
int cr_regexp_canonicalize(CharRange *cr, BOOL is_unicode);
#ifdef CONFIG_ALL_UNICODE #ifdef CONFIG_ALL_UNICODE
LRE_BOOL lre_is_id_start(uint32_t c); LRE_BOOL lre_is_id_start(uint32_t c);

View file

@ -36,7 +36,8 @@ struct list_head {
#define LIST_HEAD_INIT(el) { &(el), &(el) } #define LIST_HEAD_INIT(el) { &(el), &(el) }
/* return the pointer of type 'type *' containing 'el' as field 'member' */ /* return the pointer of type 'type *' containing 'el' as field 'member' */
#define list_entry(el, type, member) container_of(el, type, member) #define list_entry(el, type, member) \
((type *)((uint8_t *)(el) - offsetof(type, member)))
static inline void init_list_head(struct list_head *head) static inline void init_list_head(struct list_head *head)
{ {

View file

@ -140,19 +140,19 @@ static inline unsigned long long js_trace_malloc_ptr_offset(uint8_t *ptr,
} }
/* default memory allocation functions with memory limitation */ /* default memory allocation functions with memory limitation */
static size_t js_trace_malloc_usable_size(const void *ptr) static inline size_t js_trace_malloc_usable_size(void *ptr)
{ {
#if defined(__APPLE__) #if defined(__APPLE__)
return malloc_size(ptr); return malloc_size(ptr);
#elif defined(_WIN32) #elif defined(_WIN32)
return _msize((void *)ptr); return _msize(ptr);
#elif defined(EMSCRIPTEN) #elif defined(EMSCRIPTEN)
return 0; return 0;
#elif defined(__linux__) #elif defined(__linux__)
return malloc_usable_size((void *)ptr); return malloc_usable_size(ptr);
#else #else
/* change this to `return 0;` if compilation fails */ /* change this to `return 0;` if compilation fails */
return malloc_usable_size((void *)ptr); return malloc_usable_size(ptr);
#endif #endif
} }
@ -264,7 +264,18 @@ static const JSMallocFunctions trace_mf = {
js_trace_malloc, js_trace_malloc,
js_trace_free, js_trace_free,
js_trace_realloc, js_trace_realloc,
js_trace_malloc_usable_size, #if defined(__APPLE__)
malloc_size,
#elif defined(_WIN32)
(size_t (*)(const void *))_msize,
#elif defined(EMSCRIPTEN)
NULL,
#elif defined(__linux__)
(size_t (*)(const void *))malloc_usable_size,
#else
/* change this to `NULL,` if compilation fails */
malloc_usable_size,
#endif
}; };
#define PROG_NAME "qjs" #define PROG_NAME "qjs"
@ -443,10 +454,8 @@ int main(int argc, char **argv)
} }
} }
#ifdef CONFIG_BIGNUM
if (load_jscalc) if (load_jscalc)
bignum_ext = 1; bignum_ext = 1;
#endif
if (trace_memory) { if (trace_memory) {
js_trace_malloc_init(&trace_data); js_trace_malloc_init(&trace_data);

View file

@ -76,7 +76,9 @@ static const FeatureEntry feature_list[] = {
{ "promise", "Promise" }, { "promise", "Promise" },
#define FE_MODULE_LOADER 9 #define FE_MODULE_LOADER 9
{ "module-loader", NULL }, { "module-loader", NULL },
#ifdef CONFIG_BIGNUM
{ "bigint", "BigInt" }, { "bigint", "BigInt" },
#endif
}; };
void namelist_add(namelist_t *lp, const char *name, const char *short_name, void namelist_add(namelist_t *lp, const char *name, const char *short_name,
@ -330,7 +332,6 @@ static const char main_c_template1[] =
static const char main_c_template2[] = static const char main_c_template2[] =
" js_std_loop(ctx);\n" " js_std_loop(ctx);\n"
" js_std_free_handlers(rt);\n"
" JS_FreeContext(ctx);\n" " JS_FreeContext(ctx);\n"
" JS_FreeRuntime(rt);\n" " JS_FreeRuntime(rt);\n"
" return 0;\n" " return 0;\n"
@ -344,8 +345,8 @@ void help(void)
"usage: " PROG_NAME " [options] [files]\n" "usage: " PROG_NAME " [options] [files]\n"
"\n" "\n"
"options are:\n" "options are:\n"
"-c only output bytecode to a C file\n" "-c only output bytecode in a C file\n"
"-e output main() and bytecode to a C file (default = executable output)\n" "-e output main() and bytecode in a C file (default = executable output)\n"
"-o output set the output filename\n" "-o output set the output filename\n"
"-N cname set the C name of the generated data\n" "-N cname set the C name of the generated data\n"
"-m compile as Javascript module (default=autodetect)\n" "-m compile as Javascript module (default=autodetect)\n"

View file

@ -82,7 +82,6 @@ DEF(length, "length")
DEF(fileName, "fileName") DEF(fileName, "fileName")
DEF(lineNumber, "lineNumber") DEF(lineNumber, "lineNumber")
DEF(message, "message") DEF(message, "message")
DEF(cause, "cause")
DEF(errors, "errors") DEF(errors, "errors")
DEF(stack, "stack") DEF(stack, "stack")
DEF(name, "name") DEF(name, "name")
@ -167,23 +166,22 @@ DEF(revoke, "revoke")
DEF(async, "async") DEF(async, "async")
DEF(exec, "exec") DEF(exec, "exec")
DEF(groups, "groups") DEF(groups, "groups")
DEF(indices, "indices")
DEF(status, "status") DEF(status, "status")
DEF(reason, "reason") DEF(reason, "reason")
DEF(globalThis, "globalThis") DEF(globalThis, "globalThis")
DEF(bigint, "bigint")
#ifdef CONFIG_BIGNUM #ifdef CONFIG_BIGNUM
DEF(bigint, "bigint")
DEF(bigfloat, "bigfloat") DEF(bigfloat, "bigfloat")
DEF(bigdecimal, "bigdecimal") DEF(bigdecimal, "bigdecimal")
DEF(roundingMode, "roundingMode") DEF(roundingMode, "roundingMode")
DEF(maximumSignificantDigits, "maximumSignificantDigits") DEF(maximumSignificantDigits, "maximumSignificantDigits")
DEF(maximumFractionDigits, "maximumFractionDigits") DEF(maximumFractionDigits, "maximumFractionDigits")
#endif #endif
/* the following 3 atoms are only used with CONFIG_ATOMICS */ #ifdef CONFIG_ATOMICS
DEF(not_equal, "not-equal") DEF(not_equal, "not-equal")
DEF(timed_out, "timed-out") DEF(timed_out, "timed-out")
DEF(ok, "ok") DEF(ok, "ok")
/* */ #endif
DEF(toJSON, "toJSON") DEF(toJSON, "toJSON")
/* class names */ /* class names */
DEF(Object, "Object") DEF(Object, "Object")
@ -211,13 +209,15 @@ DEF(Int16Array, "Int16Array")
DEF(Uint16Array, "Uint16Array") DEF(Uint16Array, "Uint16Array")
DEF(Int32Array, "Int32Array") DEF(Int32Array, "Int32Array")
DEF(Uint32Array, "Uint32Array") DEF(Uint32Array, "Uint32Array")
#ifdef CONFIG_BIGNUM
DEF(BigInt64Array, "BigInt64Array") DEF(BigInt64Array, "BigInt64Array")
DEF(BigUint64Array, "BigUint64Array") DEF(BigUint64Array, "BigUint64Array")
#endif
DEF(Float32Array, "Float32Array") DEF(Float32Array, "Float32Array")
DEF(Float64Array, "Float64Array") DEF(Float64Array, "Float64Array")
DEF(DataView, "DataView") DEF(DataView, "DataView")
DEF(BigInt, "BigInt")
#ifdef CONFIG_BIGNUM #ifdef CONFIG_BIGNUM
DEF(BigInt, "BigInt")
DEF(BigFloat, "BigFloat") DEF(BigFloat, "BigFloat")
DEF(BigFloatEnv, "BigFloatEnv") DEF(BigFloatEnv, "BigFloatEnv")
DEF(BigDecimal, "BigDecimal") DEF(BigDecimal, "BigDecimal")

View file

@ -751,7 +751,6 @@ static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val,
JSValue ret; JSValue ret;
JSValueConst options_obj; JSValueConst options_obj;
BOOL backtrace_barrier = FALSE; BOOL backtrace_barrier = FALSE;
BOOL is_async = FALSE;
int flags; int flags;
if (argc >= 2) { if (argc >= 2) {
@ -759,9 +758,6 @@ static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val,
if (get_bool_option(ctx, &backtrace_barrier, options_obj, if (get_bool_option(ctx, &backtrace_barrier, options_obj,
"backtrace_barrier")) "backtrace_barrier"))
return JS_EXCEPTION; return JS_EXCEPTION;
if (get_bool_option(ctx, &is_async, options_obj,
"async"))
return JS_EXCEPTION;
} }
str = JS_ToCStringLen(ctx, &len, argv[0]); str = JS_ToCStringLen(ctx, &len, argv[0]);
@ -774,8 +770,6 @@ static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val,
flags = JS_EVAL_TYPE_GLOBAL; flags = JS_EVAL_TYPE_GLOBAL;
if (backtrace_barrier) if (backtrace_barrier)
flags |= JS_EVAL_FLAG_BACKTRACE_BARRIER; flags |= JS_EVAL_FLAG_BACKTRACE_BARRIER;
if (is_async)
flags |= JS_EVAL_FLAG_ASYNC;
ret = JS_Eval(ctx, str, len, "<evalScript>", flags); ret = JS_Eval(ctx, str, len, "<evalScript>", flags);
JS_FreeCString(ctx, str); JS_FreeCString(ctx, str);
if (!ts->recv_pipe && --ts->eval_script_recurse == 0) { if (!ts->recv_pipe && --ts->eval_script_recurse == 0) {
@ -1976,13 +1970,6 @@ static int64_t get_time_ms(void)
clock_gettime(CLOCK_MONOTONIC, &ts); clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000);
} }
static int64_t get_time_ns(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
}
#else #else
/* more portable, but does not work if the date is updated */ /* more portable, but does not work if the date is updated */
static int64_t get_time_ms(void) static int64_t get_time_ms(void)
@ -1991,21 +1978,8 @@ static int64_t get_time_ms(void)
gettimeofday(&tv, NULL); gettimeofday(&tv, NULL);
return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000);
} }
static int64_t get_time_ns(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return (int64_t)tv.tv_sec * 1000000000 + (tv.tv_usec * 1000);
}
#endif #endif
static JSValue js_os_now(JSContext *ctx, JSValue this_val,
int argc, JSValue *argv)
{
return JS_NewFloat64(ctx, (double)get_time_ns() / 1e6);
}
static void unlink_timer(JSRuntime *rt, JSOSTimer *th) static void unlink_timer(JSRuntime *rt, JSOSTimer *th)
{ {
if (th->link.prev) { if (th->link.prev) {
@ -2088,38 +2062,6 @@ static JSClassDef js_os_timer_class = {
.gc_mark = js_os_timer_mark, .gc_mark = js_os_timer_mark,
}; };
/* return a promise */
static JSValue js_os_sleepAsync(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSRuntime *rt = JS_GetRuntime(ctx);
JSThreadState *ts = JS_GetRuntimeOpaque(rt);
int64_t delay;
JSOSTimer *th;
JSValue promise, resolving_funcs[2];
if (JS_ToInt64(ctx, &delay, argv[0]))
return JS_EXCEPTION;
promise = JS_NewPromiseCapability(ctx, resolving_funcs);
if (JS_IsException(promise))
return JS_EXCEPTION;
th = js_mallocz(ctx, sizeof(*th));
if (!th) {
JS_FreeValue(ctx, promise);
JS_FreeValue(ctx, resolving_funcs[0]);
JS_FreeValue(ctx, resolving_funcs[1]);
return JS_EXCEPTION;
}
th->has_object = FALSE;
th->timeout = get_time_ms() + delay;
th->func = JS_DupValue(ctx, resolving_funcs[0]);
list_add_tail(&th->link, &ts->os_timers);
JS_FreeValue(ctx, resolving_funcs[0]);
JS_FreeValue(ctx, resolving_funcs[1]);
return promise;
}
static void call_handler(JSContext *ctx, JSValueConst func) static void call_handler(JSContext *ctx, JSValueConst func)
{ {
JSValue ret, func1; JSValue ret, func1;
@ -3088,13 +3030,6 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val,
goto done; goto done;
} }
/* getpid() -> pid */
static JSValue js_os_getpid(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
return JS_NewInt32(ctx, getpid());
}
/* waitpid(pid, block) -> [pid, status] */ /* waitpid(pid, block) -> [pid, status] */
static JSValue js_os_waitpid(JSContext *ctx, JSValueConst this_val, static JSValue js_os_waitpid(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) int argc, JSValueConst *argv)
@ -3339,7 +3274,6 @@ static void *worker_func(void *opaque)
JSRuntime *rt; JSRuntime *rt;
JSThreadState *ts; JSThreadState *ts;
JSContext *ctx; JSContext *ctx;
JSValue promise;
rt = JS_NewRuntime(); rt = JS_NewRuntime();
if (rt == NULL) { if (rt == NULL) {
@ -3366,11 +3300,8 @@ static void *worker_func(void *opaque)
js_std_add_helpers(ctx, -1, NULL); js_std_add_helpers(ctx, -1, NULL);
promise = JS_LoadModule(ctx, args->basename, args->filename); if (!JS_RunModule(ctx, args->basename, args->filename))
if (JS_IsException(promise))
js_std_dump_error(ctx); js_std_dump_error(ctx);
/* XXX: check */
JS_FreeValue(ctx, promise);
free(args->filename); free(args->filename);
free(args->basename); free(args->basename);
free(args); free(args);
@ -3690,10 +3621,8 @@ static const JSCFunctionListEntry js_os_funcs[] = {
OS_FLAG(SIGTTIN), OS_FLAG(SIGTTIN),
OS_FLAG(SIGTTOU), OS_FLAG(SIGTTOU),
#endif #endif
JS_CFUNC_DEF("now", 0, js_os_now ),
JS_CFUNC_DEF("setTimeout", 2, js_os_setTimeout ), JS_CFUNC_DEF("setTimeout", 2, js_os_setTimeout ),
JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ), JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ),
JS_CFUNC_DEF("sleepAsync", 1, js_os_sleepAsync ),
JS_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ), JS_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ),
JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ), JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ),
JS_CFUNC_DEF("chdir", 0, js_os_chdir ), JS_CFUNC_DEF("chdir", 0, js_os_chdir ),
@ -3721,7 +3650,6 @@ static const JSCFunctionListEntry js_os_funcs[] = {
JS_CFUNC_DEF("symlink", 2, js_os_symlink ), JS_CFUNC_DEF("symlink", 2, js_os_symlink ),
JS_CFUNC_DEF("readlink", 1, js_os_readlink ), JS_CFUNC_DEF("readlink", 1, js_os_readlink ),
JS_CFUNC_DEF("exec", 1, js_os_exec ), JS_CFUNC_DEF("exec", 1, js_os_exec ),
JS_CFUNC_DEF("getpid", 0, js_os_getpid ),
JS_CFUNC_DEF("waitpid", 2, js_os_waitpid ), JS_CFUNC_DEF("waitpid", 2, js_os_waitpid ),
OS_FLAG(WNOHANG), OS_FLAG(WNOHANG),
JS_CFUNC_DEF("pipe", 0, js_os_pipe ), JS_CFUNC_DEF("pipe", 0, js_os_pipe ),

View file

@ -172,7 +172,6 @@ DEF(set_loc_uninitialized, 3, 0, 0, loc)
DEF( get_loc_check, 3, 0, 1, loc) DEF( get_loc_check, 3, 0, 1, loc)
DEF( put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */ DEF( put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */
DEF( put_loc_check_init, 3, 1, 0, loc) DEF( put_loc_check_init, 3, 1, 0, loc)
DEF(get_loc_checkthis, 3, 0, 1, loc)
DEF(get_var_ref_check, 3, 0, 1, var_ref) DEF(get_var_ref_check, 3, 0, 1, var_ref)
DEF(put_var_ref_check, 3, 1, 0, var_ref) /* must come after get_var_ref_check */ DEF(put_var_ref_check, 3, 1, 0, var_ref) /* must come after get_var_ref_check */
DEF(put_var_ref_check_init, 3, 1, 0, var_ref) DEF(put_var_ref_check_init, 3, 1, 0, var_ref)
@ -183,7 +182,6 @@ DEF( goto, 5, 0, 0, label) /* must come after if_true */
DEF( catch, 5, 0, 1, label) DEF( catch, 5, 0, 1, label)
DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */ DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */
DEF( ret, 1, 1, 0, none) /* used to return from the finally block */ DEF( ret, 1, 1, 0, none) /* used to return from the finally block */
DEF( nip_catch, 1, 2, 1, none) /* catch ... a -> a */
DEF( to_object, 1, 1, 1, none) DEF( to_object, 1, 1, 1, none)
//DEF( to_string, 1, 1, 1, none) //DEF( to_string, 1, 1, 1, none)
@ -210,6 +208,7 @@ DEF( for_of_next, 2, 3, 5, u8)
DEF(iterator_check_object, 1, 1, 1, none) DEF(iterator_check_object, 1, 1, 1, none)
DEF(iterator_get_value_done, 1, 1, 2, none) DEF(iterator_get_value_done, 1, 1, 2, none)
DEF( iterator_close, 1, 3, 0, none) DEF( iterator_close, 1, 3, 0, none)
DEF(iterator_close_return, 1, 4, 4, none)
DEF( iterator_next, 1, 4, 4, none) DEF( iterator_next, 1, 4, 4, none)
DEF( iterator_call, 2, 4, 5, u8) DEF( iterator_call, 2, 4, 5, u8)
DEF( initial_yield, 1, 0, 0, none) DEF( initial_yield, 1, 0, 0, none)
@ -257,7 +256,6 @@ DEF( and, 1, 2, 1, none)
DEF( xor, 1, 2, 1, none) DEF( xor, 1, 2, 1, none)
DEF( or, 1, 2, 1, none) DEF( or, 1, 2, 1, none)
DEF(is_undefined_or_null, 1, 1, 1, none) DEF(is_undefined_or_null, 1, 1, 1, none)
DEF( private_in, 1, 2, 1, none)
#ifdef CONFIG_BIGNUM #ifdef CONFIG_BIGNUM
DEF( mul_pow10, 1, 2, 1, none) DEF( mul_pow10, 1, 2, 1, none)
DEF( math_mod, 1, 2, 1, none) DEF( math_mod, 1, 2, 1, none)
@ -272,8 +270,6 @@ def( leave_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */
def( label, 5, 0, 0, label) /* emitted in phase 1, removed in phase 3 */ def( label, 5, 0, 0, label) /* emitted in phase 1, removed in phase 3 */
/* the following opcodes must be in the same order as the 'with_x' and
get_var_undef, get_var and put_var opcodes */
def(scope_get_var_undef, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ def(scope_get_var_undef, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */
def( scope_get_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ def( scope_get_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */
def( scope_put_var, 7, 1, 0, atom_u16) /* emitted in phase 1, removed in phase 2 */ def( scope_put_var, 7, 1, 0, atom_u16) /* emitted in phase 1, removed in phase 2 */
@ -281,13 +277,10 @@ def(scope_delete_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase
def( scope_make_ref, 11, 0, 2, atom_label_u16) /* emitted in phase 1, removed in phase 2 */ def( scope_make_ref, 11, 0, 2, atom_label_u16) /* emitted in phase 1, removed in phase 2 */
def( scope_get_ref, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ def( scope_get_ref, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */
def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */
def(scope_get_var_checkthis, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2, only used to return 'this' in derived class constructors */
def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phase 1, removed in phase 2 */ def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phase 1, removed in phase 2 */
def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in phase 1, removed in phase 2 */ def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in phase 1, removed in phase 2 */
def(scope_put_private_field, 7, 2, 0, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */ def(scope_put_private_field, 7, 1, 1, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */
def(scope_in_private_field, 7, 1, 1, atom_u16) /* obj -> res emitted in phase 1, removed in phase 2 */
def(get_field_opt_chain, 5, 1, 1, atom) /* emitted in phase 1, removed in phase 2 */
def(get_array_el_opt_chain, 1, 2, 1, none) /* emitted in phase 1, removed in phase 2 */
def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */ def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */
def( line_num, 5, 0, 0, u32) /* emitted in phase 1, removed in phase 3 */ def( line_num, 5, 0, 0, u32) /* emitted in phase 1, removed in phase 3 */

File diff suppressed because it is too large Load diff

View file

@ -307,9 +307,6 @@ static inline JS_BOOL JS_VALUE_IS_NAN(JSValue v)
#define JS_EVAL_FLAG_COMPILE_ONLY (1 << 5) #define JS_EVAL_FLAG_COMPILE_ONLY (1 << 5)
/* don't include the stack frames before this eval in the Error() backtraces */ /* don't include the stack frames before this eval in the Error() backtraces */
#define JS_EVAL_FLAG_BACKTRACE_BARRIER (1 << 6) #define JS_EVAL_FLAG_BACKTRACE_BARRIER (1 << 6)
/* allow top-level await in normal script. JS_Eval() returns a
promise. Only allowed with JS_EVAL_TYPE_GLOBAL */
#define JS_EVAL_FLAG_ASYNC (1 << 7)
typedef JSValue JSCFunction(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); typedef JSValue JSCFunction(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
typedef JSValue JSCFunctionMagic(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic); typedef JSValue JSCFunctionMagic(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic);
@ -736,13 +733,13 @@ JSValue JS_GetPropertyStr(JSContext *ctx, JSValueConst this_obj,
JSValue JS_GetPropertyUint32(JSContext *ctx, JSValueConst this_obj, JSValue JS_GetPropertyUint32(JSContext *ctx, JSValueConst this_obj,
uint32_t idx); uint32_t idx);
int JS_SetPropertyInternal(JSContext *ctx, JSValueConst obj, int JS_SetPropertyInternal(JSContext *ctx, JSValueConst this_obj,
JSAtom prop, JSValue val, JSValueConst this_obj, JSAtom prop, JSValue val,
int flags); int flags);
static inline int JS_SetProperty(JSContext *ctx, JSValueConst this_obj, static inline int JS_SetProperty(JSContext *ctx, JSValueConst this_obj,
JSAtom prop, JSValue val) JSAtom prop, JSValue val)
{ {
return JS_SetPropertyInternal(ctx, this_obj, prop, val, this_obj, JS_PROP_THROW); return JS_SetPropertyInternal(ctx, this_obj, prop, val, JS_PROP_THROW);
} }
int JS_SetPropertyUint32(JSContext *ctx, JSValueConst this_obj, int JS_SetPropertyUint32(JSContext *ctx, JSValueConst this_obj,
uint32_t idx, JSValue val); uint32_t idx, JSValue val);
@ -834,15 +831,7 @@ typedef struct {
void JS_SetSharedArrayBufferFunctions(JSRuntime *rt, void JS_SetSharedArrayBufferFunctions(JSRuntime *rt,
const JSSharedArrayBufferFunctions *sf); const JSSharedArrayBufferFunctions *sf);
typedef enum JSPromiseStateEnum {
JS_PROMISE_PENDING,
JS_PROMISE_FULFILLED,
JS_PROMISE_REJECTED,
} JSPromiseStateEnum;
JSValue JS_NewPromiseCapability(JSContext *ctx, JSValue *resolving_funcs); JSValue JS_NewPromiseCapability(JSContext *ctx, JSValue *resolving_funcs);
JSPromiseStateEnum JS_PromiseState(JSContext *ctx, JSValue promise);
JSValue JS_PromiseResult(JSContext *ctx, JSValue promise);
/* is_handled = TRUE means that the rejection is handled */ /* is_handled = TRUE means that the rejection is handled */
typedef void JSHostPromiseRejectionTracker(JSContext *ctx, JSValueConst promise, typedef void JSHostPromiseRejectionTracker(JSContext *ctx, JSValueConst promise,
@ -913,8 +902,8 @@ int JS_ResolveModule(JSContext *ctx, JSValueConst obj);
/* only exported for os.Worker() */ /* only exported for os.Worker() */
JSAtom JS_GetScriptOrModuleName(JSContext *ctx, int n_stack_levels); JSAtom JS_GetScriptOrModuleName(JSContext *ctx, int n_stack_levels);
/* only exported for os.Worker() */ /* only exported for os.Worker() */
JSValue JS_LoadModule(JSContext *ctx, const char *basename, JSModuleDef *JS_RunModule(JSContext *ctx, const char *basename,
const char *filename); const char *filename);
/* C function definition */ /* C function definition */
typedef enum JSCFunctionEnum { /* XXX: should rename for namespace isolation */ typedef enum JSCFunctionEnum { /* XXX: should rename for namespace isolation */

View file

@ -42,7 +42,6 @@
//#define DUMP_TABLE_SIZE //#define DUMP_TABLE_SIZE
//#define DUMP_CC_TABLE //#define DUMP_CC_TABLE
//#define DUMP_DECOMP_TABLE //#define DUMP_DECOMP_TABLE
//#define DUMP_CASE_FOLDING_SPECIAL_CASES
/* Ideas: /* Ideas:
- Generalize run length encoding + index for all tables - Generalize run length encoding + index for all tables
@ -218,16 +217,15 @@ static const char *unicode_prop_short_name[] = {
#undef DEF #undef DEF
}; };
#undef UNICODE_PROP_LIST #undef UNICODE_SPROP_LIST
typedef struct { typedef struct {
/* case conv */ /* case conv */
uint8_t u_len; uint8_t u_len;
uint8_t l_len; uint8_t l_len;
uint8_t f_len; int u_data[CC_LEN_MAX];
int u_data[CC_LEN_MAX]; /* to upper case */ int l_data[CC_LEN_MAX];
int l_data[CC_LEN_MAX]; /* to lower case */ int f_code;
int f_data[CC_LEN_MAX]; /* to case folding */
uint8_t combining_class; uint8_t combining_class;
uint8_t is_compat:1; uint8_t is_compat:1;
@ -501,7 +499,7 @@ void parse_case_folding(CCInfo *tab, const char *filename)
FILE *f; FILE *f;
char line[1024]; char line[1024];
const char *p; const char *p;
int code, status; int code;
CCInfo *ci; CCInfo *ci;
f = fopen(filename, "rb"); f = fopen(filename, "rb");
@ -532,28 +530,14 @@ void parse_case_folding(CCInfo *tab, const char *filename)
/* locale dependent casing */ /* locale dependent casing */
while (isspace(*p)) while (isspace(*p))
p++; p++;
status = *p; if (*p != 'C' && *p != 'S')
if (status != 'C' && status != 'S' && status != 'F')
continue; continue;
p = get_field(line, 2); p = get_field(line, 2);
assert(p != NULL); assert(p != 0);
if (status == 'S') { assert(ci->f_code == 0);
/* we always select the simple case folding and assume it ci->f_code = strtoul(p, NULL, 16);
* comes after the full case folding case */ assert(ci->f_code != 0 && ci->f_code != code);
assert(ci->f_len >= 2);
ci->f_len = 0;
} else {
assert(ci->f_len == 0);
}
for(;;) {
while (isspace(*p))
p++;
if (*p == ';')
break;
assert(ci->l_len < CC_LEN_MAX);
ci->f_data[ci->f_len++] = strtoul(p, (char **)&p, 16);
}
} }
fclose(f); fclose(f);
@ -880,21 +864,19 @@ void dump_cc_info(CCInfo *ci, int i)
for(j = 0; j < ci->l_len; j++) for(j = 0; j < ci->l_len; j++)
printf(" %05x", ci->l_data[j]); printf(" %05x", ci->l_data[j]);
} }
if (ci->f_len != 0) { if (ci->f_code != 0) {
printf(" F:"); printf(" F: %05x", ci->f_code);
for(j = 0; j < ci->f_len; j++)
printf(" %05x", ci->f_data[j]);
} }
printf("\n"); printf("\n");
} }
void dump_unicode_data(CCInfo *tab) void dump_data(CCInfo *tab)
{ {
int i; int i;
CCInfo *ci; CCInfo *ci;
for(i = 0; i <= CHARCODE_MAX; i++) { for(i = 0; i <= CHARCODE_MAX; i++) {
ci = &tab[i]; ci = &tab[i];
if (ci->u_len != 0 || ci->l_len != 0 || ci->f_len != 0) { if (ci->u_len != 0 || ci->l_len != 0 || ci->f_code != 0) {
dump_cc_info(ci, i); dump_cc_info(ci, i);
} }
} }
@ -904,8 +886,8 @@ BOOL is_complicated_case(const CCInfo *ci)
{ {
return (ci->u_len > 1 || ci->l_len > 1 || return (ci->u_len > 1 || ci->l_len > 1 ||
(ci->u_len > 0 && ci->l_len > 0) || (ci->u_len > 0 && ci->l_len > 0) ||
(ci->f_len != ci->l_len) || (ci->f_code != 0) != ci->l_len ||
(memcmp(ci->f_data, ci->l_data, ci->f_len * sizeof(ci->f_data[0])) != 0)); (ci->f_code != 0 && ci->l_data[0] != ci->f_code));
} }
#ifndef USE_TEST #ifndef USE_TEST
@ -921,9 +903,9 @@ enum {
RUN_TYPE_UF_D1_EXT, RUN_TYPE_UF_D1_EXT,
RUN_TYPE_U_EXT, RUN_TYPE_U_EXT,
RUN_TYPE_LF_EXT, RUN_TYPE_LF_EXT,
RUN_TYPE_UF_EXT2, RUN_TYPE_U_EXT2,
RUN_TYPE_LF_EXT2, RUN_TYPE_L_EXT2,
RUN_TYPE_UF_EXT3, RUN_TYPE_U_EXT3,
}; };
#endif #endif
@ -939,9 +921,9 @@ const char *run_type_str[] = {
"UF_D1_EXT", "UF_D1_EXT",
"U_EXT", "U_EXT",
"LF_EXT", "LF_EXT",
"UF_EXT2", "U_EXT2",
"LF_EXT2", "L_EXT2",
"UF_EXT3", "U_EXT3",
}; };
typedef struct { typedef struct {
@ -954,13 +936,6 @@ typedef struct {
int data_index; /* 'data' coming from the table */ int data_index; /* 'data' coming from the table */
} TableEntry; } TableEntry;
static int simple_to_lower(CCInfo *tab, int c)
{
if (tab[c].l_len != 1)
return c;
return tab[c].l_data[0];
}
/* code (17), len (7), type (4) */ /* code (17), len (7), type (4) */
void find_run_type(TableEntry *te, CCInfo *tab, int code) void find_run_type(TableEntry *te, CCInfo *tab, int code)
@ -974,15 +949,15 @@ void find_run_type(TableEntry *te, CCInfo *tab, int code)
te->code = code; te->code = code;
if (ci->l_len == 1 && ci->l_data[0] == code + 2 && if (ci->l_len == 1 && ci->l_data[0] == code + 2 &&
ci->f_len == 1 && ci->f_data[0] == ci->l_data[0] && ci->f_code == ci->l_data[0] &&
ci->u_len == 0 && ci->u_len == 0 &&
ci1->l_len == 1 && ci1->l_data[0] == code + 2 && ci1->l_len == 1 && ci1->l_data[0] == code + 2 &&
ci1->f_len == 1 && ci1->f_data[0] == ci1->l_data[0] && ci1->f_code == ci1->l_data[0] &&
ci1->u_len == 1 && ci1->u_data[0] == code && ci1->u_len == 1 && ci1->u_data[0] == code &&
ci2->l_len == 0 && ci2->l_len == 0 &&
ci2->f_len == 0 && ci2->f_code == 0 &&
ci2->u_len == 1 && ci2->u_data[0] == code) { ci2->u_len == 1 && ci2->u_data[0] == code) {
te->len = 3; te->len = 3;
te->data = 0; te->data = 0;
@ -997,7 +972,7 @@ void find_run_type(TableEntry *te, CCInfo *tab, int code)
if (ci1->u_len != 1 || if (ci1->u_len != 1 ||
ci1->u_data[0] != ci->u_data[0] + len || ci1->u_data[0] != ci->u_data[0] + len ||
ci1->l_len != 0 || ci1->l_len != 0 ||
ci1->f_len != 1 || ci1->f_data[0] != ci1->u_data[0]) ci1->f_code != ci1->u_data[0])
break; break;
len++; len++;
} }
@ -1008,25 +983,21 @@ void find_run_type(TableEntry *te, CCInfo *tab, int code)
return; return;
} }
if (ci->l_len == 0 && if (ci->u_len == 2 && ci->u_data[1] == 0x399 &&
ci->u_len == 2 && ci->u_data[1] == 0x399 && ci->f_code == 0 && ci->l_len == 0) {
ci->f_len == 2 && ci->f_data[1] == 0x3B9 &&
ci->f_data[0] == simple_to_lower(tab, ci->u_data[0])) {
len = 1; len = 1;
while (code + len <= CHARCODE_MAX) { while (code + len <= CHARCODE_MAX) {
ci1 = &tab[code + len]; ci1 = &tab[code + len];
if (!(ci1->u_len == 2 && if (!(ci1->u_len == 2 &&
ci1->u_data[1] == ci->u_data[1] && ci1->u_data[1] == 0x399 &&
ci1->u_data[0] == ci->u_data[0] + len && ci1->u_data[0] == ci->u_data[0] + len &&
ci1->f_len == 2 && ci1->f_code == 0 &&
ci1->f_data[1] == ci->f_data[1] &&
ci1->f_data[0] == ci->f_data[0] + len &&
ci1->l_len == 0)) ci1->l_len == 0))
break; break;
len++; len++;
} }
te->len = len; te->len = len;
te->type = RUN_TYPE_UF_EXT2; te->type = RUN_TYPE_U_EXT2;
te->ext_data[0] = ci->u_data[0]; te->ext_data[0] = ci->u_data[0];
te->ext_data[1] = ci->u_data[1]; te->ext_data[1] = ci->u_data[1];
te->ext_len = 2; te->ext_len = 2;
@ -1034,8 +1005,7 @@ void find_run_type(TableEntry *te, CCInfo *tab, int code)
} }
if (ci->u_len == 2 && ci->u_data[1] == 0x399 && if (ci->u_len == 2 && ci->u_data[1] == 0x399 &&
ci->l_len == 1 && ci->l_len == 1 && ci->f_code == ci->l_data[0]) {
ci->f_len == 1 && ci->f_data[0] == ci->l_data[0]) {
len = 1; len = 1;
while (code + len <= CHARCODE_MAX) { while (code + len <= CHARCODE_MAX) {
ci1 = &tab[code + len]; ci1 = &tab[code + len];
@ -1044,7 +1014,7 @@ void find_run_type(TableEntry *te, CCInfo *tab, int code)
ci1->u_data[0] == ci->u_data[0] + len && ci1->u_data[0] == ci->u_data[0] + len &&
ci1->l_len == 1 && ci1->l_len == 1 &&
ci1->l_data[0] == ci->l_data[0] + len && ci1->l_data[0] == ci->l_data[0] + len &&
ci1->f_len == 1 && ci1->f_data[0] == ci1->l_data[0])) ci1->f_code == ci1->l_data[0]))
break; break;
len++; len++;
} }
@ -1056,13 +1026,13 @@ void find_run_type(TableEntry *te, CCInfo *tab, int code)
return; return;
} }
if (ci->l_len == 1 && ci->u_len == 0 && ci->f_len == 0) { if (ci->l_len == 1 && ci->u_len == 0 && ci->f_code == 0) {
len = 1; len = 1;
while (code + len <= CHARCODE_MAX) { while (code + len <= CHARCODE_MAX) {
ci1 = &tab[code + len]; ci1 = &tab[code + len];
if (!(ci1->l_len == 1 && if (!(ci1->l_len == 1 &&
ci1->l_data[0] == ci->l_data[0] + len && ci1->l_data[0] == ci->l_data[0] + len &&
ci1->u_len == 0 && ci1->f_len == 0)) ci1->u_len == 0 && ci1->f_code == 0))
break; break;
len++; len++;
} }
@ -1075,39 +1045,32 @@ void find_run_type(TableEntry *te, CCInfo *tab, int code)
if (ci->l_len == 0 && if (ci->l_len == 0 &&
ci->u_len == 1 && ci->u_len == 1 &&
ci->u_data[0] < 0x1000 && ci->u_data[0] < 0x1000 &&
ci->f_len == 1 && ci->f_data[0] == ci->u_data[0] + 0x20) { ci->f_code == ci->u_data[0] + 0x20) {
te->len = 1; te->len = 1;
te->type = RUN_TYPE_UF_D20; te->type = RUN_TYPE_UF_D20;
te->data = ci->u_data[0]; te->data = ci->u_data[0];
} else if (ci->l_len == 0 && } else if (ci->l_len == 0 &&
ci->u_len == 1 && ci->u_len == 1 &&
ci->f_len == 1 && ci->f_data[0] == ci->u_data[0] + 1) { ci->f_code == ci->u_data[0] + 1) {
te->len = 1; te->len = 1;
te->type = RUN_TYPE_UF_D1_EXT; te->type = RUN_TYPE_UF_D1_EXT;
te->ext_data[0] = ci->u_data[0]; te->ext_data[0] = ci->u_data[0];
te->ext_len = 1; te->ext_len = 1;
} else if (ci->l_len == 2 && ci->u_len == 0 && ci->f_len == 2 && } else if (ci->l_len == 2 && ci->u_len == 0 && ci->f_code == 0) {
ci->l_data[0] == ci->f_data[0] &&
ci->l_data[1] == ci->f_data[1]) {
te->len = 1; te->len = 1;
te->type = RUN_TYPE_LF_EXT2; te->type = RUN_TYPE_L_EXT2;
te->ext_data[0] = ci->l_data[0]; te->ext_data[0] = ci->l_data[0];
te->ext_data[1] = ci->l_data[1]; te->ext_data[1] = ci->l_data[1];
te->ext_len = 2; te->ext_len = 2;
} else if (ci->u_len == 2 && ci->l_len == 0 && ci->f_len == 2 && } else if (ci->u_len == 2 && ci->l_len == 0 && ci->f_code == 0) {
ci->f_data[0] == simple_to_lower(tab, ci->u_data[0]) &&
ci->f_data[1] == simple_to_lower(tab, ci->u_data[1])) {
te->len = 1; te->len = 1;
te->type = RUN_TYPE_UF_EXT2; te->type = RUN_TYPE_U_EXT2;
te->ext_data[0] = ci->u_data[0]; te->ext_data[0] = ci->u_data[0];
te->ext_data[1] = ci->u_data[1]; te->ext_data[1] = ci->u_data[1];
te->ext_len = 2; te->ext_len = 2;
} else if (ci->u_len == 3 && ci->l_len == 0 && ci->f_len == 3 && } else if (ci->u_len == 3 && ci->l_len == 0 && ci->f_code == 0) {
ci->f_data[0] == simple_to_lower(tab, ci->u_data[0]) &&
ci->f_data[1] == simple_to_lower(tab, ci->u_data[1]) &&
ci->f_data[2] == simple_to_lower(tab, ci->u_data[2])) {
te->len = 1; te->len = 1;
te->type = RUN_TYPE_UF_EXT3; te->type = RUN_TYPE_U_EXT3;
te->ext_data[0] = ci->u_data[0]; te->ext_data[0] = ci->u_data[0];
te->ext_data[1] = ci->u_data[1]; te->ext_data[1] = ci->u_data[1];
te->ext_data[2] = ci->u_data[2]; te->ext_data[2] = ci->u_data[2];
@ -1225,7 +1188,7 @@ void build_conv_table(CCInfo *tab)
te = conv_table; te = conv_table;
for(code = 0; code <= CHARCODE_MAX; code++) { for(code = 0; code <= CHARCODE_MAX; code++) {
ci = &tab[code]; ci = &tab[code];
if (ci->u_len == 0 && ci->l_len == 0 && ci->f_len == 0) if (ci->u_len == 0 && ci->l_len == 0 && ci->f_code == 0)
continue; continue;
assert(te - conv_table < countof(conv_table)); assert(te - conv_table < countof(conv_table));
find_run_type(te, tab, code); find_run_type(te, tab, code);
@ -1281,7 +1244,7 @@ void build_conv_table(CCInfo *tab)
/* find the data index for ext_data */ /* find the data index for ext_data */
for(i = 0; i < conv_table_len; i++) { for(i = 0; i < conv_table_len; i++) {
te = &conv_table[i]; te = &conv_table[i];
if (te->type == RUN_TYPE_UF_EXT3) { if (te->type == RUN_TYPE_U_EXT3) {
int p, v; int p, v;
v = 0; v = 0;
for(j = 0; j < 3; j++) { for(j = 0; j < 3; j++) {
@ -1295,8 +1258,8 @@ void build_conv_table(CCInfo *tab)
for(i = 0; i < conv_table_len; i++) { for(i = 0; i < conv_table_len; i++) {
te = &conv_table[i]; te = &conv_table[i];
if (te->type == RUN_TYPE_LF_EXT2 || if (te->type == RUN_TYPE_L_EXT2 ||
te->type == RUN_TYPE_UF_EXT2 || te->type == RUN_TYPE_U_EXT2 ||
te->type == RUN_TYPE_U2L_399_EXT2) { te->type == RUN_TYPE_U2L_399_EXT2) {
int p, v; int p, v;
v = 0; v = 0;
@ -1359,54 +1322,6 @@ void dump_case_conv_table(FILE *f)
fprintf(f, "\n};\n\n"); fprintf(f, "\n};\n\n");
} }
static CCInfo *global_tab;
static int sp_cc_cmp(const void *p1, const void *p2)
{
CCInfo *c1 = &global_tab[*(const int *)p1];
CCInfo *c2 = &global_tab[*(const int *)p2];
if (c1->f_len < c2->f_len) {
return -1;
} else if (c2->f_len < c1->f_len) {
return 1;
} else {
return memcmp(c1->f_data, c2->f_data, sizeof(c1->f_data[0]) * c1->f_len);
}
}
/* dump the case special cases (multi character results which are
identical and need specific handling in lre_canonicalize() */
void dump_case_folding_special_cases(CCInfo *tab)
{
int i, len, j;
int *perm;
perm = malloc(sizeof(perm[0]) * (CHARCODE_MAX + 1));
for(i = 0; i <= CHARCODE_MAX; i++)
perm[i] = i;
global_tab = tab;
qsort(perm, CHARCODE_MAX + 1, sizeof(perm[0]), sp_cc_cmp);
for(i = 0; i <= CHARCODE_MAX;) {
if (tab[perm[i]].f_len <= 1) {
i++;
} else {
len = 1;
while ((i + len) <= CHARCODE_MAX && !sp_cc_cmp(&perm[i], &perm[i + len]))
len++;
if (len > 1) {
for(j = i; j < i + len; j++)
dump_cc_info(&tab[perm[j]], perm[j]);
}
i += len;
}
}
free(perm);
global_tab = NULL;
}
int tabcmp(const int *tab1, const int *tab2, int n) int tabcmp(const int *tab1, const int *tab2, int n)
{ {
int i; int i;
@ -1433,7 +1348,7 @@ void compute_internal_props(void)
for(i = 0; i <= CHARCODE_MAX; i++) { for(i = 0; i <= CHARCODE_MAX; i++) {
CCInfo *ci = &unicode_db[i]; CCInfo *ci = &unicode_db[i];
has_ul = (ci->u_len != 0 || ci->l_len != 0 || ci->f_len != 0); has_ul = (ci->u_len != 0 || ci->l_len != 0 || ci->f_code != 0);
if (has_ul) { if (has_ul) {
assert(get_prop(i, PROP_Cased)); assert(get_prop(i, PROP_Cased));
} else { } else {
@ -1448,10 +1363,10 @@ void compute_internal_props(void)
set_prop(i, PROP_Changes_When_Titlecased1, set_prop(i, PROP_Changes_When_Titlecased1,
get_prop(i, PROP_Changes_When_Titlecased) ^ (ci->u_len != 0)); get_prop(i, PROP_Changes_When_Titlecased) ^ (ci->u_len != 0));
set_prop(i, PROP_Changes_When_Casefolded1, set_prop(i, PROP_Changes_When_Casefolded1,
get_prop(i, PROP_Changes_When_Casefolded) ^ (ci->f_len != 0)); get_prop(i, PROP_Changes_When_Casefolded) ^ (ci->f_code != 0));
/* XXX: reduce table size (438 bytes) */ /* XXX: reduce table size (438 bytes) */
set_prop(i, PROP_Changes_When_NFKC_Casefolded1, set_prop(i, PROP_Changes_When_NFKC_Casefolded1,
get_prop(i, PROP_Changes_When_NFKC_Casefolded) ^ (ci->f_len != 0)); get_prop(i, PROP_Changes_When_NFKC_Casefolded) ^ (ci->f_code != 0));
#if 0 #if 0
/* TEST */ /* TEST */
#define M(x) (1U << GCAT_ ## x) #define M(x) (1U << GCAT_ ## x)
@ -1882,10 +1797,8 @@ void check_case_conv(void)
ci->u_len = 1; ci->u_len = 1;
ci->u_data[0] = code; ci->u_data[0] = code;
} }
if (ci->f_len == 0) { if (ci->f_code == 0)
ci->f_len = 1; ci->f_code = code;
ci->f_data[0] = code;
}
error = 0; error = 0;
l = check_conv(res, code, 0); l = check_conv(res, code, 0);
@ -1899,7 +1812,7 @@ void check_case_conv(void)
error++; error++;
} }
l = check_conv(res, code, 2); l = check_conv(res, code, 2);
if (l != ci->f_len || tabcmp((int *)res, ci->f_data, l)) { if (l != 1 || res[0] != ci->f_code) {
printf("ERROR: F\n"); printf("ERROR: F\n");
error++; error++;
} }
@ -3094,12 +3007,11 @@ int main(int argc, char **argv)
unicode_db_path); unicode_db_path);
parse_prop_list(filename); parse_prop_list(filename);
// dump_unicode_data(unicode_db); // dump_data(unicode_db);
build_conv_table(unicode_db);
#ifdef DUMP_CASE_FOLDING_SPECIAL_CASES build_conv_table(unicode_db);
dump_case_folding_special_cases(unicode_db);
#endif // dump_table();
if (!outfilename) { if (!outfilename) {
#ifdef USE_TEST #ifdef USE_TEST

View file

@ -72,7 +72,6 @@ DEF(Coptic, "Copt,Qaac")
DEF(Cuneiform, "Xsux") DEF(Cuneiform, "Xsux")
DEF(Cypriot, "Cprt") DEF(Cypriot, "Cprt")
DEF(Cyrillic, "Cyrl") DEF(Cyrillic, "Cyrl")
DEF(Cypro_Minoan, "Cpmn")
DEF(Deseret, "Dsrt") DEF(Deseret, "Dsrt")
DEF(Devanagari, "Deva") DEF(Devanagari, "Deva")
DEF(Dives_Akuru, "Diak") DEF(Dives_Akuru, "Diak")
@ -105,7 +104,6 @@ DEF(Javanese, "Java")
DEF(Kaithi, "Kthi") DEF(Kaithi, "Kthi")
DEF(Kannada, "Knda") DEF(Kannada, "Knda")
DEF(Katakana, "Kana") DEF(Katakana, "Kana")
DEF(Kawi, "Kawi")
DEF(Kayah_Li, "Kali") DEF(Kayah_Li, "Kali")
DEF(Kharoshthi, "Khar") DEF(Kharoshthi, "Khar")
DEF(Khmer, "Khmr") DEF(Khmer, "Khmr")
@ -140,7 +138,6 @@ DEF(Mro, "Mroo")
DEF(Multani, "Mult") DEF(Multani, "Mult")
DEF(Myanmar, "Mymr") DEF(Myanmar, "Mymr")
DEF(Nabataean, "Nbat") DEF(Nabataean, "Nbat")
DEF(Nag_Mundari, "Nagm")
DEF(Nandinagari, "Nand") DEF(Nandinagari, "Nand")
DEF(New_Tai_Lue, "Talu") DEF(New_Tai_Lue, "Talu")
DEF(Newa, "Newa") DEF(Newa, "Newa")
@ -157,7 +154,6 @@ DEF(Old_Persian, "Xpeo")
DEF(Old_Sogdian, "Sogo") DEF(Old_Sogdian, "Sogo")
DEF(Old_South_Arabian, "Sarb") DEF(Old_South_Arabian, "Sarb")
DEF(Old_Turkic, "Orkh") DEF(Old_Turkic, "Orkh")
DEF(Old_Uyghur, "Ougr")
DEF(Oriya, "Orya") DEF(Oriya, "Orya")
DEF(Osage, "Osge") DEF(Osage, "Osge")
DEF(Osmanya, "Osma") DEF(Osmanya, "Osma")
@ -196,11 +192,8 @@ DEF(Thai, "Thai")
DEF(Tibetan, "Tibt") DEF(Tibetan, "Tibt")
DEF(Tifinagh, "Tfng") DEF(Tifinagh, "Tfng")
DEF(Tirhuta, "Tirh") DEF(Tirhuta, "Tirh")
DEF(Tangsa, "Tnsa")
DEF(Toto, "Toto")
DEF(Ugaritic, "Ugar") DEF(Ugaritic, "Ugar")
DEF(Vai, "Vaii") DEF(Vai, "Vaii")
DEF(Vithkuqi, "Vith")
DEF(Wancho, "Wcho") DEF(Wancho, "Wcho")
DEF(Warang_Citi, "Wara") DEF(Warang_Citi, "Wara")
DEF(Yezidi, "Yezi") DEF(Yezidi, "Yezi")

View file

@ -1,3 +1,3 @@
# Configuration for the [cargo-release](https://github.com/sunng87/cargo-release) tool. # Configuration for the [cargo-release](https://github.com/sunng87/cargo-release) tool.
tag-prefix = "libquickjs-dtp-sys-" tag-prefix = "libquickjs-sys-"

View file

@ -6,7 +6,6 @@
#![allow(non_upper_case_globals)] #![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
#![allow(non_snake_case)] #![allow(non_snake_case)]
#![allow(clippy::missing_safety_doc)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs")); include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
@ -34,7 +33,7 @@ mod tests {
let value = JS_Eval( let value = JS_Eval(
ctx, ctx,
code.as_ptr(), code.as_ptr(),
code_str.len() - 1, (code_str.len() - 1) as u64,
script.as_ptr(), script.as_ptr(),
JS_EVAL_TYPE_GLOBAL as i32, JS_EVAL_TYPE_GLOBAL as i32,
); );

View file

@ -1,7 +1,7 @@
# Configuration for the [cargo-release](https://github.com/sunng87/cargo-release) tool. # Configuration for the [cargo-release](https://github.com/sunng87/cargo-release) tool.
pre-release-replacements = [ pre-release-replacements = [
{file="README.md", search="quick-js-dtp = .*", replace="{{crate_name}} = \"{{version}}\""}, {file="README.md", search="quick-js = .*", replace="{{crate_name}} = \"{{version}}\""},
] ]
tag-prefix = "quick-js-dtp-" tag-prefix = "quick-js-"

View file

@ -47,7 +47,7 @@ pub fn run_compiled_function<'a>(
context.ensure_no_excpetion().map_err(|e| { context.ensure_no_excpetion().map_err(|e| {
if let ExecutionError::Internal(msg) = e { if let ExecutionError::Internal(msg) = e {
ExecutionError::Internal(format!("Could not evaluate compiled function: {msg}")) ExecutionError::Internal(format!("Could not evaluate compiled function: {}", msg))
} else { } else {
e e
} }
@ -66,7 +66,7 @@ pub fn to_bytecode(context: &ContextWrapper, compiled_func: &JsCompiledFunction)
*compiled_func.as_value().as_inner(), *compiled_func.as_value().as_inner(),
q::JS_WRITE_OBJ_BYTECODE as i32, q::JS_WRITE_OBJ_BYTECODE as i32,
); );
let slice = std::slice::from_raw_parts(raw, len); let slice = std::slice::from_raw_parts(raw, len as usize);
let data = slice.to_vec(); let data = slice.to_vec();
q::js_free(context.context, raw as *mut c_void); q::js_free(context.context, raw as *mut c_void);
data data
@ -122,6 +122,7 @@ pub mod tests {
"test_func.es", "test_func.es",
); );
let func = func_res let func = func_res
.ok()
.expect("func compile failed") .expect("func compile failed")
.try_into_compiled_function() .try_into_compiled_function()
.unwrap(); .unwrap();
@ -131,6 +132,7 @@ pub mod tests {
let func2_res = from_bytecode(&ctx, &bytecode); let func2_res = from_bytecode(&ctx, &bytecode);
let func2 = func2_res let func2 = func2_res
.ok()
.expect("could not read bytecode") .expect("could not read bytecode")
.try_into_compiled_function() .try_into_compiled_function()
.unwrap(); .unwrap();
@ -155,6 +157,7 @@ pub mod tests {
"test_func.es", "test_func.es",
); );
let func = func_res let func = func_res
.ok()
.expect("func compile failed") .expect("func compile failed")
.try_into_compiled_function() .try_into_compiled_function()
.unwrap(); .unwrap();
@ -163,6 +166,7 @@ pub mod tests {
assert!(!bytecode.is_empty()); assert!(!bytecode.is_empty());
let func2_res = from_bytecode(&ctx, &bytecode); let func2_res = from_bytecode(&ctx, &bytecode);
let func2 = func2_res let func2 = func2_res
.ok()
.expect("could not read bytecode") .expect("could not read bytecode")
.try_into_compiled_function() .try_into_compiled_function()
.unwrap(); .unwrap();
@ -187,7 +191,7 @@ pub mod tests {
"{the changes of me compil1ng a're slim to 0-0}", "{the changes of me compil1ng a're slim to 0-0}",
"test_func_fail.es", "test_func_fail.es",
); );
func_res.expect_err("func compiled unexpectedly"); func_res.err().expect("func compiled unexpectedly");
} }
#[test] #[test]
@ -196,6 +200,7 @@ pub mod tests {
let func_res = compile(&ctx, "let abcdef = 1;", "test_func_runfail.es"); let func_res = compile(&ctx, "let abcdef = 1;", "test_func_runfail.es");
let func = func_res let func = func_res
.ok()
.expect("func compile failed") .expect("func compile failed")
.try_into_compiled_function() .try_into_compiled_function()
.unwrap(); .unwrap();
@ -227,7 +232,9 @@ pub mod tests {
assert_eq!(1, func2.as_value().get_ref_count()); assert_eq!(1, func2.as_value().get_ref_count());
let _run_res2 = run_compiled_function(&func2).expect_err("run 2 succeeded unexpectedly"); let _run_res2 = run_compiled_function(&func2)
.err()
.expect("run 2 succeeded unexpectedly");
assert_eq!(1, func2.as_value().get_ref_count()); assert_eq!(1, func2.as_value().get_ref_count());
} }

View file

@ -221,13 +221,13 @@ pub(super) fn serialize_value(
q::JS_NewStringLen( q::JS_NewStringLen(
context, context,
bigint_string.as_ptr() as *const c_char, bigint_string.as_ptr() as *const c_char,
bigint_string.len(), bigint_string.len() as q::size_t,
) )
}; };
let s = DroppableValue::new(s, |&mut s| unsafe { let s = DroppableValue::new(s, |&mut s| unsafe {
q::JS_FreeValue(context, s); q::JS_FreeValue(context, s);
}); });
if s.tag != TAG_STRING { if (*s).tag != TAG_STRING {
return Err(ValueError::Internal( return Err(ValueError::Internal(
"Could not construct String object needed to create BigInt object".into(), "Could not construct String object needed to create BigInt object".into(),
)); ));
@ -451,23 +451,20 @@ pub(super) fn deserialize_value(
q::JS_FreeValue(context, date_constructor); q::JS_FreeValue(context, date_constructor);
}; };
let timestamp_ms = if timestamp_raw.tag == TAG_FLOAT64 { let res = if timestamp_raw.tag == TAG_FLOAT64 {
(unsafe { timestamp_raw.u.float64 } as i64) let f = unsafe { timestamp_raw.u.float64 } as i64;
let datetime = chrono::Utc.timestamp_millis(f);
Ok(JsValue::Date(datetime))
} else if timestamp_raw.tag == TAG_INT { } else if timestamp_raw.tag == TAG_INT {
(unsafe { timestamp_raw.u.int32 } as i64) let f = unsafe { timestamp_raw.u.int32 } as i64;
let datetime = chrono::Utc.timestamp_millis(f);
Ok(JsValue::Date(datetime))
} else { } else {
return Err(ValueError::Internal( Err(ValueError::Internal(
"Could not convert 'Date' instance to timestamp".into(), "Could not convert 'Date' instance to timestamp".into(),
)); ))
}; };
return res;
return chrono::Utc
.timestamp_millis_opt(timestamp_ms)
.single()
.map(JsValue::Date)
.ok_or(ValueError::Internal(
"Could not convert 'Date' instance to timestamp".into(),
));
} else { } else {
unsafe { q::JS_FreeValue(context, date_constructor) }; unsafe { q::JS_FreeValue(context, date_constructor) };
} }
@ -505,6 +502,9 @@ pub(super) fn deserialize_value(
})) }))
} }
} }
x => Err(ValueError::Internal(format!("Unhandled JS_TAG value: {x}"))), x => Err(ValueError::Internal(format!(
"Unhandled JS_TAG value: {}",
x
))),
} }
} }

View file

@ -19,7 +19,6 @@ use crate::{
use value::{JsFunction, OwnedJsObject}; use value::{JsFunction, OwnedJsObject};
#[allow(unused_imports)]
pub use value::{JsCompiledFunction, OwnedJsValue}; pub use value::{JsCompiledFunction, OwnedJsValue};
// JS_TAG_* constants from quickjs. // JS_TAG_* constants from quickjs.
@ -270,11 +269,13 @@ impl<'a> OwnedObjectRef<'a> {
if raw.tag == TAG_EXCEPTION { if raw.tag == TAG_EXCEPTION {
Err(ExecutionError::Internal(format!( Err(ExecutionError::Internal(format!(
"Exception while getting property '{name}'" "Exception while getting property '{}'",
name
))) )))
} else if raw.tag == TAG_UNDEFINED { } else if raw.tag == TAG_UNDEFINED {
Err(ExecutionError::Internal(format!( Err(ExecutionError::Internal(format!(
"Property '{name}' not found" "Property '{}' not found",
name
))) )))
} else { } else {
Ok(OwnedValueRef::new(self.value.context, raw)) Ok(OwnedValueRef::new(self.value.context, raw))
@ -680,10 +681,10 @@ impl ContextWrapper {
} }
/// Add a global JS function that is backed by a Rust function or closure. /// Add a global JS function that is backed by a Rust function or closure.
pub fn create_callback<F>( pub fn create_callback<'a, F>(
&self, &'a self,
callback: impl Callback<F> + 'static, callback: impl Callback<F> + 'static,
) -> Result<JsFunction, ExecutionError> { ) -> Result<JsFunction<'a>, ExecutionError> {
let argcount = callback.argument_count() as i32; let argcount = callback.argument_count() as i32;
let context = self.context; let context = self.context;
@ -723,8 +724,8 @@ impl ContextWrapper {
Ok(f) Ok(f)
} }
pub fn add_callback<F>( pub fn add_callback<'a, F>(
&self, &'a self,
name: &str, name: &str,
callback: impl Callback<F> + 'static, callback: impl Callback<F> + 'static,
) -> Result<(), ExecutionError> { ) -> Result<(), ExecutionError> {

View file

@ -432,7 +432,8 @@ impl<'a> OwnedJsObject<'a> {
if tag.is_exception() { if tag.is_exception() {
Err(ExecutionError::Internal(format!( Err(ExecutionError::Internal(format!(
"Exception while getting property '{name}'" "Exception while getting property '{}'",
name
))) )))
} else if tag.is_undefined() { } else if tag.is_undefined() {
Ok(None) Ok(None)
@ -443,7 +444,7 @@ impl<'a> OwnedJsObject<'a> {
pub fn property_require(&self, name: &str) -> Result<OwnedJsValue<'a>, ExecutionError> { pub fn property_require(&self, name: &str) -> Result<OwnedJsValue<'a>, ExecutionError> {
self.property(name)? self.property(name)?
.ok_or_else(|| ExecutionError::Internal(format!("Property '{name}' not found"))) .ok_or_else(|| ExecutionError::Internal(format!("Property '{}' not found", name)))
} }
/// Determine if the object is a promise by checking the presence of /// Determine if the object is a promise by checking the presence of

View file

@ -104,7 +104,7 @@ where
} }
fn call(&self, args: Vec<JsValue>) -> Result<Result<JsValue, String>, ValueError> { fn call(&self, args: Vec<JsValue>) -> Result<Result<JsValue, String>, ValueError> {
if !args.is_empty() { if args.len() != 0 {
return Ok(Err(format!( return Ok(Err(format!(
"Invalid argument count: Expected 0, got {}", "Invalid argument count: Expected 0, got {}",
args.len(), args.len(),
@ -124,7 +124,7 @@ impl_callback![
5: (A1, A2, A3, A4, A5,), 5: (A1, A2, A3, A4, A5,),
]; ];
/// A wrapper around [`Vec<JsValue>`], used for vararg callbacks. /// A wrapper around Vec<JsValue>, used for vararg callbacks.
/// ///
/// To create a callback with a variable number of arguments, a callback closure /// To create a callback with a variable number of arguments, a callback closure
/// must take a single `Arguments` argument. /// must take a single `Arguments` argument.

View file

@ -28,7 +28,7 @@ impl std::fmt::Display for Level {
Warn => "warn", Warn => "warn",
Error => "error", Error => "error",
}; };
write!(f, "{v}") write!(f, "{}", v)
} }
} }
@ -91,7 +91,7 @@ mod log {
.map(print_value) .map(print_value)
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "); .join(", ");
format!("[{parts}]") format!("[{}]", parts)
} }
JsValue::Object(map) => { JsValue::Object(map) => {
let parts = map let parts = map
@ -99,7 +99,7 @@ mod log {
.map(|(key, value)| format!("{}: {}", key, print_value(value))) .map(|(key, value)| format!("{}: {}", key, print_value(value)))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "); .join(", ");
format!("{{{parts}}}") format!("{{{}}}", parts)
} }
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
JsValue::Date(v) => v.to_string(), JsValue::Date(v) => v.to_string(),

View file

@ -32,7 +32,7 @@
//! "#).unwrap(); //! "#).unwrap();
//! ``` //! ```
#![allow(dead_code, clippy::missing_safety_doc)] #![allow(dead_code)]
#![deny(missing_docs)] #![deny(missing_docs)]
mod bindings; mod bindings;
@ -52,7 +52,6 @@ pub use self::{
/// Error on Javascript execution. /// Error on Javascript execution.
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
#[non_exhaustive]
pub enum ExecutionError { pub enum ExecutionError {
/// Code to be executed contained zero-bytes. /// Code to be executed contained zero-bytes.
InputWithZeroBytes, InputWithZeroBytes,
@ -64,6 +63,8 @@ pub enum ExecutionError {
Exception(JsValue), Exception(JsValue),
/// JS Runtime exceeded the memory limit. /// JS Runtime exceeded the memory limit.
OutOfMemory, OutOfMemory,
#[doc(hidden)]
__NonExhaustive,
} }
impl fmt::Display for ExecutionError { impl fmt::Display for ExecutionError {
@ -72,9 +73,10 @@ impl fmt::Display for ExecutionError {
match self { match self {
InputWithZeroBytes => write!(f, "Invalid script input: code contains zero byte (\\0)"), InputWithZeroBytes => write!(f, "Invalid script input: code contains zero byte (\\0)"),
Conversion(e) => e.fmt(f), Conversion(e) => e.fmt(f),
Internal(e) => write!(f, "Internal error: {e}"), Internal(e) => write!(f, "Internal error: {}", e),
Exception(e) => write!(f, "{e:?}"), Exception(e) => write!(f, "{:?}", e),
OutOfMemory => write!(f, "Out of memory: runtime memory limit exceeded"), OutOfMemory => write!(f, "Out of memory: runtime memory limit exceeded"),
__NonExhaustive => unreachable!(),
} }
} }
} }
@ -89,7 +91,6 @@ impl From<ValueError> for ExecutionError {
/// Error on context creation. /// Error on context creation.
#[derive(Debug)] #[derive(Debug)]
#[non_exhaustive]
pub enum ContextError { pub enum ContextError {
/// Runtime could not be created. /// Runtime could not be created.
RuntimeCreationFailed, RuntimeCreationFailed,
@ -97,6 +98,8 @@ pub enum ContextError {
ContextCreationFailed, ContextCreationFailed,
/// Execution error while building. /// Execution error while building.
Execution(ExecutionError), Execution(ExecutionError),
#[doc(hidden)]
__NonExhaustive,
} }
impl fmt::Display for ContextError { impl fmt::Display for ContextError {
@ -106,6 +109,7 @@ impl fmt::Display for ContextError {
RuntimeCreationFailed => write!(f, "Could not create runtime"), RuntimeCreationFailed => write!(f, "Could not create runtime"),
ContextCreationFailed => write!(f, "Could not create context"), ContextCreationFailed => write!(f, "Could not create context"),
Execution(e) => e.fmt(f), Execution(e) => e.fmt(f),
__NonExhaustive => unreachable!(),
} }
} }
} }

View file

@ -1,4 +1,6 @@
use std::collections::HashMap; use std::{collections::HashMap, convert::TryInto};
use rstest::rstest;
use super::*; use super::*;
@ -71,7 +73,11 @@ fn test_eval_pass() {
]; ];
for (index, (code, res)) in obj_cases.into_iter().enumerate() { for (index, (code, res)) in obj_cases.into_iter().enumerate() {
let full_code = format!("var v{index} = {code}; v{index}"); let full_code = format!(
"var v{index} = {code}; v{index}",
index = index,
code = code
);
assert_eq!(c.eval(&full_code), res,); assert_eq!(c.eval(&full_code), res,);
} }
@ -277,7 +283,7 @@ fn test_callback() {
c.add_callback("cb1", |flag: bool| !flag).unwrap(); c.add_callback("cb1", |flag: bool| !flag).unwrap();
assert_eq!(c.eval("cb1(true)").unwrap(), JsValue::Bool(false),); assert_eq!(c.eval("cb1(true)").unwrap(), JsValue::Bool(false),);
c.add_callback("concat2", |a: String, b: String| format!("{a}{b}")) c.add_callback("concat2", |a: String, b: String| format!("{}{}", a, b))
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
c.eval(r#"concat2("abc", "def")"#).unwrap(), c.eval(r#"concat2("abc", "def")"#).unwrap(),
@ -480,7 +486,7 @@ fn chrono_serialize() {
let now_millis = now.timestamp_millis(); let now_millis = now.timestamp_millis();
let timestamp = c let timestamp = c
.call_function("dateToTimestamp", vec![JsValue::Date(now)]) .call_function("dateToTimestamp", vec![JsValue::Date(now.clone())])
.unwrap(); .unwrap();
assert_eq!(timestamp, JsValue::Float(now_millis as f64)); assert_eq!(timestamp, JsValue::Float(now_millis as f64));
@ -494,7 +500,7 @@ fn chrono_deserialize() {
let c = build_context(); let c = build_context();
let value = c.eval(" new Date(1234567555) ").unwrap(); let value = c.eval(" new Date(1234567555) ").unwrap();
let datetime = chrono::Utc.timestamp_millis_opt(1234567555).unwrap(); let datetime = chrono::Utc.timestamp_millis(1234567555);
assert_eq!(value, JsValue::Date(datetime)); assert_eq!(value, JsValue::Date(datetime));
} }
@ -506,8 +512,8 @@ fn chrono_roundtrip() {
c.eval(" function identity(x) { return x; } ").unwrap(); c.eval(" function identity(x) { return x; } ").unwrap();
let d = chrono::Utc::now(); let d = chrono::Utc::now();
let td = JsValue::Date(d); let td = JsValue::Date(d.clone());
let td2 = c.call_function("identity", vec![td]).unwrap(); let td2 = c.call_function("identity", vec![td.clone()]).unwrap();
let d2 = if let JsValue::Date(x) = td2 { let d2 = if let JsValue::Date(x) = td2 {
x x
} else { } else {
@ -578,308 +584,302 @@ fn test_bigint_serialize_bigint() {
} }
} }
#[cfg(feature = "patch-dateparser")] #[test]
mod dateparser { fn test_console() {
use super::*; use console::Level;
use std::sync::{Arc, Mutex};
use rstest::rstest; let messages = Arc::new(Mutex::new(Vec::<(Level, Vec<JsValue>)>::new()));
#[test] let m = messages.clone();
fn test_console() { let c = Context::builder()
use console::Level; .console(move |level: Level, args: Vec<JsValue>| {
use std::sync::{Arc, Mutex}; m.lock().unwrap().push((level, args));
})
.build()
.unwrap();
let messages = Arc::new(Mutex::new(Vec::<(Level, Vec<JsValue>)>::new())); c.eval(
r#"
let m = messages.clone();
let c = Context::builder()
.console(move |level: Level, args: Vec<JsValue>| {
m.lock().unwrap().push((level, args));
})
.build()
.unwrap();
c.eval(
r#"
console.log("hi"); console.log("hi");
console.error(false); console.error(false);
"#, "#,
) )
.unwrap();
let m = messages.lock().unwrap();
assert_eq!(
*m,
vec![
(Level::Log, vec![JsValue::from("hi")]),
(Level::Error, vec![JsValue::from(false)]),
]
);
}
#[test]
fn test_global_setter() {
let ctx = Context::new().unwrap();
ctx.set_global("a", "a").unwrap();
ctx.eval("a + 1").unwrap();
}
// Test suite was taken from V8
// Source: https://github.com/v8/v8/blob/9433ad119aadfe10d60935029195c31f25ab8624/test/mjsunit/date-parse.js
#[rstest]
// testCasesES5Misc
#[case("2000-01-01T08:00:00.000Z", 946713600000)]
#[case("2000-01-01T08:00:00Z", 946713600000)]
#[case("2000-01-01T08:00Z", 946713600000)]
#[case("2000-01T08:00:00.000Z", 946713600000)]
#[case("2000T08:00:00.000Z", 946713600000)]
#[case("2000T08:00Z", 946713600000)]
#[case("2000-01T00:00:00.000-08:00", 946713600000)]
#[case("2000-01T08:00:00.001Z", 946713600001)]
#[case("2000-01T08:00:00.099Z", 946713600099)]
#[case("2000-01T08:00:00.999Z", 946713600999)]
#[case("2000-01T00:00:00.001-08:00", 946713600001)]
#[case("2000-01-01T24:00Z", 946771200000)]
#[case("2000-01-01T24:00:00Z", 946771200000)]
#[case("2000-01-01T24:00:00.000Z", 946771200000)]
fn test_date_iso(#[case] input: &str, #[case] expect: i64) {
let ctx = Context::new().unwrap();
let res = ctx
.eval(&format!(r#"new Date("{}").getTime()"#, input))
.unwrap(); .unwrap();
let m = messages.lock().unwrap(); let n: f64 = res.try_into().unwrap();
assert_eq!(n, expect as f64);
}
assert_eq!( #[rstest]
*m, #[case("Sat, 01-Jan-2000 08:00:00 GMT", 946713600000)]
vec![ #[case("Sat, 01-Jan-2000 08:00:00 GMT+0", 946713600000)]
(Level::Log, vec![JsValue::from("hi")]), #[case("Sat, 01-Jan-2000 08:00:00 GMT+00", 946713600000)]
(Level::Error, vec![JsValue::from(false)]), #[case("Sat, 01-Jan-2000 08:00:00 GMT+000", 946713600000)]
] #[case("Sat, 01-Jan-2000 08:00:00 GMT+0000", 946713600000)]
); #[case("Sat, 01-Jan-2000 08:00:00 GMT+00:00", 946713600000)]
} #[case("Sat, 01 Jan 2000 08:00:00 GMT", 946713600000)]
#[case("Saturday, 01-Jan-00 08:00:00 GMT", 946713600000)]
#[case("01 Jan 00 08:00 -0000", 946713600000)]
#[case("01 Jan 00 08:00 +0000", 946713600000)]
fn test_date_gmt(#[case] input: &str, #[case] expect: i64) {
let ctx = Context::new().unwrap();
let res = ctx
.eval(&format!(r#"new Date("{}").getTime()"#, input))
.unwrap();
#[test] let n: f64 = res.try_into().unwrap();
fn test_global_setter() { assert_eq!(n, expect as f64);
let ctx = Context::new().unwrap(); }
ctx.set_global("a", "a").unwrap();
ctx.eval("a + 1").unwrap();
}
// Test suite was taken from V8 #[rstest]
// Source: https://github.com/v8/v8/blob/9433ad119aadfe10d60935029195c31f25ab8624/test/mjsunit/date-parse.js // EST (-5:00)
#[case("Sat, 01-Jan-2000 03:00:00 UTC-0500", 946713600000)]
#[case("Sat, 01-Jan-2000 03:00:00 UTC-05:00", 946713600000)]
#[case("Sat, 01-Jan-2000 03:00:00 EST", 946713600000)]
#[case("Sat, 01 Jan 2000 03:00:00 EST", 946713600000)]
#[case("Saturday, 01-Jan-00 03:00:00 EST", 946713600000)]
#[case("01 Jan 00 03:00 -0500", 946713600000)]
// EDT (-4:00)
#[case("Sat, 01-Jan-2000 04:00:00 EDT", 946713600000)]
#[case("Sat, 01 Jan 2000 04:00:00 EDT", 946713600000)]
#[case("Saturday, 01-Jan-00 04:00:00 EDT", 946713600000)]
#[case("01 Jan 00 04:00 -0400", 946713600000)]
// CST (-6:00)
#[case("Sat, 01-Jan-2000 02:00:00 CST", 946713600000)]
#[case("Sat, 01 Jan 2000 02:00:00 CST", 946713600000)]
#[case("Saturday, 01-Jan-00 02:00:00 CST", 946713600000)]
#[case("01 Jan 00 02:00 -0600", 946713600000)]
// CDT (-5:00)
#[case("Sat, 01-Jan-2000 03:00:00 CDT", 946713600000)]
#[case("Sat, 01 Jan 2000 03:00:00 CDT", 946713600000)]
#[case("Saturday, 01-Jan-00 03:00:00 CDT", 946713600000)]
#[case("01 Jan 00 03:00 -0500", 946713600000)]
// MST (-7:00)
#[case("Sat, 01-Jan-2000 01:00:00 MST", 946713600000)]
#[case("Sat, 01 Jan 2000 01:00:00 MST", 946713600000)]
#[case("Saturday, 01-Jan-00 01:00:00 MST", 946713600000)]
#[case("01 Jan 00 01:00 -0700", 946713600000)]
// MDT (-6:00)
#[case("Sat, 01-Jan-2000 02:00:00 MDT", 946713600000)]
#[case("Sat, 01 Jan 2000 02:00:00 MDT", 946713600000)]
#[case("Saturday, 01-Jan-00 02:00:00 MDT", 946713600000)]
#[case("01 Jan 00 02:00 -0600", 946713600000)]
// PST (-8:00)
#[case("Sat, 01-Jan-2000 00:00:00 PST", 946713600000)]
#[case("Sat, 01 Jan 2000 00:00:00 PST", 946713600000)]
#[case("Saturday, 01-Jan-00 00:00:00 PST", 946713600000)]
#[case("01 Jan 00 00:00 -0800", 946713600000)]
#[case("Sat, 01-Jan-2000 PST", 946713600000)]
// PDT (-7:00)
#[case("Sat, 01-Jan-2000 01:00:00 PDT", 946713600000)]
#[case("Sat, 01 Jan 2000 01:00:00 PDT", 946713600000)]
#[case("Saturday, 01-Jan-00 01:00:00 PDT", 946713600000)]
#[case("01 Jan 00 01:00 -0700", 946713600000)]
fn test_date_tz(#[case] input: &str, #[case] expect: i64) {
let ctx = Context::new().unwrap();
let res = ctx
.eval(&format!(r#"new Date("{}").getTime()"#, input))
.unwrap();
#[rstest] let n: f64 = res.try_into().unwrap();
// testCasesES5Misc assert_eq!(n, expect as f64);
#[case("2000-01-01T08:00:00.000Z", 946713600000)] }
#[case("2000-01-01T08:00:00Z", 946713600000)]
#[case("2000-01-01T08:00Z", 946713600000)] #[rstest]
#[case("2000-01T08:00:00.000Z", 946713600000)] // Special handling for years in the [0, 100) range.
#[case("2000T08:00:00.000Z", 946713600000)] #[case("Sat, 01 Jan 0 08:00:00 UT", 946713600000)] // year 2000
#[case("2000T08:00Z", 946713600000)] #[case("Sat, 01 Jan 49 08:00:00 UT", 2493100800000)] // year 2049
#[case("2000-01T00:00:00.000-08:00", 946713600000)] #[case("Sat, 01 Jan 50 08:00:00 UT", -631123200000)] // year 1950
#[case("2000-01T08:00:00.001Z", 946713600001)] #[case("Sat, 01 Jan 99 08:00:00 UT", 915177600000)] // year 1999
#[case("2000-01T08:00:00.099Z", 946713600099)] #[case("Sat, 01 Jan 100 08:00:00 UT", -59011430400000)] // year 100
#[case("2000-01T08:00:00.999Z", 946713600999)] // Test PM after time.
#[case("2000-01T00:00:00.001-08:00", 946713600001)] #[case("Sat, 01-Jan-2000 08:00 PM UT", 946756800000)]
#[case("2000-01-01T24:00Z", 946771200000)] #[case("Sat, 01 Jan 2000 08:00 PM UT", 946756800000)]
#[case("2000-01-01T24:00:00Z", 946771200000)] #[case("Jan 01 2000 08:00 PM UT", 946756800000)]
#[case("2000-01-01T24:00:00.000Z", 946771200000)] #[case("Jan 01 08:00 PM UT 2000", 946756800000)]
fn test_date_iso(#[case] input: &str, #[case] expect: i64) { #[case("Saturday, 01-Jan-00 08:00 PM UT", 946756800000)]
#[case("01 Jan 00 08:00 PM +0000", 946756800000)]
fn test_date_special(#[case] input: &str, #[case] expect: i64) {
let ctx = Context::new().unwrap();
let res = ctx
.eval(&format!(r#"new Date("{}").getTime()"#, input))
.unwrap();
let n: f64 = res.try_into().unwrap();
assert_eq!(n, expect as f64);
}
#[rstest]
#[case("2000-01-01TZ")]
#[case("2000-01-01T60Z")]
#[case("2000-01-01T60:60Z")]
#[case("2000-01-0108:00Z")]
#[case("2000-01-01T08Z")]
#[case("2000-01-01T24:01")]
#[case("2000-01-01T24:00:01")]
#[case("2000-01-01T24:00:00.001")]
#[case("2000-01-01T24:00:00.999Z")]
#[case("May 25 2008 1:30 (PM)) UTC")]
#[case("May 25 2008 1:30( )AM (PM)")]
#[case("a1")]
#[case("nasfdjklsfjoaifg1")]
#[case("x_2")]
#[case("May 25 2008 AAA (GMT)")]
fn test_date_invalid(#[case] input: &str) {
let ctx = Context::new().unwrap();
let res = ctx
.eval(&format!(r#"new Date("{}").getTime()"#, input))
.unwrap();
let n: f64 = res.try_into().unwrap();
assert!(n.is_nan(), "got: {}", n);
}
#[test]
fn test_date_ut() {
let test_cases_ut = vec![
"Sat, 01-Jan-2000 08:00:00 UT",
"Sat, 01 Jan 2000 08:00:00 UT",
"Jan 01 2000 08:00:00 UT",
"Jan 01 08:00:00 UT 2000",
"Saturday, 01-Jan-00 08:00:00 UT",
"01 Jan 00 08:00 +0000",
// Ignore weekdays.
"Mon, 01 Jan 2000 08:00:00 UT",
"Tue, 01 Jan 2000 08:00:00 UT",
// Ignore prefix that is not part of a date.
"[Saturday] Jan 01 08:00:00 UT 2000",
"Ignore all of this stuff because it is annoying 01 Jan 2000 08:00:00 UT",
"[Saturday] Jan 01 2000 08:00:00 UT",
"All of this stuff is really annnoying, so it will be ignored Jan 01 2000 08:00:00 UT",
// If the three first letters of the month is a
// month name we are happy - ignore the rest.
"Sat, 01-Janisamonth-2000 08:00:00 UT",
"Sat, 01 Janisamonth 2000 08:00:00 UT",
"Janisamonth 01 2000 08:00:00 UT",
"Janisamonth 01 08:00:00 UT 2000",
"Saturday, 01-Janisamonth-00 08:00:00 UT",
"01 Janisamonth 00 08:00 +0000",
// Allow missing space between month and day.
"Janisamonthandtherestisignored01 2000 08:00:00 UT",
"Jan01 2000 08:00:00 UT",
// Allow year/month/day format.
"Sat, 2000/01/01 08:00:00 UT",
// Allow month/day/year format.
"Sat, 01/01/2000 08:00:00 UT",
// Allow month/day year format.
"Sat, 01/01 2000 08:00:00 UT",
// Allow comma instead of space after day, month and year.
"Sat, 01,Jan,2000,08:00:00 UT",
// Seconds are optional.
"Sat, 01-Jan-2000 08:00 UT",
"Sat, 01 Jan 2000 08:00 UT",
"Jan 01 2000 08:00 UT",
"Jan 01 08:00 UT 2000",
"Saturday, 01-Jan-00 08:00 UT",
"01 Jan 00 08:00 +0000",
// Allow AM/PM after the time.
"Sat, 01-Jan-2000 08:00 AM UT",
"Sat, 01 Jan 2000 08:00 AM UT",
"Jan 01 2000 08:00 AM UT",
"Jan 01 08:00 AM UT 2000",
"Saturday, 01-Jan-00 08:00 AM UT",
"01 Jan 00 08:00 AM +0000",
// White space and stuff in parenthesis is
// apparently allowed in most places where white
// space is allowed.
" Sat, 01-Jan-2000 08:00:00 UT ",
" Sat, 01 Jan 2000 08:00:00 UT ",
" Saturday, 01-Jan-00 08:00:00 UT ",
" 01 Jan 00 08:00 +0000 ",
" ()(Sat, 01-Jan-2000) Sat, 01-Jan-2000 08:00:00 UT ",
" Sat()(Sat, 01-Jan-2000)01 Jan 2000 08:00:00 UT ",
" Sat,(02)01 Jan 2000 08:00:00 UT ",
" Sat, 01(02)Jan 2000 08:00:00 UT ",
" Sat, 01 Jan 2000 (2001)08:00:00 UT ",
" Sat, 01 Jan 2000 (01)08:00:00 UT ",
" Sat, 01 Jan 2000 (01:00:00)08:00:00 UT ",
" Sat, 01 Jan 2000 08:00:00 (CDT)UT ",
" Sat, 01 Jan 2000 08:00:00 UT((((CDT))))",
" Saturday, 01-Jan-00 ()(((asfd)))(Sat, 01-Jan-2000)08:00:00 UT ",
" 01 Jan 00 08:00 ()(((asdf)))(Sat, 01-Jan-2000)+0000 ",
" 01 Jan 00 08:00 +0000()((asfd)(Sat, 01-Jan-2000)) ",
];
let mut passed = 0;
let mut failed = 0;
for case in test_cases_ut {
let ctx = Context::new().unwrap(); let ctx = Context::new().unwrap();
let res = ctx let res = ctx
.eval(&format!(r#"new Date("{input}").getTime()"#)) .eval(&format!(r#"new Date("{}").getTime()"#, case))
.unwrap(); .unwrap();
let n: f64 = res.try_into().unwrap(); let n: f64 = res.try_into().unwrap();
assert_eq!(n, expect as f64);
}
#[rstest] if n == 946713600000.0 {
#[case("Sat, 01-Jan-2000 08:00:00 GMT", 946713600000)] passed += 1;
#[case("Sat, 01-Jan-2000 08:00:00 GMT+0", 946713600000)] } else {
#[case("Sat, 01-Jan-2000 08:00:00 GMT+00", 946713600000)] println!("FAIL: `{}` - {}", case, n);
#[case("Sat, 01-Jan-2000 08:00:00 GMT+000", 946713600000)] failed += 1;
#[case("Sat, 01-Jan-2000 08:00:00 GMT+0000", 946713600000)]
#[case("Sat, 01-Jan-2000 08:00:00 GMT+00:00", 946713600000)]
#[case("Sat, 01 Jan 2000 08:00:00 GMT", 946713600000)]
#[case("Saturday, 01-Jan-00 08:00:00 GMT", 946713600000)]
#[case("01 Jan 00 08:00 -0000", 946713600000)]
#[case("01 Jan 00 08:00 +0000", 946713600000)]
fn test_date_gmt(#[case] input: &str, #[case] expect: i64) {
let ctx = Context::new().unwrap();
let res = ctx
.eval(&format!(r#"new Date("{input}").getTime()"#))
.unwrap();
let n: f64 = res.try_into().unwrap();
assert_eq!(n, expect as f64);
}
#[rstest]
// EST (-5:00)
#[case("Sat, 01-Jan-2000 03:00:00 UTC-0500", 946713600000)]
#[case("Sat, 01-Jan-2000 03:00:00 UTC-05:00", 946713600000)]
#[case("Sat, 01-Jan-2000 03:00:00 EST", 946713600000)]
#[case("Sat, 01 Jan 2000 03:00:00 EST", 946713600000)]
#[case("Saturday, 01-Jan-00 03:00:00 EST", 946713600000)]
#[case("01 Jan 00 03:00 -0500", 946713600000)]
// EDT (-4:00)
#[case("Sat, 01-Jan-2000 04:00:00 EDT", 946713600000)]
#[case("Sat, 01 Jan 2000 04:00:00 EDT", 946713600000)]
#[case("Saturday, 01-Jan-00 04:00:00 EDT", 946713600000)]
#[case("01 Jan 00 04:00 -0400", 946713600000)]
// CST (-6:00)
#[case("Sat, 01-Jan-2000 02:00:00 CST", 946713600000)]
#[case("Sat, 01 Jan 2000 02:00:00 CST", 946713600000)]
#[case("Saturday, 01-Jan-00 02:00:00 CST", 946713600000)]
#[case("01 Jan 00 02:00 -0600", 946713600000)]
// CDT (-5:00)
#[case("Sat, 01-Jan-2000 03:00:00 CDT", 946713600000)]
#[case("Sat, 01 Jan 2000 03:00:00 CDT", 946713600000)]
#[case("Saturday, 01-Jan-00 03:00:00 CDT", 946713600000)]
#[case("01 Jan 00 03:00 -0500", 946713600000)]
// MST (-7:00)
#[case("Sat, 01-Jan-2000 01:00:00 MST", 946713600000)]
#[case("Sat, 01 Jan 2000 01:00:00 MST", 946713600000)]
#[case("Saturday, 01-Jan-00 01:00:00 MST", 946713600000)]
#[case("01 Jan 00 01:00 -0700", 946713600000)]
// MDT (-6:00)
#[case("Sat, 01-Jan-2000 02:00:00 MDT", 946713600000)]
#[case("Sat, 01 Jan 2000 02:00:00 MDT", 946713600000)]
#[case("Saturday, 01-Jan-00 02:00:00 MDT", 946713600000)]
#[case("01 Jan 00 02:00 -0600", 946713600000)]
// PST (-8:00)
#[case("Sat, 01-Jan-2000 00:00:00 PST", 946713600000)]
#[case("Sat, 01 Jan 2000 00:00:00 PST", 946713600000)]
#[case("Saturday, 01-Jan-00 00:00:00 PST", 946713600000)]
#[case("01 Jan 00 00:00 -0800", 946713600000)]
#[case("Sat, 01-Jan-2000 PST", 946713600000)]
// PDT (-7:00)
#[case("Sat, 01-Jan-2000 01:00:00 PDT", 946713600000)]
#[case("Sat, 01 Jan 2000 01:00:00 PDT", 946713600000)]
#[case("Saturday, 01-Jan-00 01:00:00 PDT", 946713600000)]
#[case("01 Jan 00 01:00 -0700", 946713600000)]
fn test_date_tz(#[case] input: &str, #[case] expect: i64) {
let ctx = Context::new().unwrap();
let res = ctx
.eval(&format!(r#"new Date("{input}").getTime()"#))
.unwrap();
let n: f64 = res.try_into().unwrap();
assert_eq!(n, expect as f64);
}
#[rstest]
// Special handling for years in the [0, 100) range.
#[case("Sat, 01 Jan 0 08:00:00 UT", 946713600000)] // year 2000
#[case("Sat, 01 Jan 49 08:00:00 UT", 2493100800000)] // year 2049
#[case("Sat, 01 Jan 50 08:00:00 UT", -631123200000)] // year 1950
#[case("Sat, 01 Jan 99 08:00:00 UT", 915177600000)] // year 1999
#[case("Sat, 01 Jan 100 08:00:00 UT", -59011430400000)] // year 100
// Test PM after time.
#[case("Sat, 01-Jan-2000 08:00 PM UT", 946756800000)]
#[case("Sat, 01 Jan 2000 08:00 PM UT", 946756800000)]
#[case("Jan 01 2000 08:00 PM UT", 946756800000)]
#[case("Jan 01 08:00 PM UT 2000", 946756800000)]
#[case("Saturday, 01-Jan-00 08:00 PM UT", 946756800000)]
#[case("01 Jan 00 08:00 PM +0000", 946756800000)]
fn test_date_special(#[case] input: &str, #[case] expect: i64) {
let ctx = Context::new().unwrap();
let res = ctx
.eval(&format!(r#"new Date("{input}").getTime()"#))
.unwrap();
let n: f64 = res.try_into().unwrap();
assert_eq!(n, expect as f64);
}
#[rstest]
#[case("2000-01-01TZ")]
#[case("2000-01-01T60Z")]
#[case("2000-01-01T60:60Z")]
#[case("2000-01-0108:00Z")]
#[case("2000-01-01T08Z")]
#[case("2000-01-01T24:01")]
#[case("2000-01-01T24:00:01")]
#[case("2000-01-01T24:00:00.001")]
#[case("2000-01-01T24:00:00.999Z")]
#[case("May 25 2008 1:30 (PM)) UTC")]
#[case("May 25 2008 1:30( )AM (PM)")]
#[case("a1")]
#[case("nasfdjklsfjoaifg1")]
#[case("x_2")]
#[case("May 25 2008 AAA (GMT)")]
fn test_date_invalid(#[case] input: &str) {
let ctx = Context::new().unwrap();
let res = ctx
.eval(&format!(r#"new Date("{input}").getTime()"#))
.unwrap();
let n: f64 = res.try_into().unwrap();
assert!(n.is_nan(), "got: {n}");
}
#[test]
fn test_date_ut() {
let test_cases_ut = vec![
"Sat, 01-Jan-2000 08:00:00 UT",
"Sat, 01 Jan 2000 08:00:00 UT",
"Jan 01 2000 08:00:00 UT",
"Jan 01 08:00:00 UT 2000",
"Saturday, 01-Jan-00 08:00:00 UT",
"01 Jan 00 08:00 +0000",
// Ignore weekdays.
"Mon, 01 Jan 2000 08:00:00 UT",
"Tue, 01 Jan 2000 08:00:00 UT",
// Ignore prefix that is not part of a date.
"[Saturday] Jan 01 08:00:00 UT 2000",
"Ignore all of this stuff because it is annoying 01 Jan 2000 08:00:00 UT",
"[Saturday] Jan 01 2000 08:00:00 UT",
"All of this stuff is really annnoying, so it will be ignored Jan 01 2000 08:00:00 UT",
// If the three first letters of the month is a
// month name we are happy - ignore the rest.
"Sat, 01-Janisamonth-2000 08:00:00 UT",
"Sat, 01 Janisamonth 2000 08:00:00 UT",
"Janisamonth 01 2000 08:00:00 UT",
"Janisamonth 01 08:00:00 UT 2000",
"Saturday, 01-Janisamonth-00 08:00:00 UT",
"01 Janisamonth 00 08:00 +0000",
// Allow missing space between month and day.
"Janisamonthandtherestisignored01 2000 08:00:00 UT",
"Jan01 2000 08:00:00 UT",
// Allow year/month/day format.
"Sat, 2000/01/01 08:00:00 UT",
// Allow month/day/year format.
"Sat, 01/01/2000 08:00:00 UT",
// Allow month/day year format.
"Sat, 01/01 2000 08:00:00 UT",
// Allow comma instead of space after day, month and year.
"Sat, 01,Jan,2000,08:00:00 UT",
// Seconds are optional.
"Sat, 01-Jan-2000 08:00 UT",
"Sat, 01 Jan 2000 08:00 UT",
"Jan 01 2000 08:00 UT",
"Jan 01 08:00 UT 2000",
"Saturday, 01-Jan-00 08:00 UT",
"01 Jan 00 08:00 +0000",
// Allow AM/PM after the time.
"Sat, 01-Jan-2000 08:00 AM UT",
"Sat, 01 Jan 2000 08:00 AM UT",
"Jan 01 2000 08:00 AM UT",
"Jan 01 08:00 AM UT 2000",
"Saturday, 01-Jan-00 08:00 AM UT",
"01 Jan 00 08:00 AM +0000",
// White space and stuff in parenthesis is
// apparently allowed in most places where white
// space is allowed.
" Sat, 01-Jan-2000 08:00:00 UT ",
" Sat, 01 Jan 2000 08:00:00 UT ",
" Saturday, 01-Jan-00 08:00:00 UT ",
" 01 Jan 00 08:00 +0000 ",
" ()(Sat, 01-Jan-2000) Sat, 01-Jan-2000 08:00:00 UT ",
" Sat()(Sat, 01-Jan-2000)01 Jan 2000 08:00:00 UT ",
" Sat,(02)01 Jan 2000 08:00:00 UT ",
" Sat, 01(02)Jan 2000 08:00:00 UT ",
" Sat, 01 Jan 2000 (2001)08:00:00 UT ",
" Sat, 01 Jan 2000 (01)08:00:00 UT ",
" Sat, 01 Jan 2000 (01:00:00)08:00:00 UT ",
" Sat, 01 Jan 2000 08:00:00 (CDT)UT ",
" Sat, 01 Jan 2000 08:00:00 UT((((CDT))))",
" Saturday, 01-Jan-00 ()(((asfd)))(Sat, 01-Jan-2000)08:00:00 UT ",
" 01 Jan 00 08:00 ()(((asdf)))(Sat, 01-Jan-2000)+0000 ",
" 01 Jan 00 08:00 +0000()((asfd)(Sat, 01-Jan-2000)) ",
];
let mut passed = 0;
let mut failed = 0;
for case in test_cases_ut {
let ctx = Context::new().unwrap();
let res = ctx
.eval(&format!(r#"new Date("{case}").getTime()"#))
.unwrap();
let n: f64 = res.try_into().unwrap();
if n == 946713600000.0 {
passed += 1;
} else {
println!("FAIL: `{case}` - {n}");
failed += 1;
}
} }
println!("{}/{} Passed", passed, passed + failed);
assert_eq!(failed, 0);
} }
#[test] println!("{}/{} Passed", passed, passed + failed);
// Test if the JS interpreter can parse its own date format assert_eq!(failed, 0);
// (Dates from 1970 to ~2070 with 150h steps.) }
fn test_date_selfparse() {
let ctx = Context::new().unwrap(); #[test]
let res = ctx // Test if the JS interpreter can parse its own date format
.eval( // (Dates from 1970 to ~2070 with 150h steps.)
r#" fn test_date_selfparse() {
let ctx = Context::new().unwrap();
let res = ctx
.eval(
r#"
test = () => { test = () => {
for (var i = 0; i < 24 * 365 * 100; i += 150) { for (var i = 0; i < 24 * 365 * 100; i += 150) {
var ms = i * (3600 * 1000); var ms = i * (3600 * 1000);
@ -890,9 +890,8 @@ mod dateparser {
} }
test(); test();
"#, "#,
) )
.unwrap(); .unwrap();
let res: bool = res.try_into().unwrap(); let res: bool = res.try_into().unwrap();
assert!(res); assert!(res);
}
} }

View file

@ -46,8 +46,8 @@ impl BigInt {
impl std::fmt::Display for BigInt { impl std::fmt::Display for BigInt {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.inner { match self.inner {
BigIntOrI64::Int(i) => write!(f, "{i}"), BigIntOrI64::Int(i) => write!(f, "{}", i),
BigIntOrI64::BigInt(ref i) => write!(f, "{i}"), BigIntOrI64::BigInt(ref i) => write!(f, "{}", i),
} }
} }
} }

View file

@ -259,7 +259,6 @@ where
/// Error during value conversion. /// Error during value conversion.
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum ValueError { pub enum ValueError {
/// Invalid non-utf8 string. /// Invalid non-utf8 string.
InvalidString(std::str::Utf8Error), InvalidString(std::str::Utf8Error),
@ -269,6 +268,8 @@ pub enum ValueError {
Internal(String), Internal(String),
/// Received an unexpected type that could not be converted. /// Received an unexpected type that could not be converted.
UnexpectedType, UnexpectedType,
#[doc(hidden)]
__NonExhaustive,
} }
// TODO: remove this once either the Never type get's stabilized or the compiler // TODO: remove this once either the Never type get's stabilized or the compiler
@ -283,10 +284,15 @@ impl fmt::Display for ValueError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ValueError::*; use ValueError::*;
match self { match self {
InvalidString(e) => write!(f, "Value conversion failed - invalid non-utf8 string: {e}"), InvalidString(e) => write!(
f,
"Value conversion failed - invalid non-utf8 string: {}",
e
),
StringWithZeroBytes(_) => write!(f, "String contains \\0 bytes",), StringWithZeroBytes(_) => write!(f, "String contains \\0 bytes",),
Internal(e) => write!(f, "Value conversion failed - internal error: {e}"), Internal(e) => write!(f, "Value conversion failed - internal error: {}", e),
UnexpectedType => write!(f, "Could not convert - received unexpected type"), UnexpectedType => write!(f, "Could not convert - received unexpected type"),
__NonExhaustive => unreachable!(),
} }
} }
} }