e7738de6d2
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
136 lines
5.7 KiB
TypeScript
136 lines
5.7 KiB
TypeScript
type TokenName =
|
|
| "comment"
|
|
| "string"
|
|
| "char"
|
|
| "keyword"
|
|
| "type"
|
|
| "function"
|
|
| "number"
|
|
| "variable"
|
|
| "id"
|
|
| "key"
|
|
| "boolean"
|
|
| "punctuation"
|
|
| "operator";
|
|
|
|
type Grammar = ReadonlyArray<readonly [TokenName, RegExp]>;
|
|
|
|
function escapeHtml(value: string): string {
|
|
return String(value)
|
|
.replaceAll("&", "&")
|
|
.replaceAll("<", "<")
|
|
.replaceAll(">", ">")
|
|
.replaceAll('"', """)
|
|
.replaceAll("'", "'");
|
|
}
|
|
|
|
const GRAMMARS: Record<string, Grammar> = {
|
|
bash: [
|
|
["string", /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/],
|
|
["comment", /#.*/],
|
|
["variable", /\$\{?[A-Za-z_][A-Za-z0-9_]*\}?/],
|
|
["key", /--?[A-Za-z0-9][A-Za-z0-9-]*(?:=[^\s\\]+)?/],
|
|
["keyword", /\b(?:if|then|else|elif|fi|for|while|do|done|case|esac|function|export|local|readonly|return|exit|set|unset)\b/],
|
|
["function", /(?<![A-Za-z0-9_./-])(?:zero|pnpm|npm|git|curl|make|node|npx|bash|sh|zsh|cd|mkdir|rm|cp|mv|cat|sed|rg)(?=\s|$)/],
|
|
["number", /\b\d+\b/],
|
|
["operator", /&&|\|\||\\|[|<>;&=]/],
|
|
["punctuation", /[{}()[\]]/],
|
|
],
|
|
zero: [
|
|
["comment", /\/\/.*/],
|
|
["string", /"(?:[^"\\]|\\.)*"/],
|
|
["char", /'(?:[^'\\\n]|\\(?:[nrt0'"\\]|x[0-9A-Fa-f]{2}))'/],
|
|
["keyword", /\b(?:pub|fn|let|var|return|raises|if|else|while|for|in|match|check|rescue|raise|use|interface|enum|choice|type|alias|const|as|break|continue|defer|export|extern|packed|static|meta|mut|test|and|or|not|true|false|null)\b/],
|
|
["type", /\b(?:Void|World|Self|Bool|bool|char|u4|u8|u16|u32|u64|i8|i16|i32|i64|usize|isize|f16|f32|f64|Span|MutSpan|Maybe|Alloc|Arena|FixedBufAlloc|GeneralAlloc|NullAlloc|PageAlloc|ref|mutref|owned|Slice|Array|String|Error|Io|Fs|Net|Env|Args|Clock|Rand|Proc|Sync|Cancel|Reader|Writer|File|Path|Conn|Listener|Address|Config|Type|Field|c_int|c_long|c_size|cstr|[A-Z][A-Za-z0-9_]*)\b/],
|
|
["function", /\b[a-z_]\w*(?=\s*\()/],
|
|
["number", /\b(?:\d+\.\d+(?:[eE][+-]?\d+)?|0x[0-9a-fA-F_]+|0b[01_]+|0o[0-7_]+|\d[\d_]*(?:_[A-Za-z][A-Za-z0-9_]*)?)\b/],
|
|
["variable", /\b[a-z_]\w*(?=\.)/],
|
|
["punctuation", /[{}()\[\];,.<>]/],
|
|
["operator", /:|[-+*/%=!&|^~@?]+/],
|
|
],
|
|
"zero-graph": [
|
|
["comment", /\/\/.*/],
|
|
["string", /"(?:[^"\\]|\\.)*"/],
|
|
["id", /#[A-Za-z_][A-Za-z0-9_]*/],
|
|
["keyword", /\b(?:zero-graph|zero-program-graph-patch|origin|module|hash|node|edge|expect|graphHash|set|insert|insertEdge|replace|delete|rename|addFunction|addMain|addParam|addReturnBinary|addLetLiteral|addLetBinary|addReturnValue|addCheckWrite|addCheckWriteValue|addTest|replaceFunctionBody|replaceBlockBody|end|let|var|return|if|else|while|for|in|match|check|rescue|raise|use|test|and|or|not)\b/],
|
|
["type", /\b(?:Function|Param|Block|Identifier|Literal|MethodCall|Call|FieldAccess|Let|Var|Return|Check|If|While|For|Match|TypeRef|ArrayLiteral|Unary|Binary|Void|World|Self|Bool|bool|char|u4|u8|u16|u32|u64|i8|i16|i32|i64|usize|isize|f16|f32|f64|Span|MutSpan|Maybe|Alloc|Arena|FixedBufAlloc|GeneralAlloc|NullAlloc|PageAlloc|ref|mutref|owned|Slice|Array|String|Error|[A-Z][A-Za-z0-9_]*)\b/],
|
|
["boolean", /\b(?:true|false|null)\b/],
|
|
["key", /\b[A-Za-z_][A-Za-z0-9_-]*(?=\s*(?::|=))/],
|
|
["function", /\b(?:std|[a-z_][A-Za-z0-9_]*)\.[A-Za-z_][A-Za-z0-9_.]*(?=\s|$)/],
|
|
["number", /\b(?:v\d+|graph:[0-9a-fA-F]+|nodehash:[0-9a-fA-F]+|0x[0-9a-fA-F_]+|0b[01_]+|0o[0-7_]+|\d[\d_]*)\b/],
|
|
["variable", /\b[a-z_][A-Za-z0-9_]*(?=\.)/],
|
|
["punctuation", /[{}()\[\];,.<>]/],
|
|
["operator", /:|=|[-+*/%!&|^~@?]+/],
|
|
],
|
|
};
|
|
|
|
const LANGUAGE_ALIASES: Record<string, string> = {
|
|
sh: "bash",
|
|
shell: "bash",
|
|
zsh: "bash",
|
|
graph: "zero-graph",
|
|
"program-graph": "zero-graph",
|
|
"zero-program-graph": "zero-graph",
|
|
"graph-patch": "zero-graph",
|
|
"zero-graph-patch": "zero-graph",
|
|
"program-graph-patch": "zero-graph",
|
|
"zero-program-graph-patch": "zero-graph",
|
|
};
|
|
|
|
function normalizeLanguage(language: string): string {
|
|
const key = language.trim().toLowerCase();
|
|
return LANGUAGE_ALIASES[key] ?? key;
|
|
}
|
|
|
|
function looksLikeGraph(code: string): boolean {
|
|
const text = code.trimStart();
|
|
return (
|
|
/^zero-graph\s+v\d+\b/.test(text) ||
|
|
/^zero-program-graph-patch\s+v\d+\b/.test(text) ||
|
|
/^(?:node|edge)\s+#[A-Za-z_][A-Za-z0-9_]*\b/m.test(text) ||
|
|
/^(?:expect\s+graphHash|set\s+node=|insert\s+node=|insertEdge\s+|replace\s+node=|rename\s+node=|delete\s+node=|replaceFunctionBody\s+|replaceBlockBody\s+)/m.test(text)
|
|
);
|
|
}
|
|
|
|
export function highlightLanguage(language: string, code: string): string | null {
|
|
const normalized = normalizeLanguage(language);
|
|
if (GRAMMARS[normalized]) return normalized;
|
|
if ((normalized === "" || normalized === "text" || normalized === "txt") && looksLikeGraph(code)) {
|
|
return "zero-graph";
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function highlight(code: string, language: string): string {
|
|
const normalized = highlightLanguage(language, code);
|
|
const grammar = normalized ? GRAMMARS[normalized] : null;
|
|
if (!grammar) return escapeHtml(code);
|
|
|
|
const combined = new RegExp(
|
|
grammar.map(([, pattern]) => `(${pattern.source})`).join("|"),
|
|
"gm",
|
|
);
|
|
|
|
const names = grammar.map(([name]) => name);
|
|
let result = "";
|
|
let lastIndex = 0;
|
|
|
|
for (const match of code.matchAll(combined)) {
|
|
const matchIndex = match.index ?? 0;
|
|
if (matchIndex > lastIndex) {
|
|
result += escapeHtml(code.slice(lastIndex, matchIndex));
|
|
}
|
|
const groupIndex = match.slice(1).findIndex((g) => g !== undefined);
|
|
if (groupIndex === -1) continue;
|
|
const tokenType = names[groupIndex];
|
|
const tokenText = match[0];
|
|
result += `<span class="hl-${tokenType} hl-${normalized}-${tokenType}">${escapeHtml(tokenText)}</span>`;
|
|
lastIndex = matchIndex + tokenText.length;
|
|
}
|
|
|
|
if (lastIndex < code.length) {
|
|
result += escapeHtml(code.slice(lastIndex));
|
|
}
|
|
return result;
|
|
}
|