Files

107 lines
3.7 KiB
TypeScript
Raw Permalink Normal View History

2026-08-27 21:09:14 +00:00
// Generates `xml_byte_class.h` (nibble LUTs, Highway SIMD kernel) and `xml_byte_class.rs` (the
// derived 256-entry table, Rust scalar indexer): both come from this one table so they agree.
import { mkdirSync } from "node:fs";
import { resolve } from "node:path";
import type { Config } from "./config.ts";
import { writeIfChanged } from "./fs.ts";
// Indexed everywhere: `&`, `\r`, and the control characters XML forbids.
const ALWAYS = 0x07;
// Indexed only between `<` and the next `>`: `\t`, `\n`, `"`, `'`, `=`.
const TAG = 0x38;
const LT = 0x40;
const GT = 0x80;
const LUT_LO = [0x03, 0x03, 0x13, 0x03, 0x03, 0x03, 0x07, 0x13, 0x03, 0x0a, 0x0a, 0x03, 0x43, 0x23, 0x83, 0x03];
const LUT_HI = [0x09, 0x02, 0x14, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
const classOf = (b: number): number => LUT_LO[b & 0xf] & LUT_HI[b >> 4];
function check() {
const expectedClass = (b: number): number => {
const ch = String.fromCharCode(b);
if (ch === "<") return LT;
if (ch === ">") return GT;
if (ch === "&" || ch === "\r") return ALWAYS;
if (b < 0x20 && ch !== "\t" && ch !== "\n") return ALWAYS;
if ("\t\n\"'=".includes(ch)) return TAG;
return 0;
};
for (let b = 0; b < 0x100; b++) {
const got = classOf(b);
const expected = b < 0x80 ? expectedClass(b) : 0;
const ok = expected === 0 ? got === 0 : (got & expected) !== 0 && (got & ~expected) === 0;
if (!ok) {
throw new Error(
`xml_byte_class: byte 0x${b.toString(16)} classifies as 0x${got.toString(16)}, expected class 0x${expected.toString(16)}`,
);
}
}
}
export function generateXmlByteClass(cfg: Config): { h: string; rs: string } {
check();
const banner = (comment: string) => [
`${comment} Generated by scripts/build/xmlByteClass.ts at configure time. Do not`,
`${comment} edit. The same definition feeds the Highway XML kernel (these nibble`,
`${comment} LUTs) and the Rust scalar indexer (the derived 256-entry table).`,
`${comment}`,
`${comment} cls = LUT_LO[b & 0xF] & LUT_HI[b >> 4]`,
`${comment} 0x07 always & \\r control 0x38 tag \\t \\n " ' =`,
`${comment} 0x40 < 0x80 >`,
"",
];
const hex = (v: number) => `0x${v.toString(16).padStart(2, "0")}`;
const h = [
...banner("//"),
"#pragma once",
"#include <stdint.h>",
"",
`#define BUN_XML_CLASS_ALWAYS ${hex(ALWAYS)}`,
`#define BUN_XML_CLASS_TAG ${hex(TAG)}`,
`#define BUN_XML_CLASS_LT ${hex(LT)}`,
`#define BUN_XML_CLASS_GT ${hex(GT)}`,
"",
`alignas(16) static const uint8_t kBunXmlLutLo[16] = { ${LUT_LO.map(hex).join(", ")} };`,
`alignas(16) static const uint8_t kBunXmlLutHi[16] = { ${LUT_HI.map(hex).join(", ")} };`,
"",
].join("\n");
const table: string[] = [];
for (let row = 0; row < 256; row += 16) {
const cells = [];
for (let b = row; b < row + 16; b++) cells.push(hex(classOf(b)));
table.push(` ${cells.join(", ")},`);
}
const allow = "#[allow(dead_code, unreachable_pub, unused)]";
const rs = [
...banner("//"),
allow,
`pub const CLASS_ALWAYS: u8 = ${hex(ALWAYS)};`,
allow,
`pub const CLASS_TAG: u8 = ${hex(TAG)};`,
allow,
`pub const CLASS_LT: u8 = ${hex(LT)};`,
allow,
`pub const CLASS_GT: u8 = ${hex(GT)};`,
"",
"/// `LUT_LO[b & 0xF] & LUT_HI[b >> 4]` for every byte `b`.",
allow,
"#[rustfmt::skip]",
"pub const XML_BYTE_CLASS: [u8; 256] = [",
...table,
"];",
"",
].join("\n");
mkdirSync(cfg.codegenDir, { recursive: true });
const hPath = resolve(cfg.codegenDir, "xml_byte_class.h");
const rsPath = resolve(cfg.codegenDir, "xml_byte_class.rs");
writeIfChanged(hPath, h);
writeIfChanged(rsPath, rs);
return { h: hPath, rs: rsPath };
}