Repository: playcanvas/engine
Pull request #7133 (2024-11-22 14:49:23)

Add support for simple compare expression to preprocessor
the preprocessor handled cases like
```
#ifdef XXX
#if defined(XXX)
```

now it additionally handles compares, like
```
#if XX == 3
#if XX != 4
#if XX < 5
#if XX == bloom
```


Patch:
diff --git a/src/core/preprocessor.js b/src/core/preprocessor.js
index 3f5f271e6..bd8871a36 100644
--- a/src/core/preprocessor.js
+++ b/src/core/preprocessor.js
@@ -30,8 +30,11 @@ const IDENTIFIER = /([\w-]+)/;
 // [!]defined(EXPRESSION)
 const DEFINED = /(!|\s)?defined\(([\w-]+)\)/;
 
+// Matches comparison operators like ==, !=, <, <=, >, >=
+const COMPARISON = /([a-z_]\w*)\s*(==|!=|<|<=|>|>=)\s*([\w"']+)/i;
+
 // currently unsupported characters in the expression: | & < > = + -
-const INVALID = /[><=|&+-]/g;
+const INVALID = /[|&+-]/g;
 
 // #include "identifier"
 const INCLUDE = /include[ \t]+"([\w-]+)"\r?(?:\n|$)/g;
@@ -374,6 +377,7 @@ class Preprocessor {
      * - expression
      * - defined(expression)
      * - !defined(expression)
+     * - simple comparisons like "XX == 3" or "XX != test"
      *
      * But does not handle more complex cases, which would require more complex system:
      *
@@ -388,7 +392,7 @@ class Preprocessor {
         const correct = INVALID.exec(expression) === null;
         Debug.assert(correct, `Resolving expression like this is not supported: ${expression}`);
 
-        // if the format is defined(expression), extract expression
+        // if the format is 'defined(expression)', extract expression
         let invert = false;
         const defined = DEFINED.exec(expression);
         if (defined) {
@@ -396,6 +400,29 @@ class Preprocessor {
             expression = defined[2];
         }
 
+        // if the expression is a comparison, evaluate it
+        const comparison = COMPARISON.exec(expression);
+        if (comparison) {
+            const left = defines.get(comparison[1]) ?? comparison[1];
+            const right = defines.get(comparison[3]) ?? comparison[3];
+            const operator = comparison[2];
+
+            let 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;
+            }
+
+            return {
+                result,
+                error: !correct
+            };
+        }
+
         // test if expression define exists
         expression = expression.trim();
         let exists = defines.has(expression);


Test patch:
diff --git a/test/core/preprocessor.test.mjs b/test/core/preprocessor.test.mjs
index 79503ca4b..d722980cc 100644
--- a/test/core/preprocessor.test.mjs
+++ b/test/core/preprocessor.test.mjs
@@ -73,6 +73,28 @@ describe('Preprocessor', function () {
         #ifdef (UNKNOWN)
             #define TEST14  // this should not be defined
         #endif
+
+        #define INDEX 3
+        #if INDEX == 3
+            CMP1
+        #endif
+
+        #if INDEX != 3
+            CMP2
+        #endif
+
+        #if INDEX > 2
+            CMP3
+        #endif
+
+        #define NAME hello
+        #if NAME == hello
+            CMP4
+        #endif
+
+        #if NAME != hello
+            CMP5
+        #endif
     `;
 
     it('returns false for MORPH_A', function () {
@@ -154,4 +176,25 @@ describe('Preprocessor', function () {
     it('returns true for nested', function () {
         expect(Preprocessor.run(srcData, includes).includes('nested')).to.equal(true);
     });
+
+    it('returns true for CMP1', function () {
+        expect(Preprocessor.run(srcData, includes).includes('CMP1')).to.equal(true);
+    });
+
+    it('returns false for CMP2', function () {
+        expect(Preprocessor.run(srcData, includes).includes('CMP2')).to.equal(false);
+    });
+
+    it('returns true for CMP3', function () {
+        expect(Preprocessor.run(srcData, includes).includes('CMP3')).to.equal(true);
+    });
+
+    it('returns true for CMP4', function () {
+        expect(Preprocessor.run(srcData, includes).includes('CMP4')).to.equal(true);
+    });
+
+    it('returns false for CMP5', function () {
+        expect(Preprocessor.run(srcData, includes).includes('CMP5')).to.equal(false);
+    });
+
 });