diff options
author | Ryo Nihei <nihei.dev@gmail.com> | 2021-08-22 19:15:03 +0900 |
---|---|---|
committer | Ryo Nihei <nihei.dev@gmail.com> | 2021-08-22 19:15:03 +0900 |
commit | 02674d7264aea363a8f7b7839ab77ce64ba720db (patch) | |
tree | a849d566004b7d1e711fa4c667a54a5eab322c78 | |
parent | Support %left and %right to specify precedences and associativities (diff) | |
download | cotia-02674d7264aea363a8f7b7839ab77ce64ba720db.tar.gz cotia-02674d7264aea363a8f7b7839ab77ce64ba720db.tar.xz |
Add a column number to an error message
-rw-r--r-- | error/error.go | 5 | ||||
-rw-r--r-- | go.mod | 2 | ||||
-rw-r--r-- | go.sum | 4 | ||||
-rw-r--r-- | grammar/grammar.go | 28 | ||||
-rw-r--r-- | spec/lexer.go | 62 | ||||
-rw-r--r-- | spec/lexer_test.go | 10 | ||||
-rw-r--r-- | spec/parser_test.go | 82 |
7 files changed, 116 insertions, 77 deletions
diff --git a/error/error.go b/error/error.go index 1781f2a..0e5d3af 100644 --- a/error/error.go +++ b/error/error.go @@ -39,6 +39,7 @@ type SpecError struct { FilePath string SourceName string Row int + Col int } func (e *SpecError) Error() string { @@ -46,8 +47,8 @@ func (e *SpecError) Error() string { if e.SourceName != "" { fmt.Fprintf(&b, "%v: ", e.SourceName) } - if e.Row != 0 { - fmt.Fprintf(&b, "%v: ", e.Row) + if e.Row != 0 && e.Col != 0 { + fmt.Fprintf(&b, "%v:%v: ", e.Row, e.Col) } fmt.Fprintf(&b, "error: %v", e.Cause) if e.Detail != "" { @@ -3,6 +3,6 @@ module github.com/nihei9/vartan go 1.16 require ( - github.com/nihei9/maleeni v0.3.0 + github.com/nihei9/maleeni v0.4.0 github.com/spf13/cobra v1.1.3 ) @@ -118,8 +118,8 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nihei9/maleeni v0.3.0 h1:jNKxnHcoegf7iy1K253dl3nUlrV0BvZDr8a5pp+AF6E= -github.com/nihei9/maleeni v0.3.0/go.mod h1:BWTzKvhWqb4xDo5koZCQJ2eLdkgKL2WY7KlXa7J0tSg= +github.com/nihei9/maleeni v0.4.0 h1:ca1V9U7lZuf5c01Ls4HqlYf7eyhDWdGBv+uyBkChdf0= +github.com/nihei9/maleeni v0.4.0/go.mod h1:d5x5jHHuema6IUi+aDPczMZQ4AlNokcKbEgB5T+70dI= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= diff --git a/grammar/grammar.go b/grammar/grammar.go index 450c526..c4bbae3 100644 --- a/grammar/grammar.go +++ b/grammar/grammar.go @@ -116,6 +116,7 @@ func (b *GrammarBuilder) Build() (*Grammar, error) { Cause: semErrTermCannotBeSkipped, Detail: sym, Row: prod.Pos.Row, + Col: prod.Pos.Col, }) continue } @@ -128,6 +129,7 @@ func (b *GrammarBuilder) Build() (*Grammar, error) { Cause: semErrUnusedProduction, Detail: sym, Row: prod.Pos.Row, + Col: prod.Pos.Col, }) } @@ -136,6 +138,7 @@ func (b *GrammarBuilder) Build() (*Grammar, error) { Cause: semErrUnusedTerminal, Detail: sym, Row: prod.Pos.Row, + Col: prod.Pos.Col, }) } @@ -310,6 +313,7 @@ func (b *GrammarBuilder) genSymbolTableAndLexSpec(root *spec.RootNode) (*symbolT Cause: semErrDuplicateTerminal, Detail: prod.LHS, Row: prod.Pos.Row, + Col: prod.Pos.Col, }) continue } @@ -341,6 +345,7 @@ func (b *GrammarBuilder) genSymbolTableAndLexSpec(root *spec.RootNode) (*symbolT Cause: semErrDuplicateTerminal, Detail: fragment.LHS, Row: fragment.Pos.Row, + Col: fragment.Pos.Col, }) continue } @@ -376,6 +381,7 @@ func genLexEntry(prod *spec.ProductionNode) (*mlspec.LexEntry, bool, *verr.SpecE Cause: semErrDirInvalidParam, Detail: fmt.Sprintf("'mode' directive needs an ID parameter"), Row: dir.Pos.Row, + Col: dir.Pos.Col, }, nil } for _, param := range dir.Parameters { @@ -384,6 +390,7 @@ func genLexEntry(prod *spec.ProductionNode) (*mlspec.LexEntry, bool, *verr.SpecE Cause: semErrDirInvalidParam, Detail: fmt.Sprintf("'mode' directive needs an ID parameter"), Row: param.Pos.Row, + Col: param.Pos.Col, }, nil } modes = append(modes, mlspec.LexModeName(param.ID)) @@ -393,6 +400,7 @@ func genLexEntry(prod *spec.ProductionNode) (*mlspec.LexEntry, bool, *verr.SpecE Cause: semErrDirInvalidName, Detail: dir.Name, Row: dir.Pos.Row, + Col: dir.Pos.Col, }, nil } } @@ -410,6 +418,7 @@ func genLexEntry(prod *spec.ProductionNode) (*mlspec.LexEntry, bool, *verr.SpecE Cause: semErrDirInvalidParam, Detail: fmt.Sprintf("'skip' directive needs no parameter"), Row: dir.Pos.Row, + Col: dir.Pos.Col, }, nil } skip = true @@ -419,6 +428,7 @@ func genLexEntry(prod *spec.ProductionNode) (*mlspec.LexEntry, bool, *verr.SpecE Cause: semErrDirInvalidParam, Detail: fmt.Sprintf("'push' directive needs an ID parameter"), Row: dir.Pos.Row, + Col: dir.Pos.Col, }, nil } push = mlspec.LexModeName(dir.Parameters[0].ID) @@ -428,6 +438,7 @@ func genLexEntry(prod *spec.ProductionNode) (*mlspec.LexEntry, bool, *verr.SpecE Cause: semErrDirInvalidParam, Detail: fmt.Sprintf("'pop' directive needs no parameter"), Row: dir.Pos.Row, + Col: dir.Pos.Col, }, nil } pop = true @@ -436,6 +447,7 @@ func genLexEntry(prod *spec.ProductionNode) (*mlspec.LexEntry, bool, *verr.SpecE Cause: semErrDirInvalidName, Detail: dir.Name, Row: dir.Pos.Row, + Col: dir.Pos.Col, }, nil } } @@ -499,6 +511,7 @@ func (b *GrammarBuilder) genProductionsAndActions(root *spec.RootNode, symTabAnd Cause: semErrDuplicateName, Detail: prod.LHS, Row: prod.Pos.Row, + Col: prod.Pos.Col, }) } } @@ -530,6 +543,7 @@ func (b *GrammarBuilder) genProductionsAndActions(root *spec.RootNode, symTabAnd Cause: semErrUndefinedSym, Detail: elem.ID, Row: elem.Pos.Row, + Col: elem.Pos.Col, }) continue LOOP_RHS } @@ -545,10 +559,13 @@ func (b *GrammarBuilder) genProductionsAndActions(root *spec.RootNode, symTabAnd // Report the line number of a duplicate alternative. // When the alternative is empty, we report the position of its LHS. var row int + var col int if len(alt.Elements) > 0 { row = alt.Elements[0].Pos.Row + col = alt.Elements[0].Pos.Col } else { row = prod.Pos.Row + col = prod.Pos.Col } var detail string @@ -574,6 +591,7 @@ func (b *GrammarBuilder) genProductionsAndActions(root *spec.RootNode, symTabAnd Cause: semErrDuplicateProduction, Detail: detail, Row: row, + Col: col, }) continue LOOP_RHS } @@ -588,6 +606,7 @@ func (b *GrammarBuilder) genProductionsAndActions(root *spec.RootNode, symTabAnd Cause: semErrDirInvalidParam, Detail: fmt.Sprintf("'ast' directive needs a tree parameter"), Row: dir.Pos.Row, + Col: dir.Pos.Col, }) continue LOOP_RHS } @@ -598,6 +617,7 @@ func (b *GrammarBuilder) genProductionsAndActions(root *spec.RootNode, symTabAnd Cause: semErrDirInvalidParam, Detail: fmt.Sprintf("a name of a tree structure must be the same ID as an LHS of a production; LHS: %v", lhsText), Row: param.Pos.Row, + Col: param.Pos.Col, }) continue LOOP_RHS } @@ -608,6 +628,7 @@ func (b *GrammarBuilder) genProductionsAndActions(root *spec.RootNode, symTabAnd Cause: semErrDirInvalidParam, Detail: fmt.Sprintf("a position must be less than or equal to the length of an alternativ (%v)", len(alt.Elements)), Row: c.Pos.Row, + Col: c.Pos.Col, }) continue LOOP_RHS } @@ -620,6 +641,7 @@ func (b *GrammarBuilder) genProductionsAndActions(root *spec.RootNode, symTabAnd Cause: semErrDirInvalidParam, Detail: fmt.Sprintf("the expansion symbol cannot be applied to a pattern ($%v: %v)", c.Position, elem.Pattern), Row: c.Pos.Row, + Col: c.Pos.Col, }) continue LOOP_RHS } @@ -633,6 +655,7 @@ func (b *GrammarBuilder) genProductionsAndActions(root *spec.RootNode, symTabAnd Cause: semErrDirInvalidParam, Detail: fmt.Sprintf("the expansion symbol cannot be applied to a terminal symbol ($%v: %v)", c.Position, elem.ID), Row: c.Pos.Row, + Col: c.Pos.Col, }) continue LOOP_RHS } @@ -649,6 +672,7 @@ func (b *GrammarBuilder) genProductionsAndActions(root *spec.RootNode, symTabAnd Cause: semErrDirInvalidName, Detail: fmt.Sprintf("invalid directive name '%v'", dir.Name), Row: dir.Pos.Row, + Col: dir.Pos.Col, }) continue LOOP_RHS } @@ -679,6 +703,7 @@ func (b *GrammarBuilder) genPrecAndAssoc(symTab *symbolTable, prods *productionS return nil, &verr.SpecError{ Cause: semErrMDInvalidName, Row: md.Pos.Row, + Col: md.Pos.Col, } } @@ -687,6 +712,7 @@ func (b *GrammarBuilder) genPrecAndAssoc(symTab *symbolTable, prods *productionS Cause: semErrMDInvalidParam, Detail: "associativity needs at least one symbol", Row: md.Pos.Row, + Col: md.Pos.Col, } } @@ -697,6 +723,7 @@ func (b *GrammarBuilder) genPrecAndAssoc(symTab *symbolTable, prods *productionS Cause: semErrMDInvalidParam, Detail: fmt.Sprintf("'%v' is undefined", p.ID), Row: p.Pos.Row, + Col: p.Pos.Col, } } if !sym.isTerminal() { @@ -704,6 +731,7 @@ func (b *GrammarBuilder) genPrecAndAssoc(symTab *symbolTable, prods *productionS Cause: semErrMDInvalidParam, Detail: fmt.Sprintf("associativity can take only terminal symbol ('%v' is a non-terminal)", p.ID), Row: p.Pos.Row, + Col: p.Pos.Col, } } diff --git a/spec/lexer.go b/spec/lexer.go index da0cf74..258faae 100644 --- a/spec/lexer.go +++ b/spec/lexer.go @@ -37,11 +37,13 @@ const ( type Position struct { Row int + Col int } -func newPosition(row int) Position { +func newPosition(row, col int) Position { return Position{ Row: row, + Col: col, } } @@ -101,7 +103,6 @@ type lexer struct { s *mlspec.CompiledLexSpec d *mldriver.Lexer buf *token - row int } //go:embed clexspec.json @@ -118,9 +119,8 @@ func newLexer(src io.Reader) (*lexer, error) { return nil, err } return &lexer{ - s: s, - d: d, - row: 1, + s: s, + d: d, }, nil } @@ -159,7 +159,7 @@ func (l *lexer) lexAndSkipWSs() (*token, error) { return nil, err } if tok.Invalid { - return newInvalidToken(tok.Text(), newPosition(l.row)), nil + return newInvalidToken(tok.Text(), newPosition(tok.Row+1, tok.Col+1)), nil } if tok.EOF { return newEOFToken(), nil @@ -176,20 +176,19 @@ func (l *lexer) lexAndSkipWSs() (*token, error) { switch tok.KindName { case "newline": - row := l.row - l.row++ - return newSymbolToken(tokenKindNewline, newPosition(row)), nil + return newSymbolToken(tokenKindNewline, newPosition(tok.Row+1, tok.Col+1)), nil case "kw_fragment": - return newSymbolToken(tokenKindKWFragment, newPosition(l.row)), nil + return newSymbolToken(tokenKindKWFragment, newPosition(tok.Row+1, tok.Col+1)), nil case "identifier": if strings.HasPrefix(tok.Text(), "_") { return nil, &verr.SpecError{ Cause: synErrAutoGenID, Detail: tok.Text(), - Row: l.row, + Row: tok.Row + 1, + Col: tok.Col + 1, } } - return newIDToken(tok.Text(), newPosition(l.row)), nil + return newIDToken(tok.Text(), newPosition(tok.Row+1, tok.Col+1)), nil case "terminal_open": var b strings.Builder for { @@ -200,7 +199,8 @@ func (l *lexer) lexAndSkipWSs() (*token, error) { if tok.EOF { return nil, &verr.SpecError{ Cause: synErrUnclosedTerminal, - Row: l.row, + Row: tok.Row + 1, + Col: tok.Col + 1, } } switch tok.KindName { @@ -210,17 +210,19 @@ func (l *lexer) lexAndSkipWSs() (*token, error) { case "escape_symbol": return nil, &verr.SpecError{ Cause: synErrIncompletedEscSeq, - Row: l.row, + Row: tok.Row + 1, + Col: tok.Col + 1, } case "terminal_close": pat := b.String() if pat == "" { return nil, &verr.SpecError{ Cause: synErrEmptyPattern, - Row: l.row, + Row: tok.Row + 1, + Col: tok.Col + 1, } } - return newTerminalPatternToken(pat, newPosition(l.row)), nil + return newTerminalPatternToken(pat, newPosition(tok.Row+1, tok.Col+1)), nil } } case "literal_pattern": @@ -228,22 +230,23 @@ func (l *lexer) lexAndSkipWSs() (*token, error) { if pat == "" { return nil, &verr.SpecError{ Cause: synErrEmptyPattern, - Row: l.row, + Row: tok.Row + 1, + Col: tok.Col + 1, } } - return newTerminalPatternToken(mlspec.EscapePattern(pat), newPosition(l.row)), nil + return newTerminalPatternToken(mlspec.EscapePattern(pat), newPosition(tok.Row+1, tok.Col+1)), nil case "colon": - return newSymbolToken(tokenKindColon, newPosition(l.row)), nil + return newSymbolToken(tokenKindColon, newPosition(tok.Row+1, tok.Col+1)), nil case "or": - return newSymbolToken(tokenKindOr, newPosition(l.row)), nil + return newSymbolToken(tokenKindOr, newPosition(tok.Row+1, tok.Col+1)), nil case "semicolon": - return newSymbolToken(tokenKindSemicolon, newPosition(l.row)), nil + return newSymbolToken(tokenKindSemicolon, newPosition(tok.Row+1, tok.Col+1)), nil case "directive_marker": - return newSymbolToken(tokenKindDirectiveMarker, newPosition(l.row)), nil + return newSymbolToken(tokenKindDirectiveMarker, newPosition(tok.Row+1, tok.Col+1)), nil case "tree_node_open": - return newSymbolToken(tokenKindTreeNodeOpen, newPosition(l.row)), nil + return newSymbolToken(tokenKindTreeNodeOpen, newPosition(tok.Row+1, tok.Col+1)), nil case "tree_node_close": - return newSymbolToken(tokenKindTreeNodeClose, newPosition(l.row)), nil + return newSymbolToken(tokenKindTreeNodeClose, newPosition(tok.Row+1, tok.Col+1)), nil case "position": // Remove '$' character and convert to an integer. num, err := strconv.Atoi(tok.Text()[1:]) @@ -253,15 +256,16 @@ func (l *lexer) lexAndSkipWSs() (*token, error) { if num == 0 { return nil, &verr.SpecError{ Cause: synErrZeroPos, - Row: l.row, + Row: tok.Row + 1, + Col: tok.Col + 1, } } - return newPositionToken(num, newPosition(l.row)), nil + return newPositionToken(num, newPosition(tok.Row+1, tok.Col+1)), nil case "expansion": - return newSymbolToken(tokenKindExpantion, newPosition(l.row)), nil + return newSymbolToken(tokenKindExpantion, newPosition(tok.Row+1, tok.Col+1)), nil case "metadata_marker": - return newSymbolToken(tokenKindMetaDataMarker, newPosition(l.row)), nil + return newSymbolToken(tokenKindMetaDataMarker, newPosition(tok.Row+1, tok.Col+1)), nil default: - return newInvalidToken(tok.Text(), newPosition(l.row)), nil + return newInvalidToken(tok.Text(), newPosition(tok.Row+1, tok.Col+1)), nil } } diff --git a/spec/lexer_test.go b/spec/lexer_test.go index e5d999e..51e5f59 100644 --- a/spec/lexer_test.go +++ b/spec/lexer_test.go @@ -9,23 +9,23 @@ import ( func TestLexer_Run(t *testing.T) { idTok := func(text string) *token { - return newIDToken(text, newPosition(1)) + return newIDToken(text, newPosition(1, 0)) } termPatTok := func(text string) *token { - return newTerminalPatternToken(text, newPosition(1)) + return newTerminalPatternToken(text, newPosition(1, 0)) } symTok := func(kind tokenKind) *token { - return newSymbolToken(kind, newPosition(1)) + return newSymbolToken(kind, newPosition(1, 0)) } posTok := func(num int) *token { - return newPositionToken(num, newPosition(1)) + return newPositionToken(num, newPosition(1, 0)) } invalidTok := func(text string) *token { - return newInvalidToken(text, newPosition(1)) + return newInvalidToken(text, newPosition(1, 0)) } tests := []struct { diff --git a/spec/parser_test.go b/spec/parser_test.go index 4821562..d20f6f9 100644 --- a/spec/parser_test.go +++ b/spec/parser_test.go @@ -111,6 +111,12 @@ func TestParse(t *testing.T) { frag.Pos = pos return frag } + newPos := func(row int) Position { + return Position{ + Row: row, + Col: 0, + } + } tests := []struct { caption string @@ -256,13 +262,13 @@ foo: 'foo'; alt( withElemPos( id("foo"), - newPosition(6), + newPos(6), ), ), - newPosition(6), + newPos(6), ), ), - newPosition(5), + newPos(5), ), }, LexProductions: []*ProductionNode{ @@ -272,13 +278,13 @@ foo: 'foo'; alt( withElemPos( pat(`foo`), - newPosition(13), + newPos(13), ), ), - newPosition(13), + newPos(13), ), ), - newPosition(13), + newPos(13), ), }, }, @@ -463,42 +469,42 @@ fragment number: "[0-9]"; withAltPos( withAltDir( alt( - withElemPos(id("exp"), newPosition(4)), - withElemPos(pat(`\+`), newPosition(4)), - withElemPos(id("id"), newPosition(4)), + withElemPos(id("exp"), newPos(4)), + withElemPos(pat(`\+`), newPos(4)), + withElemPos(id("id"), newPos(4)), ), withDirPos( dir("ast", withParamPos( treeParam("exp", - withTreeChildPos(pos(1), newPosition(4)), - withTreeChildPos(pos(2), newPosition(4))), - newPosition(4), + withTreeChildPos(pos(1), newPos(4)), + withTreeChildPos(pos(2), newPos(4))), + newPos(4), ), ), - newPosition(4), + newPos(4), ), ), - newPosition(4), + newPos(4), ), withAltPos( alt( - withElemPos(id("id"), newPosition(5)), + withElemPos(id("id"), newPos(5)), ), - newPosition(5), + newPos(5), ), ), withDirPos( dir("mode", withParamPos( idParam("default"), - newPosition(2), + newPos(2), ), ), - newPosition(2), + newPos(2), ), ), - newPosition(3), + newPos(3), ), }, LexProductions: []*ProductionNode{ @@ -509,18 +515,18 @@ fragment number: "[0-9]"; alt( withElemPos( pat(`\u{0020}+`), - newPosition(7), + newPos(7), ), ), withDirPos( dir("skip"), - newPosition(7), + newPos(7), ), ), - newPosition(7), + newPos(7), ), ), - newPosition(7), + newPos(7), ), withProdPos( prod("id", @@ -528,23 +534,23 @@ fragment number: "[0-9]"; alt( withElemPos( pat(`\f{letter}(\f{letter}|\f{number})*`), - newPosition(8), + newPos(8), ), ), - newPosition(8), + newPos(8), ), ), - newPosition(8), + newPos(8), ), }, Fragments: []*FragmentNode{ withFragmentPos( frag("letter", "[A-Za-z_]"), - newPosition(9), + newPos(9), ), withFragmentPos( frag("number", "[0-9]"), - newPosition(10), + newPos(10), ), }, }, @@ -575,29 +581,29 @@ id: "[A-Za-z0-9_]+"; MetaData: []*DirectiveNode{ withDirPos( leftAssoc( - withParamPos(idParam("l1"), newPosition(2)), - withParamPos(idParam("l2"), newPosition(2)), + withParamPos(idParam("l1"), newPos(2)), + withParamPos(idParam("l2"), newPos(2)), ), - newPosition(2), + newPos(2), ), withDirPos( leftAssoc( - withParamPos(idParam("l3"), newPosition(3)), + withParamPos(idParam("l3"), newPos(3)), ), - newPosition(3), + newPos(3), ), withDirPos( rightAssoc( - withParamPos(idParam("r1"), newPosition(4)), - withParamPos(idParam("r2"), newPosition(4)), + withParamPos(idParam("r1"), newPos(4)), + withParamPos(idParam("r2"), newPos(4)), ), - newPosition(4), + newPos(4), ), withDirPos( rightAssoc( - withParamPos(idParam("r3"), newPosition(5)), + withParamPos(idParam("r3"), newPos(5)), ), - newPosition(5), + newPos(5), ), }, Productions: []*ProductionNode{ |