Found by fuzzing (see #415).
Repro
stmt, err := memefish.ParseStatement("", "SELECT 0 .A0000")
// err == nil
sql := stmt.SQL()
// sql == "SELECT 0.A0000"
_, err = memefish.ParseStatement("", sql)
// err != nil: syntax error: :1:10: number literal cannot follow identifier without any spaces
Cause
SELECT 0 .A0000 parses as field access .A0000 on the integer literal 0 (dot-identifier lexer mode). The unparser renders the selector as expr "." ident with no space, but 0.A0000 re-lexes differently (a float-literal prefix gluing onto an identifier), so the rendered SQL is no longer valid.
SQL() needs to keep a separator when the receiver of a dot selector is an integer literal (e.g. render 0 .A0000, or (0).A0000). The same may apply to other numeric receivers worth checking (e.g. hex literals).
This breaks the round-trip guarantee that the SQL rendering of a successfully parsed statement is itself parseable.
Found by fuzzing (see #415).
Repro
Cause
SELECT 0 .A0000parses as field access.A0000on the integer literal0(dot-identifier lexer mode). The unparser renders the selector asexpr "." identwith no space, but0.A0000re-lexes differently (a float-literal prefix gluing onto an identifier), so the rendered SQL is no longer valid.SQL()needs to keep a separator when the receiver of a dot selector is an integer literal (e.g. render0 .A0000, or(0).A0000). The same may apply to other numeric receivers worth checking (e.g. hex literals).This breaks the round-trip guarantee that the SQL rendering of a successfully parsed statement is itself parseable.