summaryrefslogtreecommitdiffhomepage
path: root/src/mintage/parser.go
diff options
context:
space:
mode:
authorThomas Voss <mail@thomasvoss.com> 2025-06-06 01:32:41 +0200
committerThomas Voss <mail@thomasvoss.com> 2025-06-06 01:32:41 +0200
commit2c726e551d90d20bcd2d78545c369864cc9038e5 (patch)
tree520615a33c966c729e70e549e859a2c01dc4ba40 /src/mintage/parser.go
parent9379ea42a9b0afd1abf705bf3300f03d8bbe79f9 (diff)
Use CSV’s for mintages
Diffstat (limited to 'src/mintage/parser.go')
-rw-r--r--src/mintage/parser.go368
1 files changed, 112 insertions, 256 deletions
diff --git a/src/mintage/parser.go b/src/mintage/parser.go
index 364b6e8..daeb05d 100644
--- a/src/mintage/parser.go
+++ b/src/mintage/parser.go
@@ -1,297 +1,153 @@
package mintage
import (
- "bufio"
- "fmt"
+ "encoding/csv"
"io"
+ "os"
+ "path/filepath"
"strconv"
- "strings"
- "time"
)
-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 Data struct {
- Standard []SRow
- Commemorative []CRow
-}
+func Parse(country string) ([3]Data, error) {
+ if data, ok := cache[country]; ok {
+ return data, nil
+ }
-type SRow struct {
- Year int
- Mintmark string
- Mintages [typeCount][denoms]int
-}
+ var (
+ data [3]Data
+ err error
+ path = filepath.Join("data", "mintages", country)
+ )
-type CRow struct {
- Year int
- Mintmark string
- Name string
- Mintage [typeCount]int
+ data[TypeCirc].Standard, err = parseS(path + "-s-circ.csv")
+ if err != nil {
+ return data, err
+ }
+ data[TypeNifc].Standard, err = parseS(path + "-s-nifc.csv")
+ if err != nil {
+ return data, err
+ }
+ data[TypeProof].Standard, err = parseS(path + "-s-proof.csv")
+ if err != nil {
+ return data, err
+ }
+ data[TypeCirc].Commemorative, err = parseC(path + "-c-circ.csv")
+ if err != nil {
+ return data, err
+ }
+ data[TypeNifc].Commemorative, err = parseC(path + "-c-nifc.csv")
+ if err != nil {
+ return data, err
+ }
+ data[TypeProof].Commemorative, err = parseC(path + "-c-proof.csv")
+ if err == nil {
+ cache[country] = data
+ }
+ return data, err
}
-const (
- TypeCirc = iota
- TypeNIFC
- TypeProof
- typeCount
-)
+func parseS(path string) ([]SRow, error) {
+ rows := make([]SRow, 0, 69) /* TODO: Compute number of rows */
-const (
- Unknown = -iota - 1
- Invalid
-)
+ f, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
-const (
- denoms = 8
- ws = " \t"
-)
+ r := csv.NewReader(f)
+ r.Comment = '#'
+ r.FieldsPerRecord = 10
+ r.ReuseRecord = true
-func Parse(r io.Reader, file string) (Data, error) {
- yearsSince := time.Now().Year() - 1999 + 1
- data := Data{
- Standard: make([]SRow, 0, yearsSince),
- Commemorative: make([]CRow, 0, yearsSince),
+ /* Skip header */
+ if _, err := r.Read(); err != nil {
+ return nil, err
}
- scanner := bufio.NewScanner(r)
- for linenr := 1; scanner.Scan(); linenr++ {
- line := strings.Trim(scanner.Text(), ws)
- if isBlankOrComment(line) {
- continue
+ for {
+ record, err := r.Read()
+ if err == io.EOF {
+ break
}
-
- if len(line) < 4 || !isNumeric(line[:4], false) {
- return Data{}, SyntaxError{
- expected: "4-digit year",
- got: line,
- linenr: linenr,
- file: file,
- }
+ if err != nil {
+ return nil, err
}
- var (
- commem bool
- mintmark string
- )
- year, _ := strconv.Atoi(line[:4])
- line = line[4:]
-
- if len(line) != 0 {
- if strings.ContainsRune(ws, rune(line[0])) {
- commem = true
- goto out
- }
- if line[0] != '-' {
- return Data{}, SyntaxError{
- expected: "end-of-line or mintmark",
- got: line,
- linenr: linenr,
- file: file,
- }
- }
+ data := SRow{Mintmark: record[1]}
- if line = line[1:]; len(line) == 0 {
- return Data{}, SyntaxError{
- expected: "mintmark",
- got: "end-of-line",
- linenr: linenr,
- file: file,
- }
- }
+ data.Year, err = strconv.Atoi(record[0])
+ if err != nil {
+ return nil, err
+ }
- switch i := strings.IndexAny(line, ws); i {
- case 0:
- return Data{}, SyntaxError{
- expected: "mintmark",
- got: "whitespace",
- linenr: linenr,
- file: file,
+ for i, s := range record[2:] {
+ if s == "" {
+ data.Mintages[i] = Unknown
+ } else {
+ data.Mintages[i], err = strconv.Atoi(s)
+ if err != nil {
+ data.Mintages[i] = Invalid
}
- case -1:
- mintmark = line
- default:
- mintmark, line = line[:i], line[i:]
- commem = true
}
}
- out:
-
- if !commem {
- row := SRow{
- Year: year,
- Mintmark: mintmark,
- }
- for i := range row.Mintages {
- line = ""
- for isBlankOrComment(line) {
- if !scanner.Scan() {
- return Data{}, SyntaxError{
- expected: "mintage row",
- got: "end-of-file",
- linenr: linenr,
- file: file,
- }
- }
- line = strings.Trim(scanner.Text(), ws)
- linenr++
- }
-
- tokens := strings.FieldsFunc(line, func(r rune) bool {
- return strings.ContainsRune(ws, r)
- })
- if tokcnt := len(tokens); tokcnt != denoms {
- word := "entries"
- if tokcnt == 1 {
- word = "entry"
- }
- return Data{}, SyntaxError{
- expected: fmt.Sprintf("%d mintage entries", denoms),
- got: fmt.Sprintf("%d %s", tokcnt, word),
- linenr: linenr,
- file: file,
- }
- }
-
- for j, tok := range tokens {
- if tok != "?" && !isNumeric(tok, true) {
- return Data{}, SyntaxError{
- expected: "numeric mintage figure or ‘?’",
- got: tok,
- linenr: linenr,
- file: file,
- }
- }
- if tok == "?" {
- row.Mintages[i][j] = Unknown
- } else {
- row.Mintages[i][j] = atoiWithDots(tok)
- }
- }
- }
+ rows = append(rows, data)
+ }
- data.Standard = append(data.Standard, row)
- } else {
- row := CRow{
- Year: year,
- Mintmark: mintmark,
- }
- line = strings.TrimLeft(line, ws)
- if line[0] != '"' {
- return Data{}, SyntaxError{
- expected: "string",
- got: line,
- linenr: linenr,
- file: file,
- }
- }
+ return rows, nil
+}
- line = line[1:]
- switch i := strings.IndexByte(line, '"'); i {
- case -1:
- return Data{}, SyntaxError{
- expected: "closing quote",
- got: "end-of-line",
- linenr: linenr,
- file: file,
- }
- case 0:
- return Data{}, SyntaxError{
- expected: "commemorated event",
- got: "empty string",
- linenr: linenr,
- file: file,
- }
- default:
- row.Name, line = line[:i], line[i+1:]
- }
+func parseC(path string) ([]CRow, error) {
+ rows := make([]CRow, 0, 69) /* TODO: Compute number of rows */
- if len(line) != 0 {
- return Data{}, SyntaxError{
- expected: "end-of-line",
- got: line,
- linenr: linenr,
- file: file,
- }
- }
+ f, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
- for isBlankOrComment(line) {
- if !scanner.Scan() {
- return Data{}, SyntaxError{
- expected: "mintage row",
- got: "end-of-file",
- linenr: linenr,
- file: file,
- }
- }
- line = strings.Trim(scanner.Text(), ws)
- linenr++
- }
+ r := csv.NewReader(f)
+ r.Comment = '#'
+ r.FieldsPerRecord = 4
+ r.ReuseRecord = true
- tokens := strings.FieldsFunc(line, func(r rune) bool {
- return strings.ContainsRune(ws, r)
- })
- if tokcnt := len(tokens); tokcnt != typeCount {
- word := "entries"
- if tokcnt == 1 {
- word = "entry"
- }
- return Data{}, SyntaxError{
- expected: fmt.Sprintf("%d mintage entries", typeCount),
- got: fmt.Sprintf("%d %s", tokcnt, word),
- linenr: linenr,
- file: file,
- }
- }
+ /* Skip header */
+ if _, err := r.Read(); err != nil {
+ return nil, err
+ }
- for i, tok := range tokens {
- if tok == "?" {
- row.Mintage[i] = Unknown
- } else {
- row.Mintage[i] = atoiWithDots(tok)
- }
- }
+ for {
+ record, err := r.Read()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return nil, err
+ }
- data.Commemorative = append(data.Commemorative, row)
+ data := CRow{
+ Name: record[1],
+ Mintmark: record[2],
}
- }
- return data, nil
-}
+ data.Year, err = strconv.Atoi(record[0])
+ if err != nil {
+ return nil, err
+ }
-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
+ s := record[3]
+ if s == "" {
+ data.Mintage = Unknown
+ } else {
+ data.Mintage, err = strconv.Atoi(s)
+ if err != nil {
+ data.Mintage = Invalid
}
}
- }
- return true
-}
-func atoiWithDots(s string) int {
- n := 0
- for _, ch := range s {
- if ch == '.' {
- continue
- }
- n = n*10 + int(ch) - '0'
+ rows = append(rows, data)
}
- return n
-}
-func isBlankOrComment(s string) bool {
- return len(s) == 0 || s[0] == '#'
+ return rows, nil
}