Skip to content

Use -1 as the BNF EOF symbol - #1492

Merged
sergey-shandar merged 19 commits into
mainfrom
agent/eof-minus-one
Aug 11, 2026
Merged

Use -1 as the BNF EOF symbol#1492
sergey-shandar merged 19 commits into
mainfrom
agent/eof-minus-one

Conversation

@sergey-shandar

@sergey-shandar sergey-shandar commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • move semantic BNF EOF from 2^24 - 1 to -1
  • keep the current ordinary-symbol domain 0 .. 2^24 - 2 unchanged
  • keep exactly 2^24 terminal values in the current number-based BNF
  • keep TerminalRange packed as two 24-bit stored endpoints
  • reuse the existing stored EOF code 2^24 - 1 for semantic EOF -1
  • treat ranges whose old endpoint was EOF as a breaking semantic change and regenerate them under the new ordering
  • keep logical EOF synthesis in parser/recognizer backends and track parser progress with (idx, eofConsumed)
  • keep the later uint256 ordinary-symbol expansion and its TerminalRange representation choice as separate work
  • keep related TODOs open, with implementation order documented directly instead of blocker metadata

Why

The EOF change does not need to expand the current terminal space:

before: [0 .. 2^24 - 2] | EOF=2^24-1
after:  EOF=-1 | [0 .. 2^24 - 2]

It is the same number of terminal values, so the 24-bit endpoint codec does not need to grow. The stored EOF code can remain 2^24 - 1.

The semantic ordering does change, however. For example, the old EOF-inclusive fullRange = [0, 2^24 - 1] becomes the ordinary-only [0, 2^24 - 2]. Existing serialized ranges that used EOF as an endpoint are therefore breaking and should be regenerated rather than supported through a compatibility layer.

The later bigint migration is different because it expands ordinary symbols to the full 0 .. 2^256 - 1 domain in addition to EOF. That is where a new TerminalRange representation is needed.

Docs-only change; no runtime checks required.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
functionalscript 7c5eb6d Commit Preview URL

Branch Preview URL
Aug 11 2026, 09:10 PM

@sergey-shandar

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bca9d02e25

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/bnf/todo/eof-minus-one.md Outdated
Comment thread fjs/bnf/todo/eof-minus-one.md Outdated

@o2alexanderfedin o2alexanderfedin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. Reviewed at bca9d02. One new todo/ file, no code — so the review is whether the proposal's premises are true and its plan is self-consistent. I checked each factual claim against the tree rather than reading past them.

Claim Verified
EOF is currently the largest terminal value, 2^24 - 1 offset = 24, mask = (1 << offset) - 1 = 16777215, eof = oneEncode(mask)
The bigint plan reserves 2^256 - 1 bigint-symbols.md:48EOF = 2^256 - 1
Two 25-bit endpoints stay inside the safe-integer range max packed ≈ 2^49 + 2^24, well under 2^53 − 1 ✓
An encoded uint256 endpoint reaches 2^256, needing 257 bits

The one that most deserved checking is the range-map note, because it reads as forward-looking. It isn't — bigint-symbols.md:163 currently states outright that "-1n is not a BNF symbol and must never be accepted from an alphabet adapter or terminal range. It exists only inside the range-map representation." This proposal makes -1 exactly a BNF symbol, so those two documents will contradict each other. That is acknowledged rather than accidental: the task list has an explicit item to update bigint-symbols.md to depend on this task instead of reserving the maximal value, and the -2 suggestion gives internal cut points somewhere to go.

Worth noting the proposal also fixes a latent inconsistency that exists today: fullRange is [0, 16777215], which includes the EOF symbol value, so the current alphabet and the EOF sentinel already overlap. Restricting fullRange to ordinary symbols is a correctness improvement, not only a width-independence one.

Mechanical checks:

  • All 7 relative links in the document resolve.
  • No CHANGELOG entry, correctly — §8.3 exempts PRs touching only todo/.
  • npx tsc clean, 2357 pass / 0 fail, unchanged from main as expected for a doc-only change.
  • CI green.

@sergey-shandar

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 69426c130b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/bnf/todo/bigint-symbols.md Outdated
@sergey-shandar

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0ae15d1386

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/bnf/todo/eof-minus-one.md Outdated

@o2alexanderfedin o2alexanderfedin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving at 0ae15d1. The five new commits reconcile the sibling documents, which resolves the contradiction I flagged — and the fix went further than the one document I named.

The contradiction is gone, repo-wide

I swept all of fjs/bnf/todo/ rather than just re-reading bigint-symbols.md:

  • No document asserts a maximal-value EOF any more. EOF = 2^256 - 1 and "Reserve the maximal value" are both gone.
  • The `-1n` is not a BNF symbol and must never be accepted statement is gone.
  • Every EOF definition across the five documents now reads -1bigint-symbols.md, eof-minus-one.md, terminal-range-representation.md, unicode-rules.md, and utf8-token-symbols.md all agree.

The four remaining "maximal value" mentions are all correct rather than residue: two describe the current behaviour being changed (the Problem statement and a task item to update callers that assume it), and two state the new rule — utf8-token-symbols.md in particular now validates against 0n <= encoded && encoded < 2^256 explicitly "not against a reserved maximal value."

The new section closes a real gap

6688a55 adds Logical EOF in parser input, which answers the question the original draft left open: if EOF is semantic and not in the physical alphabet, who produces it? The answer — adapters supply only physical symbols and never append -1, each backend synthesizes exactly one logical EOF after the last symbol, public positions stay in 0 <= idx <= input.length, and eofConsumed lives in internal state — is consistent with the task item that already said EOF is parser semantics rather than an alphabet value. Worth having written down before implementation rather than discovered during it.

Also a small correctness touch: the bigint reservation is now described in the past tense, since that document no longer proposes it.

Mechanical

  • Links 144/144 against main, none added, 0 label/target mismatches.
  • npx tsc clean, 2357 pass / 0 fail — unchanged, as expected for a documentation-only change.
  • No CHANGELOG entry, correct under §8.3.
  • CI green.

@sergey-shandar

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7cd6d51192

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/bnf/todo/eof-minus-one.md Outdated
Comment thread fjs/bnf/todo/eof-minus-one.md Outdated

@o2alexanderfedin o2alexanderfedin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving at 73da4ac. Three commits that take the logical-EOF section from "who synthesizes it" to "what it does to control flow" — and the addition is the part of this design most likely to bite during implementation, so it is good to see it settled on paper.

Making EOF count as progress is the right call

7cd6d51 widens the cursor to (idx, eofConsumed) and states that matching EOF is progress even though the public idx does not move. That distinction is load-bearing in exactly the places the document lists:

  • repetition's no-progress termination check would otherwise treat EOF as a nullable match;
  • alternative selection would be free to discard an EOF-consuming branch for a later nullable one on the grounds that idx is unchanged;
  • backtracking that restored only idx would leave EOF spuriously consumed for a sibling branch.

Each of those is a real miscompile waiting to happen once EOF stops being an ordinary in-band symbol, and none is obvious from the EOF = -1 decision alone.

b547dda's lexicographic (idx, false) < (idx, true) for failure high-water tracking is consistent with that: a failure that got as far as consuming EOF genuinely is further along than one that did not.

The recognizer side agrees

73da4ac gives the streaming counterpart: chunk boundaries are not end-of-input, EOF is synthesized only at explicit finalization, and finish(state) = λ(δTerminal(EOF, state)) pins the semantics without over-specifying the representation. recognizer-backend.md now also declares itself blocked by the EOF task, which it genuinely is.

Consistency and mechanics

  • Ten EOF = -1 definitions across the bnf/todo set, all in agreement.
  • The three "append EOF" hits are the prohibition stated in both documents, not a contradiction — callers never append, backends synthesize.
  • Links 144/144 against main, none added, 0 label/target mismatches, including the new "Blocked by" reference.
  • npx tsc clean, 2357 pass / 0 fail, unchanged as expected for documentation.
  • CI green.

@sergey-shandar

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 73da4ac9ee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/bnf/todo/recognizer-backend.md Outdated
@sergey-shandar

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 17bdecd0de

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/bnf/todo/eof-minus-one.md Outdated

@o2alexanderfedin o2alexanderfedin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving at 17bdecd. Clarify recognizer EOF acceptance fixes something real, not a wording nit, so it is worth stating what it fixes.

The previous finalization would have silently broken existing grammars

The formulation this replaces was:

finish(state) = λ(δTerminal(EOF, state))

That forces every grammar through the EOF transition. An existing whole-input grammar that never mentions eof, and that is already accepting after its last physical symbol, would be rejected if δTerminal moved that accepting state to a sink — which is the ordinary thing for a transition on a symbol the grammar does not expect. No grammar author would have written anything wrong; the recognizer would just have stopped accepting inputs it accepts today.

The new form is backward-compatible:

beforeEOF = λ(state)
afterEOF  = λ(δTerminal(EOF, state))
finish(state) = beforeEOF || afterEOF

and the document states the consequence directly — the EOF transition may not erase acceptance already established at the physical end, while a grammar whose final rule requires eof can still return false before and true after. Allowing a backend to compile an optional final EOF path instead of literally evaluating both states, as long as the accepted language matches, is the right level to pin: contract over representation.

The two documents still agree

I checked this specifically, because the commit also drops the sentence calling finalization "the streaming equivalent of the parser cursor transition." Dropping it is correct rather than a loss: in eof-minus-one.md the parser only takes the (len, false) → (len, true) step when the grammar actually matches eof, whereas the old recognizer text took it unconditionally. Both documents now say the same thing — one EOF opportunity is made available, and the grammar decides whether to use it. "Synthesize exactly one logical EOF" means availability, not forced consumption, and nothing in either file now claims otherwise.

The added task item covers both acceptance paths explicitly, which is the right thing to prove.

Mechanics

  • Only markdown changed (0 non-.md files).
  • Links 144/144 against main, none added, 0 label/target mismatches.
  • npx tsc clean, 2357 pass / 0 fail — unchanged, as expected.
  • CI: everything green except macos-intel, still running when I checked; the other platforms and CodeQL all passed, and nothing here can affect a build.

@sergey-shandar

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9831f49e6d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/bnf/todo/eof-minus-one.md Outdated
Comment thread fjs/bnf/todo/bigint-symbols.md
@sergey-shandar

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: 7c5eb6dd31

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@sergey-shandar
sergey-shandar marked this pull request as ready for review August 11, 2026 21:16
@sergey-shandar
sergey-shandar added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit 1e61226 Aug 11, 2026
19 checks passed

@o2alexanderfedin o2alexanderfedin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving at 7c5eb6d. Nine commits, and they materially simplify the design rather than polishing it — net −241 lines across the three documents.

The simplification is a real improvement

What I approved previously required a +1 offset on every endpoint, a temporary 25-bit packed form, and therefore a wire-format change. That is all gone. Semantic EOF is still -1, but the stored representation is untouched: EOF keeps stored code 2^24 - 1, every ordinary symbol keeps its existing code, and the packed range stays two 24-bit endpoints. There are exactly 2^24 terminal values before and after.

I verified the proposed codec exhaustively rather than eyeballing the three sample rows:

encodeTerminal = value => (value + terminalSize) & terminalMask
decodeTerminal = value => ((value + 1) & terminalMask) - 1

Over the whole semantic domain -1 .. 2^24 - 2: 16777216 values → 16777216 distinct codes, zero round-trip failures. Bijective and exact.

It is not order-preserving — encode(-1) is 2^24 - 1, above every ordinary code — and the document says so itself in the right place: "The stored codes are an implementation representation, not semantic terminal ordering. Range operations that care about semantic ordering must compare decoded terminal values." That was my first concern on reading the new mapping, and it is already answered.

The breaking consequence is stated plainly too: fullRange moves from [0, 2^24 - 1] to [0, 2^24 - 2], old serialized ranges whose endpoint was EOF now decode to -1, and the resolution is to regenerate in-repo data rather than carry a compatibility layer. For content-addressed BNF data that is the honest call.

Cross-document consistency

Two things looked like leftovers and are not:

  • terminal-range-representation.md still shows encodeTerminal(value) = value + 1, but it now sits under Alternatives to investigate for the bigint domain, where 2^256 + 1 semantic values genuinely cannot fit a 256-bit code. Correctly scoped to the future decision rather than the current one.
  • eof-minus-one.md still contains EOF = 2^24 - 1 twice — both inside before:/current-state blocks describing today's representation.

That document also now states outright that moving semantic EOF does not require changing the current packing, which is what makes the two tasks independent.

Mechanics

  • Markdown only (0 non-.md files).
  • Links 144/144 against main, none added, 0 label/target mismatches.
  • npx tsc clean.
  • CI green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants