diff options
Diffstat (limited to 'lib/mintage/parser.go')
| -rw-r--r-- | lib/mintage/parser.go | 318 | 
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] == '#' +} |