Buckets:
| addToLibrary({ | |
| // Removes all C++ '//' and '/* */' comments from the given source string. | |
| // N.b. will also eat comments inside strings. | |
| $remove_cpp_comments_in_shaders: (code) => { | |
| var i = 0, out = '', ch, next, len = code.length; | |
| for (; i < len; ++i) { | |
| ch = code[i]; | |
| if (ch == '/') { | |
| next = code[i+1]; | |
| if (next == '/') { | |
| while (i < len && code[i+1] != '\n') ++i; | |
| } else if (next == '*') { | |
| while (i < len && (code[i-1] != '*' || code[i] != '/')) ++i; | |
| } else { | |
| out += ch; | |
| } | |
| } else { | |
| out += ch; | |
| } | |
| } | |
| return out; | |
| }, | |
| // Finds the index of closing parens from the opening parens at arr[i]. | |
| // Used polymorphically for strings ("foo") and token arrays (['(', 'foo', ')']) as input. | |
| $find_closing_parens_index: (arr, i, opening='(', closing=')') => { | |
| for (var nesting = 0; i < arr.length; ++i) { | |
| if (arr[i] == opening) ++nesting; | |
| if (arr[i] == closing && --nesting == 0) { | |
| return i; | |
| } | |
| } | |
| }, | |
| // Runs C preprocessor algorithm on the given string 'code'. | |
| // Supported preprocessor directives: #if, #ifdef, #ifndef, #else, #elif, #endif, #define and #undef. | |
| // predefs: Specifies a dictionary of { 'key1': function(arg0, arg1) {...}, 'key2': ... } of predefined preprocessing variables | |
| $preprocess_c_code__deps: ['$find_closing_parens_index'], | |
| $preprocess_c_code: function(code, defs = {}) { | |
| var i = 0, // iterator over the input string | |
| len = code.length, // cache input length | |
| out = '', // generates the preprocessed output string | |
| stack = [1]; // preprocessing stack (state of active/inactive #ifdef/#else blocks we are currently inside) | |
| // a mapping 'symbolname' -> function(args) which evaluates the given cpp macro, e.g. #define FOO(x) x+10. | |
| defs['defined'] = (args) => { // built-in "#if defined(x)"" macro. | |
| #if ASSERTIONS | |
| assert(args.length == 1); | |
| assert(/^[A-Za-z0-9_$]+$/.test(args[0].trim())); // Test that a C preprocessor identifier contains only valid characters (we likely parsed wrong if this fails) | |
| #endif | |
| return defs[args[0].trim()] ? 1 : 0; | |
| }; | |
| // Returns true if str[i] is whitespace. | |
| function isWhitespace(str, i) { | |
| return !(str.charCodeAt(i) > 32); // Compare as negation to treat end-of-string undefined as whitespace | |
| } | |
| // Returns index to the next whitespace character starting at str[i]. | |
| function nextWhitespace(str, i) { | |
| while (!isWhitespace(str, i)) ++i; | |
| return i; | |
| } | |
| // Returns an integer ID classification of the character at str[idx], used for tokenization purposes. | |
| function classifyChar(str, idx) { | |
| var cc = str.charCodeAt(idx); | |
| #if ASSERTIONS | |
| assert(!(cc > 127), "only 7-bit ASCII can be used in preprocessor #if/#ifdef/#define statements"); | |
| #endif | |
| if (cc > 32) { | |
| if (cc < 48) return 1; // an operator symbol, any of !"#$%&'()*+,-./ | |
| if (cc < 58) return 2; // a number 0123456789 | |
| if (cc < 65) return 1; // an operator symbol, any of :;<=>?@ | |
| if (cc < 91 || cc == 95/*_*/) return 3; // a character, any of A-Z or _ | |
| if (cc < 97) return 1; // an operator symbol, any of [\]^` | |
| if (cc < 123) return 3; // a character, any of a-z | |
| return 1; // an operator symbol, any of {|}~ | |
| } | |
| return cc < 33 ? 0 : 4; // 0=whitespace, 4=end-of-string | |
| } | |
| // Returns a tokenized array of the given string expression, i.e. "FOO > BAR && BAZ" -> ["FOO", ">", "BAR", "&&", "BAZ"] | |
| // Optionally keeps whitespace as tokens to be able to reconstruct the original input string. | |
| /** | |
| * @param {string} exprString | |
| * @param {(number|boolean)=} keepWhitespace Optional, can be omitted. Defaults to false. | |
| */ | |
| function tokenize(exprString, keepWhitespace) { | |
| var out = [], len = exprString.length; | |
| for (var i = 0; i <= len; ++i) { | |
| var kind = classifyChar(exprString, i); | |
| if (kind == 2/*0-9*/ || kind == 3/*a-z*/) { // a character or a number | |
| for (var j = i+1; j <= len; ++j) { | |
| var kind2 = classifyChar(exprString, j); | |
| if (kind2 != kind && (kind2 != 2/*0-9*/ || kind != 3/*a-z*/)) { // parse number sequence "423410", and identifier sequence "FOO32BAR" | |
| out.push(exprString.substring(i, j)); | |
| i = j-1; | |
| break; | |
| } | |
| } | |
| } else if (kind == 1/*operator symbol*/) { | |
| // Lookahead for two-character operators. | |
| var op2 = exprString.slice(i, i + 2); | |
| if (['<=', '>=', '==', '!=', '&&', '||'].includes(op2)) { | |
| out.push(op2); | |
| ++i; | |
| } else { | |
| out.push(exprString[i]); | |
| } | |
| } | |
| } | |
| return out; | |
| } | |
| // Expands preprocessing macros on substring str[lineStart...lineEnd] | |
| /** | |
| * @param {string} str | |
| * @param {number} lineStart | |
| * @param {number=} lineEnd Optional, may be omitted. | |
| */ | |
| function expandMacros(str, lineStart, lineEnd=str.length) { | |
| var len = str.length; | |
| var out = ''; | |
| for (var i = lineStart; i < lineEnd; ++i) { | |
| var kind = classifyChar(str, i); | |
| if (kind == 3/*a-z*/) { | |
| for (var j = i + 1; j <= lineEnd; ++j) { | |
| var kind2 = classifyChar(str, j); | |
| if (kind2 != 2/*0-9*/ && kind2 != 3/*a-z*/) { | |
| var symbol = str.substring(i, j); | |
| #if MIN_FIREFOX_VERSION < 92 | |
| // Firefox only introduced Object.hasOwn() in Firefox 92. | |
| if (defs.hasOwnProperty(symbol)) { | |
| #else | |
| if (Object.hasOwn(defs, symbol)) { | |
| #endif | |
| var pp = defs[symbol], expanded; | |
| if (typeof pp == 'function') { // definition is a function? | |
| if (pp.length) { // Expanding a macro? (#define FOO(X) ...) | |
| while (str[j] && isWhitespace(str, j)) ++j; | |
| if (str[j] == '(') { | |
| var closeParens = find_closing_parens_index(str, j); | |
| // N.b. this has a limitation that multiparameter macros cannot nest with other multiparameter macros | |
| // e.g. FOO(a, BAR(b, c)) is not supported. | |
| expanded = pp(str.substring(j+1, closeParens).split(',')); | |
| if (expanded === !!expanded) expanded = expanded|0; // Convert boolean true/false to int 1/0 | |
| j = closeParens+1; | |
| } else { | |
| var start = j; | |
| j = nextWhitespace(str, j); | |
| expanded = pp([str.substring(start, j)]); | |
| } | |
| } else { // A zero-arg function macro (#define FOO() BAR)? | |
| expanded = pp(); | |
| } | |
| } else { // Definition is either a boolean, an integer or a string.. in any case, not a macro. | |
| // Expand boolean args from defs, e.g. 'FOO': true as integer 1, | |
| // so that further preprocessing won't attempt to search for | |
| // a preprocessing macro 'true' as being defined. | |
| expanded = (pp === !!pp ? pp|0 : pp); | |
| } | |
| return expandMacros(str.substring(lineStart, i) + expanded + str.substring(j, lineEnd), 0); | |
| } | |
| out += symbol; | |
| i = j-1; | |
| break; | |
| } | |
| } | |
| } else { | |
| out += str[i]; | |
| } | |
| } | |
| return out; | |
| } | |
| // Given a token list e.g. ['2', '>', '1'], returns a function that evaluates that token list. | |
| function buildExprTree(tokens) { | |
| // Consume tokens array into a function tree until the tokens array is exhausted | |
| // to a single root node that evaluates it. | |
| while (tokens.length > 1 || typeof tokens[0] != 'function') { | |
| tokens = ((tokens) => { | |
| // Find the index 'i' of the operator we should evaluate next: | |
| var i, j, p, operatorAndPriority = -2; | |
| for (j = 0; j < tokens.length; ++j) { | |
| if ((p = ['*', '/', '+', '-', '!', '<', '<=', '>', '>=', '==', '!=', '&&', '||', '('].indexOf(tokens[j])) > operatorAndPriority) { | |
| i = j; | |
| operatorAndPriority = p; | |
| } | |
| } | |
| if (operatorAndPriority == 13 /* parens '(' */) { | |
| // Find the closing parens position | |
| j = find_closing_parens_index(tokens, i); | |
| if (j) { | |
| tokens.splice(i, j+1-i, buildExprTree(tokens.slice(i+1, j))); | |
| return tokens; | |
| } | |
| } | |
| if (operatorAndPriority == 4 /* unary ! */) { | |
| // Special case: the unary operator ! needs to evaluate right-to-left. | |
| i = tokens.lastIndexOf('!'); | |
| var innerExpr = buildExprTree(tokens.slice(i+1, i+2)); | |
| tokens.splice(i, 2, function() { return !innerExpr(); }) | |
| return tokens; | |
| } | |
| // A binary operator: | |
| if (operatorAndPriority >= 0) { | |
| var left = buildExprTree(tokens.slice(0, i)); | |
| var right = buildExprTree(tokens.slice(i+1)); | |
| switch(tokens[i]) { | |
| case '&&': return [function() { return left() && right(); }]; | |
| case '||': return [function() { return left() || right(); }]; | |
| case '==': return [function() { return left() == right(); }]; | |
| case '!=': return [function() { return left() != right(); }]; | |
| case '<' : return [function() { return left() < right(); }]; | |
| case '<=': return [function() { return left() <= right(); }]; | |
| case '>' : return [function() { return left() > right(); }]; | |
| case '>=': return [function() { return left() >= right(); }]; | |
| case '+': return [function() { return left() + right(); }]; | |
| case '-': return [function() { return left() - right(); }]; | |
| case '*': return [function() { return left() * right(); }]; | |
| case '/': return [function() { return Math.floor(left() / right()); }]; | |
| } | |
| } | |
| // else a number: | |
| #if ASSERTIONS | |
| assert(tokens[i] !== ')', 'parse failure, mismatched parentheses in parsing' + tokens.toString()); | |
| assert(operatorAndPriority == -1); | |
| #endif | |
| var num = Number(tokens[i]); | |
| return [function() { return num; }] | |
| })(tokens); | |
| } | |
| return tokens[0]; | |
| } | |
| // Preprocess the input one line at a time. | |
| for (; i < len; ++i) { | |
| // Find the start of the current line. | |
| var lineStart = i; | |
| // Seek iterator to end of current line. | |
| i = code.indexOf('\n', i); | |
| if (i < 0) i = len; | |
| // Find the first non-whitespace character on the line. | |
| for (var j = lineStart; j < i && isWhitespace(code, j);) ++j; | |
| // Is this a non-preprocessor directive line? | |
| var thisLineIsInActivePreprocessingBlock = stack[stack.length-1]; | |
| if (code[j] != '#') { // non-preprocessor line? | |
| if (thisLineIsInActivePreprocessingBlock) { | |
| out += expandMacros(code, lineStart, i) + '\n'; | |
| } | |
| continue; | |
| } | |
| // This is a preprocessor directive line, e.g. #ifdef or #define. | |
| // Parse the line as #<directive> <expression> | |
| var space = nextWhitespace(code, j); | |
| var directive = code.substring(j+1, space); | |
| var expression = code.substring(space, i).trim(); | |
| switch(directive) { | |
| case 'if': | |
| var tokens = tokenize(expandMacros(expression, 0)); | |
| var exprTree = buildExprTree(tokens); | |
| var evaluated = exprTree(); | |
| stack.push(!!evaluated * stack[stack.length-1]); | |
| break; | |
| case 'elif': | |
| var tokens = tokenize(expandMacros(expression, 0)); | |
| var exprTree = buildExprTree(tokens); | |
| var evaluated = exprTree(); | |
| // If the previous #if / #elif block was executed, output NaN so that all further #elif and #else blocks will | |
| // short to false. | |
| stack[stack.length-1] = !!evaluated * (stack[stack.length-1] ? NaN : 1-stack[stack.length-1]); | |
| break; | |
| case 'ifdef': stack.push(!!defs[expression] * stack[stack.length-1]); break; | |
| case 'ifndef': stack.push(!defs[expression] * stack[stack.length-1]); break; | |
| case 'else': stack[stack.length-1] = (1-stack[stack.length-1]) * stack[stack.length-2]; break; | |
| case 'endif': stack.pop(); break; | |
| case 'define': | |
| if (thisLineIsInActivePreprocessingBlock) { | |
| // This could either be a macro with input args (#define MACRO(x,y) x+y), or a direct expansion #define FOO 2, | |
| // figure out which. | |
| var macroStart = expression.indexOf('('); | |
| var firstWs = nextWhitespace(expression, 0); | |
| if (firstWs < macroStart) macroStart = 0; | |
| if (macroStart > 0) { // #define MACRO( x , y , z ) <statement of x,y and z> | |
| var macroEnd = expression.indexOf(')', macroStart); | |
| let params = expression.substring(macroStart+1, macroEnd).split(',').map(x => x.trim()); | |
| let value = tokenize(expression.substring(macroEnd+1).trim()) | |
| defs[expression.substring(0, macroStart)] = (args) => { | |
| var ret = ''; | |
| value.forEach((x) => { | |
| var argIndex = params.indexOf(x); | |
| ret += (argIndex >= 0) ? args[argIndex] : x; | |
| }); | |
| return ret; | |
| }; | |
| } else { // #define FOO (x + y + z) | |
| let value = expandMacros(expression.substring(firstWs+1).trim(), 0); | |
| defs[expression.substring(0, firstWs)] = () => value; | |
| } | |
| } | |
| break; | |
| case 'undef': if (thisLineIsInActivePreprocessingBlock) delete defs[expression]; break; | |
| default: | |
| if (directive != 'version' && directive != 'pragma' && directive != 'extension' && directive != 'line') { // GLSL shader compiler specific #directives. | |
| #if ASSERTIONS | |
| err('Unrecognized preprocessor directive #' + directive + '!'); | |
| #endif | |
| } | |
| // Unknown preprocessor macro, just pass through the line to output. | |
| out += expandMacros(code, lineStart, i) + '\n'; | |
| } | |
| } | |
| return out; | |
| } | |
| }); | |
Xet Storage Details
- Size:
- 14.2 kB
- Xet hash:
- 2029fdb272389038ef71b5a68fd77cdb57ffade5442f64326b46348f24474049
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.