Not every "CSV" file uses commas; tab- and semicolon-separated files are everywhere. Today you introduce a dialect that carries the delimiter, so the same state machine can parse any single-character separator.
Parse with a configurable delimiter by threading a dialect through the parser.
The C in CSV says comma, but the format is used with every separator under the sun: tabs for TSV, semicolons in locales where the comma is a decimal point, pipes in log exports. Rather than hard-code the comma, you introduce a dialect, a small configuration value that describes how a particular family of files is punctuated. Today it holds just the delimiter, but you give it a struct of its own because the next lessons add trimming and comment characters, and threading one dialect value through the parser now is far cleaner than adding parameters one at a time later.
The refactor is mechanical: every place the state machine compared a rune to the
literal comma now compares it to the dialect’s delimiter. Introduce a ParseWith
that takes a dialect, and redefine the original Parse as ParseWith with a default
comma dialect, so all your earlier behavior and tests are preserved exactly. The
payoff is immediate and worth testing: with a semicolon delimiter a comma loses its
special meaning and becomes ordinary field content, which is the whole reason
semicolon files exist in comma-using locales. One state machine, many dialects.
// introduce the dialect now, with room for the options coming nexttype Dialect struct { Delimiter rune /* Trim, Comment added later */ }var DefaultDialect = Dialect{Delimiter: ','}func ParseWith(d Dialect, input string) ([][]string, error) { /* the state machine */ }func Parse(input string) ([][]string, error) { return ParseWith(DefaultDialect, input) }// everywhere the code compared a rune to ',' now compares to d.Delimiter