107 lines
3.7 KiB
TypeScript
107 lines
3.7 KiB
TypeScript
// 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 };
|
|
}
|