Skip to content

Commit b5eba1e

Browse files
authored
Merge pull request #765 from ydah/non-need-union
Support parser generation without %union directive (Bison compatibility)
2 parents b10daa7 + 880859c commit b5eba1e

12 files changed

Lines changed: 236 additions & 34 deletions

File tree

lib/lrama/grammar/code/rule_action.rb

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ class RuleAction < Code
1111
#
1212
# @rbs!
1313
# @rule: Rule
14+
# @grammar: Grammar
1415

15-
# @rbs (type: ::Symbol, token_code: Lexer::Token::UserCode, rule: Rule) -> void
16-
def initialize(type:, token_code:, rule:)
16+
# @rbs (type: ::Symbol, token_code: Lexer::Token::UserCode, rule: Rule, grammar: Grammar) -> void
17+
def initialize(type:, token_code:, rule:, grammar:)
1718
super(type: type, token_code: token_code)
1819
@rule = rule
20+
@grammar = grammar
1921
end
2022

2123
private
@@ -53,19 +55,31 @@ def reference_to_c(ref)
5355
case
5456
when ref.type == :dollar && ref.name == "$" # $$
5557
tag = ref.ex_tag || lhs.tag
56-
raise_tag_not_found_error(ref) unless tag
57-
# @type var tag: Lexer::Token::Tag
58-
"(yyval.#{tag.member})"
58+
if tag
59+
# @type var tag: Lexer::Token::Tag
60+
"(yyval.#{tag.member})"
61+
elsif union_not_defined?
62+
# When %union is not defined, YYSTYPE defaults to int
63+
"(yyval)"
64+
else
65+
raise_tag_not_found_error(ref)
66+
end
5967
when ref.type == :at && ref.name == "$" # @$
6068
"(yyloc)"
6169
when ref.type == :index && ref.name == "$" # $:$
6270
raise "$:$ is not supported"
6371
when ref.type == :dollar # $n
6472
i = -position_in_rhs + ref.index
6573
tag = ref.ex_tag || rhs[ref.index - 1].tag
66-
raise_tag_not_found_error(ref) unless tag
67-
# @type var tag: Lexer::Token::Tag
68-
"(yyvsp[#{i}].#{tag.member})"
74+
if tag
75+
# @type var tag: Lexer::Token::Tag
76+
"(yyvsp[#{i}].#{tag.member})"
77+
elsif union_not_defined?
78+
# When %union is not defined, YYSTYPE defaults to int
79+
"(yyvsp[#{i}])"
80+
else
81+
raise_tag_not_found_error(ref)
82+
end
6983
when ref.type == :at # @n
7084
i = -position_in_rhs + ref.index
7185
"(yylsp[#{i}])"
@@ -99,6 +113,11 @@ def lhs
99113
@rule.lhs
100114
end
101115

116+
# @rbs () -> bool
117+
def union_not_defined?
118+
@grammar.union.nil?
119+
end
120+
102121
# @rbs (Reference ref) -> bot
103122
def raise_tag_not_found_error(ref)
104123
raise "Tag is not specified for '$#{ref.value}' in '#{@rule.display_name}'"

lib/lrama/grammar/rule.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@ def initial_rule?
104104
id == 0
105105
end
106106

107-
# @rbs () -> String?
108-
def translated_code
107+
# @rbs (Grammar grammar) -> String?
108+
def translated_code(grammar)
109109
return nil unless token_code
110110

111-
Code::RuleAction.new(type: :rule_action, token_code: token_code, rule: self).translated_code
111+
Code::RuleAction.new(type: :rule_action, token_code: token_code, rule: self, grammar: grammar).translated_code
112112
end
113113

114114
# @rbs () -> bool

lib/lrama/output.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def user_actions
246246
<<-STR
247247
case #{rule.id + 1}: /* #{rule.as_comment} */
248248
#line #{code.line} "#{@grammar_file_path}"
249-
#{spaces}{#{rule.translated_code}}
249+
#{spaces}{#{rule.translated_code(@grammar)}}
250250
#line [@oline@] [@ofile@]
251251
break;
252252

sig/generated/lrama/grammar/code/rule_action.rbs

Lines changed: 7 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sig/generated/lrama/grammar/rule.rbs

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

spec/fixtures/common/no_union.y

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Test case for parser without %union directive
3+
*/
4+
5+
%{
6+
// Prologue
7+
%}
8+
9+
%token NUMBER
10+
%token PLUS
11+
%token MINUS
12+
13+
%%
14+
15+
program: expr
16+
;
17+
18+
expr: NUMBER
19+
| expr PLUS NUMBER
20+
| expr MINUS NUMBER
21+
;
22+
23+
%%
24+
25+
// Epilogue
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Test case for parser without %union but with typed tokens
3+
*/
4+
5+
%{
6+
// Prologue
7+
%}
8+
9+
%token <val> NUMBER
10+
%token PLUS
11+
%token MINUS
12+
13+
%type <val> expr
14+
15+
%%
16+
17+
program: expr
18+
;
19+
20+
expr: NUMBER
21+
| expr PLUS NUMBER
22+
| expr MINUS NUMBER
23+
;
24+
25+
%%
26+
27+
// Epilogue
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
%option noinput nounput noyywrap never-interactive bison-bridge bison-locations
2+
3+
%{
4+
5+
#include <stdio.h>
6+
#include <stdlib.h>
7+
#include "no_union.h"
8+
9+
%}
10+
11+
NUMBER [0-9]+
12+
13+
%%
14+
15+
{NUMBER} {
16+
((void) yylloc);
17+
*yylval = atoi(yytext);
18+
return NUMBER;
19+
}
20+
21+
[+\-] {
22+
return yytext[0];
23+
}
24+
25+
[\n|\r\n] {
26+
return(YYEOF);
27+
}
28+
29+
[[:space:]] {}
30+
31+
<<EOF>> {
32+
return(YYEOF);
33+
}
34+
35+
. {
36+
fprintf(stderr, "Illegal character '%s'\n", yytext);
37+
return(YYEOF);
38+
}
39+
40+
%%
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Integration test for parser without %union directive
3+
* This test verifies that lrama can generate parsers without %union,
4+
* just like Bison does (YYSTYPE defaults to int).
5+
*/
6+
7+
%{
8+
#include <stdio.h>
9+
#include "no_union.h"
10+
#include "no_union-lexer.h"
11+
12+
static int yyerror(YYLTYPE *loc, const char *str);
13+
%}
14+
15+
%token NUMBER
16+
17+
%locations
18+
19+
%%
20+
21+
program: /* empty */
22+
| expr { printf("=> %d\n", $1); }
23+
;
24+
25+
expr: NUMBER
26+
| expr '+' NUMBER { $$ = $1 + $3; }
27+
| expr '-' NUMBER { $$ = $1 - $3; }
28+
;
29+
30+
%%
31+
32+
static int yyerror(YYLTYPE *loc, const char *str)
33+
{
34+
fprintf(stderr, "%d.%d-%d.%d: %s\n", loc->first_line, loc->first_column, loc->last_line, loc->last_column, str);
35+
return 0;
36+
}
37+
38+
int main(int argc, char *argv[])
39+
{
40+
if (argc == 2) {
41+
yy_scan_string(argv[1]);
42+
}
43+
44+
if (yyparse()) {
45+
fprintf(stderr, "syntax error\n");
46+
return 1;
47+
}
48+
return 0;
49+
}

spec/lrama/grammar/code_spec.rb

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -248,46 +248,46 @@
248248
describe "#translated_code" do
249249
it "translates '$$' to '(yyval)' with member" do
250250
code = grammar.rules.find {|r| r.lhs.id.s_value == "rule1" }
251-
expect(code.translated_code).to eq(" (yyval.rule1) = 0; ")
251+
expect(code.translated_code(grammar)).to eq(" (yyval.rule1) = 0; ")
252252
end
253253

254254
it "translates '@$' to '(yyloc)'" do
255255
code = grammar.rules.find {|r| r.lhs.id.s_value == "rule2" }
256-
expect(code.translated_code).to eq(" (yyloc) = 0; ")
256+
expect(code.translated_code(grammar)).to eq(" (yyloc) = 0; ")
257257
end
258258

259259
it "translates '$n' to '(yyvsp)' with index and member" do
260260
code = grammar.rules.find {|r| r.lhs.id.s_value == "rule3" }
261-
expect(code.translated_code).to eq(" (yyvsp[-2].expr) + (yyvsp[0].expr); ")
261+
expect(code.translated_code(grammar)).to eq(" (yyvsp[-2].expr) + (yyvsp[0].expr); ")
262262
end
263263

264264
it "translates '@n' to '(yylsp)' with index" do
265265
code = grammar.rules.find {|r| r.lhs.id.s_value == "rule4" }
266-
expect(code.translated_code).to eq(" (yylsp[-2]) + (yylsp[0]); (yylsp[-3]); ")
266+
expect(code.translated_code(grammar)).to eq(" (yylsp[-2]) + (yylsp[0]); (yylsp[-3]); ")
267267
end
268268

269269
it "respects explicit tag in a rule" do
270270
code = grammar.rules.find {|r| r.lhs.id.s_value == "rule5" }
271-
expect(code.translated_code).to eq(" (yyvsp[-2].expr) + (yyvsp[0].integer); ")
271+
expect(code.translated_code(grammar)).to eq(" (yyvsp[-2].expr) + (yyvsp[0].integer); ")
272272
end
273273

274274
context "midrule action exists" do
275275
it "uses index on the original rule (-1)" do
276276
# midrule action in rule6
277277
code = grammar.rules.find {|r| r.lhs.id.s_value == "$@1" }
278-
expect(code.translated_code).to eq(" (yyval.integer) = (yyvsp[-1].expr); (yyloc) = (yylsp[-1]); ")
278+
expect(code.translated_code(grammar)).to eq(" (yyval.integer) = (yyvsp[-1].expr); (yyloc) = (yylsp[-1]); ")
279279

280280
code = grammar.rules.find {|r| r.lhs.id.s_value == "rule6" }
281-
expect(code.translated_code).to eq(" (yyvsp[-3].expr) + (yyvsp[0].integer); ")
281+
expect(code.translated_code(grammar)).to eq(" (yyvsp[-3].expr) + (yyvsp[0].integer); ")
282282
end
283283

284284
it "uses an explicit tag for type casting" do
285285
# midrule action in rule13
286286
code = grammar.rules.find {|r| r.lhs.id.s_value == "@5" }
287-
expect(code.translated_code).to eq(" (yyval.integer) = (yyvsp[-1].expr); (yyloc) = (yylsp[-1]); ")
287+
expect(code.translated_code(grammar)).to eq(" (yyval.integer) = (yyvsp[-1].expr); (yyloc) = (yylsp[-1]); ")
288288

289289
code = grammar.rules.find {|r| r.lhs.id.s_value == "rule13" }
290-
expect(code.translated_code).to eq(" (yyvsp[-3].expr) + (yyvsp[-1].integer); ")
290+
expect(code.translated_code(grammar)).to eq(" (yyvsp[-3].expr) + (yyvsp[-1].integer); ")
291291
end
292292
end
293293

@@ -296,38 +296,38 @@
296296
# midrule action in rule7
297297
# rule7 has tag
298298
code = grammar.rules.find {|r| r.lhs.id.s_value == "@2" }
299-
expect { code.translated_code }.to raise_error("Tag is not specified for '$$' in '@2 -> ε'")
299+
expect { code.translated_code(grammar) }.to raise_error("Tag is not specified for '$$' in '@2 -> ε'")
300300

301301
code = grammar.rules.find {|r| r.lhs.id.s_value == "rule7" }
302-
expect { code.translated_code }.to raise_error("Tag is not specified for '$2' in 'rule7 -> expr @2 '+' expr'")
302+
expect { code.translated_code(grammar) }.to raise_error("Tag is not specified for '$2' in 'rule7 -> expr @2 '+' expr'")
303303

304304
# midrule action in rule8
305305
# rule8 has no tag
306306
code = grammar.rules.find {|r| r.lhs.id.s_value == "@3" }
307-
expect { code.translated_code }.to raise_error("Tag is not specified for '$$' in '@3 -> ε'")
307+
expect { code.translated_code(grammar) }.to raise_error("Tag is not specified for '$$' in '@3 -> ε'")
308308

309309
code = grammar.rules.find {|r| r.lhs.id.s_value == "rule8" }
310-
expect { code.translated_code }.to raise_error("Tag is not specified for '$2' in 'rule8 -> expr @3 '+' expr'")
310+
expect { code.translated_code(grammar) }.to raise_error("Tag is not specified for '$2' in 'rule8 -> expr @3 '+' expr'")
311311
end
312312
end
313313

314314
context "$: is used" do
315315
it "translates '$:$' to '-yylen' and '$:n' to index from the last of array" do
316316
code = grammar.rules.find {|r| r.lhs.id.s_value == "rule9" }
317-
expect(code.translated_code).to eq(" (-2 - 1); (-1 - 1); (0 - 1); ")
317+
expect(code.translated_code(grammar)).to eq(" (-2 - 1); (-1 - 1); (0 - 1); ")
318318

319319
code = grammar.rules.find {|r| r.lhs.id.s_value == "rule10" }
320-
expect { code.translated_code }.to raise_error("$:$ is not supported")
320+
expect { code.translated_code(grammar) }.to raise_error("$:$ is not supported")
321321

322322
# midrule action in rule11
323323
code = grammar.rules.find {|r| r.lhs.id.s_value == "@4" }
324-
expect(code.translated_code).to eq(" (0 - 1); ")
324+
expect(code.translated_code(grammar)).to eq(" (0 - 1); ")
325325

326326
code = grammar.rules.find {|r| r.lhs.id.s_value == "rule11" }
327-
expect(code.translated_code).to eq(" (-3 - 1); (-2 - 1); (-1 - 1); (0 - 1); ")
327+
expect(code.translated_code(grammar)).to eq(" (-3 - 1); (-2 - 1); (-1 - 1); (0 - 1); ")
328328

329329
code = grammar.rules.find {|r| r.lhs.id.s_value == "rule12" }
330-
expect(code.translated_code).to eq(" (-2 - 1); (-1 - 1); (0 - 1); ")
330+
expect(code.translated_code(grammar)).to eq(" (-2 - 1); (-1 - 1); (0 - 1); ")
331331
end
332332
end
333333
end

0 commit comments

Comments
 (0)