Skip to content

chore(language): a where comparison is one grammar rule over the expression grammar's arithmetic - #565

Merged
FBumann merged 3 commits into
mainfrom
claude/expression-parser-language-split-kdqegz
Sep 20, 2026
Merged

FBumann merged 3 commits into
mainfrom
claude/expression-parser-language-split-kdqegz

Conversation

@FBumann

@FBumann FBumann commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Prompt: "Rebuild it as two PRs on #562. First a chore(language) that lands the one atom and the dispatch with no behaviour change: same typed nodes, same messages, golden output and schema unmoved. Then #469's semantics become one new branch in that dispatch plus its lowering, docs and tests."

Note

The following content was generated by AI.

Stacked on #562. The where grammar has one comparison rule, side <op> side, and a side is the expression grammar's own arithmetic. Resolution reads the two sides with the schema in hand and hands back the same typed nodes with the same messages. This is the half that #469 then lands on as one more branch in that dispatch.

What this changes

  • One comparison rule. The three rules the grammar had, a name against a literal, two relation columns, and position() against an integer, are one: (column | ARITHMETIC) <op> (quoted | column | ARITHMETIC), where ARITHMETIC is the expression grammar's arithmetic half, now exposed. grouped_by, grouped_within, columns, the signed-number and integer regexes, _position_comparison and _Quoted are gone.
  • One unresolved node. UnresolvedComparisonNode(left, op, right) carries the two sides bare. position(...) arrives as the FunctionCallNode an expression would carry it in, the way dual(...) does. A relation column is a ColumnNode and a quoted label a QuotedNode, since neither has a spelling in an expression. UnresolvedPositionNode is gone.
  • Dispatch in resolution. _comparison reads the sides: a position() call on the left goes to _position, which reads the dimension, by= and within= off the call node; a name or column against a literal or a second column is the plain form the dtype rules are written for, held in a private _Plain and read by the code that read the old node. Every typed node, every message and every test that writes a where string is unchanged.
  • Depth. A where side is held to the nesting an expression is, measured through the arithmetic under a comparison.

What moved from the grammar into resolution

Two shapes the grammar refused as Failed to parse where string are parsed now and refused in resolution, each with a message that names the rewrite. Both are covered in TestAWhereSideIsReadInResolution.

  • Arithmetic on a side (c > 2 * k, 2 < c, sum(c, over=g) >= k). This refusal is transient: the follow-up PR replaces it with feat(language): a where may compare arithmetic over parameters #469's arithmetic path.
  • A position() call not of its shape (position(g, h), position(g, edge=1), position(g, by=[a, b])), or compared against something other than an integer (position(g) == 1.5, position(g) == c).

Two inputs change outcome, both because a where side is now read by the expression grammar's literal rules:

  • c < inf loads as a comparison against infinity. Before, inf was read as the bare word 'inf' and refused as "comparing it to the string 'inf'".
  • position(g) == 1.0 loads as position 1. Before, the integer regex refused it at parse.
Rebased onto main at bda1fc5

Merged, not replayed: the hard rule here is never to force-push. main came
in at the bottom of the stack (#494) and was carried up through every branch
between, so this one's base is current.

Clean at this level: no conflict, and the suite passes unchanged.

Gates

pixi.sh is refused by this environment's egress proxy, so the gates ran from a uv environment on Python 3.12. That is a departure from the "everything runs in a pixi environment" default.

gate result
pytest -n auto 1351 passed, 6 skipped (#562: 1333 passed, 6 skipped)
ruff check, ruff format clean
pyrefly check 0 errors
typos, reuse lint clean
mkdocs build --strict clean, with the docs.python.org inventory dropped for the run, which the proxy refuses with a 403
compile-tex not run, for want of a TeX distribution

python -m tools.schema and python -m tests.typesetting.golden were re-run after the change: neither the schema nor any golden .out file moved.

Coverage moved
  • test_position_converts_a_dimension_to_where_a_row_sits asserted the fields of UnresolvedPositionNode; it is test_position_is_a_call_on_the_left_hand_side, asserting the call node and the literal on the right, with a within=[a, b] case added.
  • test_a_quoted_right_hand_side_is_a_label asserted .value and .quoted; it asserts the QuotedNode or NameNode on the right.
  • test_a_position_is_not_confused_with_a_name argued from the order of the alternation, which no longer exists; the case it protected is in test_position_is_a_call_on_the_left_hand_side.
  • New: test_a_relation_column_is_named_with_a_dot, test_a_side_is_any_arithmetic_to_the_grammar, test_a_bracketed_predicate_is_still_a_predicate, test_a_where_side_is_held_to_the_depth_an_expression_is, and the two tests in TestAWhereSideIsReadInResolution.
Line count

src/ is +197 / −115. The grammar module is 8 lines shorter and the resolver 83 longer: the two refusals above are about 30 of those, _position_shape, _side_name and _Plain the rest. This PR trades three grammar rules and a grammar-side node for a dispatch in resolution; it does not claim to be a cleanup on its own, and the follow-up removes the transient refusal.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EEeM2YoAk4Xr2uWB5qwMsH


Generated by Claude Code


Generated by Claude Code

@read-the-docs-community

read-the-docs-community Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

@FBumann
FBumann added this pull request to stack #560 September 19, 2026 17:35
@FBumann
FBumann removed this pull request from stack #560 September 19, 2026 18:10
@FBumann
FBumann added this pull request to stack #574 September 19, 2026 18:10
@FBumann
FBumann removed this pull request from stack #574 September 19, 2026 18:12
@FBumann
FBumann added this pull request to stack #575 September 19, 2026 18:12
@FBumann
FBumann removed this pull request from stack #575 September 20, 2026 20:34
@FBumann
FBumann added this pull request to stack #583 September 20, 2026 20:35
Base automatically changed from fix/relation-read-rule to main September 20, 2026 20:51
…ession grammar's arithmetic

The where grammar's three comparison rules — a name against a literal,
two relation columns, and position() against an integer — are one rule,
side <op> side, where a side is the expression grammar's ARITHMETIC. What
a side is, resolution decides with the schema in hand, and it hands back
the same typed nodes with the same messages.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EEeM2YoAk4Xr2uWB5qwMsH
…als it points at

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EEeM2YoAk4Xr2uWB5qwMsH
…ather than Any

`explicit-any` is an error on `src/math_spec` since #572, and that PR took
only the fold's return type from #567, leaving `_nested` on this branch
with a signature whose `Any` no longer even imports. `_ParsedWhere` is the
union the measurement walks, which is #567's own answer for this file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V2SFmoxEF3SnaPKp7HbZTk
@FBumann
FBumann force-pushed the claude/expression-parser-language-split-kdqegz branch from 352a4e6 to 18c3f69 Compare September 20, 2026 20:51
@FBumann
FBumann removed this pull request from stack #583 September 20, 2026 20:53
@FBumann
FBumann merged commit 1f4f8c5 into main Sep 20, 2026
5 checks passed
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