use std::io::Write;
use std::process::{Command, Stdio};
use hermes_atom_table::AtomTable;
use hermes_parser::lexer::{GrammarContext, JSLexer};
use hermes_parser::token_kinds::TokenKind;
use hermes_support::manager::SourceErrorManager;
fn context_flag(ctx: GrammarContext) -> &'static str {
match ctx {
GrammarContext::AllowDiv => "--context=div",
GrammarContext::AllowRegExp => "--context=regexp",
GrammarContext::Type => "--context=type",
GrammarContext::AllowJSXIdentifier => "--context=jsx",
}
}
fn rust_dump(src: &str, ctx: GrammarContext, strict: bool) -> String {
let mut sm = SourceErrorManager::new();
let id = sm.add_buffer("t", src);
let tab = AtomTable::new();
let mut lex = JSLexer::new(id, &mut sm, &tab, ctx);
if !strict {
lex.set_strict_mode(false);
}
let mut out = String::new();
loop {
let k = lex.advance(ctx).kind();
lex.dump_token(&mut out);
out.push('\n');
if k == TokenKind::eof {
break;
}
}
out
}
fn js_lexer_dump_bin() -> std::path::PathBuf {
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../../cmake-build-asan/bin/js-lexer-dump")
}
fn cpp_dump_flags(bin: &std::path::Path, src: &str, flags: &[&str]) -> Option<String> {
let mut child = Command::new(bin)
.args(flags)
.arg("-")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.ok()?;
child
.stdin
.take()
.unwrap()
.write_all(src.as_bytes())
.ok()?;
let out = child.wait_with_output().ok()?;
Some(String::from_utf8(out.stdout).unwrap())
}
#[test]
fn differential_punctuators_and_trivia() {
let corpus = [
"{ } ( ) [ ] ; , ~ : @",
"= == === => != !== ! < <= << <<= > >= >> >>> >>= >>>=",
"+ ++ += - -- -= * *= ** **= / /= % %= & && &= | || |= ^ ^= ? ?? ?. ??= ...",
";;;\n;",
";;\n\t ;; \n\n ;",
"; /* block\ncomment */ ;",
"; // line comment\n;",
"; /* no newline */ ;",
"\u{feff}; ;",
"\u{00a0}; ;",
"; \u{2028} ;",
"; \u{2029} ;",
"?.;",
". .. ... ....",
"\t\t; \t ;",
"",
"; // tail",
"#!/usr/bin/env hermes\n;",
"foo bar baz",
"_x $y a1 Z9",
"function for while return yield static implements",
"if in var break continue switch this true false null case catch const",
"debugger default delete do else finally instanceof new throw try typeof",
"void with export import class extends super enum interface package",
"private protected public",
"let async await of as from get set",
"caf\u{00e9} \u{4e2d}\u{6587} na\u{00ef}ve",
"\u{00e9}tude \u{03b1}\u{03b2}\u{03b3}",
"\u{00aa} \u{00ab}\u{00bb} \u{fb00} x",
"a\u{2026} b",
"\u{00a0}\u{00aa}",
"\\u0041\\u0042 ab\\u0063",
"x\\u{1F600}y",
"x;y\nz",
"0 1 42 1000000000 9007199254740993",
"0.1 .5 3.14159 1e10 2E-3 6.022e23 1_000_000",
"0xff 0xDEAD_BEEF 0o17 0b1010 0XAB 0O7 0B11",
"10n 0xffn 255n 0n",
"0 .5 .25",
"1+2 3*4 5;6",
"0123 010 07",
"123456789 1234567890 999999999",
"5. .5 0. 1.e3",
"'a' \"b\" 'hello world'",
"'a\\tb' \"x\\ny\" '\\r\\\\\\''",
"'\\x41\\x7e' '\\u00e9\\u4e2d'",
"'caf\u{00e9}' \"\u{4e2d}\u{6587}\"",
"'\\0' '\\101' '\\x00end'",
"'a\\\nb' 'line\\\r\ncont'",
"#foo #_bar x.#priv",
"'a' #b 5 ;",
"`hello` `a b c`",
"`a${",
"`x${ `y${ `done`",
"`tab\\tnl\\n` `raw\\u00e9`",
"`not\\9esc`",
"`cr\rlf`",
"`uni\u{4e2d}` `astral\u{1f600}`",
];
run_differential("div", &corpus, GrammarContext::AllowDiv, true);
}
#[test]
fn differential_regexp() {
let corpus = [
"/abc/g /x/ /[a-z]+/gi",
"/[/]/ /a\\/b/ /\\d+/", "/foo/gimsuy", "/\u{4e2d}/u", "x = /re/g", ];
run_differential("regexp", &corpus, GrammarContext::AllowRegExp, true);
}
#[test]
fn differential_type() {
let corpus = [
"{| a: number |} | string",
"<T> >> << ?? %checks",
"@flow @decorator a b",
"Array<string> Map<K, V>",
"x | y & z",
"{ a: 1 } [1, 2]",
];
run_differential("type", &corpus, GrammarContext::Type, true);
}
#[test]
fn differential_jsx() {
let corpus = [
"<div-foo a-b>",
"<my-element data-x>",
"x-y-z foo-bar",
"< a-b > < /c-d >",
];
run_differential("jsx", &corpus, GrammarContext::AllowJSXIdentifier, true);
}
#[test]
fn differential_nonstrict() {
let corpus = [
"implements interface package private protected public static yield",
"function yield for static return var public",
"let static = 1; var private = yield;",
"0123 010 07 0o17",
"08 09 00 019",
"'\\07' '\\101' '\\0'",
"static 0123 '\\77' yield",
];
run_differential("nonstrict", &corpus, GrammarContext::AllowDiv, false);
}
fn rust_dump_jsx_child(src: &str) -> String {
let mut sm = SourceErrorManager::new();
let id = sm.add_buffer("t", src);
let tab = AtomTable::new();
let mut lex = JSLexer::new(id, &mut sm, &tab, GrammarContext::AllowJSXIdentifier);
let mut out = String::new();
loop {
let k = lex.advance_in_jsx_child().kind();
lex.dump_token(&mut out);
out.push('\n');
if k == TokenKind::eof {
break;
}
}
out
}
#[test]
fn differential_jsx_child() {
let corpus = [
"hello world{",
"a&b<c{", "xABy<", "text\u{4e2d}more{", "line1\nline2<", "a¬anentity b{",
"{<",
"<{",
"&<",
"just text",
];
let bin = js_lexer_dump_bin();
if !bin.exists() {
if std::env::var_os("REQUIRE_DIFFERENTIAL").is_some() {
panic!("REQUIRE_DIFFERENTIAL=1 but js-lexer-dump not built at {bin:?}");
}
eprintln!("skip: js-lexer-dump not built at {bin:?}");
return;
}
let mut compared = 0usize;
for &src in &corpus {
let cpp = cpp_dump_flags(&bin, src, &["--context=jsx", "--jsx-child"])
.expect("js-lexer-dump exists but failed to run");
assert_eq!(rust_dump_jsx_child(src), cpp, "mismatch for {src:?}");
compared += 1;
}
eprintln!("differential[jsx-child] compared {compared} corpus entries");
}
fn run_differential(label: &str, corpus: &[&str], ctx: GrammarContext, strict: bool) {
let bin = js_lexer_dump_bin();
if !bin.exists() {
if std::env::var_os("REQUIRE_DIFFERENTIAL").is_some() {
panic!("REQUIRE_DIFFERENTIAL=1 but js-lexer-dump not built at {bin:?}");
}
eprintln!("skip: js-lexer-dump not built at {bin:?}");
return;
}
let mut flags = vec![context_flag(ctx)];
if !strict {
flags.push("--non-strict");
}
let mut compared = 0usize;
for &src in corpus {
let cpp = cpp_dump_flags(&bin, src, &flags).expect("js-lexer-dump exists but failed to run");
assert_eq!(rust_dump(src, ctx, strict), cpp, "mismatch for {src:?}");
compared += 1;
}
eprintln!("differential[{label}] compared {compared} corpus entries");
}