diff --git a/bibtex.go b/bibtex.go index 8de3390..f93c45e 100644 --- a/bibtex.go +++ b/bibtex.go @@ -3,7 +3,6 @@ package bibtex import ( "bytes" "fmt" - "log" "sort" "strconv" "strings" @@ -272,16 +271,24 @@ func (bib *BibTex) AddStringVar(key string, val BibString) { } // GetStringVar looks up a string by its key. -func (bib *BibTex) GetStringVar(key string) *BibVar { +func (bib *BibTex) GetStringVar(key string) (*BibVar, error) { if bv, ok := bib.StringVar[key]; ok { - return bv + return bv, nil } if v, ok := bib.getDefaultVar(key); ok { - return v + return v, nil } // This is undefined. - log.Fatalf("%s: %s", ErrUnknownStringVar, key) - return nil + return nil, fmt.Errorf("%s: %s", ErrUnknownStringVar, key) +} + +// MustGetStringVar is like GetStringVar but panics on errors +func (bib *BibTex) MustGetStringVar(key string) *BibVar { + v, err := bib.GetStringVar(key) + if err != nil { + panic(err) + } + return v } // getDefaultVar is a fallback for looking up keys (e.g. 3-character month) diff --git a/bibtex.y b/bibtex.y index 39e131b..cad6286 100644 --- a/bibtex.y +++ b/bibtex.y @@ -3,6 +3,7 @@ package bibtex import ( "io" + "fmt" ) type bibTag struct { @@ -60,9 +61,9 @@ preambleentry : tATSIGN tPREAMBLE tLBRACE longstring tRBRACE { $$ = $4 } ; longstring : tIDENT { $$ = NewBibConst($1) } - | tBAREIDENT { $$ = bib.GetStringVar($1) } + | tBAREIDENT { v,err := bib.GetStringVar($1); if err != nil { bibtexlex.Error(fmt.Sprintf("%v",err)) } else {$$ = v} } | longstring tPOUND tIDENT { $$ = NewBibComposite($1); $$.(*BibComposite).Append(NewBibConst($3))} - | longstring tPOUND tBAREIDENT { $$ = NewBibComposite($1); $$.(*BibComposite).Append(bib.GetStringVar($3)) } + | longstring tPOUND tBAREIDENT { $$ = NewBibComposite($1); v,err := bib.GetStringVar($3); if err != nil {bibtexlex.Error(fmt.Sprintf("%v",err))} else {$$.(*BibComposite).Append(v)} } ; tag : /* empty */ { } diff --git a/bibtex.y.go b/bibtex.y.go index a3c771e..5fb472b 100644 --- a/bibtex.y.go +++ b/bibtex.y.go @@ -8,6 +8,7 @@ import __yyfmt__ "fmt" //line bibtex.y:2 import ( + "fmt" "io" ) @@ -18,7 +19,7 @@ type bibTag struct { var bib *BibTex // Only for holding current bib -//line bibtex.y:16 +//line bibtex.y:17 type bibtexSymType struct { yys int bibtex *BibTex @@ -71,7 +72,7 @@ const bibtexEofCode = 1 const bibtexErrCode = 2 const bibtexInitialStackSize = 16 -//line bibtex.y:76 +//line bibtex.y:77 // Parse is the entry point to the bibtex parser. func Parse(r io.Reader) (*BibTex, error) { @@ -88,7 +89,7 @@ func Parse(r io.Reader) (*BibTex, error) { } //line yacctab:1 -var bibtexExca = [...]int{ +var bibtexExca = [...]int8{ -1, 1, 1, -1, -2, 0, @@ -98,8 +99,7 @@ const bibtexPrivate = 57344 const bibtexLast = 61 -var bibtexAct = [...]int{ - +var bibtexAct = [...]int8{ 22, 39, 40, 41, 9, 10, 11, 24, 23, 44, 43, 27, 48, 26, 21, 20, 25, 8, 50, 28, 29, 33, 33, 49, 18, 16, 38, 19, 17, 14, @@ -108,8 +108,8 @@ var bibtexAct = [...]int{ 54, 53, 33, 7, 32, 4, 1, 6, 5, 3, 2, } -var bibtexPact = [...]int{ +var bibtexPact = [...]int16{ -1000, -1000, 46, -1000, -1000, -1000, -1000, 0, 19, 17, 13, 12, -2, -3, -10, -10, -4, -6, -10, -10, 25, 20, 41, -1000, -1000, 36, 39, 34, 33, 10, @@ -117,24 +117,24 @@ var bibtexPact = [...]int{ -1000, 14, 2, -1000, -1000, 28, 27, -1000, -14, -10, -1000, -1000, -1000, -1000, 11, } -var bibtexPgo = [...]int{ +var bibtexPgo = [...]int8{ 0, 60, 59, 2, 58, 1, 0, 57, 56, 55, } -var bibtexR1 = [...]int{ +var bibtexR1 = [...]int8{ 0, 8, 1, 1, 1, 1, 1, 2, 2, 9, 9, 4, 4, 7, 7, 6, 6, 6, 6, 3, 3, 5, 5, } -var bibtexR2 = [...]int{ +var bibtexR2 = [...]int8{ 0, 1, 0, 2, 2, 2, 2, 7, 7, 5, 5, 7, 7, 5, 5, 1, 1, 3, 3, 0, 3, 1, 3, } -var bibtexChk = [...]int{ +var bibtexChk = [...]int16{ -1000, -8, -1, -2, -9, -4, -7, 7, 17, 4, 5, 6, 12, 15, 12, 15, 12, 15, 12, 15, 17, 17, -6, 18, 17, -6, 17, 17, -6, -6, @@ -142,8 +142,8 @@ var bibtexChk = [...]int{ -3, 17, -5, 18, 17, -6, -6, 13, 10, 9, 16, 13, 13, -3, -6, } -var bibtexDef = [...]int{ +var bibtexDef = [...]int8{ 2, -2, 1, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 16, 0, 0, 0, 0, 0, @@ -151,16 +151,17 @@ var bibtexDef = [...]int{ 21, 0, 0, 17, 18, 0, 0, 7, 19, 0, 8, 11, 12, 22, 20, } -var bibtexTok1 = [...]int{ +var bibtexTok1 = [...]int8{ 1, } -var bibtexTok2 = [...]int{ +var bibtexTok2 = [...]int8{ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, } -var bibtexTok3 = [...]int{ + +var bibtexTok3 = [...]int8{ 0, } @@ -242,9 +243,9 @@ func bibtexErrorMessage(state, lookAhead int) string { expected := make([]int, 0, 4) // Look for shiftable tokens. - base := bibtexPact[state] + base := int(bibtexPact[state]) for tok := TOKSTART; tok-1 < len(bibtexToknames); tok++ { - if n := base + tok; n >= 0 && n < bibtexLast && bibtexChk[bibtexAct[n]] == tok { + if n := base + tok; n >= 0 && n < bibtexLast && int(bibtexChk[int(bibtexAct[n])]) == tok { if len(expected) == cap(expected) { return res } @@ -254,13 +255,13 @@ func bibtexErrorMessage(state, lookAhead int) string { if bibtexDef[state] == -2 { i := 0 - for bibtexExca[i] != -1 || bibtexExca[i+1] != state { + for bibtexExca[i] != -1 || int(bibtexExca[i+1]) != state { i += 2 } // Look for tokens that we accept or reduce. for i += 2; bibtexExca[i] >= 0; i += 2 { - tok := bibtexExca[i] + tok := int(bibtexExca[i]) if tok < TOKSTART || bibtexExca[i+1] == 0 { continue } @@ -291,30 +292,30 @@ func bibtexlex1(lex bibtexLexer, lval *bibtexSymType) (char, token int) { token = 0 char = lex.Lex(lval) if char <= 0 { - token = bibtexTok1[0] + token = int(bibtexTok1[0]) goto out } if char < len(bibtexTok1) { - token = bibtexTok1[char] + token = int(bibtexTok1[char]) goto out } if char >= bibtexPrivate { if char < bibtexPrivate+len(bibtexTok2) { - token = bibtexTok2[char-bibtexPrivate] + token = int(bibtexTok2[char-bibtexPrivate]) goto out } } for i := 0; i < len(bibtexTok3); i += 2 { - token = bibtexTok3[i+0] + token = int(bibtexTok3[i+0]) if token == char { - token = bibtexTok3[i+1] + token = int(bibtexTok3[i+1]) goto out } } out: if token == 0 { - token = bibtexTok2[1] /* unknown char */ + token = int(bibtexTok2[1]) /* unknown char */ } if bibtexDebug >= 3 { __yyfmt__.Printf("lex %s(%d)\n", bibtexTokname(token), uint(char)) @@ -369,7 +370,7 @@ bibtexstack: bibtexS[bibtexp].yys = bibtexstate bibtexnewstate: - bibtexn = bibtexPact[bibtexstate] + bibtexn = int(bibtexPact[bibtexstate]) if bibtexn <= bibtexFlag { goto bibtexdefault /* simple state */ } @@ -380,8 +381,8 @@ bibtexnewstate: if bibtexn < 0 || bibtexn >= bibtexLast { goto bibtexdefault } - bibtexn = bibtexAct[bibtexn] - if bibtexChk[bibtexn] == bibtextoken { /* valid shift */ + bibtexn = int(bibtexAct[bibtexn]) + if int(bibtexChk[bibtexn]) == bibtextoken { /* valid shift */ bibtexrcvr.char = -1 bibtextoken = -1 bibtexVAL = bibtexrcvr.lval @@ -394,7 +395,7 @@ bibtexnewstate: bibtexdefault: /* default state action */ - bibtexn = bibtexDef[bibtexstate] + bibtexn = int(bibtexDef[bibtexstate]) if bibtexn == -2 { if bibtexrcvr.char < 0 { bibtexrcvr.char, bibtextoken = bibtexlex1(bibtexlex, &bibtexrcvr.lval) @@ -403,18 +404,18 @@ bibtexdefault: /* look through exception table */ xi := 0 for { - if bibtexExca[xi+0] == -1 && bibtexExca[xi+1] == bibtexstate { + if bibtexExca[xi+0] == -1 && int(bibtexExca[xi+1]) == bibtexstate { break } xi += 2 } for xi += 2; ; xi += 2 { - bibtexn = bibtexExca[xi+0] + bibtexn = int(bibtexExca[xi+0]) if bibtexn < 0 || bibtexn == bibtextoken { break } } - bibtexn = bibtexExca[xi+1] + bibtexn = int(bibtexExca[xi+1]) if bibtexn < 0 { goto ret0 } @@ -436,10 +437,10 @@ bibtexdefault: /* find a state where "error" is a legal shift action */ for bibtexp >= 0 { - bibtexn = bibtexPact[bibtexS[bibtexp].yys] + bibtexErrCode + bibtexn = int(bibtexPact[bibtexS[bibtexp].yys]) + bibtexErrCode if bibtexn >= 0 && bibtexn < bibtexLast { - bibtexstate = bibtexAct[bibtexn] /* simulate a shift of "error" */ - if bibtexChk[bibtexstate] == bibtexErrCode { + bibtexstate = int(bibtexAct[bibtexn]) /* simulate a shift of "error" */ + if int(bibtexChk[bibtexstate]) == bibtexErrCode { goto bibtexstack } } @@ -475,7 +476,7 @@ bibtexdefault: bibtexpt := bibtexp _ = bibtexpt // guard against "declared and not used" - bibtexp -= bibtexR2[bibtexn] + bibtexp -= int(bibtexR2[bibtexn]) // bibtexp is now the index of $0. Perform the default action. Iff the // reduced production is ε, $1 is possibly out of range. if bibtexp+1 >= len(bibtexS) { @@ -486,16 +487,16 @@ bibtexdefault: bibtexVAL = bibtexS[bibtexp+1] /* consult goto table to find next state */ - bibtexn = bibtexR1[bibtexn] - bibtexg := bibtexPgo[bibtexn] + bibtexn = int(bibtexR1[bibtexn]) + bibtexg := int(bibtexPgo[bibtexn]) bibtexj := bibtexg + bibtexS[bibtexp].yys + 1 if bibtexj >= bibtexLast { - bibtexstate = bibtexAct[bibtexg] + bibtexstate = int(bibtexAct[bibtexg]) } else { - bibtexstate = bibtexAct[bibtexj] - if bibtexChk[bibtexstate] != -bibtexn { - bibtexstate = bibtexAct[bibtexg] + bibtexstate = int(bibtexAct[bibtexj]) + if int(bibtexChk[bibtexstate]) != -bibtexn { + bibtexstate = int(bibtexAct[bibtexg]) } } // dummy call; replaced with literal code @@ -503,46 +504,46 @@ bibtexdefault: case 1: bibtexDollar = bibtexS[bibtexpt-1 : bibtexpt+1] -//line bibtex.y:36 +//line bibtex.y:37 { } case 2: bibtexDollar = bibtexS[bibtexpt-0 : bibtexpt+1] -//line bibtex.y:39 +//line bibtex.y:40 { bibtexVAL.bibtex = NewBibTex() bib = bibtexVAL.bibtex } case 3: bibtexDollar = bibtexS[bibtexpt-2 : bibtexpt+1] -//line bibtex.y:40 +//line bibtex.y:41 { bibtexVAL.bibtex = bibtexDollar[1].bibtex bibtexVAL.bibtex.AddEntry(bibtexDollar[2].bibentry) } case 4: bibtexDollar = bibtexS[bibtexpt-2 : bibtexpt+1] -//line bibtex.y:41 +//line bibtex.y:42 { bibtexVAL.bibtex = bibtexDollar[1].bibtex } case 5: bibtexDollar = bibtexS[bibtexpt-2 : bibtexpt+1] -//line bibtex.y:42 +//line bibtex.y:43 { bibtexVAL.bibtex = bibtexDollar[1].bibtex bibtexVAL.bibtex.AddStringVar(bibtexDollar[2].bibtag.key, bibtexDollar[2].bibtag.val) } case 6: bibtexDollar = bibtexS[bibtexpt-2 : bibtexpt+1] -//line bibtex.y:43 +//line bibtex.y:44 { bibtexVAL.bibtex = bibtexDollar[1].bibtex bibtexVAL.bibtex.AddPreamble(bibtexDollar[2].strings) } case 7: bibtexDollar = bibtexS[bibtexpt-7 : bibtexpt+1] -//line bibtex.y:46 +//line bibtex.y:47 { bibtexVAL.bibentry = NewBibEntry(bibtexDollar[2].strval, bibtexDollar[4].strval) for _, t := range bibtexDollar[6].bibtags { @@ -551,7 +552,7 @@ bibtexdefault: } case 8: bibtexDollar = bibtexS[bibtexpt-7 : bibtexpt+1] -//line bibtex.y:47 +//line bibtex.y:48 { bibtexVAL.bibentry = NewBibEntry(bibtexDollar[2].strval, bibtexDollar[4].strval) for _, t := range bibtexDollar[6].bibtags { @@ -560,78 +561,88 @@ bibtexdefault: } case 9: bibtexDollar = bibtexS[bibtexpt-5 : bibtexpt+1] -//line bibtex.y:50 +//line bibtex.y:51 { } case 10: bibtexDollar = bibtexS[bibtexpt-5 : bibtexpt+1] -//line bibtex.y:51 +//line bibtex.y:52 { } case 11: bibtexDollar = bibtexS[bibtexpt-7 : bibtexpt+1] -//line bibtex.y:54 +//line bibtex.y:55 { bibtexVAL.bibtag = &bibTag{key: bibtexDollar[4].strval, val: bibtexDollar[6].strings} } case 12: bibtexDollar = bibtexS[bibtexpt-7 : bibtexpt+1] -//line bibtex.y:55 +//line bibtex.y:56 { bibtexVAL.bibtag = &bibTag{key: bibtexDollar[4].strval, val: bibtexDollar[6].strings} } case 13: bibtexDollar = bibtexS[bibtexpt-5 : bibtexpt+1] -//line bibtex.y:58 +//line bibtex.y:59 { bibtexVAL.strings = bibtexDollar[4].strings } case 14: bibtexDollar = bibtexS[bibtexpt-5 : bibtexpt+1] -//line bibtex.y:59 +//line bibtex.y:60 { bibtexVAL.strings = bibtexDollar[4].strings } case 15: bibtexDollar = bibtexS[bibtexpt-1 : bibtexpt+1] -//line bibtex.y:62 +//line bibtex.y:63 { bibtexVAL.strings = NewBibConst(bibtexDollar[1].strval) } case 16: bibtexDollar = bibtexS[bibtexpt-1 : bibtexpt+1] -//line bibtex.y:63 +//line bibtex.y:64 { - bibtexVAL.strings = bib.GetStringVar(bibtexDollar[1].strval) + v, err := bib.GetStringVar(bibtexDollar[1].strval) + if err != nil { + bibtexlex.Error(fmt.Sprintf("%v", err)) + } else { + bibtexVAL.strings = v + } } case 17: bibtexDollar = bibtexS[bibtexpt-3 : bibtexpt+1] -//line bibtex.y:64 +//line bibtex.y:65 { bibtexVAL.strings = NewBibComposite(bibtexDollar[1].strings) bibtexVAL.strings.(*BibComposite).Append(NewBibConst(bibtexDollar[3].strval)) } case 18: bibtexDollar = bibtexS[bibtexpt-3 : bibtexpt+1] -//line bibtex.y:65 +//line bibtex.y:66 { bibtexVAL.strings = NewBibComposite(bibtexDollar[1].strings) - bibtexVAL.strings.(*BibComposite).Append(bib.GetStringVar(bibtexDollar[3].strval)) + v, err := bib.GetStringVar(bibtexDollar[3].strval) + if err != nil { + bibtexlex.Error(fmt.Sprintf("%v", err)) + } else { + bibtexVAL.strings.(*BibComposite).Append(v) + } } case 19: bibtexDollar = bibtexS[bibtexpt-0 : bibtexpt+1] -//line bibtex.y:68 +//line bibtex.y:69 { } case 20: bibtexDollar = bibtexS[bibtexpt-3 : bibtexpt+1] -//line bibtex.y:69 +//line bibtex.y:70 { bibtexVAL.bibtag = &bibTag{key: bibtexDollar[1].strval, val: bibtexDollar[3].strings} } case 21: bibtexDollar = bibtexS[bibtexpt-1 : bibtexpt+1] -//line bibtex.y:72 +//line bibtex.y:73 { if bibtexDollar[1].bibtag != nil { bibtexVAL.bibtags = []*bibTag{bibtexDollar[1].bibtag} @@ -639,7 +650,7 @@ bibtexdefault: } case 22: bibtexDollar = bibtexS[bibtexpt-3 : bibtexpt+1] -//line bibtex.y:73 +//line bibtex.y:74 { if bibtexDollar[3].bibtag == nil { bibtexVAL.bibtags = bibtexDollar[1].bibtags diff --git a/bibtex_test.go b/bibtex_test.go index 1def1ca..f5d82c7 100644 --- a/bibtex_test.go +++ b/bibtex_test.go @@ -49,7 +49,7 @@ func TestString(t *testing.T) { bibtex := NewBibTex() bibtex.AddStringVar("cat", &BibVar{Key: "cat", Value: NewBibConst("meowmeow")}) entry := NewBibEntry("article", "abcd") - entry.AddField("title", bibtex.GetStringVar("cat")) + entry.AddField("title", bibtex.MustGetStringVar("cat")) bibtex.AddEntry(entry) expected := `@article{abcd, @@ -82,6 +82,56 @@ func TestParser(t *testing.T) { } } +// TestParser_ErrorHandling checks that the parser returns an error value for malformed bibtex +func TestParser_ErrorHandling(t *testing.T) { + examples, err := filepath.Glob("example/*.badbib") + if err != nil { + t.Fatal(err) + } + + for _, ex := range examples { + t.Logf("Parsing example: %s", ex) + b, err := ioutil.ReadFile(ex) + if err != nil { + t.Errorf("Cannot read %s: %v", ex, err) + } + _, err = Parse(bytes.NewReader(b)) + if err == nil { + t.Fatalf("Expected parser error got none") + } + } + +} + +// TestParser_ErrorHandling checks that the parser returns an error value for malformed bibtex +func TestParser_ParseGoodEntryAfterBadEntry(t *testing.T) { + goodEntry := `@article{agneroptimize, + title={The microarchitecture of Intel, AMD and VIA CPUs: An optimization guide for assembly programmers and compiler makers}, + author={Fog, Agner}, + journal={Copenhagen University College of Engineering}, + pages={02--29}, + year={2012} +}` + badEntry := `@article{agneroptimize, + title={The microarchitecture of Intel, AMD and VIA CPUs: An optimization guide for assembly programmers and compiler makers}, + author={Fog, Agner}, + journal=Copenhagen University College of Engineering, + pages={02--29}, + year={2012} +}` + + _, err := Parse(strings.NewReader(badEntry)) + if err == nil { + t.Fatal("Expected parser error got none") + } + + _, err = Parse(strings.NewReader(goodEntry)) + if err != nil { + t.Fatalf("Did not expect parser error but got %v", err) + } + +} + // Tests that multiple parse returns different instances of the parsed BibTex. // Otherwise the number of entries will pile up. (Issue #4) func TestMultiParse(t *testing.T) { diff --git a/example/unquoted-string-complex.badbib b/example/unquoted-string-complex.badbib new file mode 100644 index 0000000..f99b98f --- /dev/null +++ b/example/unquoted-string-complex.badbib @@ -0,0 +1,7 @@ +@article{agneroptimize, + title={The microarchitecture of Intel, AMD and VIA CPUs: An optimization guide for assembly programmers and compiler makers}, + author={Fog, Agner}, + journal=Copenhagen University College of Engineering, + pages={02--29}, + year={2012} +} \ No newline at end of file diff --git a/example/unquoted-string-simple.badbib b/example/unquoted-string-simple.badbib new file mode 100644 index 0000000..beebd21 --- /dev/null +++ b/example/unquoted-string-simple.badbib @@ -0,0 +1,4 @@ +@inproceedings{A, + title = thisIsInvalid, + +}` \ No newline at end of file