summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--data/mintages/ad28
-rw-r--r--mintages/errors.go46
-rw-r--r--mintages/parser.go124
3 files changed, 198 insertions, 0 deletions
diff --git a/data/mintages/ad b/data/mintages/ad
new file mode 100644
index 0000000..6a9b7ac
--- /dev/null
+++ b/data/mintages/ad
@@ -0,0 +1,28 @@
+BEGIN 2014
+
+BEGIN CIRC
+60.000 60.000 860.000 860.000 860.000 340.000 511.843 360.000
+0 0 0 0 0 0 0 1.072.400
+0 0 0 0 0 0 2.339.200 0
+2.582.395 1.515.000 2.191.421 1.103.000 1.213.000 968.800 17.000 794.588
+2.430.000 2.550.000 1.800.000 980.000 1.014.000 890.000 0 868.000
+2.447.000 1.727.000 2.100.000 1.610.000 1.570.000 930.000 0 1.058.310
+0 0 0 860.000 175.000 740.000 0 1.500.000
+200.000 700.000 0 1.400.000 1.420.000 600.000 50.000 1.474.500
+700.000 450.000 400.000 700.000 700.000 380.000 0 1.708.000
+0 0 0 0 0 0 0 2.075.250
+
+BEGIN BU
+70.000 70.000 70.000 70.000 70.000 70.000 70.000 70.000
+40.000 40.000 40.000 40.000 40.000 40.000 40.000 40.000
+35.000 35.000 35.000 35.000 35.000 35.000 35.000 35.000
+22.000 22.000 22.000 22.000 22.000 22.000 22.000 22.000
+20.000 20.000 20.000 20.000 20.000 20.000 20.000 20.000
+15.000 15.000 15.000 15.000 15.000 15.000 15.000 15.000
+12.000 12.000 12.000 12.000 12.000 12.000 12.000 12.000
+10.500 10.500 10.500 10.500 10.500 10.500 10.500 10.500
+10.500 10.500 10.500 10.500 10.500 10.500 10.500 10.500
+10.500 10.500 10.500 10.500 10.500 10.500 10.500 10.500
+
+BEGIN PROOF
+3.000 3.000 3.000 3.000 3.000 3.000 3.000 3.000
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
+}