UNPKG

wgsl-plus

Version:

A WGSL preprocessor, prettifier, minifier, obfuscator, and compiler with C-style macros, conditional compilation, file linking, and multi-format output for WebGPU shaders.

192 lines (191 loc) 9.13 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = prettify; const tokenize_wgsl_1 = __importDefault(require("../tokenization/tokenize-wgsl")); // Helper function to check if a token is punctuation function isPunctuation(token) { return token.type === 'operator' && /^[\(\)\{\}\[\],;:.]$/.test(token.value); } // Helper function to check if angle brackets are part of a type parameter function isTypeAngleBracket(tokens, index) { const token = tokens[index]; // Only process angle brackets if (token.value !== '<' && token.value !== '>') { return false; } if (token.value === '<') { // Check if preceded by a type name const prevToken = index > 0 ? tokens[index - 1] : null; return prevToken && (prevToken.type === 'identifier' || prevToken.type === 'builtin'); } else { // For '>', check if it's closing a type angle bracket let depth = 1; for (let i = index - 1; i >= 0; i--) { if (tokens[i].value === '<') depth--; if (tokens[i].value === '>') depth++; if (depth === 0) { // Found the matching '<', check if it's part of a type return i > 0 && (tokens[i - 1].type === 'identifier' || tokens[i - 1].type === 'builtin'); } } } return false; } function prettify(code) { const formattedLines = []; let currentLevel = 0; // Indentation level let inStructDefinition = false; // Track if inside a struct definition let inForLoop = false; // Track if inside a for loop initialization // Tokenize the code const tokens = (0, tokenize_wgsl_1.default)(code); // Process tokens and format the code let line = ''; let i = 0; while (i < tokens.length) { const token = tokens[i]; const nextToken = i + 1 < tokens.length ? tokens[i + 1] : null; const prevToken = i > 0 ? tokens[i - 1] : null; // Handle for loop detection if (token.type === 'keyword' && token.value === 'for') { inForLoop = true; } else if (inForLoop && token.value === ')') { inForLoop = false; } // Handle comments separately if (token.type === 'comment') { // Check if it's a multi-line comment if (token.value.startsWith('/*')) { // Format multi-line comments by normalizing indentation const lines = token.value.split('\n'); if (lines.length > 1) { // Add the first line formattedLines.push(' '.repeat(currentLevel) + lines[0].trim()); // For middle lines, preserve relative indentation but normalize it for (let j = 1; j < lines.length - 1; j++) { const lineContent = lines[j].trim(); formattedLines.push(' '.repeat(currentLevel) + ' ' + lineContent); } // For the last line, which has the closing */ if (lines.length > 1) { const lastLine = lines[lines.length - 1].trim(); formattedLines.push(' '.repeat(currentLevel) + ' ' + lastLine); } } else { formattedLines.push(' '.repeat(currentLevel) + token.value); } } else { // Single-line comments formattedLines.push(' '.repeat(currentLevel) + token.value); } i++; continue; } // Define token types for easier reference const isKeyword = token.type === 'keyword'; const isIdentifier = token.type === 'identifier' || token.type === 'builtin'; const isAttribute = token.type === 'attribute'; const isOperator = token.type === 'operator' && !isPunctuation(token); const isNextOperator = (nextToken === null || nextToken === void 0 ? void 0 : nextToken.type) === 'operator' && !isPunctuation(nextToken); const isNextPunctuation = nextToken ? isPunctuation(nextToken) : false; // Add the token to the current line line += token.value; // Handle special spacing cases if (nextToken) { // Special case 1: Always add space after comma or semicolon in for loops if ((token.value === ',' || (token.value === ';' && inForLoop))) { line += ' '; } // Special case 2: No spaces for type angle brackets else if ((token.value === '<' && isTypeAngleBracket(tokens, i)) || (token.value === '>' && isTypeAngleBracket(tokens, i)) || (nextToken.value === '<' && isTypeAngleBracket(tokens, i + 1)) || (nextToken.value === '>' && isTypeAngleBracket(tokens, i + 1))) { // Don't add space } // Special case 3: No space before closing parentheses/brackets/braces else if (nextToken.value === ')' || nextToken.value === ']' || nextToken.value === '}') { // Don't add space before closing brackets } // Special case 4: No space before comma or semicolon else if (nextToken.value === ',' || nextToken.value === ';') { // Don't add space } // Special case 5: Handle colon in type annotations (position: vec3<f32>) else if (token.value === ':' || nextToken.value === ':') { // No space before colon, but add space after if (token.value === ':') { line += ' '; } // No space should be added before a colon } // Special case 6: No space after opening parentheses for control structures else if ((token.value === 'if' || token.value === 'for' || token.value === 'while') && nextToken && nextToken.value === '(') { // No space between if/for/while and opening parenthesis } // General spacing logic else { const isKeyword = token.type === 'keyword'; const isIdentifier = token.type === 'identifier' || token.type === 'builtin'; const isAttribute = token.type === 'attribute'; const isOperator = token.type === 'operator' && !isPunctuation(token); const isNextOperator = nextToken.type === 'operator' && !isPunctuation(nextToken); const isNextPunctuation = isPunctuation(nextToken); if (((isKeyword || isIdentifier || isAttribute) && !(isNextPunctuation && ['(', '['].includes(nextToken.value))) || // Always add space before { after an identifier or keyword (nextToken.value === '{') || (token.value === ':' && (nextToken.type === 'identifier' || nextToken.type === 'keyword' || nextToken.type === 'builtin')) || (token.value === ')' && nextToken.value === '{') || (isOperator && !isNextOperator) || (!isOperator && isNextOperator && !(nextToken.value === '<' && isTypeAngleBracket(tokens, i + 1)))) { line += ' '; } } } // Handle line breaks and indentation if ((token.value === ';' && !inForLoop) || (inStructDefinition && token.value === ',')) { formattedLines.push(' '.repeat(currentLevel) + line.trim()); line = ''; } else if (token.value === '{') { formattedLines.push(' '.repeat(currentLevel) + line.trim()); line = ''; currentLevel++; if ((i > 0 && tokens[i - 1].value === 'struct') || (i > 1 && tokens[i - 2].value === 'struct')) { inStructDefinition = true; } } else if (token.value === '}') { currentLevel = Math.max(currentLevel - 1, 0); formattedLines.push(' '.repeat(currentLevel) + line.trim()); line = ''; // Handle struct or other definition ending with }; if (nextToken && nextToken.value === ';') { // For struct definitions, put the }; on the same line formattedLines[formattedLines.length - 1] = formattedLines[formattedLines.length - 1].replace(/}$/, '};'); i++; // Skip the semicolon if (inStructDefinition) { inStructDefinition = false; } } } i++; } // Add any remaining line if (line.trim()) { formattedLines.push(' '.repeat(currentLevel) + line.trim()); } return formattedLines.join('\n'); }