UNPKG

highlight-ts

Version:

Highlight.JS in TypeScript (and ES6).

171 lines (130 loc) 5.77 kB
import { StringRegExp, LanguageDef, SyntaxDef, KeywordGroup, CompiledSyntaxDef, CompiledLanguageDef, CompiledKeywords } from './types'; function reStr(re: StringRegExp | undefined): string { return (re && (re as RegExp).source) || (re as string); } const noneRe: RegExp = { exec: (/*s*/) => null } as any as RegExp; function langRe(language: LanguageDef, value: StringRegExp, global?: true): RegExp { return new RegExp( reStr(value), 'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '') ); } export function compileLanguage(language: LanguageDef): CompiledLanguageDef { const cached_modes: [SyntaxDef, CompiledSyntaxDef][] = []; function getCompiled(sub: SyntaxDef): CompiledSyntaxDef | undefined { for (const [mode, compiled] of cached_modes) { if (sub === mode) { return compiled; } } } const cached_variants: [SyntaxDef, SyntaxDef[]][] = []; function getVariants(mode: SyntaxDef): SyntaxDef[] | undefined { if (!mode.variants || !mode.variants.length) { return undefined; } for (const [mode_, variants] of cached_variants) { if (mode === mode_) { return variants; } } const variants = mode.variants.map(variant => ({ ...mode, variants: undefined, ...variant })); cached_variants.push([mode, variants]); return variants; }; function compileMode(mode: SyntaxDef, parent?: CompiledSyntaxDef, parent_terminator_end?: string): CompiledSyntaxDef { const already_compiled = getCompiled(mode); if (already_compiled) { return already_compiled; } const compiled: CompiledSyntaxDef = { lexemesRe: langRe(language, mode.lexemes || /\w+/, true), relevance: mode.relevance == null ? 1 : mode.relevance, contains: [], terminators: noneRe, subLanguage: mode.subLanguage == null ? undefined : typeof mode.subLanguage == 'string' ? [mode.subLanguage] : mode.subLanguage }; cached_modes.push([mode, compiled]); if (mode.className) { compiled.className = mode.className; } if (mode.illegal) { compiled.illegalRe = langRe(language, mode.illegal); } for (const key of ['endsParent', 'endsWithParent', 'skip', 'excludeBegin', 'excludeEnd', 'returnBegin', 'returnEnd']) { if (mode[key as keyof SyntaxDef]) { compiled[key as keyof CompiledSyntaxDef] = true; } } // compile parenthes let compiled_terminator_end: string | undefined; if (parent) { const begin = mode.beginKeywords ? (`\\b(${mode.beginKeywords.split(/\s+/).join('|')})\\b`) : (mode.begin || /\B|\b/); mode.begin = begin; compiled.beginRe = langRe(language, begin); const end = !mode.end && !mode.endsWithParent ? /\B|\b/ : mode.end; if (end) { mode.end = end; compiled.endRe = langRe(language, end); } compiled_terminator_end = reStr(end) || ''; if (mode.endsWithParent && parent_terminator_end) { compiled_terminator_end += (end ? '|' : '') + parent_terminator_end; } } // compile keywords const keywords = mode.keywords || mode.beginKeywords; if (keywords) { const compiled_keywords: CompiledKeywords = {} as CompiledKeywords; const flatten = (className: string, str: string) => { if (language.case_insensitive) { str = str.toLowerCase(); } const kws = str.split(/\s+/); for (const kw of kws) { const [key, rel] = kw.split(/\|/) as [KeywordGroup, string]; compiled_keywords[key] = [className, rel ? Number(rel) : 1]; } }; if (typeof keywords == 'string') { // string flatten('keyword', keywords); } else { for (const className in keywords) { flatten(className, keywords[className as KeywordGroup] as string); } } compiled.keywords = compiled_keywords; } // compile contains const contains: SyntaxDef[] = []; if (mode.contains && mode.contains.length) { // expand mode for (const child of mode.contains) { const sub = child === 'self' ? mode : child; const variants = getVariants(sub) || (sub.endsWithParent && [{ ...sub }]) || [sub]; for (const variant of variants) { contains.push(variant); } } compiled.contains = contains.map(child => compileMode(child, compiled, compiled_terminator_end)); } if (mode.starts) { compiled.starts = compileMode(mode.starts, parent, parent_terminator_end); } const terminators = [ ...contains.map(child => child.beginKeywords ? `\\.?(${child.begin})\\.?` : child.begin), compiled_terminator_end, mode.illegal ].map(reStr).filter(Boolean); if (terminators.length) compiled.terminators = langRe(language, terminators.join('|'), true); return compiled; } const compiled: CompiledLanguageDef = compileMode(language) as CompiledLanguageDef; if (language.case_insensitive) compiled.case_insensitive = true; return compiled; }