Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/src/lib/libc_preprocessor.js
6171 views
1
addToLibrary({
2
// Removes all C++ '//' and '/* */' comments from the given source string.
3
// N.b. will also eat comments inside strings.
4
$remove_cpp_comments_in_shaders: (code) => {
5
var i = 0, out = '', ch, next, len = code.length;
6
for (; i < len; ++i) {
7
ch = code[i];
8
if (ch == '/') {
9
next = code[i+1];
10
if (next == '/') {
11
while (i < len && code[i+1] != '\n') ++i;
12
} else if (next == '*') {
13
while (i < len && (code[i-1] != '*' || code[i] != '/')) ++i;
14
} else {
15
out += ch;
16
}
17
} else {
18
out += ch;
19
}
20
}
21
return out;
22
},
23
24
// Finds the index of closing parens from the opening parens at arr[i].
25
// Used polymorphically for strings ("foo") and token arrays (['(', 'foo', ')']) as input.
26
$find_closing_parens_index: (arr, i, opening='(', closing=')') => {
27
for (var nesting = 0; i < arr.length; ++i) {
28
if (arr[i] == opening) ++nesting;
29
if (arr[i] == closing && --nesting == 0) {
30
return i;
31
}
32
}
33
},
34
35
// Runs C preprocessor algorithm on the given string 'code'.
36
// Supported preprocessor directives: #if, #ifdef, #ifndef, #else, #elif, #endif, #define and #undef.
37
// predefs: Specifies a dictionary of { 'key1': function(arg0, arg1) {...}, 'key2': ... } of predefined preprocessing variables
38
$preprocess_c_code__deps: ['$find_closing_parens_index'],
39
$preprocess_c_code: function(code, defs = {}) {
40
var i = 0, // iterator over the input string
41
len = code.length, // cache input length
42
out = '', // generates the preprocessed output string
43
stack = [1]; // preprocessing stack (state of active/inactive #ifdef/#else blocks we are currently inside)
44
// a mapping 'symbolname' -> function(args) which evaluates the given cpp macro, e.g. #define FOO(x) x+10.
45
defs['defined'] = (args) => { // built-in "#if defined(x)"" macro.
46
#if ASSERTIONS
47
assert(args.length == 1);
48
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)
49
#endif
50
return defs[args[0].trim()] ? 1 : 0;
51
};
52
53
// Returns true if str[i] is whitespace.
54
function isWhitespace(str, i) {
55
return !(str.charCodeAt(i) > 32); // Compare as negation to treat end-of-string undefined as whitespace
56
}
57
58
// Returns index to the next whitespace character starting at str[i].
59
function nextWhitespace(str, i) {
60
while (!isWhitespace(str, i)) ++i;
61
return i;
62
}
63
64
// Returns an integer ID classification of the character at str[idx], used for tokenization purposes.
65
function classifyChar(str, idx) {
66
var cc = str.charCodeAt(idx);
67
#if ASSERTIONS
68
assert(!(cc > 127), "Only 7-bit ASCII can be used in preprocessor #if/#ifdef/#define statements!");
69
#endif
70
if (cc > 32) {
71
if (cc < 48) return 1; // an operator symbol, any of !"#$%&'()*+,-./
72
if (cc < 58) return 2; // a number 0123456789
73
if (cc < 65) return 1; // an operator symbol, any of :;<=>?@
74
if (cc < 91 || cc == 95/*_*/) return 3; // a character, any of A-Z or _
75
if (cc < 97) return 1; // an operator symbol, any of [\]^`
76
if (cc < 123) return 3; // a character, any of a-z
77
return 1; // an operator symbol, any of {|}~
78
}
79
return cc < 33 ? 0 : 4; // 0=whitespace, 4=end-of-string
80
}
81
82
// Returns a tokenized array of the given string expression, i.e. "FOO > BAR && BAZ" -> ["FOO", ">", "BAR", "&&", "BAZ"]
83
// Optionally keeps whitespace as tokens to be able to reconstruct the original input string.
84
/**
85
* @param {string} exprString
86
* @param {(number|boolean)=} keepWhitespace Optional, can be omitted. Defaults to false.
87
*/
88
function tokenize(exprString, keepWhitespace) {
89
var out = [], len = exprString.length;
90
for (var i = 0; i <= len; ++i) {
91
var kind = classifyChar(exprString, i);
92
if (kind == 2/*0-9*/ || kind == 3/*a-z*/) { // a character or a number
93
for (var j = i+1; j <= len; ++j) {
94
var kind2 = classifyChar(exprString, j);
95
if (kind2 != kind && (kind2 != 2/*0-9*/ || kind != 3/*a-z*/)) { // parse number sequence "423410", and identifier sequence "FOO32BAR"
96
out.push(exprString.substring(i, j));
97
i = j-1;
98
break;
99
}
100
}
101
} else if (kind == 1/*operator symbol*/) {
102
// Lookahead for two-character operators.
103
var op2 = exprString.slice(i, i + 2);
104
if (['<=', '>=', '==', '!=', '&&', '||'].includes(op2)) {
105
out.push(op2);
106
++i;
107
} else {
108
out.push(exprString[i]);
109
}
110
}
111
}
112
return out;
113
}
114
115
// Expands preprocessing macros on substring str[lineStart...lineEnd]
116
/**
117
* @param {string} str
118
* @param {number} lineStart
119
* @param {number=} lineEnd Optional, may be omitted.
120
*/
121
function expandMacros(str, lineStart, lineEnd=str.length) {
122
var len = str.length;
123
var out = '';
124
for (var i = lineStart; i < lineEnd; ++i) {
125
var kind = classifyChar(str, i);
126
if (kind == 3/*a-z*/) {
127
for (var j = i + 1; j <= lineEnd; ++j) {
128
var kind2 = classifyChar(str, j);
129
if (kind2 != 2/*0-9*/ && kind2 != 3/*a-z*/) {
130
var symbol = str.substring(i, j);
131
if (Object.hasOwn(defs, symbol)) {
132
var pp = defs[symbol], expanded;
133
if (typeof pp == 'function') { // definition is a function?
134
if (pp.length) { // Expanding a macro? (#define FOO(X) ...)
135
while (str[j] && isWhitespace(str, j)) ++j;
136
if (str[j] == '(') {
137
var closeParens = find_closing_parens_index(str, j);
138
// N.b. this has a limitation that multiparameter macros cannot nest with other multiparameter macros
139
// e.g. FOO(a, BAR(b, c)) is not supported.
140
expanded = pp(str.substring(j+1, closeParens).split(','));
141
if (expanded === !!expanded) expanded = expanded|0; // Convert boolean true/false to int 1/0
142
j = closeParens+1;
143
} else {
144
var start = j;
145
j = nextWhitespace(str, j);
146
expanded = pp([str.substring(start, j)]);
147
}
148
} else { // A zero-arg function macro (#define FOO() BAR)?
149
expanded = pp();
150
}
151
} else { // Definition is either a boolean, an integer or a string.. in any case, not a macro.
152
// Expand boolean args from defs, e.g. 'FOO': true as integer 1,
153
// so that further preprocessing won't attempt to search for
154
// a preprocessing macro 'true' as being defined.
155
expanded = (pp === !!pp ? pp|0 : pp);
156
}
157
return expandMacros(str.substring(lineStart, i) + expanded + str.substring(j, lineEnd), 0);
158
}
159
out += symbol;
160
i = j-1;
161
break;
162
}
163
}
164
} else {
165
out += str[i];
166
}
167
}
168
return out;
169
}
170
171
// Given a token list e.g. ['2', '>', '1'], returns a function that evaluates that token list.
172
function buildExprTree(tokens) {
173
// Consume tokens array into a function tree until the tokens array is exhausted
174
// to a single root node that evaluates it.
175
while (tokens.length > 1 || typeof tokens[0] != 'function') {
176
tokens = ((tokens) => {
177
// Find the index 'i' of the operator we should evaluate next:
178
var i, j, p, operatorAndPriority = -2;
179
for (j = 0; j < tokens.length; ++j) {
180
if ((p = ['*', '/', '+', '-', '!', '<', '<=', '>', '>=', '==', '!=', '&&', '||', '('].indexOf(tokens[j])) > operatorAndPriority) {
181
i = j;
182
operatorAndPriority = p;
183
}
184
}
185
186
if (operatorAndPriority == 13 /* parens '(' */) {
187
// Find the closing parens position
188
j = find_closing_parens_index(tokens, i);
189
if (j) {
190
tokens.splice(i, j+1-i, buildExprTree(tokens.slice(i+1, j)));
191
return tokens;
192
}
193
}
194
195
if (operatorAndPriority == 4 /* unary ! */) {
196
// Special case: the unary operator ! needs to evaluate right-to-left.
197
i = tokens.lastIndexOf('!');
198
var innerExpr = buildExprTree(tokens.slice(i+1, i+2));
199
tokens.splice(i, 2, function() { return !innerExpr(); })
200
return tokens;
201
}
202
203
// A binary operator:
204
if (operatorAndPriority >= 0) {
205
var left = buildExprTree(tokens.slice(0, i));
206
var right = buildExprTree(tokens.slice(i+1));
207
switch(tokens[i]) {
208
case '&&': return [function() { return left() && right(); }];
209
case '||': return [function() { return left() || right(); }];
210
case '==': return [function() { return left() == right(); }];
211
case '!=': return [function() { return left() != right(); }];
212
case '<' : return [function() { return left() < right(); }];
213
case '<=': return [function() { return left() <= right(); }];
214
case '>' : return [function() { return left() > right(); }];
215
case '>=': return [function() { return left() >= right(); }];
216
case '+': return [function() { return left() + right(); }];
217
case '-': return [function() { return left() - right(); }];
218
case '*': return [function() { return left() * right(); }];
219
case '/': return [function() { return Math.floor(left() / right()); }];
220
}
221
}
222
// else a number:
223
#if ASSERTIONS
224
assert(tokens[i] !== ')', 'Parsing failure, mismatched parentheses in parsing!' + tokens.toString());
225
assert(operatorAndPriority == -1);
226
#endif
227
var num = Number(tokens[i]);
228
return [function() { return num; }]
229
})(tokens);
230
}
231
return tokens[0];
232
}
233
234
// Preprocess the input one line at a time.
235
for (; i < len; ++i) {
236
// Find the start of the current line.
237
var lineStart = i;
238
239
// Seek iterator to end of current line.
240
i = code.indexOf('\n', i);
241
if (i < 0) i = len;
242
243
// Find the first non-whitespace character on the line.
244
for (var j = lineStart; j < i && isWhitespace(code, j);) ++j;
245
246
// Is this a non-preprocessor directive line?
247
var thisLineIsInActivePreprocessingBlock = stack[stack.length-1];
248
if (code[j] != '#') { // non-preprocessor line?
249
if (thisLineIsInActivePreprocessingBlock) {
250
out += expandMacros(code, lineStart, i) + '\n';
251
}
252
continue;
253
}
254
// This is a preprocessor directive line, e.g. #ifdef or #define.
255
256
// Parse the line as #<directive> <expression>
257
var space = nextWhitespace(code, j);
258
var directive = code.substring(j+1, space);
259
var expression = code.substring(space, i).trim();
260
switch(directive) {
261
case 'if':
262
var tokens = tokenize(expandMacros(expression, 0));
263
var exprTree = buildExprTree(tokens);
264
var evaluated = exprTree();
265
stack.push(!!evaluated * stack[stack.length-1]);
266
break;
267
case 'elif':
268
var tokens = tokenize(expandMacros(expression, 0));
269
var exprTree = buildExprTree(tokens);
270
var evaluated = exprTree();
271
// If the previous #if / #elif block was executed, output NaN so that all further #elif and #else blocks will
272
// short to false.
273
stack[stack.length-1] = !!evaluated * (stack[stack.length-1] ? NaN : 1-stack[stack.length-1]);
274
break;
275
case 'ifdef': stack.push(!!defs[expression] * stack[stack.length-1]); break;
276
case 'ifndef': stack.push(!defs[expression] * stack[stack.length-1]); break;
277
case 'else': stack[stack.length-1] = (1-stack[stack.length-1]) * stack[stack.length-2]; break;
278
case 'endif': stack.pop(); break;
279
case 'define':
280
if (thisLineIsInActivePreprocessingBlock) {
281
// This could either be a macro with input args (#define MACRO(x,y) x+y), or a direct expansion #define FOO 2,
282
// figure out which.
283
var macroStart = expression.indexOf('(');
284
var firstWs = nextWhitespace(expression, 0);
285
if (firstWs < macroStart) macroStart = 0;
286
if (macroStart > 0) { // #define MACRO( x , y , z ) <statement of x,y and z>
287
var macroEnd = expression.indexOf(')', macroStart);
288
let params = expression.substring(macroStart+1, macroEnd).split(',').map(x => x.trim());
289
let value = tokenize(expression.substring(macroEnd+1).trim())
290
defs[expression.substring(0, macroStart)] = (args) => {
291
var ret = '';
292
value.forEach((x) => {
293
var argIndex = params.indexOf(x);
294
ret += (argIndex >= 0) ? args[argIndex] : x;
295
});
296
return ret;
297
};
298
} else { // #define FOO (x + y + z)
299
let value = expandMacros(expression.substring(firstWs+1).trim(), 0);
300
defs[expression.substring(0, firstWs)] = () => value;
301
}
302
}
303
break;
304
case 'undef': if (thisLineIsInActivePreprocessingBlock) delete defs[expression]; break;
305
default:
306
if (directive != 'version' && directive != 'pragma' && directive != 'extension' && directive != 'line') { // GLSL shader compiler specific #directives.
307
#if ASSERTIONS
308
err('Unrecognized preprocessor directive #' + directive + '!');
309
#endif
310
}
311
312
// Unknown preprocessor macro, just pass through the line to output.
313
out += expandMacros(code, lineStart, i) + '\n';
314
}
315
}
316
return out;
317
}
318
});
319
320