aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyo Nihei <nihei.dev@gmail.com>2021-10-28 01:41:21 +0900
committerRyo Nihei <nihei.dev@gmail.com>2021-10-28 01:41:21 +0900
commit83bc2b1307d0e73424437649d26b804f20a83c38 (patch)
tree1abb5a6067a66548917fb6cb38335aafc5cd4fe1
parentUse maleeni v0.5.1 (diff)
downloadurubu-83bc2b1307d0e73424437649d26b804f20a83c38.tar.gz
urubu-83bc2b1307d0e73424437649d26b804f20a83c38.tar.xz
Add name directive to specify a grammar name
-rw-r--r--driver/conflict_test.go14
-rw-r--r--driver/lac_test.go2
-rw-r--r--driver/parser_test.go105
-rw-r--r--driver/semantic_action_test.go4
-rw-r--r--driver/syntax_error_test.go8
-rw-r--r--grammar/first_test.go10
-rw-r--r--grammar/follow_test.go10
-rw-r--r--grammar/grammar.go40
-rw-r--r--grammar/lalr1_test.go2
-rw-r--r--grammar/lr0_test.go4
-rw-r--r--grammar/parsing_table_test.go4
-rw-r--r--grammar/semantic_error.go1
-rw-r--r--grammar/slr1_test.go2
-rw-r--r--spec/grammar.go1
14 files changed, 205 insertions, 2 deletions
diff --git a/driver/conflict_test.go b/driver/conflict_test.go
index 0fa00f6..87b7b17 100644
--- a/driver/conflict_test.go
+++ b/driver/conflict_test.go
@@ -20,6 +20,8 @@ func TestParserWithConflicts(t *testing.T) {
{
caption: "when a shift/reduce conflict occurred, we prioritize the shift action",
specSrc: `
+%name test
+
expr
: expr assign expr
| id
@@ -48,6 +50,8 @@ assign: '=';
{
caption: "when a reduce/reduce conflict occurred, we prioritize the production defined earlier in the grammar",
specSrc: `
+%name test
+
s
: a
| b
@@ -71,6 +75,8 @@ id: "[A-Za-z0-9_]+";
{
caption: "left associativities defined earlier in the grammar have higher precedence",
specSrc: `
+%name test
+
%left mul
%left add
@@ -116,6 +122,8 @@ mul: '*';
{
caption: "left associativities defined in the same line have the same precedence",
specSrc: `
+%name test
+
%left add sub
expr
@@ -160,6 +168,8 @@ sub: '-';
{
caption: "right associativities defined earlier in the grammar have higher precedence",
specSrc: `
+%name test
+
%right r1
%right r2
@@ -206,6 +216,8 @@ id: "[A-Za-z0-9_]+";
{
caption: "right associativities defined in the same line have the same precedence",
specSrc: `
+%name test
+
%right r1 r2
expr
@@ -251,6 +263,8 @@ id: "[A-Za-z0-9_]+";
{
caption: "left and right associativities can be mixed",
specSrc: `
+%name test
+
%left mul div
%left add sub
%right assign
diff --git a/driver/lac_test.go b/driver/lac_test.go
index 6406f95..aa32d05 100644
--- a/driver/lac_test.go
+++ b/driver/lac_test.go
@@ -10,6 +10,8 @@ import (
func TestParserWithLAC(t *testing.T) {
specSrc := `
+%name test
+
S
: C C
;
diff --git a/driver/parser_test.go b/driver/parser_test.go
index af3e9a1..4d31ec1 100644
--- a/driver/parser_test.go
+++ b/driver/parser_test.go
@@ -35,6 +35,8 @@ func TestParser_Parse(t *testing.T) {
}{
{
specSrc: `
+%name test
+
expr
: expr "\+" term
| term
@@ -107,6 +109,8 @@ id: "[A-Za-z_][0-9A-Za-z_]*";
// The driver can reduce productions that have the empty alternative and can generate a CST (and AST) node.
{
specSrc: `
+%name test
+
s
: foo bar
;
@@ -128,6 +132,8 @@ bar_text: "bar";
// The driver can reduce productions that have the empty alternative and can generate a CST (and AST) node.
{
specSrc: `
+%name test
+
s
: foo bar
;
@@ -148,9 +154,48 @@ bar_text: "bar";
),
),
},
+ // `name` is missing.
+ {
+ specSrc: `
+a
+ : foo
+ ;
+foo: "foo";
+`,
+ src: `foo`,
+ specErr: true,
+ },
+ // `name` needs a parameter.
+ {
+ specSrc: `
+%name
+
+a
+ : foo
+ ;
+foo: "foo";
+`,
+ src: `foo`,
+ specErr: true,
+ },
+ // `name` takes just one parameter.
+ {
+ specSrc: `
+%name test foo
+
+a
+ : foo
+ ;
+foo: "foo";
+`,
+ src: `foo`,
+ specErr: true,
+ },
// Production `b` is unused.
{
specSrc: `
+%name test
+
a
: foo
;
@@ -164,6 +209,8 @@ foo: "foo";
// Terminal `bar` is unused.
{
specSrc: `
+%name test
+
s
: foo
;
@@ -176,6 +223,8 @@ bar: "bar";
// Production `b` and terminal `bar` is unused.
{
specSrc: `
+%name test
+
a
: foo
;
@@ -191,6 +240,8 @@ bar: "bar";
// A terminal used in productions cannot have the skip directive.
{
specSrc: `
+%name test
+
a
: foo
;
@@ -201,6 +252,8 @@ foo: "foo" #skip;
},
{
specSrc: `
+%name test
+
mode_tran_seq
: mode_tran_seq mode_tran
| mode_tran
@@ -225,6 +278,8 @@ whitespace: "\u{0020}+" #skip;
},
{
specSrc: `
+%name test
+
s
: foo bar
;
@@ -237,6 +292,8 @@ bar: "bar";
// The parser can skips specified tokens.
{
specSrc: `
+%name test
+
s
: foo bar
;
@@ -249,6 +306,8 @@ white_space: "[\u{0009}\u{0020}]+" #skip;
// A grammar can contain fragments.
{
specSrc: `
+%name test
+
s
: tagline
;
@@ -260,6 +319,8 @@ fragment words: "[A-Za-z\u{0020}]+";
// A grammar can contain ast actions.
{
specSrc: `
+%name test
+
list
: "\[" elems "]" #ast #(list $2...)
;
@@ -295,6 +356,8 @@ id: "[A-Za-z]+";
// The first element of a tree structure must be the same ID as an LHS of a production.
{
specSrc: `
+%name test
+
s
: foo #ast #(start $1)
;
@@ -308,6 +371,8 @@ bar: "bar";
// An ast action cannot be applied to a terminal symbol.
{
specSrc: `
+%name test
+
s
: foo
;
@@ -320,6 +385,8 @@ foo
// The expansion cannot be applied to a terminal symbol.
{
specSrc: `
+%name test
+
s
: foo #ast #(s $1...)
;
@@ -330,6 +397,8 @@ foo: "foo";
// A production must not have a duplicate alternative.
{
specSrc: `
+%name test
+
s
: foo
| foo
@@ -341,6 +410,8 @@ foo: "foo";
// A production must not have a duplicate alternative.
{
specSrc: `
+%name test
+
a
: foo
;
@@ -355,6 +426,8 @@ foo: "foo";
// A production must not have a duplicate alternative.
{
specSrc: `
+%name test
+
a
: foo
;
@@ -372,6 +445,8 @@ bar: "bar";
// A terminal and a non-terminal (start symbol) are duplicates.
{
specSrc: `
+%name test
+
a
: foo
;
@@ -383,6 +458,8 @@ a: "a";
// A terminal and a non-terminal (not start symbol) are duplicates.
{
specSrc: `
+%name test
+
a
: foo
;
@@ -398,6 +475,8 @@ b: "a";
// Invalid associativity type
{
specSrc: `
+%name test
+
%foo
s
@@ -411,6 +490,8 @@ a: 'a';
// Associativity needs at least one symbol.
{
specSrc: `
+%name test
+
%left
s
@@ -424,6 +505,8 @@ a: 'a';
// Associativity cannot take an undefined symbol.
{
specSrc: `
+%name test
+
%left b
s
@@ -437,6 +520,8 @@ a: 'a';
// Associativity cannot take a non-terminal symbol.
{
specSrc: `
+%name test
+
%left s
s
@@ -450,6 +535,8 @@ a: 'a';
// The 'prec' directive can set precedence and associativity of a production.
{
specSrc: `
+%name test
+
%left mul div
%left add sub
@@ -501,6 +588,8 @@ div: '/';
// The 'prec' directive needs an ID parameter.
{
specSrc: `
+%name test
+
s
: a #prec
;
@@ -512,6 +601,8 @@ a: 'a';
// The 'prec' directive cannot take an unknown symbol.
{
specSrc: `
+%name test
+
s
: a #prec foo
;
@@ -523,6 +614,8 @@ a: 'a';
// The 'prec' directive cannot take a non-terminal symbol.
{
specSrc: `
+%name test
+
s
: foo #prec bar
| bar
@@ -542,6 +635,8 @@ b: 'b';
// The grammar can contain the 'error' symbol.
{
specSrc: `
+%name test
+
s
: id id id ';'
| error ';'
@@ -555,6 +650,8 @@ id: "[A-Za-z_]+";
// The grammar can contain the 'recover' directive.
{
specSrc: `
+%name test
+
seq
: seq elem
| elem
@@ -572,6 +669,8 @@ id: "[A-Za-z_]+";
// The 'recover' directive cannot take a parameter.
{
specSrc: `
+%name test
+
seq
: seq elem
| elem
@@ -590,6 +689,8 @@ id: "[A-Za-z_]+";
// You cannot use the error symbol as a non-terminal symbol.
{
specSrc: `
+%name test
+
s
: foo
;
@@ -605,6 +706,8 @@ bar: 'bar';
// You cannot use the error symbol as a terminal symbol.
{
specSrc: `
+%name test
+
s
: foo
| error
@@ -618,6 +721,8 @@ error: 'error';
// You cannot use the error symbol as a terminal symbol, even if given the skip directive.
{
specSrc: `
+%name test
+
s
: foo
;
diff --git a/driver/semantic_action_test.go b/driver/semantic_action_test.go
index 889016b..e36784d 100644
--- a/driver/semantic_action_test.go
+++ b/driver/semantic_action_test.go
@@ -48,6 +48,8 @@ func (a *testSemAct) MissError(cause *mldriver.Token) {
func TestParserWithSemanticAction(t *testing.T) {
specSrcWithErrorProd := `
+%name test
+
seq
: seq elem semicolon
| elem semicolon
@@ -65,6 +67,8 @@ char: "[a-z]";
`
specSrcWithoutErrorProd := `
+%name test
+
seq
: seq elem semicolon
| elem semicolon
diff --git a/driver/syntax_error_test.go b/driver/syntax_error_test.go
index a019b61..579ead1 100644
--- a/driver/syntax_error_test.go
+++ b/driver/syntax_error_test.go
@@ -19,6 +19,8 @@ func TestParserWithSyntaxErrors(t *testing.T) {
{
caption: "the parser can report a syntax error",
specSrc: `
+%name test
+
s
: foo
;
@@ -31,6 +33,8 @@ foo: 'foo';
{
caption: "when the parser reduced a production having the reduce directive, the parser will recover from an error state",
specSrc: `
+%name test
+
seq
: seq elem ';'
| elem ';'
@@ -51,6 +55,8 @@ c: 'c';
{
caption: "After the parser shifts the error symbol, symbols are ignored until a symbol the parser can perform shift appears",
specSrc: `
+%name test
+
seq
: seq elem ';'
| elem ';'
@@ -73,6 +79,8 @@ c: 'c';
{
caption: "when the parser performs shift three times, the parser recovers from the error state",
specSrc: `
+%name test
+
seq
: seq elem ';'
| elem ';'
diff --git a/grammar/first_test.go b/grammar/first_test.go
index 041f411..578825e 100644
--- a/grammar/first_test.go
+++ b/grammar/first_test.go
@@ -24,6 +24,8 @@ func TestGenFirst(t *testing.T) {
{
caption: "productions contain only non-empty productions",
src: `
+%name test
+
expr
: expr add term
| term
@@ -61,6 +63,8 @@ id: "[A-Za-z_][0-9A-Za-z_]*";
{
caption: "productions contain the empty start production",
src: `
+%name test
+
s
:
;
@@ -73,6 +77,8 @@ s
{
caption: "productions contain an empty production",
src: `
+%name test
+
s
: foo bar
;
@@ -90,6 +96,8 @@ bar: "bar";
{
caption: "a start production contains a non-empty alternative and empty alternative",
src: `
+%name test
+
s
: foo
|
@@ -105,6 +113,8 @@ foo: "foo";
{
caption: "a production contains non-empty alternative and empty alternative",
src: `
+%name test
+
s
: foo
;
diff --git a/grammar/follow_test.go b/grammar/follow_test.go
index 3500d14..ba2d973 100644
--- a/grammar/follow_test.go
+++ b/grammar/follow_test.go
@@ -22,6 +22,8 @@ func TestFollowSet(t *testing.T) {
{
caption: "productions contain only non-empty productions",
src: `
+%name test
+
expr
: expr add term
| term
@@ -50,6 +52,8 @@ id: "[A-Za-z_][0-9A-Za-z_]*";
{
caption: "productions contain an empty start production",
src: `
+%name test
+
s
:
;
@@ -62,6 +66,8 @@ s
{
caption: "productions contain an empty production",
src: `
+%name test
+
s
: foo
;
@@ -78,6 +84,8 @@ foo
{
caption: "a start production contains a non-empty alternative and empty alternative",
src: `
+%name test
+
s
: foo
|
@@ -92,6 +100,8 @@ foo: "foo";
{
caption: "a production contains non-empty alternative and empty alternative",
src: `
+%name test
+
s
: foo
;
diff --git a/grammar/grammar.go b/grammar/grammar.go
index 39a10a8..2294219 100644
--- a/grammar/grammar.go
+++ b/grammar/grammar.go
@@ -82,6 +82,7 @@ func (pa *precAndAssoc) productionAssociativity(prod productionNum) assocType {
const reservedSymbolNameError = "error"
type Grammar struct {
+ name string
lexSpec *mlspec.LexSpec
skipLexKinds []mlspec.LexKindName
kindAliases map[symbol]string
@@ -104,6 +105,37 @@ type GrammarBuilder struct {
}
func (b *GrammarBuilder) Build() (*Grammar, error) {
+ var specName string
+ {
+ errOccurred := false
+ for _, md := range b.AST.MetaData {
+ if md.Name != "name" {
+ continue
+ }
+
+ if len(md.Parameters) != 1 || md.Parameters[0].ID == "" {
+ b.errs = append(b.errs, &verr.SpecError{
+ Cause: semErrMDInvalidParam,
+ Detail: fmt.Sprintf("'name' takes just one ID parameter"),
+ Row: md.Pos.Row,
+ Col: md.Pos.Col,
+ })
+
+ errOccurred = true
+ break
+ }
+
+ specName = md.Parameters[0].ID
+ break
+ }
+
+ if specName == "" && !errOccurred {
+ b.errs = append(b.errs, &verr.SpecError{
+ Cause: semErrMDMissingName,
+ })
+ }
+ }
+
symTabAndLexSpec, err := b.genSymbolTableAndLexSpec(b.AST)
if err != nil {
return nil, err
@@ -167,10 +199,10 @@ func (b *GrammarBuilder) Build() (*Grammar, error) {
return nil, b.errs
}
- // FIXME
- symTabAndLexSpec.lexSpec.Name = "lex"
+ symTabAndLexSpec.lexSpec.Name = specName
return &Grammar{
+ name: specName,
lexSpec: symTabAndLexSpec.lexSpec,
skipLexKinds: symTabAndLexSpec.skip,
kindAliases: symTabAndLexSpec.aliases,
@@ -868,6 +900,9 @@ func (b *GrammarBuilder) genPrecAndAssoc(symTab *symbolTable, prods *productionS
assocTy = assocTypeLeft
case "right":
assocTy = assocTypeRight
+ case "name":
+ // Since `name` is used for a purpose other than priority, we will ignore it here.
+ continue
default:
return nil, &verr.SpecError{
Cause: semErrMDInvalidName,
@@ -1150,6 +1185,7 @@ func Compile(gram *Grammar, opts ...CompileOption) (*spec.CompiledGrammar, error
}
return &spec.CompiledGrammar{
+ Name: gram.name,
LexicalSpecification: &spec.LexicalSpecification{
Lexer: "maleeni",
Maleeni: &spec.Maleeni{
diff --git a/grammar/lalr1_test.go b/grammar/lalr1_test.go
index 1cf8762..beb2707 100644
--- a/grammar/lalr1_test.go
+++ b/grammar/lalr1_test.go
@@ -10,6 +10,8 @@ import (
func TestGenLALR1Automaton(t *testing.T) {
// This grammar belongs to LALR(1) class, not SLR(1).
src := `
+%name test
+
S: L eq R | R;
L: ref R | id;
R: L;
diff --git a/grammar/lr0_test.go b/grammar/lr0_test.go
index 4a613dd..cde3f0a 100644
--- a/grammar/lr0_test.go
+++ b/grammar/lr0_test.go
@@ -17,6 +17,8 @@ type expectedLRState struct {
func TestGenLR0Automaton(t *testing.T) {
src := `
+%name test
+
expr
: expr add term
| term
@@ -225,6 +227,8 @@ id: "[A-Za-z_][0-9A-Za-z_]*";
func TestLR0AutomatonContainingEmptyProduction(t *testing.T) {
src := `
+%name test
+
s
: foo bar
;
diff --git a/grammar/parsing_table_test.go b/grammar/parsing_table_test.go
index feec74a..833a4d4 100644
--- a/grammar/parsing_table_test.go
+++ b/grammar/parsing_table_test.go
@@ -16,6 +16,8 @@ type expectedState struct {
func TestGenLALRParsingTable(t *testing.T) {
src := `
+%name test
+
S: L eq R | R;
L: ref R | id;
R: L;
@@ -286,6 +288,8 @@ id: "[A-Za-z0-9_]+";
func TestGenSLRParsingTable(t *testing.T) {
src := `
+%name test
+
expr
: expr add term
| term
diff --git a/grammar/semantic_error.go b/grammar/semantic_error.go
index c4ab9f6..d540c03 100644
--- a/grammar/semantic_error.go
+++ b/grammar/semantic_error.go
@@ -17,6 +17,7 @@ func (e *SemanticError) Error() string {
var (
semErrMDInvalidName = newSemanticError("invalid meta data name")
semErrMDInvalidParam = newSemanticError("invalid parameter")
+ semErrMDMissingName = newSemanticError("name is missing")
semErrUnusedProduction = newSemanticError("unused production")
semErrUnusedTerminal = newSemanticError("unused terminal")
semErrTermCannotBeSkipped = newSemanticError("a terminal used in productions cannot be skipped")
diff --git a/grammar/slr1_test.go b/grammar/slr1_test.go
index c773c6a..954446f 100644
--- a/grammar/slr1_test.go
+++ b/grammar/slr1_test.go
@@ -9,6 +9,8 @@ import (
func TestGenSLR1Automaton(t *testing.T) {
src := `
+%name test
+
expr
: expr add term
| term
diff --git a/spec/grammar.go b/spec/grammar.go
index 825fc2c..bf14d86 100644
--- a/spec/grammar.go
+++ b/spec/grammar.go
@@ -3,6 +3,7 @@ package spec
import mlspec "github.com/nihei9/maleeni/spec"
type CompiledGrammar struct {
+ Name string `json:"name"`
LexicalSpecification *LexicalSpecification `json:"lexical_specification"`
ParsingTable *ParsingTable `json:"parsing_table"`
ASTAction *ASTAction `json:"ast_action"`