Skip to content

Commit 2c5d60a

Browse files
committed
Fixing two bugs in the preprocessor related to empty macros
1 parent 1f51744 commit 2c5d60a

File tree

4 files changed

+61
-16
lines changed

4 files changed

+61
-16
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"engines": {
44
"node": ">=16"
55
},
6-
"version": "2.0.0",
6+
"version": "2.0.1",
77
"description": "A GLSL ES 1.0 and 3.0 parser and preprocessor that can preserve whitespace and comments",
88
"scripts": {
99
"prepare": "npm run build && ./prepublish.sh",

src/preprocessor/generator.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ type Generator = (
2424
/**
2525
* Stringify an AST
2626
*/
27-
// const makeGenerator = (generators: NodeGenerators): Generator => {
2827
// @ts-ignore
2928
const makeGeneratorPreprocessor = makeGenerator as (
3029
generators: NodePreprocessorGenerators

src/preprocessor/preprocessor.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,41 @@ true
8585
`);
8686
});
8787

88+
test('ifdef inside else is properly expanded', () => {
89+
// Regression: Make sure #ifdef MACRO inside #else isn't expanded
90+
const program = `
91+
#define MACRO
92+
#ifdef NOT_DEFINED
93+
false
94+
#else
95+
#ifdef MACRO
96+
____true
97+
#endif
98+
#endif
99+
`;
100+
101+
const ast = parse(program);
102+
preprocessAst(ast);
103+
expect(generate(ast)).toBe(`
104+
____true
105+
`);
106+
});
107+
108+
test('macro without body becoms empty string', () => {
109+
// There is intentionally whitespace after MACRO to make sure it doesn't apply
110+
// to the expansion-to-nothing
111+
const program = `
112+
#define MACRO
113+
fn(MACRO);
114+
`;
115+
116+
const ast = parse(program);
117+
preprocessAst(ast);
118+
expect(generate(ast)).toBe(`
119+
fn();
120+
`);
121+
});
122+
88123
test('if expression', () => {
89124
const program = `
90125
#define A

src/preprocessor/preprocessor.ts

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { NodeVisitor, Path, visit } from '../ast/visit';
22
import {
33
PreprocessorAstNode,
4-
PreprocessorConditionalNode,
54
PreprocessorElseIfNode,
65
PreprocessorIdentifierNode,
76
PreprocessorIfNode,
@@ -175,7 +174,6 @@ const expandFunctionMacro = (
175174

176175
while ((startMatch = startRegex.exec(current))) {
177176
const result = scanFunctionArgs(
178-
// current.substr(startMatch.index + startMatch[0].length)
179177
current.substring(startMatch.index + startMatch[0].length)
180178
);
181179
if (result === null) {
@@ -188,7 +186,7 @@ const expandFunctionMacro = (
188186
);
189187
const { args, length: argLength } = result;
190188

191-
// The total lenth of the raw text to replace is the macro name in the
189+
// The total length of the raw text to replace is the macro name in the
192190
// text (startMatch), plus the length of the arguments, plus one to
193191
// encompass the closing paren that the scan fn skips
194192
const matchLength = startMatch[0].length + argLength + 1;
@@ -251,8 +249,13 @@ const expandObjectMacro = (
251249
const regex = new RegExp(`\\b${macroName}\\b`, 'g');
252250
let expanded = text;
253251
if (regex.test(text)) {
252+
// Macro definitions like
253+
// #define MACRO
254+
// Have null for the body. Make it empty string if null to avoid 'null' expanded
255+
const replacement = macro.body || '';
256+
254257
const firstPass = tokenPaste(
255-
text.replace(new RegExp(`\\b${macroName}\\b`, 'g'), macro.body)
258+
text.replace(new RegExp(`\\b${macroName}\\b`, 'g'), replacement)
256259
);
257260
// Scan expanded text for more expansions. Ignore the expanded macro because
258261
// of the self-reference rule
@@ -278,7 +281,7 @@ const expandInExpressions = (
278281
macros: Macros,
279282
...expressions: PreprocessorAstNode[]
280283
) => {
281-
expressions.filter(identity).forEach((expression) => {
284+
expressions.forEach((expression) => {
282285
visitPreprocessedAst(expression, {
283286
unary_defined: {
284287
enter: (path) => {
@@ -496,20 +499,28 @@ const preprocessAst = (
496499
return;
497500
}
498501

499-
// Expand macros
502+
// Expand macros in if/else *expressions* only. Macros are expanded in:
503+
// #if X + 1
504+
// #elif Y + 2
505+
// But *not* in
506+
// # ifdef X
507+
// Because X should not be expanded in the ifdef. Note that
508+
// # if defined(X)
509+
// does have an expression, but the skip() in unary_defined prevents
510+
// macro expansion in there. Checking for .expression and filtering out
511+
// any conditionals without expressions is how ifdef is avoided.
512+
// It's not great that ifdef is skipped differentaly than defined().
500513
expandInExpressions(
501514
macros,
502-
// Expression might not exist, since ifPart can be #ifdef which
503-
// doesn't have an expression key
504-
(node.ifPart as PreprocessorIfNode).expression,
505-
...node.elseIfParts.map(
506-
(elif: PreprocessorElseIfNode) => elif.expression
507-
),
508-
node.elsePart?.body
515+
...[
516+
(node.ifPart as PreprocessorIfNode).expression,
517+
...node.elseIfParts.map(
518+
(elif: PreprocessorElseIfNode) => elif.expression
519+
),
520+
].filter(identity)
509521
);
510522

511523
if (evaluateIfPart(macros, node.ifPart)) {
512-
// Yuck! So much type casting in this file
513524
path.replaceWith(node.ifPart.body);
514525
// Keeping this commented out block in case I can find a way to
515526
// conditionally evaluate shaders

0 commit comments

Comments
 (0)