diff options
author | Ryo Nihei <nihei.dev@gmail.com> | 2021-10-28 01:41:21 +0900 |
---|---|---|
committer | Ryo Nihei <nihei.dev@gmail.com> | 2021-10-28 01:41:21 +0900 |
commit | 83bc2b1307d0e73424437649d26b804f20a83c38 (patch) | |
tree | 1abb5a6067a66548917fb6cb38335aafc5cd4fe1 | |
parent | Use maleeni v0.5.1 (diff) | |
download | urubu-83bc2b1307d0e73424437649d26b804f20a83c38.tar.gz urubu-83bc2b1307d0e73424437649d26b804f20a83c38.tar.xz |
Add name directive to specify a grammar name
-rw-r--r-- | driver/conflict_test.go | 14 | ||||
-rw-r--r-- | driver/lac_test.go | 2 | ||||
-rw-r--r-- | driver/parser_test.go | 105 | ||||
-rw-r--r-- | driver/semantic_action_test.go | 4 | ||||
-rw-r--r-- | driver/syntax_error_test.go | 8 | ||||
-rw-r--r-- | grammar/first_test.go | 10 | ||||
-rw-r--r-- | grammar/follow_test.go | 10 | ||||
-rw-r--r-- | grammar/grammar.go | 40 | ||||
-rw-r--r-- | grammar/lalr1_test.go | 2 | ||||
-rw-r--r-- | grammar/lr0_test.go | 4 | ||||
-rw-r--r-- | grammar/parsing_table_test.go | 4 | ||||
-rw-r--r-- | grammar/semantic_error.go | 1 | ||||
-rw-r--r-- | grammar/slr1_test.go | 2 | ||||
-rw-r--r-- | spec/grammar.go | 1 |
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"` |