diff options
author | Ryo Nihei <nihei.dev@gmail.com> | 2022-03-30 00:44:17 +0900 |
---|---|---|
committer | Ryo Nihei <nihei.dev@gmail.com> | 2022-03-30 00:50:06 +0900 |
commit | a1e4ae763cbf824f0d32a706cfe0d9603ce99b02 (patch) | |
tree | e32f48d6d5d3f56d495a8684653e913f14ca5ec8 | |
parent | Move directives given to lexical productions (diff) | |
download | urubu-a1e4ae763cbf824f0d32a706cfe0d9603ce99b02.tar.gz urubu-a1e4ae763cbf824f0d32a706cfe0d9603ce99b02.tar.xz |
Allow an alternative to have multiple directives
-rw-r--r-- | driver/parser_test.go | 118 | ||||
-rw-r--r-- | grammar/grammar.go | 30 | ||||
-rw-r--r-- | grammar/semantic_error.go | 1 | ||||
-rw-r--r-- | spec/parser.go | 21 | ||||
-rw-r--r-- | spec/parser_test.go | 52 |
5 files changed, 199 insertions, 23 deletions
diff --git a/driver/parser_test.go b/driver/parser_test.go index 9d89efa..d964cbc 100644 --- a/driver/parser_test.go +++ b/driver/parser_test.go @@ -252,7 +252,104 @@ foo #skip src: `foo`, specErr: true, }, - // A lexical production cannot have alternative productions. + // A production cannot have production directives. + { + specSrc: ` +%name test + +s #prec foo + : foo + ; + +foo: 'foo' #skip; +`, + src: `foo`, + specErr: true, + }, + // A production can have multiple alternative productions. + { + specSrc: ` +%name test + +%left mul div +%left add sub + +expr + : expr add expr + | expr sub expr + | expr mul expr + | expr div expr + | int + | sub int #prec mul #ast int sub // This 'sub' means the unary minus symbol. + ; + +int + : "0|[1-9][0-9]*"; +add + : '+'; +sub + : '-'; +mul + : '*'; +div + : '/'; +`, + src: `-1*-2+3-4/5`, + ast: nonTermNode("expr", + nonTermNode("expr", + nonTermNode("expr", + nonTermNode("expr", + termNode("int", "1"), + termNode("sub", "-"), + ), + termNode("mul", "*"), + nonTermNode("expr", + termNode("int", "2"), + termNode("sub", "-"), + ), + ), + termNode("add", "+"), + nonTermNode("expr", + termNode("int", "3"), + ), + ), + termNode("sub", "-"), + nonTermNode("expr", + nonTermNode("expr", + termNode("int", "4"), + ), + termNode("div", "/"), + nonTermNode("expr", + termNode("int", "5"), + ), + ), + ), + }, + // A lexical production can have multiple production directives. + { + specSrc: ` +%name test + +s + : push_a push_b pop pop + ; + +push_a #mode default #push a + : '->a'; +push_b #mode a #push b + : '->b'; +pop #mode a b #pop + : '<-'; +`, + src: `->a->b<-<-`, + ast: nonTermNode("s", + termNode("push_a", "->a"), + termNode("push_b", "->b"), + termNode("pop", "<-"), + termNode("pop", "<-"), + ), + }, + // A lexical production cannot have alternative directives. { specSrc: ` %name test @@ -266,7 +363,7 @@ foo: 'foo' #skip; src: `foo`, specErr: true, }, - // A directive must not be duplicated. + // A production directive must not be duplicated. { specSrc: ` %name test @@ -281,6 +378,23 @@ foo #skip #skip src: `foo`, specErr: true, }, + // An alternative directive must not be duplicated. + { + specSrc: ` +%name test + +s + : foo bar #ast foo bar #ast foo bar + ; + +foo + : 'foo'; +bar + : 'bar'; +`, + src: `foobar`, + specErr: true, + }, { specSrc: ` %name test diff --git a/grammar/grammar.go b/grammar/grammar.go index c5726d7..2cdf2b3 100644 --- a/grammar/grammar.go +++ b/grammar/grammar.go @@ -571,12 +571,12 @@ func genLexEntry(prod *spec.ProductionNode) (*mlspec.LexEntry, bool, string, *ve } } - if alt.Directive != nil { + if len(alt.Directives) > 0 { return nil, false, "", &verr.SpecError{ Cause: semErrInvalidAltDir, Detail: "a lexical production cannot have alternative directives", - Row: alt.Directive.Pos.Row, - Col: alt.Directive.Pos.Col, + Row: alt.Directives[0].Pos.Row, + Col: alt.Directives[0].Pos.Col, }, nil } @@ -680,6 +680,16 @@ func (b *GrammarBuilder) genProductionsAndActions(root *spec.RootNode, symTabAnd return nil, fmt.Errorf("symbol '%v' is undefined", prod.LHS) } + if len(prod.Directives) > 0 { + b.errs = append(b.errs, &verr.SpecError{ + Cause: semErrInvalidProdDir, + Detail: "a production cannot have production directives", + Row: prod.Directives[0].Pos.Row, + Col: prod.Directives[0].Pos.Col, + }) + continue + } + LOOP_RHS: for _, alt := range prod.RHS { altSyms := make([]symbol, len(alt.Elements)) @@ -788,8 +798,18 @@ func (b *GrammarBuilder) genProductionsAndActions(root *spec.RootNode, symTabAnd } prods.append(p) - if alt.Directive != nil { - dir := alt.Directive + dirConsumed := map[string]struct{}{} + for _, dir := range alt.Directives { + if _, consumed := dirConsumed[dir.Name]; consumed { + b.errs = append(b.errs, &verr.SpecError{ + Cause: semErrDuplicateDir, + Detail: dir.Name, + Row: dir.Pos.Row, + Col: dir.Pos.Col, + }) + } + dirConsumed[dir.Name] = struct{}{} + switch dir.Name { case "ast": if len(dir.Parameters) == 0 { diff --git a/grammar/semantic_error.go b/grammar/semantic_error.go index 7ff1ba7..04cc020 100644 --- a/grammar/semantic_error.go +++ b/grammar/semantic_error.go @@ -32,5 +32,6 @@ var ( semErrDirInvalidName = newSemanticError("invalid directive name") semErrDirInvalidParam = newSemanticError("invalid parameter") semErrDuplicateDir = newSemanticError("a directive must not be duplicated") + semErrInvalidProdDir = newSemanticError("invalid production directive") semErrInvalidAltDir = newSemanticError("invalid alternative directive") ) diff --git a/spec/parser.go b/spec/parser.go index fd11f83..2d16614 100644 --- a/spec/parser.go +++ b/spec/parser.go @@ -30,9 +30,9 @@ func (n *ProductionNode) isLexical() bool { } type AlternativeNode struct { - Elements []*ElementNode - Directive *DirectiveNode - Pos Position + Elements []*ElementNode + Directives []*DirectiveNode + Pos Position } type ElementNode struct { @@ -375,12 +375,19 @@ func (p *parser) parseAlternative() *AlternativeNode { firstElemPos = elems[0].Pos } - dir := p.parseDirective() + var dirs []*DirectiveNode + for { + dir := p.parseDirective() + if dir == nil { + break + } + dirs = append(dirs, dir) + } return &AlternativeNode{ - Elements: elems, - Directive: dir, - Pos: firstElemPos, + Elements: elems, + Directives: dirs, + Pos: firstElemPos, } } diff --git a/spec/parser_test.go b/spec/parser_test.go index e579fad..0772dae 100644 --- a/spec/parser_test.go +++ b/spec/parser_test.go @@ -43,8 +43,8 @@ func TestParse(t *testing.T) { alt.Pos = pos return alt } - withAltDir := func(alt *AlternativeNode, dir *DirectiveNode) *AlternativeNode { - alt.Directive = dir + withAltDir := func(alt *AlternativeNode, dirs ...*DirectiveNode) *AlternativeNode { + alt.Directives = dirs return alt } dir := func(name string, params ...*ParameterNode) *DirectiveNode { @@ -362,6 +362,43 @@ whitespace #mode default m1 m2 #skip }, }, { + caption: "an alternative of a production can have multiple alternative directives", + src: ` +s + : foo bar #prec baz #ast foo bar + ; +`, + ast: &RootNode{ + Productions: []*ProductionNode{ + prod("s", + withAltDir( + alt(id("foo"), id("bar")), + dir("prec", idParam("baz")), + dir("ast", idParam("foo"), idParam("bar")), + ), + ), + }, + }, + }, + { + caption: "a lexical production can have multiple production directives", + src: ` +foo #mode a #push b + : 'foo'; +`, + ast: &RootNode{ + LexProductions: []*ProductionNode{ + withProdDir( + prod("foo", + alt(pat("foo")), + ), + dir("mode", idParam("a")), + dir("push", idParam("b")), + ), + }, + }, + }, + { caption: "a production must be followed by a newline", src: ` s: foo; foo: "foo"; @@ -774,14 +811,11 @@ func testAlternativeNode(t *testing.T, alt, expected *AlternativeNode, checkPosi for i, elem := range alt.Elements { testElementNode(t, elem, expected.Elements[i], checkPosition) } - if expected.Directive == nil && alt.Directive != nil { - t.Fatalf("unexpected directive; want: nil, got: %+v", alt.Directive) + if len(alt.Directives) != len(expected.Directives) { + t.Fatalf("unexpected alternative directive count; want: %v directive, got: %v directive", len(expected.Directives), len(alt.Directives)) } - if expected.Directive != nil { - if alt.Directive == nil { - t.Fatalf("a directive is not set; want: %+v, got: nil", expected.Directive) - } - testDirectives(t, []*DirectiveNode{alt.Directive}, []*DirectiveNode{expected.Directive}, checkPosition) + if len(alt.Directives) > 0 { + testDirectives(t, alt.Directives, expected.Directives, checkPosition) } if checkPosition { testPosition(t, alt.Pos, expected.Pos) |