UNPKG

@wgslx/wgslx

Version:

Extended WebGPU shading language tools

299 lines (248 loc) 7.58 kB
// Template discovery // https://www.w3.org/TR/WGSL/#template-lists-sec // // In our implementation of the parser, we preprocess code to deliminate template brackets from // greater than and less than characters. We replace them with double angle quotation marks // U+00AB «, and U+00BB ». As this is done per file, templates opening and closing can not cross // file boundaries. import { LINE_BREAK_REGEX, matchBlankspace, matchBlockComment, matchIdentPatternToken, matchLineEndingComment, matchLiteral, } from './patterns'; import {TEMPLATE_END, TEMPLATE_START} from './util'; interface UnclosedCandidate { position: number; depth: number; } interface TemplateList { startPosition: number; endPosition: number; } export function preprocess(text: string) { text = stripComments(text); const templateLists = discoverTemplates(text); text = text.replaceAll(/[<>]/g, (character, offset) => { switch (character) { case '<': return templateLists.some((t) => t.startPosition === offset) ? TEMPLATE_START : '<'; case '>': return templateLists.some((t) => t.endPosition === offset) ? TEMPLATE_END : '>'; } throw new Error('Unidentified character.'); }); return text; } function firstIndexOf( text: string, position: number, ...values: string[] ): [number, string] { let minIndex = Number.MAX_SAFE_INTEGER; let minChar = ''; for (let value of values) { const index = text.indexOf(value, position); if (index !== -1 && index < minIndex) { minIndex = index; minChar = value; } } if (minIndex === Number.MAX_SAFE_INTEGER) { return [-1, '']; } return [minIndex, minChar]; } export function stripComments(text: string) { const lines = text.split(LINE_BREAK_REGEX); for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { const [firstCommentIndex, firstCommentType] = firstIndexOf( lines[lineIndex], 0, '//', '/*', '*/' ); if (firstCommentIndex === -1) { lines[lineIndex] = lines[lineIndex].trimEnd(); continue; } if (firstCommentType === '*/') { throw new Error(`Line ${lineIndex + 1}: Unexpected block comment close.`); } if (firstCommentType === '//') { // Trim the end as no tokens will reference it anymore. lines[lineIndex] = lines[lineIndex].substring(0, firstCommentIndex); continue; } let depth = 1; let startLine = lineIndex; let column = firstCommentIndex + 2; while (depth !== 0) { const [nextBlockIndex, nextBlockType] = firstIndexOf( lines[lineIndex], column, '/*', '*/' ); if (nextBlockIndex === -1) { if (lineIndex === lines.length - 1) { throw new Error(`Line ${startLine}: Unclosed block comment.`); } // Try the next line. lineIndex += 1; column = 0; continue; } if (nextBlockType === '/*') { depth += 1; column = nextBlockIndex + 2; continue; } if (nextBlockType === '*/') { depth -= 1; column = nextBlockIndex + 2; continue; } throw new Error('Unreachable code.'); } if (startLine == lineIndex) { // Same line block comment, replace with whitespace since it can affect token positioning. lines[lineIndex] = lines[lineIndex].substring(0, firstCommentIndex) + ' '.repeat(column - firstCommentIndex) + lines[lineIndex].substring(column); lineIndex -= 1; // Revisit the line for any other comments. continue; } lines[startLine] = lines[startLine].substring(0, firstCommentIndex); for (let j = startLine + 1; j < lineIndex; j++) { lines[j] = ''; } lines[lineIndex] = ' '.repeat(column) + lines[lineIndex].substring(column); lineIndex -= 1; // Revisit the line for any other comments. } return lines.join('\n'); } export function discoverTemplates(text: string) { const discoveredTemplateLists: TemplateList[] = []; const pendingCandidatesStack: UnclosedCandidate[] = []; let currentPosition = 0; let nestingDepth = 0; function matchAdvance( ...matchers: Array<(text: string, position: number) => string | undefined> ) { const startPosition = currentPosition; let matched = false; rematch: do { for (let matcher of matchers) { const match = matcher(text, currentPosition); if (match) { currentPosition += match.length; matched = true; continue rematch; } } matched = false; } while (matched === true && matchers.length > 1); return startPosition === currentPosition ? undefined : text.substring(startPosition, currentPosition); } function startsWithAdvance(...consts: string[]) { const rest = text.substring(currentPosition); for (const str of consts) { if (rest.startsWith(str)) { currentPosition += str.length; return true; } } return false; } // Advance CurrentPosition past blankspace, comments, and literals. while (currentPosition < text.length) { // Advance currentPosition past blankspace. matchAdvance( matchBlankspace, matchLiteral, matchLineEndingComment, matchBlockComment ); if (matchAdvance(matchIdentPatternToken)) { // Advance CurrentPosition past blankspace and comments, if present. matchAdvance(matchBlankspace, matchLineEndingComment, matchBlockComment); if (startsWithAdvance('<')) { pendingCandidatesStack.push({ position: currentPosition - 1, depth: nestingDepth, }); if (startsWithAdvance('<') || startsWithAdvance('=')) { // From assumption 1, no template parameter starts with '<' (U+003C), so the // previous code point cannot be the start of a template list. Therefore the // current and previous code point must be '<<' operator. pendingCandidatesStack.pop(); } } continue; } if (startsWithAdvance('>')) { if ( pendingCandidatesStack.length > 0 && pendingCandidatesStack[pendingCandidatesStack.length - 1].depth === nestingDepth ) { const pending = pendingCandidatesStack.pop()!; // Asserted due to array length check. discoveredTemplateLists.push({ startPosition: pending.position, endPosition: currentPosition - 1, }); } else { startsWithAdvance('='); } continue; } if (startsWithAdvance('(', '[')) { nestingDepth++; continue; } if (startsWithAdvance(')', ']')) { nestingDepth--; continue; } if (startsWithAdvance('!')) { startsWithAdvance('='); continue; } if (startsWithAdvance('=')) { if (startsWithAdvance('=')) { continue; } nestingDepth = 0; pendingCandidatesStack.length = 0; continue; } if (startsWithAdvance(';', '{', ':')) { nestingDepth = 0; pendingCandidatesStack.length = 0; continue; } if (startsWithAdvance('&&', '||')) { while ( pendingCandidatesStack.length && pendingCandidatesStack[pendingCandidatesStack.length - 1].depth >= nestingDepth ) { pendingCandidatesStack.pop(); } continue; } currentPosition += 1; } return discoveredTemplateLists; }