build-a-csv-parser / lesson-09.md
Lesson 09 · The quoting state machine

Stray text after a closing quote

Once a quoted field closes, the only things allowed next are a delimiter, a newline, or the end of input. Anything else is malformed, and today you report it at the position of the offending character, completing the quoting state machine.

The goal

Report an error when characters other than a delimiter or newline follow a closing quote.

Start here - the target
TO DO
Scenario: Extra characters after a closing quote
Giventhe input "a"b (the raw characters quote a quote b), where a character follows the closing quote
Whenit is parsed
Thenparsing fails with an error at line 1, column 4, the position of the stray b, with a message about unexpected text after a closing quote
Anda space also counts, so "a" b (quote a quote space b) fails at column 4 as well, while "a","b" (two quoted fields) parses cleanly to [["a", "b"]]
Background

A quoted field makes a tidy promise: after its closing quote, the field is over, so the very next thing must be a separator. Concretely, once you read the closing quote you enter a brief after-quote state where only three things are legal, a delimiter to start the next field, a record terminator to end the row, or the end of the input. A " followed by a" followed by b violates this, because b is neither, and the honest response is to reject it at the position of that b rather than silently gluing it onto the field.

This includes whitespace: a space after a closing quote, as in "a" b, is stray text too, because leading and trailing spaces are significant in this parser and a quoted field’s boundary is exact. Reporting these at the offending column, using the positioned error from the previous lesson, closes out the quoting state machine. With it, your parser now has a complete model of quotes: it opens them only at a field’s start, reads any content including delimiters, newlines, and escaped quotes, requires a matching close, and forbids anything unexpected around the boundary. Every later chapter builds on this core without changing it.

Make it work
// after the CLOSING quote you are in an "after-quote" state; the next rune must be
// a delimiter -> start the next field
// a newline / carriage return / end of input -> end the record
// anything else (including a space) -> ParseError at the current column
CheckpointDONE
The quoting state machine is complete, accepting well-formed quoted fields and rejecting malformed ones with positions. Commit and stop here.