import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
DEF = ROOT / "include/hermes/Parser/HTMLEntities.def"
OUT = Path(__file__).resolve().parent / "src/html_entities.rs"
ROW = re.compile(r"^HTML_ENTITY\((\w+),\s*(0x[0-9a-fA-F]+)\)", re.M)
EXPECTED = 253
def main():
if not DEF.exists():
sys.exit(f"error: {DEF} not found — run from a full hermes checkout")
src = DEF.read_text()
rows = [(name, int(value, 0)) for name, value in ROW.findall(src)]
if len(rows) != EXPECTED:
sys.exit(
f"error: HTMLEntities.def has {len(rows)} rows, expected {EXPECTED}"
)
rows.sort(key=lambda nv: nv[0])
for i in range(1, len(rows)):
if rows[i][0] <= rows[i - 1][0]:
sys.exit(
f"error: duplicate/unsorted entity name at index {i}: "
f"{rows[i][0]!r}"
)
out = []
out.append("// Copyright (c) Meta Platforms, Inc. and affiliates.")
out.append("//")
out.append("// This source code is licensed under the MIT license found in the")
out.append("// LICENSE file in the root directory of this source tree.")
out.append("//")
out.append(
"// GENERATED by gen_html_entities.py from "
"include/hermes/Parser/HTMLEntities.def."
)
out.append("// DO NOT EDIT BY HAND.")
out.append("//")
out.append("//! XHTML named-entity table for the JSX lexer. Port of the")
out.append("//! `initializeHTMLEntities` map in `lib/Parser/JSLexer.cpp`, but")
out.append("//! emitted sorted by name so `&name;` lookup is a binary search.")
out.append("")
out.append(
f"/// The XHTML named entities, sorted by name. Each entry maps an "
f"entity\n/// name (the bytes between `&` and `;`) to its Unicode code "
f"point."
)
out.append(f"pub static HTML_ENTITIES: [(&[u8], u32); {len(rows)}] = [")
for name, value in rows:
out.append(f' (b"{name}", 0x{value:04x}),')
out.append("];")
out.append("")
out.append("/// Look up an XHTML named entity by name (the bytes between `&`")
out.append("/// and `;`). Returns its code point, or `None` if unknown.")
out.append("pub fn lookup(name: &[u8]) -> Option<u32> {")
out.append(
" HTML_ENTITIES"
)
out.append(" .binary_search_by(|(n, _)| (*n).cmp(name))")
out.append(" .ok()")
out.append(" .map(|i| HTML_ENTITIES[i].1)")
out.append("}")
out.append("")
out.append("#[cfg(test)]")
out.append("mod tests {")
out.append(" use super::*;")
out.append("")
out.append(" #[test]")
out.append(" fn known_entities() {")
out.append(" assert_eq!(lookup(b\"amp\"), Some(0x26));")
out.append(" assert_eq!(lookup(b\"lt\"), Some(0x3c));")
out.append(" assert_eq!(lookup(b\"nope\"), None);")
out.append(" }")
out.append("")
out.append(" #[test]")
out.append(" fn table_is_sorted_and_full() {")
out.append(f" assert_eq!(HTML_ENTITIES.len(), {len(rows)});")
out.append(" for w in HTML_ENTITIES.windows(2) {")
out.append(" assert!(w[0].0 < w[1].0, \"table not sorted by name\");")
out.append(" }")
out.append(" }")
out.append("}")
out.append("")
OUT.write_text("\n".join(out))
print(f"wrote {OUT} ({len(rows)} entities)")
if __name__ == "__main__":
main()