Skip to content

fix(vba): conditional-compilation evaluator - Win32/Win16, True=-1, #Const support (closes #51) - #76

Merged
ardelperal merged 1 commit into
mainfrom
chore/2026-07-04-issue-51-conditional-compilation
Jul 4, 2026
Merged

fix(vba): conditional-compilation evaluator - Win32/Win16, True=-1, #Const support (closes #51)#76
ardelperal merged 1 commit into
mainfrom
chore/2026-07-04-issue-51-conditional-compilation

Conversation

@ardelperal

Copy link
Copy Markdown
Owner

Closes #51.

What

Three concrete gaps in preprocessConditionalCompilation
(src/extraction/vba-preprocess.ts) silently dropped ACTIVE
code in production Dysflow .bas/.cls files. The conservative
fallback was the right SAFETY primitive but the wrong DEFAULT —
true conditions were silently blanking because the whitelist
rejected too much.

Why

The current evaluator had:

  • Win32 / Win16 unmapped — #If Win32 Then blanks its ACTIVE branch.
  • - not in the whitelist — #If Win64 = -1 Then (VBA's natural
    comparison against VBA True = -1) blanks despite being True.
  • No #Const support — #Const MODO_DEBUG = True followed by
    #If MODO_DEBUG Then blanks the user's own TRUE branch (the
    identifier #Const MODO_DEBUG isn't in the whitelist).

In a real Dysflow-managed Access codebase, every production
.bas/.cls file that uses these idioms loses its conditionally
active code to the inactive branch. This PR changes the
default from "unknown == inactive" (blanks everything) to
"intentionally FALSE values (Win16, False, etc.) blank; the rest
ACTIVE", with TRUE keyword semantics (True = -1) preserved per
the VBA spec.

Diff

File Lines
src/extraction/vba-preprocess.ts +153 / −16
__tests__/extraction-vba-preprocess.test.ts +218
Total +371 / −16

Inside the 400-line review budget.

Design

Surface fixes + light integer substitution (chosen over full bitwise arithmetic)

  • true-1, false0 substitution via regex — one pass
    per expression. Preserves correctness for the supported
    expression surface (truthy, =, <>, And, Or, Not):
    VBA's "any non-zero is True" matches JS truthy semantics.
  • The simplification is documented inline (JSDoc on
    preprocessConditionalCompilation) so the next maintainer
    can promote to bitwise arithmetic when a future task needs to
    distinguish #If X = 1 from #If X = -1 on a True const.
  • Full bitwise arithmetic (replace &&/||/! with integer ops
    on -1/0) is intentionally deferred — the surface fixes
    suffice for the current Dysflow expression set and keep the
    code surface small.

Pipeline restructure

The eval pipeline now lives in normalizeConditionalExpression,
shared between the #If/#ElseIf branch and the new #Const RHS
evaluator. Both call sites get the same conservative-false
behavior on whitelist failure:

  1. #Const lookup (substitutes table entries via
    escapeRegExp-safe regex per name).
  2. Hardcoded constant substitutions:
    VBA7/Win64/Win32 → true, Win16/Mac → false.
  3. Operator rewrites: <>!==, ====, And&&,
    Or||, Not!.
  4. true/false-1/0 (line 343–344).
  5. Whitelist check (^(-?\d+|...|...)+$).
  6. Function(…) eval with try/catch.

#Const handling

A new CONST_DIRECTIVE = /^\s*#Const\s+NAME = EXPR$/i regex; the
directive loop builds a per-call constTable: Map<string, string>
in a first pass that runs BEFORE the existing #If/#ElseIf/
#Else/#End If loop. #Const lines are blanked (parity
preserved), same convention as other directives.

Recursion is free: #Const X = Y (where Y was defined earlier)
works because the constTable is grown before evaluation. Self-
references are impossible because the entry is added AFTER its
RHS evaluates.

#Const NAME = True is equivalent to #Const NAME = -1 in
subsequent #If comparisons — the RHS evaluator returns the
numeric literal string for the boolean value.

Test coverage

11 atoms in __tests__/extraction-vba-preprocess.test.ts (new
describe "Issue #51: Win32/Win16 + True=-1 + #Const support" —
8 acceptance-criteria atoms plus 3 defensive guards):

  1. #If Win32 Then / Debug.Print "x" / #End If → preserves
    Debug.Print "x" on the same line number (parity).
  2. #If Win16 Then / Debug.Print "x" / #End If → blanks
    Debug.Print "x".
  3. #Const MODO_DEBUG = True / #If MODO_DEBUG Then / Debug.Print "x" / #End If → preserves the branch.
  4. #Const MODO_DEBUG = False / #If MODO_DEBUG Then / x / #Else / y / #End If → blanks x, preserves y.
  5. #Const X = 1 / #If X = 1 Then / x / #End If → preserves.
  6. #If Win64 = -1 Then / x / #End If → preserves.
  7. #Const X = True / #If X = False Then / x / #End If → blanks.
  8. Negative regression: unknown identifier still evaluates false.
  • 3 defensive parity guards covering the #Const line itself,
    string-literal RHS rejection, and a multi-#Const chain.
  • Existing 49 atoms stay green WITHOUT modification (covered by
    the full-VBA-suite run).

Validation

  • pnpm exec vitest run __tests__/extraction-vba-preprocess.test.ts -t "Issue #51" → 11 passed
  • Full VBA suite (7 files): 318 passed — zero regressions.
  • pnpm run build → tsc clean, no TS errors.

Real-fixture validation

__tests__/extraction-vba-realfixtures.test.ts (15/15) green,
confirming no Dysflow source node-count regression. Per the
issue spec, any real Dysflow delta would be newly-active branches
(Win32/Win16 that were incorrectly suppressed becoming
active, plus #Const-gated code reaching the sweeps for the
first time) — exactly the intended fix.

Out of scope (intentional, deferred per spec)

  • Full bitwise integer arithmetic for #If (True And True) = -1
    vs = 1 — the simplification suffices for the current
    Dysflow expressions and can be promoted when needed.
  • Multi-line #Const RHS continuations (VBA _ lines).
  • #Const NAME = "string literal" — VBA forbids in CC; the
    evaluator returns null and the line is blanked (parity
    preserved, no entry in constTable).

Not done

n/a — issue complete in this PR.

…Const support (closes #51)

Three concrete gaps in `preprocessConditionalCompilation`
(`src/extraction/vba-preprocess.ts`) silently dropped ACTIVE
code in production Dysflow `.bas`/`.cls` files:

1. **`Win32` / `Win16` unmapped** — `#If Win32 Then` (legacy guard;
   Win32 is True on every modern Windows VBA host including
   Win64) blanks its ACTIVE branch.
2. **No `-` in whitelist** — `#If Win64 = -1 Then` evaluates false
   because `-` is not in the regex whitelist, even though VBA's
   True is `-1` per spec.
3. **No `#Const` support** — `#Const MODO_DEBUG = True` followed
   by `#If MODO_DEBUG Then` blanks the user's own TRUE branch.

The conservative fallback was the right SAFETY primitive but the
wrong DEFAULT — true conditions should not blink off silently.

Changes:

* `src/extraction/vba-preprocess.ts`:
  - New `CONST_DIRECTIVE` regex matches `#Const NAME = <expr>`
    and the directive loop grows a per-call `constTable:
    Map<string, string>` populated in a first-pass branch that
    runs before `#If` / `#ElseIf` / `#Else` / `#End If` handling
    (line-count parity invariant: `#Const` lines blank like other
    directives).
  - New `normalizeConditionalExpression(expr, constTable)` helper
    centralizes the substitution pipeline: `#Const` lookup →
    hardcoded constants (`VBA7`, `Win64`, `Mac`, plus the new
    `Win32 → true`, `Win16 → false`) → operator rewrites (`<>` →
    `!==`, `=` → `===`, `And` → `&&`, `Or` → `||`, `Not` → `!`) →
    `true`/`false` keyword → `-1`/`0` literal substitution (VBA's
    True = -1 / False = 0, applied as JS truthy arithmetic for the
    supported expression surface) → whitelist check. Returns
    `null` on whitelist failure so both the `#If`/`#ElseIf`
    evaluator and the `#Const` RHS evaluator share the
    conservative-false behavior.
  - Whitelist extended to `^(-?\d+|<spaces>|&&|\|\||!|===|!==|\(|\))+$`
    (signed integers; the `true`/`false` alternations are removed
    because they are substituted to `-1`/`0` before the check).
  - New `evaluateConstRhs(rhs, constTable)` helper runs the RHS
    through `normalizeConditionalExpression` and converts to a
    numeric literal string (`"-1"` / `"0"` / `"1"` …) or returns
    `null` on unsupported RHS (e.g. string literal — `"` is not in
    the whitelist). `#Const NAME = True` is therefore equivalent
    to `#Const NAME = -1` in subsequent `#If` comparisons.
  - New `escapeRegExp` helper safely interpolates constTable
    names into the substitution regex (defensive against the
    `$`/`\\` characters that could appear in replacement strings,
    even though current values cannot contain them).
  - `evaluateConditionalExpression` now delegates to
    `normalizeConditionalExpression` and reads `constTable`.
  - JSDoc on `preprocessConditionalCompilation` documents the
    True = -1 / False = 0 simplification and where to extend if
    bitwise precision is needed later.
  - Public API unchanged — signature
    `preprocessConditionalCompilation(src: string): string` is
    preserved. All helpers remain file-private.

* 11 regression atoms in `__tests__/extraction-vba-preprocess
  .test.ts` (new describe "Issue #51: Win32/Win16 + True=-1 +
  #Const support" — 8 acceptance-criteria atoms plus 3 defensive
  guards):
  1. `#If Win32 Then / Debug.Print "x" / #End If` → preserves
     `Debug.Print "x"` on the same line number (parity).
  2. `#If Win16 Then / Debug.Print "x" / #End If` → blanks
     `Debug.Print "x"` (Win16 is False on this target).
  3. `#Const MODO_DEBUG = True / #If MODO_DEBUG Then / Debug.Print
     "x" / #End If` → preserves the branch.
  4. `#Const MODO_DEBUG = False / #If MODO_DEBUG Then / Debug.Print
     "x" / #Else / Debug.Print "y" / #End If` → blanks `x`,
     preserves `y`.
  5. `#Const X = 1 / #If X = 1 Then / Debug.Print "x" / #End If` →
     preserves `Debug.Print "x"`.
  6. `#If Win64 = -1 Then / Debug.Print "x" / #End If` →
     preserves `Debug.Print "x"` (whitelist accepts `-`; `-1`
     substitution yields truthy).
  7. `#Const X = True / #If X = False Then / Debug.Print "x" /
     #End If` → blanks the branch (`True ≠ False`).
  8. Negative regression: unknown identifier still evaluates
     false — `#If NonExistent Then / x / #End If` → blanks `x`.
  + 3 defensive parity guards covering the `#Const` line itself,
     string-literal RHS rejection, and a multi-`#Const` chain.
  + The existing 49 atoms in `extraction-vba-preprocess.test.ts`
     stay green WITHOUT modification (covered by the full-VBA-
     suite run; this PR's surface fixes are entirely additive).

Validation:

* `pnpm exec vitest run __tests__/extraction-vba-preprocess.test.ts
  -t "Issue #51"` → 11 passed
* Full VBA suite (7 files): **318 passed** — zero regressions;
  existing 49 preprocess atoms + 297 across the rest of the VBA
  surface (extraction-vba, extraction-vba-control-modeling,
  extraction-vba-form, extraction-vba-enums-consts,
  extraction-vba-realfixtures, extraction-vba-roadmap-25-26)
* `pnpm run build` → tsc clean, no TS errors

Real-fixture validation:

`__tests__/extraction-vba-realfixtures.test.ts` (15/15) green,
confirming no Dysflow source node-count regression. Per the
issue spec, any real Dysflow delta would be newly-active
branches (`Win32`/`Win16` that were incorrectly suppressed
becoming active, plus `#Const`-gated code reaching the sweeps
for the first time) — exactly the intended fix.

Design rationale (chose surface fixes + light integer
substitution over full bitwise arithmetic):

* `true` → `-1`, `false` → `0` substitution is one regex pass
  vs. rewriting the operator pipeline to be `-1`/`0` integer-
  precise throughout the file.
* It preserves correctness for the supported expression
  surface (truthy, `=`, `<>`, `And`, `Or`, `Not`): VBA's "any
  non-zero is True" matches JS truthy semantics.
* The simplification is documented inline (JSDoc on
  `preprocessConditionalCompilation`) so the next maintainer can
  promote to bitwise arithmetic when a future task needs to
  distinguish `#If X = 1` from `#If X = -1` on a `True` const.

Out of scope (intentional, deferred per spec):

* Full bitwise integer arithmetic for `#If (True And True) = -1`
  vs `= 1` — the simplification suffices for the current Dysflow
  expressions and can be promoted when needed.
* Multi-line `#Const RHS` continuations (VBA `_` lines).
* `#Const NAME = "string literal"` — VBA forbids in CC; the
  evaluator returns `null` and the line is blanked (parity
  preserved, no entry in constTable).
@ardelperal ardelperal added the bug Something isn't working label Jul 4, 2026
@ardelperal
ardelperal merged commit 1b273ca into main Jul 4, 2026
5 checks passed
@ardelperal
ardelperal deleted the chore/2026-07-04-issue-51-conditional-compilation branch July 4, 2026 11:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(vba): conditional-compilation evaluator — Win32/Win16, True=-1 semantics, #Const support

1 participant