diff options
Diffstat (limited to 'mintage/parser.go')
-rw-r--r-- | mintage/parser.go | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/mintage/parser.go b/mintage/parser.go new file mode 100644 index 0000000..242a3bb --- /dev/null +++ b/mintage/parser.go @@ -0,0 +1,204 @@ +package mintage + +import ( + "bufio" + "fmt" + "io" + "strconv" + "strings" + "unicode" +) + +const ( + _ = -iota + Unknown // Unknown mintage + Invalid // All mintages <= than this are invalid +) + +type SyntaxError struct { + expected, got string + file string + linenr int +} + +func (e SyntaxError) Error() string { + return fmt.Sprintf("%s:%d: syntax error: expected %s but got %s", + e.file, e.linenr, e.expected, e.got) +} + +type Row struct { + Label string + Cols [8]int +} + +type Set struct { + StartYear int + Circ, BU, Proof []Row +} + +func Parse(reader io.Reader, file string) (Set, error) { + var ( + data Set // Our data struct + slice *[]Row // Where to append mintages + year int // The current year we are at + ) + + scanner := bufio.NewScanner(reader) + for linenr := 1; scanner.Scan(); linenr++ { + var mintmark struct { + s string + star bool + } + + line := scanner.Text() + tokens := strings.FieldsFunc(strings.TrimSpace(line), unicode.IsSpace) + + switch { + case len(tokens) == 0: + continue + case tokens[0] == "BEGIN": + if len(tokens)-1 != 1 { + return Set{}, SyntaxError{ + expected: "single argument to ‘BEGIN’", + got: fmt.Sprintf("%d arguments", len(tokens)-1), + file: file, + linenr: linenr, + } + } + + arg := tokens[1] + + switch arg { + case "CIRC": + slice = &data.Circ + case "BU": + slice = &data.BU + case "PROOF": + slice = &data.Proof + default: + if !isNumeric(arg, false) { + return Set{}, SyntaxError{ + expected: "‘CIRC’, ‘BU’, ‘PROOF’, or a year", + got: arg, + file: file, + linenr: linenr, + } + } + data.StartYear, _ = strconv.Atoi(arg) + } + + year = data.StartYear - 1 + case isLabel(tokens[0]): + n := len(tokens[0]) + if n > 2 && tokens[0][n-2] == '*' { + mintmark.star = true + mintmark.s = tokens[0][:n-2] + } else { + mintmark.s = tokens[0][:n-1] + } + tokens = tokens[1:] + if !isNumeric(tokens[0], true) && tokens[0] != "?" { + return Set{}, SyntaxError{ + expected: "mintage row after label", + got: tokens[0], + file: file, + linenr: linenr, + } + } + fallthrough + case isNumeric(tokens[0], true), tokens[0] == "?": + switch { + case slice == nil: + return Set{}, SyntaxError{ + expected: "coin type declaration", + got: tokens[0], + file: file, + linenr: linenr, + } + case data.StartYear == 0: + return Set{}, SyntaxError{ + expected: "start year declaration", + got: tokens[0], + file: file, + linenr: linenr, + } + } + + numcoins := len(Row{}.Cols) + tokcnt := len(tokens) + + if tokcnt != numcoins { + word := "entries" + if tokcnt == 1 { + word = "entry" + } + return Set{}, SyntaxError{ + expected: fmt.Sprintf("%d mintage entries", numcoins), + got: fmt.Sprintf("%d %s", tokcnt, word), + file: file, + linenr: linenr, + } + } + + var row Row + switch { + case mintmark.s == "": + year++ + row.Label = strconv.Itoa(year) + case mintmark.star: + year++ + fallthrough + default: + row.Label = fmt.Sprintf("%d %s", year, mintmark.s) + } + + for i, tok := range tokens { + if tok == "?" { + row.Cols[i] = Unknown + } else { + row.Cols[i] = atoiWithDots(tok) + } + } + *slice = append(*slice, row) + default: + return Set{}, SyntaxError{ + expected: "‘BEGIN’ directive or mintage row", + got: fmt.Sprintf("invalid token ‘%s’", tokens[0]), + file: file, + linenr: linenr, + } + } + } + + return data, nil +} + +func isNumeric(s string, dot bool) bool { + for _, ch := range s { + switch ch { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + default: + if ch != '.' || !dot { + return false + } + } + } + return true +} + +func isLabel(s string) bool { + n := len(s) + return (n > 2 && s[n-1] == ':' && s[n-2] == '*') || + (n > 1 && s[n-1] == ':') +} + +func atoiWithDots(s string) int { + n := 0 + for _, ch := range s { + if ch == '.' { + continue + } + n = n*10 + int(ch) - '0' + } + return n +} |