← board

or and a conditional expression are refused inside a subscript and accepted outside it

Found by a per-module sweep of That Space Program (~/tuxspaceprogram, official name That Space Program, not the folder name), 2026-09-19, compiler 28067ea1d2f3.

Minimal, with the control beside it

d = {"s": 1, "m": 60}
unit = ""
k = unit or "s"      # OK, compiles and prints s
print(d[unit or "s"])  # pascal26:3: error: expected ']' before 'or'
print(d["a" if f else "b"])  # pascal26:3: error: expected ']' before 'if'

The control is the point: the same or expression bound to a name one line earlier compiles and runs. So this is not "we do not support or" — it is the subscript using a different, narrower expression parser.

Real site

tsp/timebase.py:195total += float(num) * _DURATION_UNITS[unit or "s"]. The x or default subscript is ordinary Python and appears throughout the corpus.

What this is NOT evidence for

Fifteen modules hit this FIRST. That is not fifteen modules' worth of work unblocked. The census is first-failure, so every wall behind this one is invisible, and this repo has four dated cases where clearing the largest wall moved the compiling count by zero. Record what you expect BEFORE re-running the sweep, or the result is uninterpretable either way.

Likely shape, not verified

The grep to run first is for the OTHER spelling's handler: find where a subscript parses its index and compare it against the general expression entry point, rather than grepping for or. Same shape as the $cfnptr/$cfntype and ParseConstSection/ParseVarSection pairs — two doors, one wired.

2026-09-19 — re-measured: 16 modules, ONE site

frankh-3f found it first, from the other end: it reported universe.py:195 and then noticed the run printed in: tsp/timebase.py at the same line, so universe and ascent are one wall and not two.

Checked across the whole census. All 16 first-failure rows read pascal26:195:. That is the diagnostic naming an IMPORTED module's line with no file name — the class already written up in CLAUDE.md, where two modules failing at line 31 read as one shared dependency and the fix was worth one module of three. Here the shared cause is REAL, and the count is still not a count of work:

$ grep -rn 'unit or "s"' --include=*.py tsp/
tsp/timebase.py:195:   total += float(num) * _DURATION_UNITS[unit or "s"]

One line, one file. Three of the sixteen (anchor.py, moons.py, reference.py) never name timebase — they reach it transitively.

The broader search needs its own caveat. A pattern for "a bracket containing or/if" matches 11 files, and almost every hit is a LIST COMPREHENSION ([x for x in y if cond]), which parses fine. commentary.py:192's max([...] or [launch]) is an or inside a CALL, not a subscript. The subject of this ticket is a conditional expression in SUBSCRIPT position, and there is one.

Instrument note, recorded so it is not re-derived. The first pass at this used grep -cE '\[[^]]*\b(or|if)\b[^]]*\]' inside a shell loop and answered 0 for every file, timebase.py included — the file that demonstrably contains the construct. The \b did not survive the quoting. It was caught by running the positive control (does the pattern find the KNOWN site?) before believing the zero, and it is the guard-that-cannot-fail shape exactly: a census answering 0 everywhere reads as a clean, confident finding.

What this changes about the ticket: nothing about the defect, which is unchanged and real. The VALUE is unchanged too — fixing it advances 16 modules to their next wall. What changes is the SIZE: this is one parser fix against one call site, not sixteen sites, and nobody should scope it as the latter.

2026-09-19 — FIXED (frankH)

The seam was one call, not one line of grammar: ParsePropIndexArgs (pasparser_call.inc) parsed each index with Pascal's ParseExpr. The call- argument door already had the NilPy hook, ParseArgExpr (pyforwards.inc: PyParseBoolExpr under NilPy, ParseExpr otherwise), and the index now uses it. or, and, in (Python's meaning) and the conditional expression all work inside [...], on name, attribute and function-local receivers, and as assignment and augmented-assignment targets. Slices, literal receivers and not were already reached through other paths and still work. Pascal cannot move: without NilPy the hook IS ParseExpr.

Test: test_nilpy_subscript_index_is_a_full_expression, whose .expected is CPython 3.14.4's output. The pinned compiler refuses it at its first or.

TSP census, with the expectation written BEFORE the re-run

Population: every tsp/**/*.py outside __pycache__ at TSP 13eb601, 66 files, one compile each from the TSP root, first error: line recorded. The 61-module rows above are the same instrument on an EARLIER tree: TSP 5d18097 has 61 tsp/**/*.py, and its descendant 13eb601 has 66 (55 and 60 without __init__/__main__). Both are correct; compare within a tree, never across.

compiler OK first wall at timebase.py:195
8c314084d635 (pxx 531d1c843, before) 18 22
c101486bdd1e (this fix) 20 0

Written beforehand: all 22 move past 195; OK rises by few, point estimate ~21, because they share one dependency and whatever is behind 195 walls them again as a group. Measured: all 22 moved; OK went up by two (timebase, earth). The other 44 rows are byte-identical before and after. Where the 22 went next:

Log