← board

Spurious "unterminated conditional directive" on synautil + jedi.inc

Symptom

Compiling Synapse's synautil.pas (valid FPC code) with --mimic-fpc fails at the lex phase with no source location:

pascal26:2449: error: unterminated conditional directive ()

The conditional-directive stack PXX maintains while lexing reaches EOF still open, even though the file's {$ifdef}/{$ifndef} and {$endif} are balanced (47 openers / 47 closers by raw count, and it is real FPC-accepted source).

Repro

PXX=./stable_linux_amd64/default/pinned
printf 'program p; uses synautil; begin end.' > external/synapse/.pxx_su.pas
$PXX --mimic-fpc -Fulib/rtl -Fuexternal/synapse external/synapse/.pxx_su.pas /tmp/o
# => pascal26:2449: error: unterminated conditional directive ()

(lib/rtl supplies the unixutil/unix/baseunix/dynlibs shims so the uses clause resolves; the directive error is independent of them.)

What is and isn't the trigger (narrowing done)

Three-way isolation points at an interaction between jedi.inc's define set and synautil's conditional branches, not either alone:

So a symbol jedi.inc defines activates a synautil conditional branch whose directives PXX mis-pairs. It is not a single-define flip: re-adding any one of UNICODE/NEXTGEN/CIL/DELPHIX_SEATTLE_UP/VER100/COMPILER15_UP/MSWINDOWS/POSIX/ OS2/DELPHI/BCB/BDS2006_UP to the no-jedi compile does not reproduce it — it needs jedi's full define set.

Ruled out as the cause (each tested in isolation, all handled correctly):

Likely area: the conditional-skip lexer mis-tracking nesting depth on some directive inside an inactive branch that becomes reachable only under jedi's defines (candidate: a {$ifdef} opener in a skipped region whose matching {$endif} is on a labeled/peculiar line, or a directive form consumed without incrementing/decrementing the stack while skipping). Bisecting by source position from outside is confounded because the "unterminated" check is a global lex-phase EOF check reported only after the uses clause parses — Track A can instrument the conditional stack directly far faster.

Done when

Resolution (2026-06-25, v59)

Root cause: the lexer's inactive-branch skip scanned char-by-char and processed any open-brace-dollar sequence as a conditional directive, but did NOT skip comments or string literals. jedi.inc line 115 is a COMMENT that quotes two directives ("{$IFNDEF CLR} {$IFDEF MSWINDOWS}"). When jedi's include guard is ACTIVE (first include, e.g. via synautil) the active lexer treats the line as a comment and ignores the quoted directives — balanced. When the guard is INACTIVE (a 2nd include — synautil pulls synafpc, which includes jedi again, so JEDI_INC is already defined) the dead-branch skip processed the two quoted directives as real openers, pushing phantom conditionals that never close → the EOF check reported "unterminated conditional directive". The jedi/synautil "interaction" was exactly this: jedi alone is the first (active) include; synautil pulls it a second time (inactive).

Fix (lexer.inc, SkipSpace inactive path): mirror the active path — skip brace comments (with the nest-on-directive-open-brace rule), paren-star comments, line comments, optional C-style comments, and single-quoted strings, so only a genuine top-level directive updates the conditional-nesting stack. Self-host byte-identical; the synautil compile now passes the conditional phase and stops on a genuine parse gap, not a directive error.

Regression: test/test_cond_comment_skip.pas + test/cond_comment_guard.inc (a guarded include pulled twice; the dead 2nd body contains a comment quoting directives). Fails pre-fix ("unterminated conditional directive"), prints 42 post-fix. Pinned v59.