Skip to main content
Module

x/superserial/parse.ts

A comprehensive Serializer/Deserializer that can handle any data type.
Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
const NUM_CHARS = new Set(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]);
export type AstRoot = | AstUndefined | AstNull | AstBool | AstNumber | AstBigInt | AstString | AstSymbol | AstArray | AstObject | AstRegExp | AstDate | AstSet | AstMap;
export type AstAny = AstRoot | AstRef;
export type AstUndefined = [type: 0];export type AstNull = [type: 1];export type AstBool = [type: 2, value: boolean];export type AstNumber = [type: 3, value: number];export type AstBigInt = [type: 4, value: bigint];export type AstString = [type: 5, value: string];export type AstSymbol = [type: 6, description: string | null];
export type AstArray = [type: 16, items: AstAny[]];export type AstObject = [ type: 17, name: string | null, entries: [AstString, AstAny][],];
export type AstRegExp = [type: 32, pattern: string, flags: string | null];export type AstDate = [type: 33, timestamp: number];export type AstSet = [type: 34, items: AstAny[]];export type AstMap = [type: 35, entries: [AstAny, AstAny][]];
export type AstRef = [type: 64, index: number];
let buf = "";let pos = 0;
function consume(s: string) { for (const c of s) { if (buf[pos] !== c) { throw error(); } pos++; }}
function white() { while (1) { switch (buf[pos]) { case "\t": case "\v": case "\f": case " ": case "\u00A0": case "\uFEFF": case "\n": case "\r": case "\u2028": case "\u2029": pos++; break; default: return; } }}
function error() { return new SyntaxError( buf[pos] ? `Unexpected token '${buf[pos]}' in SuperSerial at position ${pos + 1}` : "Unexpected end of SuperSerial input", );}
function parseAny(): AstAny { white(); if (buf[pos] === "$") { pos++; let result = ""; while (NUM_CHARS.has(buf[pos])) { result += buf[pos++]; } return [64, +result]; } return parseRoot();}
function parseRoot(): AstRoot { white(); switch (buf[pos]) { case "-": case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": { return parseNumber(); } case '"': return parseString(); case "[": return parseArray(); case "/": return parseRegExp(); default: { const name = keyword(); switch (name) { case "null": return [1]; case "true": return [2, true]; case "false": return [2, false]; } if (buf[pos] === "{") { return parseObject(name); } switch (name) { case "undefined": return [0]; case "NaN": return [3, NaN]; case "Infinity": return [3, Infinity]; } if (buf[pos] === "(") { switch (name) { case "Map": return parseMap(); case "Set": return parseSet(); case "Date": return parseDate(); case "Symbol": return parseSymbol(); default: throw error(); } } } }
throw error();}
function parseNumber(): AstNumber | AstBigInt { let result = ""; let mult = 1;
if (buf[pos] === "-") { pos++; mult = -1; } if (buf[pos] === "I") { pos++; consume("nfinity"); return [3, mult * Infinity]; } if (NUM_CHARS.has(buf[pos])) { result += buf[pos++]; } else { throw error(); } while (NUM_CHARS.has(buf[pos])) { result += buf[pos++]; } if (buf[pos] === "n") { pos++; return [4, BigInt(result) * BigInt(mult)]; } else { if (buf[pos] === ".") { result += buf[pos++]; while (NUM_CHARS.has(buf[pos])) { result += buf[pos++]; } } if (buf[pos] === "e" || buf[pos] === "E") { result += buf[pos++]; if (buf[pos] === "-" || buf[pos] === "+") { result += buf[pos++]; } if (NUM_CHARS.has(buf[pos])) { result += buf[pos++]; } else { throw error(); } while (NUM_CHARS.has(buf[pos])) { result += buf[pos++]; } } } return [3, +result * mult];}
function parseString(): AstString { let result = ""; pos++; while (1) { if (pos >= buf.length) { break; } switch (buf[pos]) { case '"': pos++; return [5, result]; case "\\": pos++; switch (buf[pos]) { case "u": { pos++; let uffff = 0; for (let i = 0; i < 4; i++) { const hex = parseInt(buf[pos], 16); if (!isFinite(hex)) { throw error(); } pos++; uffff = uffff * 16 + hex; } result += String.fromCharCode(uffff); continue; } case '"': pos++; result += '"'; continue; case "\\": pos++; result += "\\"; continue; case "b": pos++; result += "\b"; continue; case "f": pos++; result += "\f"; continue; case "n": pos++; result += "\n"; continue; case "r": pos++; result += "\r"; continue; case "t": pos++; result += "\t"; continue; } break; default: result += buf[pos++]; continue; } break; } throw error();}
function parseArray(): AstArray { pos++; white(); if (buf[pos] === "]") { pos++; return [16, []]; }
const result = [] as AstAny[]; result.push(parseAny());
white(); while (buf[pos] === ",") { pos++; result.push(parseAny()); white(); } if (buf[pos] === "]") { pos++; return [16, result]; } throw error();}
function parseObject(name: string | null = null): AstObject { pos++; white(); if (buf[pos] === "}") { pos++; return [17, name, []]; } const result = [] as [AstString, AstAny][]; while (1) { const key = parseString(); // TODO Symbol white(); if (buf[pos] !== ":") { throw error(); } pos++; result.push([key, parseAny()]); white(); if (buf[pos] === ",") { pos++; white(); continue; } if (buf[pos] === "}") { pos++; return [17, name, result]; } break; } throw error();}
function parseRegExp(): AstRegExp { pos++; let pattern = ""; if (buf[pos] === "/") { throw error(); } while (buf[pos]) { if (buf[pos] === "/") { pos++; switch (buf[pos]) { case "i": { pos++; switch (buf[pos]) { case "m": { pos++; if (buf[pos] === "g") { pos++; return [32, pattern, "img"]; } else { return [32, pattern, "im"]; } } case "g": { pos++; if (buf[pos] === "m") { pos++; return [32, pattern, "igm"]; } else { return [32, pattern, "ig"]; } } default: { return [32, pattern, "i"]; } } } case "m": { pos++; switch (buf[pos]) { case "i": { pos++; if (buf[pos] === "g") { pos++; return [32, pattern, "mig"]; } else { return [32, pattern, "mi"]; } } case "g": { pos++; if (buf[pos] === "i") { pos++; return [32, pattern, "mgi"]; } else { return [32, pattern, "mg"]; } } default: { return [32, pattern, "m"]; } } } case "g": { pos++; switch (buf[pos]) { case "m": { pos++; if (buf[pos] === "i") { pos++; return [32, pattern, "gmi"]; } else { return [32, pattern, "gm"]; } } case "i": { pos++; if (buf[pos] === "m") { pos++; return [32, pattern, "gim"]; } else { return [32, pattern, "gi"]; } } default: { return [32, pattern, "g"]; } } } } return [32, pattern, null]; } else if (buf[pos] === "\\") { pattern += buf[pos++]; pattern += buf[pos++]; } else { pattern += buf[pos++]; } } throw error();}
function parseSet(): AstSet { pos++; white(); if (buf[pos] === ")") { pos++; return [34, []]; }
const items = [] as AstAny[]; items.push(parseAny());
white(); while (buf[pos] === ",") { pos++; items.push(parseAny()); white(); } if (buf[pos] === ")") { pos++; return [34, items]; } throw error();}
function parseMap(): AstMap { pos++; white(); if (buf[pos] === ")") { pos++; return [35, []]; } const entries = [] as [AstAny, AstAny][]; while (1) { const key = parseAny(); white(); consume("=>"); white(); const value = parseAny(); entries.push([key, value]); white(); if (buf[pos] === ",") { pos++; white(); continue; } if (buf[pos] === ")") { pos++; break; } throw error(); } return [35, entries];}
function parseSymbol(): AstSymbol { pos++; white(); if (buf[pos] === ")") { pos++; return [6, null]; } if (buf[pos] === '"') { const valueString = parseString(); white(); if (buf[pos] === ")") { pos++; return [6, valueString[1]]; } } throw error();}
function parseDate(): AstDate { pos++; white(); let value = ""; let mult = 1; if (buf[pos] === "-") { pos++; mult = -1; } if (NUM_CHARS.has(buf[pos])) { value += buf[pos++]; } else { throw error(); } while (NUM_CHARS.has(buf[pos])) { value += buf[pos++]; } if (buf[pos] === ".") { value += buf[pos++]; while (NUM_CHARS.has(buf[pos])) { value += buf[pos++]; } } if (buf[pos] === "e" || buf[pos] === "E") { value += buf[pos++]; if (buf[pos] === "-" || buf[pos] === "+") { value += buf[pos++]; } if (NUM_CHARS.has(buf[pos])) { value += buf[pos++]; } else { throw error(); } while (NUM_CHARS.has(buf[pos])) { value += buf[pos++]; } } white(); if (buf[pos] === ")") { pos++; return [33, +value * mult]; } throw error();}
function keyword(): string | null { let chartCode = buf.charCodeAt(pos); let result = ""; if ( chartCode >= 65 && chartCode <= 90 || // UPPERCASE chartCode >= 97 && chartCode <= 122 || // lowercase chartCode === 95 // _ ) { result += buf[pos++]; } else { return null; } while ((chartCode = buf.charCodeAt(pos))) { if ( chartCode >= 65 && chartCode <= 90 || // UPPERCASE chartCode >= 97 && chartCode <= 122 || // lowercase chartCode >= 48 && chartCode <= 57 || // number chartCode === 95 // _ ) { result += buf[pos++]; } else { break; } } white(); return result;}
export function parse( ctx: string,): AstRoot[] { buf = ctx; pos = 0;
const roots = [] as AstRoot[]; roots.push(parseRoot()); white(); while (buf[pos] === ";") { pos++; roots.push(parseRoot()); white(); }
if (buf.length !== pos) { throw error(); }
return roots;}