playcanvas
Version:
PlayCanvas WebGL game engine
379 lines (377 loc) • 13.7 kB
JavaScript
var KEYWORD = /[ \t]*#(ifn?def|if|endif|else|elif|define|undef|extension|include)/g;
var DEFINE = /define[ \t]+([^\n]+)\r?(?:\n|$)/g;
var EXTENSION = /extension[ \t]+([\w-]+)[ \t]*:[ \t]*(enable|require)/g;
var UNDEF = /undef[ \t]+([^\n]+)\r?(?:\n|$)/g;
var IF = /(ifdef|ifndef|if)[ \t]*([^\r\n]+)\r?\n/g;
var ENDIF = /(endif|else|elif)(?:[ \t]+([^\r\n]*))?\r?\n?/g;
var IDENTIFIER = /\{?[\w-]+\}?/;
var DEFINED = /(!|\s)?defined\(([\w-]+)\)/;
var COMPARISON = /([a-z_]\w*)\s*(==|!=|<|<=|>|>=)\s*([\w"']+)/i;
var INVALID = /[+\-]/g;
var INCLUDE = /include[ \t]+"([\w-]+)(?:\s*,\s*([\w-]+))?"\r?(?:\n|$)/g;
var LOOP_INDEX = /\{i\}/g;
var FRAGCOLOR = /(pcFragColor[1-8])\b/g;
class Preprocessor {
static run(source, includes, options) {
if (includes === void 0) includes = new Map();
if (options === void 0) options = {};
Preprocessor.sourceName = options.sourceName;
source = this.stripComments(source);
source = source.split(/\r?\n/).map((line)=>line.trimEnd()).join('\n');
var defines = new Map();
var injectDefines = new Map();
source = this._preprocess(source, defines, injectDefines, includes, options.stripDefines);
var intDefines = new Map();
defines.forEach((value, key)=>{
if (Number.isInteger(parseFloat(value)) && !value.includes('.')) {
intDefines.set(key, value);
}
});
source = this.stripComments(source);
source = this.stripUnusedColorAttachments(source, options);
source = this.RemoveEmptyLines(source);
source = this.processArraySize(source, intDefines);
source = this.injectDefines(source, injectDefines);
return source;
}
static stripUnusedColorAttachments(source, options) {
if (options.stripUnusedColorAttachments) {
var counts = new Map();
var matches = source.match(FRAGCOLOR);
matches == null ? void 0 : matches.forEach((match)=>{
var index = parseInt(match.charAt(match.length - 1), 10);
var _counts_get;
counts.set(index, ((_counts_get = counts.get(index)) != null ? _counts_get : 0) + 1);
});
var anySingleUse = Array.from(counts.values()).some((count)=>count === 1);
if (anySingleUse) {
var lines = source.split('\n');
var keepLines = [];
for(var i = 0; i < lines.length; i++){
var match = lines[i].match(FRAGCOLOR);
if (match) {
var index = parseInt(match[0].charAt(match[0].length - 1), 10);
if (index > 0 && counts.get(index) === 1) {
continue;
}
}
keepLines.push(lines[i]);
}
source = keepLines.join('\n');
}
}
return source;
}
static stripComments(source) {
return source.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1');
}
static processArraySize(source, intDefines) {
if (source !== null) {
intDefines.forEach((value, key)=>{
source = source.replace(new RegExp("\\[" + key + "\\]", 'g'), "[" + value + "]");
});
}
return source;
}
static injectDefines(source, injectDefines) {
if (source !== null && injectDefines.size > 0) {
var lines = source.split('\n');
injectDefines.forEach((value, key)=>{
var regex = new RegExp(key, 'g');
for(var i = 0; i < lines.length; i++){
if (!lines[i].includes('#')) {
lines[i] = lines[i].replace(regex, value);
}
}
});
source = lines.join('\n');
}
return source;
}
static RemoveEmptyLines(source) {
if (source !== null) {
source = source.split(/\r?\n/).map((line)=>line.trim() === '' ? '' : line).join('\n');
source = source.replace(/(\n\n){3,}/g, '\n\n');
}
return source;
}
static _preprocess(source, defines, injectDefines, includes, stripDefines) {
if (defines === void 0) defines = new Map();
var originalSource = source;
var stack = [];
var error = false;
var match;
while((match = KEYWORD.exec(source)) !== null && !error){
var keyword = match[1];
switch(keyword){
case 'define':
{
DEFINE.lastIndex = match.index;
var define = DEFINE.exec(source);
error || (error = define === null);
var expression = define[1];
IDENTIFIER.lastIndex = define.index;
var identifierValue = IDENTIFIER.exec(expression);
var identifier = identifierValue[0];
var value = expression.substring(identifier.length).trim();
if (value === '') value = 'true';
var keep = Preprocessor._keep(stack);
var stripThisDefine = stripDefines;
if (keep) {
var replacementDefine = identifier.startsWith('{') && identifier.endsWith('}');
if (replacementDefine) {
stripThisDefine = true;
}
if (replacementDefine) {
injectDefines.set(identifier, value);
} else {
defines.set(identifier, value);
}
if (stripThisDefine) {
source = source.substring(0, define.index - 1) + source.substring(DEFINE.lastIndex);
KEYWORD.lastIndex = define.index - 1;
}
}
if (!stripThisDefine) {
KEYWORD.lastIndex = define.index + define[0].length;
}
break;
}
case 'undef':
{
UNDEF.lastIndex = match.index;
var undef = UNDEF.exec(source);
var identifier1 = undef[1].trim();
var keep1 = Preprocessor._keep(stack);
if (keep1) {
defines.delete(identifier1);
if (stripDefines) {
source = source.substring(0, undef.index - 1) + source.substring(UNDEF.lastIndex);
KEYWORD.lastIndex = undef.index - 1;
}
}
if (!stripDefines) {
KEYWORD.lastIndex = undef.index + undef[0].length;
}
break;
}
case 'extension':
{
EXTENSION.lastIndex = match.index;
var extension = EXTENSION.exec(source);
error || (error = extension === null);
if (extension) {
var identifier2 = extension[1];
var keep2 = Preprocessor._keep(stack);
if (keep2) {
defines.set(identifier2, 'true');
}
}
KEYWORD.lastIndex = extension.index + extension[0].length;
break;
}
case 'ifdef':
case 'ifndef':
case 'if':
{
IF.lastIndex = match.index;
var iff = IF.exec(source);
var expression1 = iff[2];
var evaluated = Preprocessor.evaluate(expression1, defines);
error || (error = evaluated.error);
var result = evaluated.result;
if (keyword === 'ifndef') {
result = !result;
}
stack.push({
anyKeep: result,
keep: result,
start: match.index,
end: IF.lastIndex
});
KEYWORD.lastIndex = iff.index + iff[0].length;
break;
}
case 'endif':
case 'else':
case 'elif':
{
ENDIF.lastIndex = match.index;
var endif = ENDIF.exec(source);
var blockInfo = stack.pop();
if (!blockInfo) {
console.error('Shader preprocessing encountered "#' + endif[1] + '" without a preceding #if #ifdef #ifndef while preprocessing ' + Preprocessor.sourceName + " on line:\n " + source.substring(match.index, match.index + 100) + "...", {
source: originalSource
});
error = true;
continue;
}
var blockCode = blockInfo.keep ? source.substring(blockInfo.end, match.index) : '';
source = source.substring(0, blockInfo.start) + blockCode + source.substring(ENDIF.lastIndex);
KEYWORD.lastIndex = blockInfo.start + blockCode.length;
var endifCommand = endif[1];
if (endifCommand === 'else' || endifCommand === 'elif') {
var result1 = false;
if (!blockInfo.anyKeep) {
if (endifCommand === 'else') {
result1 = !blockInfo.keep;
} else {
var evaluated1 = Preprocessor.evaluate(endif[2], defines);
result1 = evaluated1.result;
error || (error = evaluated1.error);
}
}
stack.push({
anyKeep: blockInfo.anyKeep || result1,
keep: result1,
start: KEYWORD.lastIndex,
end: KEYWORD.lastIndex
});
}
break;
}
case 'include':
{
var _include_;
INCLUDE.lastIndex = match.index;
var include = INCLUDE.exec(source);
error || (error = include === null);
var identifier3 = include[1].trim();
var countIdentifier = (_include_ = include[2]) == null ? void 0 : _include_.trim();
var keep3 = Preprocessor._keep(stack);
if (keep3) {
var includeSource = includes == null ? void 0 : includes.get(identifier3);
if (includeSource !== undefined) {
includeSource = this.stripComments(includeSource);
if (countIdentifier) {
var countString = defines.get(countIdentifier);
var count = parseFloat(countString);
if (Number.isInteger(count)) {
var result2 = '';
for(var i = 0; i < count; i++){
result2 += includeSource.replace(LOOP_INDEX, String(i));
}
includeSource = result2;
} else {
console.error('Include Count identifier "' + countIdentifier + '" not resolved while preprocessing ' + Preprocessor.sourceName + " on line:\n " + source.substring(match.index, match.index + 100) + "...", {
source: originalSource
});
error = true;
}
}
source = source.substring(0, include.index - 1) + includeSource + source.substring(INCLUDE.lastIndex);
KEYWORD.lastIndex = include.index - 1;
} else {
console.error('Include "' + identifier3 + '" not resolved while preprocessing ' + Preprocessor.sourceName, {
source: originalSource
});
error = true;
continue;
}
}
break;
}
}
}
if (stack.length > 0) {
console.error("Shader preprocessing reached the end of the file without encountering the necessary #endif to close a preceding #if, #ifdef, or #ifndef block. " + Preprocessor.sourceName);
error = true;
}
if (error) {
console.error('Failed to preprocess shader: ', {
source: originalSource
});
return originalSource;
}
return source;
}
static _keep(stack) {
for(var i = 0; i < stack.length; i++){
if (!stack[i].keep) {
return false;
}
}
return true;
}
static evaluateAtomicExpression(expr, defines) {
var error = false;
expr = expr.trim();
var invert = false;
var definedMatch = DEFINED.exec(expr);
if (definedMatch) {
invert = definedMatch[1] === '!';
expr = definedMatch[2].trim();
var exists = defines.has(expr);
return {
result: invert ? !exists : exists,
error
};
}
var comparisonMatch = COMPARISON.exec(expr);
if (comparisonMatch) {
var _defines_get;
var left = (_defines_get = defines.get(comparisonMatch[1].trim())) != null ? _defines_get : comparisonMatch[1].trim();
var _defines_get1;
var right = (_defines_get1 = defines.get(comparisonMatch[3].trim())) != null ? _defines_get1 : comparisonMatch[3].trim();
var operator = comparisonMatch[2].trim();
var result = false;
switch(operator){
case '==':
result = left === right;
break;
case '!=':
result = left !== right;
break;
case '<':
result = left < right;
break;
case '<=':
result = left <= right;
break;
case '>':
result = left > right;
break;
case '>=':
result = left >= right;
break;
default:
error = true;
}
return {
result,
error
};
}
var result1 = defines.has(expr);
return {
result: result1,
error
};
}
static evaluate(expression, defines) {
var correct = INVALID.exec(expression) === null;
var orSegments = expression.split('||');
for (var orSegment of orSegments){
var andSegments = orSegment.split('&&');
var andResult = true;
for (var andSegment of andSegments){
var { result, error } = Preprocessor.evaluateAtomicExpression(andSegment.trim(), defines);
if (!result || error) {
andResult = false;
break;
}
}
if (andResult) {
return {
result: true,
error: !correct
};
}
}
return {
result: false,
error: !correct
};
}
}
export { Preprocessor };