SDL_shader_tools: Short circuit && and || in preprocessor conditionals

From 20cffa9b3bb055cdfc52e1f4785b0b3e88d8e86f Mon Sep 17 00:00:00 2001
From: Anonymous Maarten <[EMAIL REDACTED]>
Date: Fri, 22 Sep 2023 19:26:19 +0200
Subject: [PATCH] Short circuit && and || in preprocessor conditionals

---
 SDL_shader_preprocessor.c                     | 75 ++++++++++++++++---
 unit_tests/preprocessor/errors/division-zero  |  2 +
 .../preprocessor/errors/division-zero.correct |  1 +
 .../errors/failing-short-circuit-and          |  3 +
 .../errors/failing-short-circuit-and.correct  |  1 +
 .../errors/failing-short-circuit-or           |  3 +
 .../errors/failing-short-circuit-or.correct   |  1 +
 unit_tests/preprocessor/errors/modulo-zero    |  2 +
 .../preprocessor/errors/modulo-zero.correct   |  1 +
 .../preprocessor/output/short-circuit-and     |  5 ++
 .../output/short-circuit-and.correct          |  1 +
 .../preprocessor/output/short-circuit-or      |  5 ++
 .../output/short-circuit-or.correct           |  1 +
 13 files changed, 90 insertions(+), 11 deletions(-)
 create mode 100644 unit_tests/preprocessor/errors/division-zero
 create mode 100644 unit_tests/preprocessor/errors/division-zero.correct
 create mode 100644 unit_tests/preprocessor/errors/failing-short-circuit-and
 create mode 100644 unit_tests/preprocessor/errors/failing-short-circuit-and.correct
 create mode 100644 unit_tests/preprocessor/errors/failing-short-circuit-or
 create mode 100644 unit_tests/preprocessor/errors/failing-short-circuit-or.correct
 create mode 100644 unit_tests/preprocessor/errors/modulo-zero
 create mode 100644 unit_tests/preprocessor/errors/modulo-zero.correct
 create mode 100644 unit_tests/preprocessor/output/short-circuit-and
 create mode 100644 unit_tests/preprocessor/output/short-circuit-and.correct
 create mode 100644 unit_tests/preprocessor/output/short-circuit-or
 create mode 100644 unit_tests/preprocessor/output/short-circuit-or.correct

diff --git a/SDL_shader_preprocessor.c b/SDL_shader_preprocessor.c
index 7f90aa8..7be0f0a 100644
--- a/SDL_shader_preprocessor.c
+++ b/SDL_shader_preprocessor.c
@@ -1616,25 +1616,35 @@ typedef struct RpnTokens
 static long interpret_rpn(Context *ctx, const RpnTokens *tokens, int tokencount, SDL_bool *_error)
 {
     DECLARE_RPN_ARRAY(stack, long, 16);
+    DECLARE_RPN_ARRAY(error_stack, const char *, 16);
     long retval = 0;
     *_error = SDL_TRUE;  /* by default */
 
     #define NEED_X_TOKENS(x) do { if (stack_size < x) goto interpret_rpn_failed; } while (SDL_FALSE)
 
-    #define BINARY_OPERATION(op) do { \
-        NEED_X_TOKENS(2); \
-        stack[stack_size-2] = stack[stack_size-2] op stack[stack_size-1]; \
-        stack_size--; \
+    #define BINARY_OPERATION(op) do {                                         \
+        NEED_X_TOKENS(2);                                                     \
+        if (error_stack[stack_size-2]) {                                      \
+            /* FIXME: error_stack[stack_size-1] gets dropped */               \
+        } else if (error_stack[stack_size-1]) {                               \
+            error_stack[stack_size-2] = error_stack[stack_size-1];            \
+        } else {                                                              \
+            stack[stack_size-2] = stack[stack_size-2] op stack[stack_size-1]; \
+        }                                                                     \
+        stack_size--;                                                         \
     } while (SDL_FALSE)
 
-    #define UNARY_OPERATION(op) do { \
-        NEED_X_TOKENS(1); \
-        stack[stack_size-1] = op stack[stack_size-1]; \
+    #define UNARY_OPERATION(op) do {                      \
+        NEED_X_TOKENS(1);                                 \
+        if (!error_stack[stack_size-1]) {                 \
+            stack[stack_size-1] = op stack[stack_size-1]; \
+        }                                                 \
     } while (SDL_FALSE)
 
     while (tokencount-- > 0) {
         if (!tokens->isoperator) {
             VERIFY_RPN_ARRAY_ALLOCATION(stack, long, 128, { goto interpret_rpn_failed; });
+            error_stack[stack_size] = NULL;
             stack[stack_size++] = (long) tokens->value;
             tokens++;
             continue;
@@ -1646,8 +1656,6 @@ static long interpret_rpn(Context *ctx, const RpnTokens *tokens, int tokencount,
             case '~': UNARY_OPERATION(~); break;
             case TOKEN_PP_UNARY_MINUS: UNARY_OPERATION(-); break;
             case TOKEN_PP_UNARY_PLUS: UNARY_OPERATION(+); break;
-            case TOKEN_OROR: BINARY_OPERATION(||); break;
-            case TOKEN_ANDAND: BINARY_OPERATION(&&); break;
             case '|': BINARY_OPERATION(|); break;
             case '^': BINARY_OPERATION(^); break;
             case '&': BINARY_OPERATION(&); break;
@@ -1663,10 +1671,50 @@ static long interpret_rpn(Context *ctx, const RpnTokens *tokens, int tokencount,
             case '+': BINARY_OPERATION(+); break;
             case '*': BINARY_OPERATION(*); break;
 
+            case TOKEN_OROR:
+                NEED_X_TOKENS(2);
+                if (error_stack[stack_size-2]) {
+                    stack_size--;
+                    break;
+                }
+                if (stack[stack_size-2]) {
+                    stack_size--;
+                    break;
+                }
+                if (error_stack[stack_size-1]) {
+                    error_stack[stack_size-2] = error_stack[stack_size-1];
+                    stack_size--;
+                    break;
+                }
+                stack[stack_size-2] = stack[stack_size-1];
+                stack_size--;
+                break;
+
+            case TOKEN_ANDAND:
+                NEED_X_TOKENS(2);
+                if (error_stack[stack_size-2]) {
+                    stack_size--;
+                    break;
+                }
+                if (!stack[stack_size-2]) {
+                    stack_size--;
+                    break;
+                }
+                if (error_stack[stack_size-1]) {
+                    error_stack[stack_size-2] = error_stack[stack_size-1];
+                    stack_size--;
+                    break;
+                }
+                stack[stack_size-2] = stack[stack_size-1];
+                stack_size--;
+                break;
+
             case '/':
                 NEED_X_TOKENS(2);
                 if (stack[stack_size-1] == 0) {
-                    goto interpret_rpn_failed;  // division by zero. !!! FIXME: report this error.
+                    error_stack[stack_size-2] = "division by zero";
+                    stack_size--;
+                    break;
                 }
                 BINARY_OPERATION(/);
                 break;
@@ -1674,7 +1722,9 @@ static long interpret_rpn(Context *ctx, const RpnTokens *tokens, int tokencount,
             case '%':
                 NEED_X_TOKENS(2);
                 if (stack[stack_size-1] == 0) {
-                    goto interpret_rpn_failed;  // division by zero. !!! FIXME: report this error.
+                    error_stack[stack_size-2] = "division by zero";
+                    stack_size--;
+                    break;
                 }
                 BINARY_OPERATION(%);
                 break;
@@ -1691,6 +1741,9 @@ static long interpret_rpn(Context *ctx, const RpnTokens *tokens, int tokencount,
 
     if (stack_size == 1) {
         retval = stack[0];
+        if (error_stack[0]) {
+            fail(ctx, error_stack[0]);
+        }
         *_error = SDL_FALSE;
     }
 
diff --git a/unit_tests/preprocessor/errors/division-zero b/unit_tests/preprocessor/errors/division-zero
new file mode 100644
index 0000000..42b670b
--- /dev/null
+++ b/unit_tests/preprocessor/errors/division-zero
@@ -0,0 +1,2 @@
+#if 4 / 0
+#endif
diff --git a/unit_tests/preprocessor/errors/division-zero.correct b/unit_tests/preprocessor/errors/division-zero.correct
new file mode 100644
index 0000000..3a5a3c9
--- /dev/null
+++ b/unit_tests/preprocessor/errors/division-zero.correct
@@ -0,0 +1 @@
+preprocessor/errors/division-zero:1: error: division by zero
diff --git a/unit_tests/preprocessor/errors/failing-short-circuit-and b/unit_tests/preprocessor/errors/failing-short-circuit-and
new file mode 100644
index 0000000..6a43464
--- /dev/null
+++ b/unit_tests/preprocessor/errors/failing-short-circuit-and
@@ -0,0 +1,3 @@
+#if 1 && 1 / 0
+#else
+#endif
diff --git a/unit_tests/preprocessor/errors/failing-short-circuit-and.correct b/unit_tests/preprocessor/errors/failing-short-circuit-and.correct
new file mode 100644
index 0000000..e752705
--- /dev/null
+++ b/unit_tests/preprocessor/errors/failing-short-circuit-and.correct
@@ -0,0 +1 @@
+preprocessor/errors/failing-short-circuit-and:1: error: division by zero
diff --git a/unit_tests/preprocessor/errors/failing-short-circuit-or b/unit_tests/preprocessor/errors/failing-short-circuit-or
new file mode 100644
index 0000000..76eb7a9
--- /dev/null
+++ b/unit_tests/preprocessor/errors/failing-short-circuit-or
@@ -0,0 +1,3 @@
+#if 0 || 1 / 0
+#else
+#endif
diff --git a/unit_tests/preprocessor/errors/failing-short-circuit-or.correct b/unit_tests/preprocessor/errors/failing-short-circuit-or.correct
new file mode 100644
index 0000000..f024729
--- /dev/null
+++ b/unit_tests/preprocessor/errors/failing-short-circuit-or.correct
@@ -0,0 +1 @@
+preprocessor/errors/failing-short-circuit-or:1: error: division by zero
diff --git a/unit_tests/preprocessor/errors/modulo-zero b/unit_tests/preprocessor/errors/modulo-zero
new file mode 100644
index 0000000..09146d4
--- /dev/null
+++ b/unit_tests/preprocessor/errors/modulo-zero
@@ -0,0 +1,2 @@
+#if 4 % 0
+#endif
diff --git a/unit_tests/preprocessor/errors/modulo-zero.correct b/unit_tests/preprocessor/errors/modulo-zero.correct
new file mode 100644
index 0000000..ed5712a
--- /dev/null
+++ b/unit_tests/preprocessor/errors/modulo-zero.correct
@@ -0,0 +1 @@
+preprocessor/errors/modulo-zero:1: error: division by zero
diff --git a/unit_tests/preprocessor/output/short-circuit-and b/unit_tests/preprocessor/output/short-circuit-and
new file mode 100644
index 0000000..2f78d50
--- /dev/null
+++ b/unit_tests/preprocessor/output/short-circuit-and
@@ -0,0 +1,5 @@
+#if 0 && 1 / 0
+bad
+#else
+good
+#endif
diff --git a/unit_tests/preprocessor/output/short-circuit-and.correct b/unit_tests/preprocessor/output/short-circuit-and.correct
new file mode 100644
index 0000000..12799cc
--- /dev/null
+++ b/unit_tests/preprocessor/output/short-circuit-and.correct
@@ -0,0 +1 @@
+good
diff --git a/unit_tests/preprocessor/output/short-circuit-or b/unit_tests/preprocessor/output/short-circuit-or
new file mode 100644
index 0000000..ed682b3
--- /dev/null
+++ b/unit_tests/preprocessor/output/short-circuit-or
@@ -0,0 +1,5 @@
+#if 1 || 1 / 0
+good
+#else
+bad
+#endif
diff --git a/unit_tests/preprocessor/output/short-circuit-or.correct b/unit_tests/preprocessor/output/short-circuit-or.correct
new file mode 100644
index 0000000..12799cc
--- /dev/null
+++ b/unit_tests/preprocessor/output/short-circuit-or.correct
@@ -0,0 +1 @@
+good