Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions lib/lrama/grammar/code/rule_action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ class RuleAction < Code
#
# @rbs!
# @rule: Rule
# @grammar: Grammar

# @rbs (type: ::Symbol, token_code: Lexer::Token::UserCode, rule: Rule) -> void
def initialize(type:, token_code:, rule:)
# @rbs (type: ::Symbol, token_code: Lexer::Token::UserCode, rule: Rule, grammar: Grammar) -> void
def initialize(type:, token_code:, rule:, grammar:)
super(type: type, token_code: token_code)
@rule = rule
@grammar = grammar
end

private
Expand Down Expand Up @@ -53,19 +55,31 @@ def reference_to_c(ref)
case
when ref.type == :dollar && ref.name == "$" # $$
tag = ref.ex_tag || lhs.tag
raise_tag_not_found_error(ref) unless tag
# @type var tag: Lexer::Token::Tag
"(yyval.#{tag.member})"
if tag
# @type var tag: Lexer::Token::Tag
"(yyval.#{tag.member})"
elsif union_not_defined?
# When %union is not defined, YYSTYPE defaults to int
"(yyval)"
else
raise_tag_not_found_error(ref)
end
when ref.type == :at && ref.name == "$" # @$
"(yyloc)"
when ref.type == :index && ref.name == "$" # $:$
raise "$:$ is not supported"
when ref.type == :dollar # $n
i = -position_in_rhs + ref.index
tag = ref.ex_tag || rhs[ref.index - 1].tag
raise_tag_not_found_error(ref) unless tag
# @type var tag: Lexer::Token::Tag
"(yyvsp[#{i}].#{tag.member})"
if tag
# @type var tag: Lexer::Token::Tag
"(yyvsp[#{i}].#{tag.member})"
elsif union_not_defined?
# When %union is not defined, YYSTYPE defaults to int
"(yyvsp[#{i}])"
else
raise_tag_not_found_error(ref)
end
when ref.type == :at # @n
i = -position_in_rhs + ref.index
"(yylsp[#{i}])"
Expand Down Expand Up @@ -99,6 +113,11 @@ def lhs
@rule.lhs
end

# @rbs () -> bool
def union_not_defined?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's more straightforward to check if %union is used in the input grammar file (by checking @grammar.union), what do you think about it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. I push the following commit. Is my understanding correct?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!

@grammar.union.nil?
end

# @rbs (Reference ref) -> bot
def raise_tag_not_found_error(ref)
raise "Tag is not specified for '$#{ref.value}' in '#{@rule.display_name}'"
Expand Down
6 changes: 3 additions & 3 deletions lib/lrama/grammar/rule.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,11 @@ def initial_rule?
id == 0
end

# @rbs () -> String?
def translated_code
# @rbs (Grammar grammar) -> String?
def translated_code(grammar)
return nil unless token_code

Code::RuleAction.new(type: :rule_action, token_code: token_code, rule: self).translated_code
Code::RuleAction.new(type: :rule_action, token_code: token_code, rule: self, grammar: grammar).translated_code
end

# @rbs () -> bool
Expand Down
2 changes: 1 addition & 1 deletion lib/lrama/output.rb
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ def user_actions
<<-STR
case #{rule.id + 1}: /* #{rule.as_comment} */
#line #{code.line} "#{@grammar_file_path}"
#{spaces}{#{rule.translated_code}}
#{spaces}{#{rule.translated_code(@grammar)}}
#line [@oline@] [@ofile@]
break;
Expand Down
9 changes: 7 additions & 2 deletions sig/generated/lrama/grammar/code/rule_action.rbs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions sig/generated/lrama/grammar/rule.rbs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions spec/fixtures/common/no_union.y
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Test case for parser without %union directive
*/

%{
// Prologue
%}

%token NUMBER
%token PLUS
%token MINUS

%%

program: expr
;

expr: NUMBER
| expr PLUS NUMBER
| expr MINUS NUMBER
;

%%

// Epilogue
27 changes: 27 additions & 0 deletions spec/fixtures/common/no_union_with_type.y
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Test case for parser without %union but with typed tokens
*/

%{
// Prologue
%}

%token <val> NUMBER
%token PLUS
%token MINUS

%type <val> expr

%%

program: expr
;

expr: NUMBER
| expr PLUS NUMBER
| expr MINUS NUMBER
;

%%

// Epilogue
40 changes: 40 additions & 0 deletions spec/fixtures/integration/no_union.l
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
%option noinput nounput noyywrap never-interactive bison-bridge bison-locations

%{

#include <stdio.h>
#include <stdlib.h>
#include "no_union.h"

%}

NUMBER [0-9]+

%%

{NUMBER} {
((void) yylloc);
*yylval = atoi(yytext);
return NUMBER;
}

[+\-] {
return yytext[0];
}

[\n|\r\n] {
return(YYEOF);
}

[[:space:]] {}

<<EOF>> {
return(YYEOF);
}

. {
fprintf(stderr, "Illegal character '%s'\n", yytext);
return(YYEOF);
}

%%
49 changes: 49 additions & 0 deletions spec/fixtures/integration/no_union.y
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Integration test for parser without %union directive
* This test verifies that lrama can generate parsers without %union,
* just like Bison does (YYSTYPE defaults to int).
*/

%{
#include <stdio.h>
#include "no_union.h"
#include "no_union-lexer.h"

static int yyerror(YYLTYPE *loc, const char *str);
%}

%token NUMBER

%locations

%%

program: /* empty */
| expr { printf("=> %d\n", $1); }
;

expr: NUMBER
| expr '+' NUMBER { $$ = $1 + $3; }
| expr '-' NUMBER { $$ = $1 - $3; }
;

%%

static int yyerror(YYLTYPE *loc, const char *str)
{
fprintf(stderr, "%d.%d-%d.%d: %s\n", loc->first_line, loc->first_column, loc->last_line, loc->last_column, str);
return 0;
}

int main(int argc, char *argv[])
{
if (argc == 2) {
yy_scan_string(argv[1]);
}

if (yyparse()) {
fprintf(stderr, "syntax error\n");
return 1;
}
return 0;
}
36 changes: 18 additions & 18 deletions spec/lrama/grammar/code_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -248,46 +248,46 @@
describe "#translated_code" do
it "translates '$$' to '(yyval)' with member" do
code = grammar.rules.find {|r| r.lhs.id.s_value == "rule1" }
expect(code.translated_code).to eq(" (yyval.rule1) = 0; ")
expect(code.translated_code(grammar)).to eq(" (yyval.rule1) = 0; ")
end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

code = grammar.rules.find {|r| r.lhs.id.s_value == "rule12" }
expect(code.translated_code).to eq(" (-2 - 1); (-1 - 1); (0 - 1); ")
expect(code.translated_code(grammar)).to eq(" (-2 - 1); (-1 - 1); (0 - 1); ")
end
end
end
Expand Down
6 changes: 6 additions & 0 deletions spec/lrama/integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ def generate_object(grammar_file_path, c_path, obj_path, command_args: [])
end
end

describe "parser without %union (YYSTYPE defaults to int)" do
it "returns 6 for '1 + 2 + 3'" do
test_parser("no_union", "1 + 2 + 3", "=> 6\n")
end
end

it "prologue and epilogue are optional" do
test_parser("prologue_epilogue_optional", "", "")
end
Expand Down
Loading