Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
254 changes: 254 additions & 0 deletions maps/html4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
[
"Aacute",
"aacute",
"Acirc",
"acirc",
"acute",
"AElig",
"aelig",
"Agrave",
"agrave",
"alefsym",
"Alpha",
"alpha",
"amp",
"and",
"ang",
"Aring",
"aring",
"asymp",
"Atilde",
"atilde",
"Auml",
"auml",
"bdquo",
"Beta",
"beta",
"brvbar",
"bull",
"cap",
"Ccedil",
"ccedil",
"cedil",
"cent",
"Chi",
"chi",
"circ",
"clubs",
"cong",
"copy",
"crarr",
"cup",
"curren",
"dagger",
"Dagger",
"darr",
"dArr",
"deg",
"Delta",
"delta",
"diams",
"divide",
"Eacute",
"eacute",
"Ecirc",
"ecirc",
"Egrave",
"egrave",
"empty",
"emsp",
"ensp",
"Epsilon",
"epsilon",
"equiv",
"Eta",
"eta",
"ETH",
"eth",
"Euml",
"euml",
"euro",
"exist",
"fnof",
"forall",
"frac12",
"frac14",
"frac34",
"frasl",
"Gamma",
"gamma",
"ge",
"gt",
"harr",
"hArr",
"hearts",
"hellip",
"Iacute",
"iacute",
"Icirc",
"icirc",
"iexcl",
"Igrave",
"igrave",
"image",
"infin",
"int",
"Iota",
"iota",
"iquest",
"isin",
"Iuml",
"iuml",
"Kappa",
"kappa",
"Lambda",
"lambda",
"lang",
"laquo",
"larr",
"lArr",
"lceil",
"ldquo",
"le",
"lfloor",
"lowast",
"loz",
"lrm",
"lsaquo",
"lsquo",
"lt",
"macr",
"mdash",
"micro",
"middot",
"minus",
"Mu",
"mu",
"nabla",
"nbsp",
"ndash",
"ne",
"ni",
"not",
"notin",
"nsub",
"Ntilde",
"ntilde",
"Nu",
"nu",
"Oacute",
"oacute",
"Ocirc",
"ocirc",
"OElig",
"oelig",
"Ograve",
"ograve",
"oline",
"Omega",
"omega",
"Omicron",
"omicron",
"oplus",
"or",
"ordf",
"ordm",
"Oslash",
"oslash",
"Otilde",
"otilde",
"otimes",
"Ouml",
"ouml",
"para",
"part",
"permil",
"perp",
"Phi",
"phi",
"Pi",
"pi",
"piv",
"plusmn",
"pound",
"prime",
"Prime",
"prod",
"prop",
"Psi",
"psi",
"quot",
"radic",
"rang",
"raquo",
"rarr",
"rArr",
"rceil",
"rdquo",
"real",
"reg",
"rfloor",
"Rho",
"rho",
"rlm",
"rsaquo",
"rsquo",
"sbquo",
"Scaron",
"scaron",
"sdot",
"sect",
"shy",
"Sigma",
"sigma",
"sigmaf",
"sim",
"spades",
"sub",
"sube",
"sum",
"sup",
"sup1",
"sup2",
"sup3",
"supe",
"szlig",
"Tau",
"tau",
"there4",
"Theta",
"theta",
"thetasym",
"thinsp",
"THORN",
"thorn",
"tilde",
"times",
"trade",
"Uacute",
"uacute",
"uarr",
"uArr",
"Ucirc",
"ucirc",
"Ugrave",
"ugrave",
"uml",
"upsih",
"Upsilon",
"upsilon",
"Uuml",
"uuml",
"weierp",
"Xi",
"xi",
"Yacute",
"yacute",
"yen",
"yuml",
"Yuml",
"Zeta",
"zeta",
"zwj",
"zwnj"
]
Comment on lines +1 to +254

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add a contract test for HTML4 hot-set integrity.

This list directly drives hot-node selection in scripts/write-decode-map.ts; a typo/duplicate/missing entry silently changes trie layout and benchmark behavior. Please add a test that asserts this array is duplicate-free and every name exists in maps/entities.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@maps/html4.json` around lines 1 - 254, The HTML4 hot-set array in
maps/html4.json is critical to hot-node selection used by
scripts/write-decode-map.ts and must be validated; add a contract test that (1)
loads maps/html4.json and asserts there are no duplicate entries, and (2) loads
maps/entities.json and asserts every string from html4.json exists as a key in
entities.json; implement the test near existing map tests (or create a new test
file) and fail the CI if duplicates or missing entity names are detected so
typos/omissions cannot silently change trie layout or benchmarks.

9 changes: 6 additions & 3 deletions scripts/trie/decode-trie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,29 +90,32 @@ export function decodeNode(
* or high (odd i) byte of slot i>>1.
*/
const packedKeySlots = Math.ceil(branchLength / 2);
const branchEnd = branchIndex + packedKeySlots + branchLength;
for (let keyIndex = 0; keyIndex < branchLength; keyIndex++) {
const slot = keyIndex >> 1;
const packed = decodeMap[branchIndex + slot];
const key = (packed >> ((keyIndex & 1) * 8)) & 0xff;
const destinationIndex = branchIndex + packedKeySlots + keyIndex;
// Pointers are relative to the end of the branch data.
decodeNode(
decodeMap,
resultMap,
prefix + String.fromCharCode(key),
(destinationIndex + decodeMap[destinationIndex]) & 0xff_ff,
(branchEnd + decodeMap[destinationIndex]) & 0xff_ff,
);
}
} else {
const branchEnd = branchIndex + branchLength;
for (let index = 0; index < branchLength; index++) {
const stored = decodeMap[branchIndex + index];
if (stored !== 0) {
const code = jumpOffset + index;
const p = branchIndex + index;
// Stored offsets are end-relative, +1 (0 = no branch).
decodeNode(
decodeMap,
resultMap,
prefix + String.fromCharCode(code),
(p + stored - 1) & 0xff_ff,
(branchEnd + stored - 1) & 0xff_ff,
);
}
}
Expand Down
Loading