diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/mintage/parser.go | 318 | ||||
-rw-r--r-- | lib/mintage/parser_test.go | 315 |
2 files changed, 333 insertions, 300 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] == '#' +} diff --git a/lib/mintage/parser_test.go b/lib/mintage/parser_test.go index 3cc8ea3..76e0f01 100644 --- a/lib/mintage/parser_test.go +++ b/lib/mintage/parser_test.go @@ -8,188 +8,151 @@ import ( func TestParserComplete(t *testing.T) { data, err := Parse(bytes.NewBuffer([]byte(` - BEGIN 2020 - BEGIN CIRC - 1.000 1001 1002 1003 1004 1005 1006 1007 - 2000 ? 2002 2003 2004 2005 2006 2007 - BEGIN BU - 1.100 1101 1102 1103 1104 1105 1106 1107 - 2100 ? 2102 2103 2104 2105 2106 2107 - BEGIN PROOF - 1.200 1201 1202 1203 1204 1205 1206 1207 - 2200 ? 2202 2203 2204 2205 2206 2207 + 2020 + 1000 1001 1002 1003 1004 1005 1006 1007 + 1100 1101 1102 1103 1104 1105 1106 1107 + 1200 1201 1202 1203 1204 1205 1206 1207 + 2021-KNM + 2.000 ? 2002 2003 2004 2005 2006 2007 + 2.100 ? 2102 2103 2104 2105 2106 2107 + 2.200 ? 2202 2203 2204 2205 2206 2207 + 2021-MdP + 3000 3001 3002 3003 3004 3005 3006 3007 + 3100 3101 3102 3103 3104 3105 3106 3107 + 3200 3201 3202 3203 3204 3205 3206 3207 + 2022 + 4000 4001 4.002 4003 4004 4005 4006 4007 + 4100 4101 4.102 4103 4104 4105 4106 4107 + 4200 4201 4.202 4203 4204 4205 4206 4207 + + 2009 "10th Anniversary of Economic and Monetary Union" + 1000 2000 3000 + 2022-⋆ "35th Anniversary of the Erasmus Programme" + 1001 ? 3001 `)), "-") if err != nil { t.Fatalf(`Expected err=nil; got "%s"`, err) } - if data.StartYear != 2020 { - t.Fatalf("Expected data.StartYear=2020; got %d", - data.StartYear) - } - for i, row := range data.Tables[TypeCirculated] { - for j, col := range row.Cols { - var n int - if i == 1 && j == 1 { - n = -1 - } else { - n = 1000*i + j + 1000 - } - if col != n { - t.Fatalf("Expected data.Tables[TypeCirculated][i][j]=%d; got %d", n, col) + for i, row := range data.Standard { + for k := TypeCirc; k <= TypeProof; k++ { + for j, col := range row.Mintages[k] { + n := 1000*(i+1) + 100*k + j + if i == 1 && j == 1 { + n = Unknown + } + if col != n { + t.Fatalf("Expected data.Standard[%d].Mintages[%d][%d]=%d; got %d", + i, k, j, col, n) + } } } } - for i, row := range data.Tables[TypeNIFC] { - for j, col := range row.Cols { - var n int - if i == 1 && j == 1 { - n = -1 - } else { - n = 1000*i + j + 1100 + for i, row := range data.Commemorative { + for k := TypeCirc; k <= TypeProof; k++ { + n := 1000*(k+1) + i + if i == 1 && k == 1 { + n = Unknown } - if col != n { - t.Fatalf("Expected data.Tables[TypeNIFC][i][j]=%d; got %d", n, col) + if row.Mintage[k] != n { + t.Fatalf("Expected row.Mintage[%d]=%d; got %d", + k, n, row.Mintage[k]) } } } - for i, row := range data.Tables[TypeProof] { - for j, col := range row.Cols { - var n int - if i == 1 && j == 1 { - n = -1 - } else { - n = 1000*i + j + 1200 - } - if col != n { - t.Fatalf("Expected data.Tables[TypeProof][i][j]=%d; got %d", n, col) - } - } + if len(data.Standard) != 4 { + t.Fatalf("Expected len(data.Standard)=2; got %d", len(data.Standard)) } - - if len(data.Tables[TypeCirculated]) != 2 { - t.Fatalf("Expected len(data.Tables[TypeCirculated])=2; got %d", len(data.Tables[TypeCirculated])) + if len(data.Commemorative) != 2 { + t.Fatalf("Expected len(data.Commemorative)=2; got %d", len(data.Commemorative)) } - if len(data.Tables[TypeNIFC]) != 2 { - t.Fatalf("Expected len(data.Tables[TypeNIFC])=2; got %d", len(data.Tables[TypeNIFC])) - } - if len(data.Tables[TypeProof]) != 2 { - t.Fatalf("Expected len(data.Tables[TypeProof])=2; got %d", len(data.Tables[TypeProof])) - } -} - -func TestParserNoProof(t *testing.T) { - data, err := Parse(bytes.NewBuffer([]byte(` - BEGIN 2020 - BEGIN CIRC - 1.000 1001 1002 1003 1004 1005 1006 1007 - 2000 ? 2002 2003 2004 2005 2006 2007 - BEGIN BU - 1.100 1101 1102 1103 1104 1105 1106 1107 - 2100 ? 2102 2103 2104 2105 2106 2107 - `)), "-") - if err != nil { - t.Fatalf(`Expected err=nil; got "%s"`, err) - } - - if len(data.Tables[TypeProof]) != 0 { - t.Fatalf("Expected len(data.Tables[TypeProof])=0; got %d", len(data.Tables[TypeProof])) + for i, x := range [...]struct { + year int + mintmark, name string + }{ + {2009, "", "10th Anniversary of Economic and Monetary Union"}, + {2022, "⋆", "35th Anniversary of the Erasmus Programme"}, + } { + if data.Commemorative[i].Year != x.year { + t.Fatalf("Expected data.Commemorative[%d].Year=%d; got %d", + i, x.year, data.Commemorative[i].Year) + } + if data.Commemorative[i].Mintmark != x.mintmark { + t.Fatalf(`Expected data.Commemorative[%d].Mintmark="%s"; got "%s"`, + i, x.mintmark, data.Commemorative[i].Mintmark) + } + if data.Commemorative[i].Name != x.name { + t.Fatalf(`Expected data.Commemorative[%d].Name="%s"; got "%s"`, + i, x.name, data.Commemorative[i].Name) + } } } func TestParserMintmarks(t *testing.T) { data, err := Parse(bytes.NewBuffer([]byte(` - BEGIN 2020 - BEGIN CIRC - 1.000 1001 1002 1003 1004 1005 1006 1007 - KNM*: 2000 ? 2002 2003 2004 2005 2006 2007 - MdP: 3000 ? 3002 3003 3004 3005 3006 3007 + 2020 + 1000 1001 1002 1003 1004 1005 1006 1007 + 1100 1101 1102 1103 1104 1105 1106 1107 + 1200 1201 1202 1203 1204 1205 1206 1207 + 2021-KNM + 2.000 ? 2002 2003 2004 2005 2006 2007 + 2.100 ? 2102 2103 2104 2105 2106 2107 + 2.200 ? 2202 2203 2204 2205 2206 2207 + 2021-MdP + 3000 3001 3002 3003 3004 3005 3006 3007 + 3100 3101 3102 3103 3104 3105 3106 3107 + 3200 3201 3202 3203 3204 3205 3206 3207 + 2022 + 4000 4001 4.002 4003 4004 4005 4006 4007 + 4100 4101 4.102 4103 4104 4105 4106 4107 + 4200 4201 4.202 4203 4204 4205 4206 4207 `)), "-") if err != nil { t.Fatalf(`Expected err=nil; got "%s"`, err) } - for i, row := range data.Tables[TypeCirculated] { - for j, col := range row.Cols { - var n int - if i > 0 && j == 1 { - n = -1 - } else { - n = 1000*i + j + 1000 + for i, row := range data.Standard { + for j, col := range row.Mintages[TypeCirc] { + n := 1000*(i+1) + j + if i == 1 && j == 1 { + n = Unknown } if col != n { - t.Fatalf("Expected data.Tables[TypeCirculated][i][j]=%d; got %d", n, col) + t.Fatalf("Expected data.Standard[%d].Mintages[TypeCirc][%d]=%d; got %d", + i, j, col, n) } } } - for i, y := range [...]int{2020, 2021, 2021} { - if data.Tables[TypeCirculated][i].Year != y { - t.Fatalf("Expected data.Tables[TypeCirculated][%d].Year=%d; got %d", - i, y, data.Tables[TypeCirculated][i].Year) + for i, y := range [...]int{2020, 2021, 2021, 2022} { + if data.Standard[i].Year != y { + t.Fatalf("Expected data.Standard[%d].Year=%d; got %d", + i, y, data.Standard[i].Year) } } - for i, s := range [...]string{"", "KNM", "MdP"} { - if data.Tables[TypeCirculated][i].Mintmark != s { - t.Fatalf(`Expected data.Tables[TypeCirculated][%d].Mintmark="%s"; got "%s"`, - i, s, data.Tables[TypeCirculated][i].Mintmark) + + for i, m := range [...]string{"", "KNM", "MdP", ""} { + if data.Standard[i].Mintmark != m { + t.Fatalf(`Expected data.Standard[%d].Mintmark="%s"; got "%s"`, + i, m, data.Standard[i].Mintmark) } } } func TestParserNoYear(t *testing.T) { _, err := Parse(bytes.NewBuffer([]byte(` - BEGIN CIRC - 1.000 1001 1002 1003 1004 1005 1006 1007 - 2000 ? 2002 2003 2004 2005 2006 2007 - BEGIN BU - 1.100 1101 1102 1103 1104 1105 1106 1107 - 2100 ? 2102 2103 2104 2105 2106 2107 - BEGIN PROOF - 1.200 1201 1202 1203 1204 1205 1206 1207 - 2200 ? 2202 2203 2204 2205 2206 2207 - `)), "-") - - var sErr SyntaxError - if !errors.As(err, &sErr) { - t.Fatalf("Expected err=SyntaxError; got %s", err) - } -} - -func TestParserNoType(t *testing.T) { - _, err := Parse(bytes.NewBuffer([]byte(` - BEGIN 2020 - 1.000 1001 1002 1003 1004 1005 1006 1007 - 2000 ? 2002 2003 2004 2005 2006 2007 - BEGIN BU - 1.100 1101 1102 1103 1104 1105 1106 1107 - 2100 ? 2102 2103 2104 2105 2106 2107 - BEGIN PROOF - 1.200 1201 1202 1203 1204 1205 1206 1207 - 2200 ? 2202 2203 2204 2205 2206 2207 - `)), "-") - - var sErr SyntaxError - if !errors.As(err, &sErr) { - t.Fatalf("Expected err=SyntaxError; got %s", err) - } -} - -func TestParserNoYearOrType(t *testing.T) { - _, err := Parse(bytes.NewBuffer([]byte(` - 1.000 1001 1002 1003 1004 1005 1006 1007 - 2000 ? 2002 2003 2004 2005 2006 2007 - BEGIN BU - 1.100 1101 1102 1103 1104 1105 1106 1107 - 2100 ? 2102 2103 2104 2105 2106 2107 - BEGIN PROOF - 1.200 1201 1202 1203 1204 1205 1206 1207 - 2200 ? 2202 2203 2204 2205 2206 2207 + 1.000 1001 1002 1003 1004 1005 1006 1007 + 1.100 1101 1102 1103 1104 1105 1106 1107 + 1.200 1201 1202 1203 1204 1205 1206 1207 + 2021 + 2000 ? 2002 2003 2004 2005 2006 2007 + 2100 ? 2102 2103 2104 2105 2106 2107 + 2200 ? 2202 2203 2204 2205 2206 2207 `)), "-") var sErr SyntaxError @@ -200,17 +163,14 @@ func TestParserNoYearOrType(t *testing.T) { func TestParserBadToken(t *testing.T) { _, err := Parse(bytes.NewBuffer([]byte(` - BEGIN 2020 - BEGIN CIRC - 1.000 1001 1002 1003 1004 1005 1006 1007 - I’m bad - 2000 ? 2002 2003 2004 2005 2006 2007 - BEGIN BU - 1.100 1101 1102 1103 1104 1105 1106 1107 - 2100 ? 2102 2103 2104 2105 2106 2107 - BEGIN PROOF - 1.200 1201 1202 1203 1204 1205 1206 1207 - 2200 ? 2202 2203 2204 2205 2206 2207 + 2020 + 1.000 1001 1002 1003 1004 1005 1006 1007 + 1.100 1101 1102 1103 1104 1105 1106 1107 + 1.200 1201 1202 1203 1204 1205 1206 1207 + 2021 Naughty! + 2000 ? 2002 2003 2004 2005 2006 2007 + 2100 ? 2102 2103 2104 2105 2106 2107 + 2200 ? 2202 2203 2204 2205 2206 2207 `)), "-") var sErr SyntaxError @@ -221,16 +181,14 @@ func TestParserBadToken(t *testing.T) { func TestParserShortRow(t *testing.T) { _, err := Parse(bytes.NewBuffer([]byte(` - BEGIN 2020 - BEGIN CIRC - 1.000 1001 1002 1003 1004 1005 1006 1007 - 2000 ? 2002 2003 2004 2005 2006 2007 - BEGIN BU - 1.100 1101 1102 1103 1104 1105 1106 1107 - 2100 ? 2102 2103 2104 2105 2106 - BEGIN PROOF - 1.200 1201 1202 1203 1204 1205 1206 1207 - 2200 ? 2202 2203 2204 2205 2206 2207 + 2020 + 1.000 1001 1002 1003 1004 1005 1006 1007 + 1.100 1101 1102 1103 1104 1105 1106 1107 + 1.200 1201 1202 1203 1204 1205 1206 1207 + 2021 + 2000 ? 2002 2003 2004 2005 2006 2007 + 2100 ? 2102 2103 2104 2105 2106 + 2200 ? 2202 2203 2204 2205 2206 2207 `)), "-") var sErr SyntaxError @@ -241,16 +199,14 @@ func TestParserShortRow(t *testing.T) { func TestParserLongRow(t *testing.T) { _, err := Parse(bytes.NewBuffer([]byte(` - BEGIN 2020 - BEGIN CIRC - 1.000 1001 1002 1003 1004 1005 1006 1007 - 2000 ? 2002 2003 2004 2005 2006 2007 - BEGIN BU - 1.100 1101 1102 1103 1104 1105 1106 1107 - 2100 ? 2102 2103 2104 2105 2106 2107 2108 - BEGIN PROOF - 1.200 1201 1202 1203 1204 1205 1206 1207 - 2200 ? 2202 2203 2204 2205 2206 2207 + 2020 + 1.000 1001 1002 1003 1004 1005 1006 1007 + 1.100 1101 1102 1103 1104 1105 1106 1107 + 1.200 1201 1202 1203 1204 1205 1206 1207 + 2021 + 2000 ? 2002 2003 2004 2005 2006 2007 + 2100 ? 2102 2103 2104 2105 2106 2107 2108 + 2200 ? 2202 2203 2204 2205 2206 2207 `)), "-") var sErr SyntaxError @@ -259,18 +215,15 @@ func TestParserLongRow(t *testing.T) { } } -func TestParserBadCoinType(t *testing.T) { +func TestParserMissingRow(t *testing.T) { _, err := Parse(bytes.NewBuffer([]byte(` - BEGIN 2020 - BEGIN CIRCULATED - 1.000 1001 1002 1003 1004 1005 1006 1007 - 2000 ? 2002 2003 2004 2005 2006 2007 - BEGIN BU - 1.100 1101 1102 1103 1104 1105 1106 1107 - 2100 ? 2102 2103 2104 2105 2106 2107 - BEGIN PROOF - 1.200 1201 1202 1203 1204 1205 1206 1207 - 2200 ? 2202 2203 2204 2205 2206 2207 + 2020 + 1.000 1001 1002 1003 1004 1005 1006 1007 + 1.100 1101 1102 1103 1104 1105 1106 1107 + 1.200 1201 1202 1203 1204 1205 1206 1207 + 2021 + 2000 ? 2002 2003 2004 2005 2006 2007 + 2200 ? 2202 2203 2204 2205 2206 2207 `)), "-") var sErr SyntaxError |