Skip to content

Commit

Permalink
Allow an alternative to have multiple directives
Browse files Browse the repository at this point in the history
  • Loading branch information
nihei9 committed Mar 29, 2022
1 parent 90f28b5 commit a1e4ae7
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 23 deletions.
118 changes: 116 additions & 2 deletions driver/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
30 changes: 25 additions & 5 deletions grammar/grammar.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions grammar/semantic_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
)
21 changes: 14 additions & 7 deletions spec/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
}
}

Expand Down
52 changes: 43 additions & 9 deletions spec/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -361,6 +361,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: `
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit a1e4ae7

Please sign in to comment.