summaryrefslogtreecommitdiffhomepage
path: root/lib/mintage/parser.go
diff options
context:
space:
mode:
Diffstat (limited to 'lib/mintage/parser.go')
-rw-r--r--lib/mintage/parser.go318
1 files changed, 199 insertions, 119 deletions
diff --git a/lib/mintage/parser.go b/lib/mintage/parser.go
index fb92e64..76f13e7 100644
--- a/lib/mintage/parser.go
+++ b/lib/mintage/parser.go
@@ -6,22 +6,7 @@ import (
"io"
"strconv"
"strings"
- "unicode"
-)
-
-type CoinType int
-
-const (
- TypeCirculated CoinType = iota
- TypeNIFC
- TypeProof
- coinTypes
-)
-
-const (
- _ = -iota
- Unknown // Unknown mintage
- Invalid // All mintages <= than this are invalid
+ "time"
)
type SyntaxError struct {
@@ -35,151 +20,248 @@ func (e SyntaxError) Error() string {
e.file, e.linenr, e.expected, e.got)
}
-type Row struct {
+type Data struct {
+ Standard []SRow
+ Commemorative []CRow
+}
+
+type SRow struct {
Year int
Mintmark string
- Cols [8]int
+ Mintages [typeCount][denoms]int
}
-type Set struct {
- StartYear int
- Tables [coinTypes][]Row
+type CRow struct {
+ Year int
+ Mintmark string
+ Name string
+ Mintage [typeCount]int
}
-func (r Row) Label() string {
- if r.Mintmark != "" {
- return fmt.Sprintf("%d %s", r.Year, r.Mintmark)
- }
- return strconv.Itoa(r.Year)
-}
+const (
+ TypeCirc = iota
+ TypeNIFC
+ TypeProof
+ typeCount
+)
-func Parse(reader io.Reader, file string) (Set, error) {
- var (
- data Set
- ctype CoinType = -1
- )
+const (
+ Unknown = -iota - 1
+ Invalid
+)
- scanner := bufio.NewScanner(reader)
+const (
+ denoms = 8
+ ws = " \t"
+)
+
+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),
+ }
+
+ scanner := bufio.NewScanner(r)
for linenr := 1; scanner.Scan(); linenr++ {
- var mintmark struct {
- s string
- star bool
+ line := strings.Trim(scanner.Text(), ws)
+ if isBlankOrComment(line) {
+ continue
}
- line := scanner.Text()
- tokens := strings.FieldsFunc(strings.TrimSpace(line), unicode.IsSpace)
+ if len(line) < 4 || !isNumeric(line[:4], false) {
+ return Data{}, SyntaxError{
+ expected: "4-digit year",
+ got: line,
+ linenr: linenr,
+ file: file,
+ }
+ }
- 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,
+ 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,
}
}
- arg := tokens[1]
+ if line = line[1:]; len(line) == 0 {
+ return Data{}, SyntaxError{
+ expected: "mintmark",
+ got: "end-of-line",
+ linenr: linenr,
+ file: file,
+ }
+ }
- switch arg {
- case "CIRC":
- ctype = TypeCirculated
- case "BU":
- ctype = TypeNIFC
- case "PROOF":
- ctype = TypeProof
+ switch i := strings.IndexAny(line, ws); i {
+ case 0:
+ return Data{}, SyntaxError{
+ expected: "mintmark",
+ got: "whitespace",
+ linenr: linenr,
+ file: file,
+ }
+ case -1:
+ mintmark = line
default:
- if !isNumeric(arg, false) {
- return Set{}, SyntaxError{
- expected: "‘CIRC’, ‘BU’, ‘PROOF’, or a year",
- got: arg,
- file: file,
+ 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)
}
}
- data.StartYear, _ = strconv.Atoi(arg)
}
- 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]
+
+ data.Standard = append(data.Standard, row)
+ } else {
+ row := CRow{
+ Year: year,
+ Mintmark: mintmark,
}
- tokens = tokens[1:]
- if !isNumeric(tokens[0], true) && tokens[0] != "?" {
- return Set{}, SyntaxError{
- expected: "mintage row after label",
- got: tokens[0],
- file: file,
+ line = strings.TrimLeft(line, ws)
+ if line[0] != '"' {
+ return Data{}, SyntaxError{
+ expected: "string",
+ got: line,
linenr: linenr,
+ file: file,
}
}
- fallthrough
- case isNumeric(tokens[0], true), tokens[0] == "?":
- switch {
- case ctype == -1:
- return Set{}, SyntaxError{
- expected: "coin type declaration",
- got: tokens[0],
- file: file,
+
+ 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 data.StartYear == 0:
- return Set{}, SyntaxError{
- expected: "start year declaration",
- got: tokens[0],
+ case 0:
+ return Data{}, SyntaxError{
+ expected: "commemorated event",
+ got: "empty string",
+ linenr: linenr,
file: file,
+ }
+ default:
+ row.Name, line = line[:i], line[i+1:]
+ }
+
+ if len(line) != 0 {
+ return Data{}, SyntaxError{
+ expected: "end-of-line",
+ got: line,
linenr: linenr,
+ file: file,
}
}
- numcoins := len(Row{}.Cols)
- tokcnt := len(tokens)
+ 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++
+ }
- if tokcnt != numcoins {
+ 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 Set{}, SyntaxError{
- expected: fmt.Sprintf("%d mintage entries", numcoins),
+ return Data{}, SyntaxError{
+ expected: fmt.Sprintf("%d mintage entries", typeCount),
got: fmt.Sprintf("%d %s", tokcnt, word),
- file: file,
linenr: linenr,
- }
- }
-
- row := Row{Mintmark: mintmark.s}
- if len(data.Tables[ctype]) == 0 {
- row.Year = data.StartYear
- } else {
- rows := data.Tables[ctype]
- row.Year = rows[len(rows)-1].Year
- if row.Mintmark == "" || mintmark.star {
- row.Year++
+ file: file,
}
}
for i, tok := range tokens {
if tok == "?" {
- row.Cols[i] = Unknown
+ row.Mintage[i] = Unknown
} else {
- row.Cols[i] = atoiWithDots(tok)
+ row.Mintage[i] = atoiWithDots(tok)
}
}
- data.Tables[ctype] = append(data.Tables[ctype], row)
- default:
- return Set{}, SyntaxError{
- expected: "‘BEGIN’ directive or mintage row",
- got: fmt.Sprintf("invalid token ‘%s’", tokens[0]),
- file: file,
- linenr: linenr,
- }
+
+ data.Commemorative = append(data.Commemorative, row)
}
}
@@ -199,12 +281,6 @@ func isNumeric(s string, dot bool) bool {
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 {
@@ -215,3 +291,7 @@ func atoiWithDots(s string) int {
}
return n
}
+
+func isBlankOrComment(s string) bool {
+ return len(s) == 0 || s[0] == '#'
+}