Repository navigation
core: read a sentence into a check - #181
Merged
Merged
Conversation
spec/sentences_corpus.py pulls every sentence, for line and from line out of sentences-corpus.md into sentences-corpus.json, which OPA loads as data.spec.sentence_corpus, so a test can parse each one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adds the parser for spec/sentences.abnf to ergo.rego: _sentence reads a check, _for_line a for line and _from_line a from line, each into a JSON shape that mirrors the sentence, or into a list of problems when the line is written wrong. _s_check_text and its two siblings print a parsed line back in the spelling the report will use. The grammar is flat, so the parser is layered regular expressions: one splits the assertions, one reads an assertion's parts, one reads the conditions of a where, and the phrase is picked by its leading words. The patterns are built once per call in _s_patterns and passed down, because Wasm rebuilds a constant rule at every reference, and the phrase union is factored by argument kind to keep them small. Every corpus line parses and prints back unchanged, in 11 seconds for all 306 under Wasm. Nothing calls it from ergo.report yet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…take within Single quotes are only safe for a string with a backslash when it has no single quote, no two backslashes in a row, no backslash at the end and no control character, or parsing the printed sentence gives a different value. The written-wrong table loses the is empy row, since that sentence parses as a comparison with a field. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An index with a leading zero and a string with a bad escape stopped the evaluation instead of giving a problem. A value or key starting with true, false, null or empty was rejected, because the split took the first match; each piece now has to end at the separator or the end. The parser accepted a where on a plain path, a lone number on the left, a phrase other than a comparison after count of, sum of or plus, and a for name given twice; each is a problem now. A quoted grammar word is a key, and the dot in front of a key is spelling, not part of the parse, so the same check always reads the same. A position written in brackets at the end of a path reads and prints as item n of, and a string prints in single quotes only when that reads back to the same value. The messages for an unclosed quote, a missing phrase, a missing number after within and an unknown now say what the spec promises. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
So that count of item 1 of x, which is how the report prints count of x[0], is a sentence. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ree messages count of x[0] printed as count of item 1 of x, which did not parse, because a count or sum took no position. The unknown-name message skips quoted strings and names each name once, the invalid-string message gives a column so that a tab or newline in the string does not break the row, and the missing-phrase message quotes is and exists as the spec does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…time lacks Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ike it Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jbpros
force-pushed
the
core/sentence-parser
branch
from
October 12, 2026 21:34
ea72f07 to
f6c1f52
Compare
The parser and sentences.md already read it as a right-side path, where a quoted word is a value and not a key, so $input named by each of "T" doesn't parse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A number has to be 0 or have a magnitude between 2.2250738585072014e-308 and 1.7976931348623157e308, as in a policy. The parser now checks the size from the text first. Comparing a number whose exponent is beyond one million crashed OPA with "panic: illegal value", as in x is between -6 and -80e-08923553, and a number above 1.8e308 made x is between 1 and 1e400 fail with "doesn't parse". Problems now keep the order of their columns, so column 6 comes before column 20. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The squashed commit is core:, so the workflow change goes in a ci: pull request after this one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ttern x is between 1e400 and 1 said "min above max" too, and x matches 1e400 printed the parser's own marker as an invalid pattern. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sentences.md says the number after within is 0 or more, as REFERENCE.md says for today's within, but x is 1 within -1 parsed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jbpros
marked this pull request as ready for review
October 13, 2026 05:55
jbpros
added a commit
that referenced
this pull request
Oct 13, 2026
Runs `python3 spec/sentences_corpus.py --check` in the `spec` job and lists it in `AGENTS.md`. It fails when `spec/sentences-corpus.json` no longer holds every line of `spec/sentences-corpus.md`. This goes after #181, which adds the script and the JSON. It's separate so that #181's squashed commit stays `core:`. Its base is `core/sentence-parser`, so once #181 merges it needs rebasing onto `main`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
jbpros
added a commit
that referenced
this pull request
Oct 13, 2026
Moves every CI job to OPA 1.20.2 and stops testing OPA 1.19. OPA 1.19 compares some numbers wrongly: `1 >= 100.0` is true, so `"min_subjects": 100.0` passes with one subject, and `range` and `compare` can be wrong too. The #181 review hit it again, where `x is between 100.0 and 1` parses on 1.19 instead of giving "min above max". A test for that can't pass while CI runs 1.19. 1.20.2 is the version the Kosli CLI embeds. - `.github/workflows/test.yml`: `test`, `wasm`, `wasm-js` and `spec` run 1.20.2. The separate `opa-1-20` job goes, since `test` now does what it did. `test`, `wasm` and `title` keep their names, as the ruleset requires them. - `README.md` says ergo needs 1.20.1 or later. 1.20.0 compares correctly but stops with `panic: illegal value` on `0.0 == 1.5` (open-policy-agent/opa#9098). I ran each example on 1.19.0, 1.20.0, 1.20.1 and 1.20.2. - `AGENTS.md`, `CONTRIBUTING.md`, and the site's `opaVersion` and bakery page name 1.20. The site files are `site:`, but they only say which OPA version ergo needs, so they go with this change. Checks run here with OPA 1.20.2: every check in `AGENTS.md`, coverage, the Wasm tests in Docker, and the Wasm JS runtime script (1009 pass, 10.5 MB). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First implementation PR after #180: the parser for
spec/sentences.abnf, inergo.rego, not yet called byergo.report.What it does.
_sentence(text)reads a check,_for_line(text)aforline and_from_line(text)afromline, each into a JSON shape that mirrors the sentence (quantifier, path,whereconditions, phrase and its arguments, ortermsforcount of,sum ofandplus), or into{"problems": [...]}when the line is written wrong._s_check_textand its two siblings print a parsed line back in one spelling:isbefore a value andequalsbefore a field,is one ofbefore values andis inbefore a path,anbeforeobject,item 3 offor a position that ends a path, single quotes for a string with a backslash when that reads back the same, one space between words. Two spellings of one check give the same parse: the dot in front of a key, brackets or words for a position, and runs of spaces are spelling only.How. The grammar is flat, so the parser is layered regular expressions: one splits the assertions on
and, one reads an assertion's parts, one reads the conditions of awhere, one picks the phrase. Each split piece has to end at its separator or the end of the line, so the regex engine backtracks past a short match likefalseinfalse-positive. Rego can't recurse and this needs none.Wasm. The first version took over two minutes for the corpus under Wasm, because Wasm rebuilds a constant rule at every reference and the patterns were a tree of constants. They're now built once per call in
_s_patternsand passed down, and the phrase union is factored by argument kind. The corpus test takes 20 s under Wasm and 0.3 s withopa test.Tests. Every one of the 306 corpus lines parses and prints back byte for byte, from
spec/sentences-corpus.json, whichspec/sentences_corpus.pywrites from the markdown. The CI check that it's current is in #185, after this PR, so the squashed commit stayscore:. Unit tests cover each phrase, each path form, the printed spellings, and the messages for sentences written wrong.Review. A fresh Opus review found twelve things, four of them real bugs: a leading-zero index and a bad string escape stopped the evaluation, keys starting with
true,false,nulloremptywere rejected, and the parser accepted awhereon a plain path, a lone number on the left, a non-comparison aftercount of, and aforname given twice. All twelve are fixed in the secondcore:commit, with the spec changed in two places it was wrong: when a string prints in single quotes, and theis empyrow of the written-wrong table, since that sentence parses as a comparison with a field.Second review. A second review compared the parser with the ABNF, converted to a Lark grammar, on 6,000 changed corpus lines and 11,616 random sentences built from the grammar, and printed back every line the parser accepted. It found three things, fixed in the last commits:
x is between -6 and -80e-08923553crashed OPA withpanic: illegal value. OPA (1.19.0, 1.20.2 and 1.21.2) panics when it compares a number whose exponent is beyond ±1,000,000, reported as Comparing a number whose exponent is beyond ±1,000,000 panics with "illegal value" open-policy-agent/opa#9365. The parser now works out a number's size from its text beforeto_number, and applies the rule a policy number already follows:0or a magnitude between2.2250738585072014e-308and1.7976931348623157e308. Otherwise the sentence getsnumber out of range: <number>.sentences.mdsays so.x is between 1 and 1e400, which failed with "doesn't parse" because OPA can't compare numbers above 1.8e308.named by each ofa left-side path, where"T"is a key. The parser andsentences.mdread a right-side path, so the ABNF now saysright-path.Problems now keep their column order, so column 6 comes before column 20. A third review found that
x is 1 within -1parsed, though the spec says the number afterwithinis 0 or more. It now gets"within" needs a number of 0 or more.Not in this PR. Wiring into
ergo.reportand the semantics of each phrase.spec/sentences.mdnow says ergo reads sentences but runs none.Checks run here:
opa check,opa fmt,opa testwith coverage (every line reached),--strict-builtin-errors, Regal, no comments, OPA 1.20.2fmt, Wasm tests in Docker, and the Wasm JS runtime script (1009 pass, 10.5 MB). On OPA 1.19, two of the 11,616 random sentences didn't print back the same inside one big evaluation, because 1.19 thinks0.5e-7equals0.5e-70. That's the known 1.19 number bug, and OPA 1.20.2 prints all of them back correctly.🤖 Generated with Claude Code