Daniel Sun created GROOVY-12171:
-----------------------------------

             Summary: Improve GString syntax error when '$' is not followed by 
a valid interpolation
                 Key: GROOVY-12171
                 URL: https://issues.apache.org/jira/browse/GROOVY-12171
             Project: Groovy
          Issue Type: Improvement
            Reporter: Daniel Sun


h3. Problem

When a double-quoted or triple-double-quoted GString places an illegal 
character immediately after {{$}} (most commonly a trailing bare dollar before 
the closing quote), the Antlr4 lexer reports a generic *token recognition 
error* at that character and never mentions the dollar. The caret location is 
often correct, but the message is hard to understand compared with Groovy 2, 
which reported {*}illegal string body character after dollar sign{*}.

Typical mistakes:
 * trailing bare {{$}} before the closer: {{{}"releases$"{}}}, 
{{"""releases$"""}}
 * {{$}} not followed by an identifier or brace expression: {{{}"a$ b"{}}}, 
{{"$"}}
 * EOF or newline immediately after {{$}} in an unclosed GString: {{"hello$}}

Valid programs are unaffected ({{{}$name{}}}, brace expressions, and {{\$}} for 
a literal dollar already work).
h3. Examples (before)
 * Source:
{noformat}
def Target = "releases$"
{noformat}
Report:
{noformat}
token recognition error at: '"' @ line 1, column 24.
   def Target = "releases$"
                          ^
{noformat}

 * Source:
{noformat}
def Target = """releases$"""
{noformat}
Report: *token recognition error at: '"'* at the first character of the closing 
{{"""}} (no mention of {{{}${}}})

 * Source:
{noformat}
def x = "a$ b"
{noformat}
Report: *token recognition error at: ' '* (no mention of {{{}${}}})

h3. Root cause

After {{GStringBegin}} / {{GStringPart}} consumes {{{}${}}}, the lexer enters 
{{GSTRING_TYPE_SELECTOR_MODE}} and expects either an opening brace or an 
identifier. Any other character raises {{{}LexerNoViableAltException{}}}. 
Default Antlr recovery surfaces that as *token recognition error at: '…'* with 
no GString-specific wording.

A related edge case: when {{$}} is the last character of the input, Antlr sets 
{{_hitEOF}} after the begin/part token and the next {{nextToken()}} emits EOF 
without ever matching in the selector mode, so the failure becomes a generic 
unexpected-EOF parse error instead of a dollar-body diagnostic.
h3. Goal

Restore a clear, Groovy 2–style diagnostic — *Illegal string body character 
after dollar sign* — with an accurate caret, and name the offending character 
when one is available. Prefer a dedicated {{GroovySyntaxError}} on the lexer 
path so the parser does not fall back to an opaque exception or a useless 
SLL→LL retry for this case.
h3. Approach

Localised handling in {{GroovyLangLexer}} only (no grammar / public API change):
 * In {{{}GSTRING_TYPE_SELECTOR_MODE{}}}, override {{notifyListeners}} / 
{{recover}} for {{LexerNoViableAltException}} to emit the dedicated dollar-body 
message (append the illegal character via {{getCharErrorDisplay}} when not EOF).
 * Override {{nextToken}} so that EOF while still in the selector mode reports 
the same message instead of a bare unexpected-EOF parse failure.
 * Throw {{GroovySyntaxError}} with source {{LEXER}} so {{AstBuilder}} treats 
it as a fatal lexical error with line/column.

h3. Expected result (after)
 * Source:
{noformat}
def Target = "releases$"
{noformat}
Expected:
{noformat}
Illegal string body character after dollar sign: '"' @ line 1, column 24.
   def Target = "releases$"
                          ^
{noformat}

 * Source:
{noformat}
def Target = """releases$"""
{noformat}
Expected: *Illegal string body character after dollar sign: '"'* at the first 
closing quote character

 * Source:
{noformat}
def x = "a$ b"
{noformat}
Expected: *Illegal string body character after dollar sign: ' '* at the space 
after {{$}}

 * Source (EOF immediately after {{{}${}}}):
{noformat}
def x = "hello$
{noformat}
Expected: *Illegal string body character after dollar sign* (no character 
suffix) at end of input

Legal cases must keep compiling:
{noformat}
def Target = "releases\$"
assert Target == 'releases$'
def name = 'world'
assert "hello $name" == 'hello world'
assert "hello ${name}" == 'hello world'
{noformat}
h3. Compatibility and performance
 * No grammar, bytecode, or public API changes — valid GStrings parse and run 
as before.
 * Extra work runs only on the failing lexical path (mode check + message 
build); successful lexing is unchanged aside from a cheap mode check when 
emitting EOF.
 *  



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to