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.

203 lines (202 loc) 9.39 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processConditionals = processConditionals; const evaluator_1 = require("./evaluator"); const macro_expander_1 = require("./macro-expander"); /** * Processes a WGSL file for conditional compilation directives */ function processConditionals(lines, defines) { // Track conditional inclusion state const conditionalStack = []; // Result lines const resultLines = []; /** * Checks if we're currently including code based on conditional stack */ const isCurrentlyIncluding = () => { for (const state of conditionalStack) { if (!state.including) { return false; } } return true; }; // Process each line for (let i = 0; i < lines.length; i++) { const line = lines[i]; const trimmedLine = line.trim(); // Check for preprocessor directives if (trimmedLine.startsWith("#")) { // Remove comments from the line before processing let directiveLine = trimmedLine; const commentIndex = directiveLine.indexOf("//"); if (commentIndex >= 0) { directiveLine = directiveLine.substring(0, commentIndex).trim(); } // Use regex to match directives with any whitespace pattern const defineMatch = directiveLine.match(/^#define\s+/); const undefMatch = directiveLine.match(/^#undef\s+/); const ifMatch = directiveLine.match(/^#if\s+/); const ifdefMatch = directiveLine.match(/^#ifdef\s+/); const ifndefMatch = directiveLine.match(/^#ifndef\s+/); const elifMatch = directiveLine.match(/^#elif\s+/); const elseMatch = directiveLine.match(/^#else(?:\s|$)/); const endifMatch = directiveLine.match(/^#endif(?:\s|$)/); // #define directive if (defineMatch) { if (isCurrentlyIncluding()) { // First check if this is a function-like macro // Only match if the opening parenthesis is immediately after the macro name with possibly whitespace // And contains a valid parameter list (identifiers separated by commas) const functionLikeRegex = /^#define\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*(?:\s*,\s*[a-zA-Z_][a-zA-Z0-9_]*)*)\s*\)\s*(.*)$/; const functionLikeMatch = directiveLine.match(functionLikeRegex); if (functionLikeMatch) { const [, name, paramsStr, value] = functionLikeMatch; const params = paramsStr.split(",").map(param => param.trim()); // Store the unexpanded value for function-like macros defines.set(name, { value: value || "", params }); } else { // Simple macro - extract the name first const macroNameMatch = directiveLine.match(/^#define\s+([a-zA-Z_][a-zA-Z0-9_]*)/); if (macroNameMatch) { const [, name] = macroNameMatch; // Extract the value (everything after the name) const valueStartIndex = directiveLine.indexOf(name) + name.length; let value = directiveLine.substring(valueStartIndex).trim(); // For simple macros, expand any referenced macros in the value const expandedValue = value ? (0, macro_expander_1.expandMacros)(value, new Map(defines)) : ""; defines.set(name, { value: expandedValue }); } } } continue; } // #undef directive if (undefMatch) { if (isCurrentlyIncluding()) { const undefMatch = directiveLine.match(/^#undef\s+([a-zA-Z_][a-zA-Z0-9_]*)$/); if (undefMatch) { const [, name] = undefMatch; defines.delete(name); } } continue; } // #if directive if (ifMatch) { const expr = directiveLine.substring(ifMatch[0].length).trim(); const result = isCurrentlyIncluding() ? (0, evaluator_1.evaluateExpression)(expr, defines) : false; conditionalStack.push({ including: result, hasBeenTrue: result, hasElse: false }); continue; } // #ifdef directive if (ifdefMatch) { const name = directiveLine.substring(ifdefMatch[0].length).trim(); const result = isCurrentlyIncluding() ? defines.has(name) : false; conditionalStack.push({ including: result, hasBeenTrue: result, hasElse: false }); continue; } // #ifndef directive if (ifndefMatch) { const name = directiveLine.substring(ifndefMatch[0].length).trim(); const result = isCurrentlyIncluding() ? !defines.has(name) : false; conditionalStack.push({ including: result, hasBeenTrue: result, hasElse: false }); continue; } // #elif directive if (elifMatch) { if (conditionalStack.length === 0) { throw new Error("Unexpected #elif without matching #if"); } const currentState = conditionalStack[conditionalStack.length - 1]; // It's an error to have #elif after #else if (currentState.hasElse) { throw new Error("Unexpected #elif after #else"); } // Only evaluate this branch if no previous branch in this if-chain has been true if (!currentState.hasBeenTrue) { const expr = directiveLine.substring(elifMatch[0].length).trim(); const parentIncluding = conditionalStack.length > 1 ? conditionalStack[conditionalStack.length - 2].including : true; // Only evaluate if parent conditions allow this code to be included if (parentIncluding) { const result = (0, evaluator_1.evaluateExpression)(expr, defines); currentState.including = result; if (result) { currentState.hasBeenTrue = true; } } else { currentState.including = false; } } else { // A previous branch was already true, so skip this one currentState.including = false; } continue; } // #else directive if (elseMatch) { if (conditionalStack.length === 0) { throw new Error("Unexpected #else without matching #if"); } const currentState = conditionalStack[conditionalStack.length - 1]; // Check if we've already seen an #else for this conditional if (currentState.hasElse) { throw new Error("Multiple #else directives for the same conditional"); } // Mark that we've seen an #else currentState.hasElse = true; if (!currentState.hasBeenTrue) { const parentIncluding = conditionalStack.length > 1 ? conditionalStack[conditionalStack.length - 2].including : true; currentState.including = parentIncluding; currentState.hasBeenTrue = true; } else { currentState.including = false; } continue; } // #endif directive if (endifMatch) { if (conditionalStack.length === 0) { throw new Error("Unexpected #endif without matching #if"); } conditionalStack.pop(); continue; } // Custom directives (#binding, #entrypoint) or #import are passed through if we're including if (isCurrentlyIncluding()) { resultLines.push(line); } continue; } // Include the line if we're in an including state if (isCurrentlyIncluding()) { // Expand macros in the line const expandedLine = (0, macro_expander_1.expandMacros)(line, defines); resultLines.push(expandedLine); } } // Check for unmatched #if/#ifdef/#ifndef if (conditionalStack.length > 0) { throw new Error("Unmatched #if, #ifdef, or #ifndef"); } return resultLines; }