Skip to content

Commit

Permalink
Add label notation
Browse files Browse the repository at this point in the history
  • Loading branch information
nihei9 committed Mar 28, 2022
1 parent 1746609 commit ed43562
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 26 deletions.
60 changes: 60 additions & 0 deletions driver/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,66 @@ s
foo: 'foo';
error: 'error' #skip;
`,
specErr: true,
},
// A label must be unique in an alternative.
{
specSrc: `
%name test
s
: foo@x bar@x
;
foo: 'foo';
bar: 'bar';
`,
specErr: true,
},
// The same label can be used between different alternatives.
{
specSrc: `
%name test
s
: foo@x bar
| foo@x
;
foo: 'foo';
bar: 'bar';
`,
src: `foo`,
},
// A label cannot be the same name as terminal symbols.
{
specSrc: `
%name test
s
: foo bar@foo
;
foo: 'foo';
bar: 'bar';
`,
specErr: true,
},
// A label cannot be the same name as non-terminal symbols.
{
specSrc: `
%name test
s
: foo@a
;
a
: bar
;
foo: 'foo';
bar: 'bar';
`,
specErr: true,
},
Expand Down
23 changes: 23 additions & 0 deletions grammar/grammar.go
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,7 @@ func (b *GrammarBuilder) genProductionsAndActions(root *spec.RootNode, symTabAnd
LOOP_RHS:
for _, alt := range prod.RHS {
altSyms := make([]symbol, len(alt.Elements))
labels := map[string]int{}
for i, elem := range alt.Elements {
var sym symbol
if elem.Pattern != "" {
Expand Down Expand Up @@ -707,6 +708,28 @@ func (b *GrammarBuilder) genProductionsAndActions(root *spec.RootNode, symTabAnd
}
}
altSyms[i] = sym

if elem.Label != nil {
if _, added := labels[elem.Label.Name]; added {
b.errs = append(b.errs, &verr.SpecError{
Cause: semErrDuplicateLabel,
Detail: elem.Label.Name,
Row: elem.Label.Pos.Row,
Col: elem.Label.Pos.Col,
})
continue LOOP_RHS
}
if _, found := symTab.toSymbol(elem.Label.Name); found {
b.errs = append(b.errs, &verr.SpecError{
Cause: semErrInvalidLabel,
Detail: elem.Label.Name,
Row: elem.Label.Pos.Row,
Col: elem.Label.Pos.Col,
})
continue LOOP_RHS
}
labels[elem.Label.Name] = i
}
}

p, err := newProduction(lhsSym, altSyms)
Expand Down
2 changes: 2 additions & 0 deletions grammar/semantic_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ var (
semErrDuplicateTerminal = newSemanticError("duplicate terminal")
semErrDuplicateName = newSemanticError("duplicate names are not allowed between terminals and non-terminals")
semErrErrSymIsReserved = newSemanticError("symbol 'error' is reserved as a terminal symbol")
semErrDuplicateLabel = newSemanticError("a label must be unique in an alternative")
semErrInvalidLabel = newSemanticError("a label must differ from terminal symbols or non-terminal symbols")
semErrDirInvalidName = newSemanticError("invalid directive name")
semErrDirInvalidParam = newSemanticError("invalid parameter")
)
3 changes: 3 additions & 0 deletions spec/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
tokenKindColon = tokenKind(":")
tokenKindOr = tokenKind("|")
tokenKindSemicolon = tokenKind(";")
tokenKindLabelMarker = tokenKind("@")
tokenKindDirectiveMarker = tokenKind("#")
tokenKindPosition = tokenKind("$")
tokenKindExpantion = tokenKind("...")
Expand Down Expand Up @@ -269,6 +270,8 @@ func (l *lexer) lexAndSkipWSs() (*token, error) {
return newSymbolToken(tokenKindOr, newPosition(tok.Row+1, tok.Col+1)), nil
case KindIDSemicolon:
return newSymbolToken(tokenKindSemicolon, newPosition(tok.Row+1, tok.Col+1)), nil
case KindIDLabelMarker:
return newSymbolToken(tokenKindLabelMarker, newPosition(tok.Row+1, tok.Col+1)), nil
case KindIDDirectiveMarker:
return newSymbolToken(tokenKindDirectiveMarker, newPosition(tok.Row+1, tok.Col+1)), nil
case KindIDPosition:
Expand Down
3 changes: 2 additions & 1 deletion spec/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,15 @@ func TestLexer_Run(t *testing.T) {
}{
{
caption: "the lexer can recognize all kinds of tokens",
src: `id"terminal"'string':|;$1...#%`,
src: `id"terminal"'string':|;@$1...#%`,
tokens: []*token{
idTok("id"),
termPatTok("terminal"),
strTok(`string`),
symTok(tokenKindColon),
symTok(tokenKindOr),
symTok(tokenKindSemicolon),
symTok(tokenKindLabelMarker),
posTok(1),
symTok(tokenKindExpantion),
symTok(tokenKindDirectiveMarker),
Expand Down
4 changes: 4 additions & 0 deletions spec/lexspec.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@
"kind": "semicolon",
"pattern": ";"
},
{
"kind": "label_marker",
"pattern": "@"
},
{
"kind": "position",
"pattern": "$(0|[1-9][0-9]*)"
Expand Down
29 changes: 25 additions & 4 deletions spec/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,16 @@ type AlternativeNode struct {
type ElementNode struct {
ID string
Pattern string
Label *LabelNode
Literally bool
Pos Position
}

type LabelNode struct {
Name string
Pos Position
}

type DirectiveNode struct {
Name string
Parameters []*ParameterNode
Expand Down Expand Up @@ -383,25 +389,40 @@ func (p *parser) parseAlternative() *AlternativeNode {
}

func (p *parser) parseElement() *ElementNode {
var elem *ElementNode
switch {
case p.consume(tokenKindID):
return &ElementNode{
elem = &ElementNode{
ID: p.lastTok.text,
Pos: p.lastTok.pos,
}
case p.consume(tokenKindTerminalPattern):
return &ElementNode{
elem = &ElementNode{
Pattern: p.lastTok.text,
Pos: p.lastTok.pos,
}
case p.consume(tokenKindStringLiteral):
return &ElementNode{
elem = &ElementNode{
Pattern: p.lastTok.text,
Literally: true,
Pos: p.lastTok.pos,
}
default:
if p.consume(tokenKindLabelMarker) {
raiseSyntaxError(p.pos.Row, synErrLabelWithNoSymbol)
}
return nil
}
if p.consume(tokenKindLabelMarker) {
if !p.consume(tokenKindID) {
raiseSyntaxError(p.pos.Row, synErrNoLabel)
}
elem.Label = &LabelNode{
Name: p.lastTok.text,
Pos: p.lastTok.pos,
}
}
return nil
return elem
}

func (p *parser) parseDirective() *DirectiveNode {
Expand Down
104 changes: 104 additions & 0 deletions spec/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,19 @@ func TestParse(t *testing.T) {
Pattern: p,
}
}
label := func(name string) *LabelNode {
return &LabelNode{
Name: name,
}
}
withLabelPos := func(label *LabelNode, pos Position) *LabelNode {
label.Pos = pos
return label
}
withLabel := func(elem *ElementNode, label *LabelNode) *ElementNode {
elem.Label = label
return elem
}
withElemPos := func(elem *ElementNode, pos Position) *ElementNode {
elem.Pos = pos
return elem
Expand Down Expand Up @@ -535,6 +548,97 @@ fragment number: "[0-9]";
},
},
},
{
caption: "a symbol can have a label",
src: `
expr
: term@lhs add term@rhs
;
`,
ast: &RootNode{
Productions: []*ProductionNode{
withProdPos(
prod("expr",
withAltPos(
alt(
withElemPos(
withLabel(
id("term"),
withLabelPos(
label("lhs"),
newPos(3),
),
),
newPos(3),
),
withElemPos(
id("add"),
newPos(3),
),
withElemPos(
withLabel(
id("term"),
withLabelPos(
label("rhs"),
newPos(3),
),
),
newPos(3),
),
),
newPos(3),
),
),
newPos(2),
),
},
},
},
{
caption: "a label must be an identifier, not a string",
src: `
foo
: bar@'baz'
;
`,
synErr: synErrNoLabel,
},
{
caption: "a label must be an identifier, not a pattern",
src: `
foo
: bar@"baz"
;
`,
synErr: synErrNoLabel,
},
{
caption: "the symbol marker @ must be followed by an identifier",
src: `
foo
: bar@
;
`,
synErr: synErrNoLabel,
},
{
caption: "a symbol cannot have more than or equal to two labels",
src: `
foo
: bar@baz@bra
;
`,
synErr: synErrLabelWithNoSymbol,
},
{
caption: "a label must follow a symbol",
src: `
foo
: @baz
;
`,
synErr: synErrLabelWithNoSymbol,
},
{
caption: "a grammar can contain left and right associativities",
src: `
Expand Down
2 changes: 2 additions & 0 deletions spec/syntax_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ var (
synErrNoProductionName = newSyntaxError("a production name is missing")
synErrNoColon = newSyntaxError("the colon must precede alternatives")
synErrNoSemicolon = newSyntaxError("the semicolon is missing at the last of an alternative")
synErrLabelWithNoSymbol = newSyntaxError("a label must follow a symbol")
synErrNoLabel = newSyntaxError("an identifier that represents a label is missing after the label marker @")
synErrNoDirectiveName = newSyntaxError("a directive needs a name")
synErrProdDirNoNewline = newSyntaxError("a production directive must be followed by a newline")
synErrSemicolonNoNewline = newSyntaxError("a semicolon must be followed by a newline")
Expand Down
Loading

0 comments on commit ed43562

Please sign in to comment.