aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyo Nihei <nihei.dev@gmail.com>2022-03-30 00:44:17 +0900
committerRyo Nihei <nihei.dev@gmail.com>2022-03-30 00:50:06 +0900
commita1e4ae763cbf824f0d32a706cfe0d9603ce99b02 (patch)
treee32f48d6d5d3f56d495a8684653e913f14ca5ec8
parentMove directives given to lexical productions (diff)
downloadurubu-a1e4ae763cbf824f0d32a706cfe0d9603ce99b02.tar.gz
urubu-a1e4ae763cbf824f0d32a706cfe0d9603ce99b02.tar.xz
Allow an alternative to have multiple directives
-rw-r--r--driver/parser_test.go118
-rw-r--r--grammar/grammar.go30
-rw-r--r--grammar/semantic_error.go1
-rw-r--r--spec/parser.go21
-rw-r--r--spec/parser_test.go52
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)