Skip to content

fix: range 'a':'b'!A1 is mistaken for sheet range a:b!A1 - #66

Open
gthb wants to merge 6 commits into
borgar:masterfrom
gthb:fix-quoted-second-endpoint-is-range-op
Open

fix: range 'a':'b'!A1 is mistaken for sheet range a:b!A1#66
gthb wants to merge 6 commits into
borgar:masterfrom
gthb:fix-quoted-second-endpoint-is-range-op

Conversation

@gthb

@gthb gthb commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

'a':'b'!A1 is lexed and parsed as the sheet range a:b. A quote on the second name makes the colon a range operator, whatever stands to its left, so the formula is the name a joined to 'b'!A1: a different formula over different sheets. Excel leaves it as written on the next save, so nothing downstream corrects it.

Cause

fx already had the rule for an unquoted first name, a:'b'!A1, but not for a quoted one, in either lexing path: a mergeRefTokens pattern group covered 'a':b!A1 and 'a':'b'!A1 together, and pExtendedContext accepted a quoted second name whenever the first was quoted too.

Fix

Both now require the second name to be unquoted. That makes the merge group a subset of the one below it, which already covers 'a':b!A1, so it goes.

addTokenMeta groups references that resolve to the same cells, so the 'dec'!B11 of 'jan':'dec'!B11 now shares a group with a lone Dec!B11 rather than with the sheet ranges.

Note

Includes #65's commits, which this needs. A cross-fork PR cannot target a branch on the fork, so they show until #65 lands. The real diff here is the last commit alone.

gthb added 6 commits September 3, 2026 15:07
`'Alpha':[Book.xlsx]Gamma!A1` and `'Alpha:Beta':Gamma!A1` fail to parse:
"Unexpected context_quote token". Their first name is a quoted scope with
no `!` after it, so it is not a scope but an operand, and `parse` has no
production for one there.

The lexer types it as a scope on the chance that a second sheet name
follows. Where nothing merges it, `mergeRefTokens` now corrects it to
`ref_named`, which is what Excel reads there and what the unquoted `a` of
`a:'b'!A1` already gets, so the two ways of writing the operation agree.

A scope that does have its `!` keeps the scope type whether or not it
merged, since a scope containing a `:` never merges with the name or
table after it, as in `'Sheet1:Sheet2'!name`.

Untouched: an unquoted scope in the same position, `1` in `1:'dec'!B11`,
which the lexer leaves as `context` and which still does not parse. A
number is not a name, so it wants a different answer.
The fix is a token type, and only the lexer specs covered it. These
assert the outcome that motivated it: both formulas reach an AST, with
the quoted scope as the name operand of the range operator.
The test asserted `'Alpha:Beta':Gamma!A1` as the name `Alpha:Beta`
joined to `Gamma!A1`, under a name that read as a claim about Excel.
Excel's reading of that form is unmeasured, and for the nearest measured
neighbour, a quoted first name followed by an unquoted second, Excel
reads the sheet range instead.

What the test is really for is that the formula reaches an AST at all,
where the quoted first name used to stop the parser. Renamed to say it
parses that way, with the scope of the claim in a comment.
The test name and its comment left the reading as fx's own. Excel takes
both formulas the same way and stores them with the second name quoted,
`'Alpha:Beta':'Gamma'!A1` and `Alpha:'[1]Gamma'!A1`.

Neither resolves: a defined name cannot contain a colon, and `Alpha` is
not a name in that workbook, so both are #NAME?. In the second form the
workbook bracket is what rules out a sheet range; the quote on the first
name is redundant and Excel drops it.
Two clauses went: the lexer's speculative typing, which the description
already gives as the cause, and a justification for the `!` exclusion
that was wrong. It said a scope containing a `:` never merges with the
name after it, which holds under `xlsx` only; in the default mode
`'Sheet1:Sheet2'!name` merges into one reference.

What is left is the rule the guard implements.
`'a':'b'!A1` was lexed and parsed as the sheet range `a:b`. A quote on
the second name makes the colon a range operator, whatever stands to its
left, so it is the name `a` joined to `'b'!A1` -- a different formula
over different sheets. Excel leaves the form as written on the next save,
so nothing downstream corrects it.

fx already had the rule for an unquoted first name, `a:'b'!A1`. It was
the quoted-first case that kept the sheet-range reading, in both lexing
paths:

- `mergeRefTokens` had a pattern group for `'a':b!A1` and `'a':'b'!A1`
  together. Narrowing its second name to unquoted makes it a subset of
  the group below it, which already covers `'a':b!A1`, so the group goes
  and the surviving one carries the rule.

- `pExtendedContext` accepted a quoted second name when the first was
  quoted too, and now accepts an unquoted one only.

Four spec files asserted the old reading and now assert the new one. Two
were already at odds with themselves: `fixRanges` asserted the sheet
range under the title "a quote around the second end is the range
operator, not a 3D reference", and a lexer comment said Excel corrects
`'[Book.xlsx]Alpha':'Gamma'!A1` into a span, which is the reading this
removes. That comment is gone, along with an `XXX` asking that fixRanges
deal with the form, which it now does.

One grouping changes with it. `addTokenMeta` gives references that
resolve to the same cells one group id, so the `'dec'!B11` of
`'jan':'dec'!B11` now shares a group with a lone `Dec!B11` instead of
with the sheet ranges.
@gthb gthb changed the title fix: a sheet range whose both ends are quoted is a range operation fix: range 'a':'b'!A1 is mistaken for sheet range a:b!A1 Sep 3, 2026
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.

1 participant