summaryrefslogtreecommitdiffhomepage
path: root/mintages
diff options
context:
space:
mode:
Diffstat (limited to 'mintages')
-rw-r--r--mintages/errors.go46
-rw-r--r--mintages/parser.go124
2 files changed, 170 insertions, 0 deletions
diff --git a/mintages/errors.go b/mintages/errors.go
new file mode 100644
index 0000000..a456c1a
--- /dev/null
+++ b/mintages/errors.go
@@ -0,0 +1,46 @@
+package mintages
+
+import "fmt"
+
+type location struct {
+ file string
+ linenr int
+}
+
+func (loc location) String() string {
+ return fmt.Sprintf("%s: %d", loc.file, loc.linenr)
+}
+
+type BadTokenError struct {
+ location
+ token string
+}
+
+func (e BadTokenError) Error() string {
+ return fmt.Sprintf("%s: unknown token ‘%s’", e.location, e.linenr, e.token)
+}
+
+type ArgCountMismatchError struct {
+ location
+ token string
+ expected, got int
+}
+
+func (e ArgCountMismatchError) Error() string {
+ var suffix string
+ if e.expected != 1 {
+ suffix = "s"
+ }
+ return fmt.Sprintf("%s: ‘%s’ token expects %d argument%s but got %d",
+ e.location, e.token, e.expected, suffix, e.got)
+}
+
+type SyntaxError struct {
+ location
+ expected, got string
+}
+
+func (e SyntaxError) Error() string {
+ return fmt.Sprintf("%s: syntax error: expected %s but got %s",
+ e.location, e.expected, e.got)
+}
diff --git a/mintages/parser.go b/mintages/parser.go
new file mode 100644
index 0000000..5c83713
--- /dev/null
+++ b/mintages/parser.go
@@ -0,0 +1,124 @@
+package mintages
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+ "unicode"
+)
+
+type coinset [8]int
+
+type Data struct {
+ StartYear int
+ Circ, Bu, Proof []coinset
+}
+
+func ForCountry(code string) (Data, error) {
+ path := filepath.Join("data", "mintages", code)
+ f, err := os.Open(path)
+ if err != nil {
+ return Data{}, err
+ }
+ defer f.Close()
+ scanner := bufio.NewScanner(f)
+
+ var (
+ data Data // Our data struct
+ slice *[]coinset // Where to append mintages
+ )
+
+ for linenr := 1; scanner.Scan(); linenr++ {
+ line := scanner.Text()
+ tokens := strings.FieldsFunc(strings.TrimSpace(line), unicode.IsSpace)
+
+ switch {
+ case len(tokens) == 0:
+ continue
+ case tokens[0] == "BEGIN":
+ if len(tokens) != 2 {
+ return Data{}, ArgCountMismatchError{
+ token: tokens[0],
+ expected: 1,
+ got: len(tokens) - 1,
+ location: location{path, 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 Data{}, SyntaxError{
+ expected: "‘CIRC’, ‘BU’, ‘PROOF’, or a year",
+ got: arg,
+ location: location{path, linenr},
+ }
+ }
+ data.StartYear, _ = strconv.Atoi(arg)
+ }
+ case isNumeric(tokens[0], true):
+ numcoins := len(coinset{})
+ tokcnt := len(tokens)
+
+ if tokcnt != numcoins {
+ return Data{}, SyntaxError{
+ expected: fmt.Sprintf("%d mintage entries", numcoins),
+ got: fmt.Sprintf("%d entries", tokcnt),
+ location: location{path, linenr},
+ }
+ }
+
+ var row coinset
+ for i, tok := range tokens {
+ row[i], _ = strconv.Atoi(strings.ReplaceAll(tok, ".", ""))
+ }
+ *slice = append(*slice, row)
+ default:
+ return Data{}, BadTokenError{
+ token: tokens[0],
+ location: location{path, linenr},
+ }
+ }
+ }
+
+ /* Pad rows of ‘unknown’ mintages at the end of each set of mintages
+ for each year that we haven’t filled in info for. This avoids
+ things accidentally breaking if the new year comes and we forget
+ to add extra rows. */
+ for _, ms := range [...]*[]coinset{&data.Circ, &data.Bu, &data.Proof} {
+ finalYear := len(*ms) + data.StartYear - 1
+ missing := time.Now().Year() - finalYear
+ for i := 0; i < missing; i++ {
+ *ms = append(*ms, coinset{-1, -1, -1, -1, -1, -1, -1, -1})
+ }
+ }
+
+ 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':
+ case '.':
+ if !dot {
+ return false
+ }
+ default:
+ return false
+ }
+ }
+ return true
+}