diff options
-rw-r--r-- | data/mintages/ad | 28 | ||||
-rw-r--r-- | mintages/errors.go | 46 | ||||
-rw-r--r-- | mintages/parser.go | 124 |
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 +} |