From 48dfb08df20dfcf63f4f188e4c717ad3917b7908 Mon Sep 17 00:00:00 2001 From: ydah Date: Wed, 21 Oct 2026 20:09:35 +0900 Subject: [PATCH] Preserve all lexer and parser parameters --- lib/lrama/grammar.rb | 14 ++-- lib/lrama/output.rb | 36 +++------- sig/generated/lrama/grammar.rbs | 4 +- spec/fixtures/integration/params.l | 4 +- spec/fixtures/integration/params.y | 30 ++++++--- spec/lrama/integration_spec.rb | 6 +- spec/lrama/output_spec.rb | 101 ++++++++++++++++++++--------- spec/lrama/parser_spec.rb | 8 +-- 8 files changed, 121 insertions(+), 82 deletions(-) diff --git a/lib/lrama/grammar.rb b/lib/lrama/grammar.rb index 974f49eab..2005fbc21 100644 --- a/lib/lrama/grammar.rb +++ b/lib/lrama/grammar.rb @@ -85,8 +85,8 @@ class Grammar attr_accessor :expect #: Integer attr_accessor :printers #: Array[Printer] attr_accessor :error_tokens #: Array[ErrorToken] - attr_accessor :lex_param #: String - attr_accessor :parse_param #: String + attr_reader :lex_params #: Array[String] + attr_reader :parse_params #: Array[String] attr_accessor :initial_action #: Grammar::Code::InitialActionCode attr_accessor :after_shift #: Lexer::Token::Base attr_accessor :before_reduce #: Lexer::Token::Base @@ -119,6 +119,8 @@ def initialize(rule_counter, midrule_action_counter, locations, define = {}) @printers = [] @destructors = [] @error_tokens = [] + @lex_params = [] + @parse_params = [] @symbols_resolver = Grammar::Symbols::Resolver.new @types = [] @rule_builders = [] @@ -335,16 +337,16 @@ def _evaluate_nodes when Node::ParamDecl node.params.each do |param| code = Grammar::Code::NoReferenceCode.new(type: :param, token_code: param).token_code.s_value - self.lex_param = code - self.parse_param = code + lex_params << code + parse_params << code end when Node::LexParamDecl node.params.each do |param| - self.lex_param = Grammar::Code::NoReferenceCode.new(type: :lex_param, token_code: param).token_code.s_value + lex_params << Grammar::Code::NoReferenceCode.new(type: :lex_param, token_code: param).token_code.s_value end when Node::ParseParamDecl node.params.each do |param| - self.parse_param = Grammar::Code::NoReferenceCode.new(type: :parse_param, token_code: param).token_code.s_value + parse_params << Grammar::Code::NoReferenceCode.new(type: :parse_param, token_code: param).token_code.s_value end when Node::UnionDecl self.set_union( diff --git a/lib/lrama/output.rb b/lib/lrama/output.rb index 24cf725c7..a6038a7b4 100644 --- a/lib/lrama/output.rb +++ b/lib/lrama/output.rb @@ -265,24 +265,16 @@ def omit_blanks(param) # b4_parse_param def parse_param - if @grammar.parse_param - omit_blanks(@grammar.parse_param) - else - "" - end + @grammar.parse_params.map { |param| omit_blanks(param) }.join(", ") end def lex_param - if @grammar.lex_param - omit_blanks(@grammar.lex_param) - else - "" - end + @grammar.lex_params.map { |param| omit_blanks(param) }.join(", ") end # b4_user_formals def user_formals - if @grammar.parse_param + if @grammar.parse_params.any? ", #{parse_param}" else "" @@ -291,7 +283,7 @@ def user_formals # b4_user_args def user_args - if @grammar.parse_param + if @grammar.parse_params.any? ", #{parse_param_name}" else "" @@ -303,19 +295,11 @@ def extract_param_name(param) end def parse_param_name - if @grammar.parse_param - extract_param_name(parse_param) - else - "" - end + @grammar.parse_params.map { |param| extract_param_name(param) }.join(", ") end def lex_param_name - if @grammar.lex_param - extract_param_name(lex_param) - else - "" - end + @grammar.lex_params.map { |param| extract_param_name(param) }.join(", ") end # b4_parse_param_use @@ -325,9 +309,7 @@ def parse_param_use(val, loc) YY_USE (#{loc}); STR - if @grammar.parse_param - str << " YY_USE (#{parse_param_name});" - end + str << @grammar.parse_params.map { |param| " YY_USE (#{extract_param_name(param)});" }.join("\n") str end @@ -337,7 +319,7 @@ def yylex_formals ary = ["&yylval"] ary << "&yylloc" if @grammar.locations - if @grammar.lex_param + if @grammar.lex_params.any? ary << lex_param_name end @@ -357,7 +339,7 @@ def table_value_equals(table, value, literal, symbol) def yyerror_args ary = ["&yylloc"] - if @grammar.parse_param + if @grammar.parse_params.any? ary << parse_param_name end diff --git a/sig/generated/lrama/grammar.rbs b/sig/generated/lrama/grammar.rbs index 5852949a7..067e13e64 100644 --- a/sig/generated/lrama/grammar.rbs +++ b/sig/generated/lrama/grammar.rbs @@ -104,9 +104,9 @@ module Lrama attr_accessor error_tokens: Array[ErrorToken] - attr_accessor lex_param: String + attr_reader lex_params: Array[String] - attr_accessor parse_param: String + attr_reader parse_params: Array[String] attr_accessor initial_action: Grammar::Code::InitialActionCode diff --git a/spec/fixtures/integration/params.l b/spec/fixtures/integration/params.l index 5fc6c60f2..fdaf1e3dd 100644 --- a/spec/fixtures/integration/params.l +++ b/spec/fixtures/integration/params.l @@ -6,7 +6,7 @@ #include #include "params.h" -#define YY_DECL int yylex (YYSTYPE *yylval, YYLTYPE *yylloc, int parser_params) +#define YY_DECL int yylex (YYSTYPE *yylval, YYLTYPE *yylloc, int lexer_value, int shared, int offset, int *errors) int yycolumn = 0; @@ -23,6 +23,8 @@ NUMBER [0-9]+ %% {NUMBER} { + if (lexer_value != 7 || shared != 5 || offset != 11 || *errors) + return YYUNDEF; yylval->val = atoi(yytext); return NUM; } diff --git a/spec/fixtures/integration/params.y b/spec/fixtures/integration/params.y index 256d50516..2d194f22e 100644 --- a/spec/fixtures/integration/params.y +++ b/spec/fixtures/integration/params.y @@ -2,19 +2,23 @@ #define YYDEBUG 1 #include -#define YY_LOCATION_PRINT(File, loc, p) ((void) 0) +#define YY_LOCATION_PRINT(File, loc, ...) ((void) 0) -#define YY_DECL yylex (YYSTYPE *lval, YYLTYPE *yylloc, int parser_params) +#define YY_DECL yylex (YYSTYPE *lval, YYLTYPE *yylloc, int lexer_value, int shared, int offset, int *errors) #include "params.h" #include "params-lexer.h" -extern int yylex(YYSTYPE *lval, YYLTYPE *yylloc, int parser_params); -static int yyerror(YYLTYPE *loc, int parse_param, const char *str); +static int lexer_value = 7; +extern int yylex(YYSTYPE *lval, YYLTYPE *yylloc, int lexer_value, int shared, int offset, int *errors); +static int yyerror(YYLTYPE *loc, int parser_value, int shared, int offset, int *errors, const char *str); %} -%param {int parse_param} +%lex-param {int lexer_value} +%parse-param {int parser_value} +%param {int shared} {int offset} +%param {int *errors} %union { int val; @@ -30,7 +34,11 @@ static int yyerror(YYLTYPE *loc, int parse_param, const char *str); %% program : /* empty */ - | expr { printf("=> %d", $1); } + | expr { + if (parser_value != 3 || shared != 5 || offset != 11 || *errors) + YYABORT; + printf("=> %d", $1); + } ; expr : NUM | expr '+' expr { $$ = $1 + $3; } @@ -42,19 +50,23 @@ expr : NUM %% -static int yyerror(YYLTYPE *loc, int parse_param, const char *str) { +static int yyerror(YYLTYPE *loc, int parser_value, int shared, int offset, int *errors, const char *str) { + if (parser_value == 3 && shared == 5 && offset == 11) + ++*errors; fprintf(stderr, "parse error: %s\\n", str); return 0; } int main(int argc, char *argv[]) { + int errors = 0; if (argc == 2) { yy_scan_string(argv[1]); } - if (yyparse(0)) { + if (yyparse(3, 5, 11, &errors)) { + printf("errors: %d", errors); fprintf(stderr, "syntax error\n"); return 1; } - return 0; + return errors != 0; } diff --git a/spec/lrama/integration_spec.rb b/spec/lrama/integration_spec.rb index e07581c3e..651d4e926 100644 --- a/spec/lrama/integration_spec.rb +++ b/spec/lrama/integration_spec.rb @@ -100,10 +100,14 @@ def generate_object(grammar_file_path, c_path, obj_path, command_args: []) test_parser("contains_at_reference", "", "") end - describe "YYDEBUG and %param are enabled" do + describe "YYDEBUG and multiple parameters are enabled" do it "returns 9 for '(1+2)*3'" do test_parser("params", "(1+2)*3", "=> 9") end + + it "passes all parser parameters to yyerror" do + test_parser("params", "(1+)", "errors: 1", expect_success: false) + end end describe "named references" do diff --git a/spec/lrama/output_spec.rb b/spec/lrama/output_spec.rb index 5fa1d04f0..46bee7a97 100644 --- a/spec/lrama/output_spec.rb +++ b/spec/lrama/output_spec.rb @@ -29,24 +29,61 @@ let(:grammar_file_path) { fixture_path("common/basic.y") } let(:header_file_path) { "y.tab.h" } + context 'with multiple parameter declarations' do + let(:text) do + <<~'GRAMMAR' + %lex-param {int lex_only} {char *input} + %parse-param {int parse_only} + %param {int shared} {void *context} + %parse-param {int *result} + %param {int last} + %% + start: ; + GRAMMAR + end + + it 'preserves declaration order in function signatures and calls' do + expect(output.lex_param).to eq("int lex_only, char *input, int shared, void *context, int last") + expect(output.parse_param).to eq("int parse_only, int shared, void *context, int *result, int last") + expect(output.lex_param_name).to eq("lex_only, input, shared, context, last") + expect(output.parse_param_name).to eq("parse_only, shared, context, result, last") + expect(output.user_formals).to eq(", int parse_only, int shared, void *context, int *result, int last") + expect(output.user_args).to eq(", parse_only, shared, context, result, last") + expect(output.yylex_formals).to eq("(&yylval, lex_only, input, shared, context, last)") + expect(output.yyerror_args).to eq("&yylloc, parse_only, shared, context, result, last") + end + + it 'marks each parser parameter as used separately' do + expect(output.parse_param_use("yyvaluep", "yylocationp")).to eq(<<-C.chomp) + YY_USE (yyvaluep); + YY_USE (yylocationp); + YY_USE (parse_only); + YY_USE (shared); + YY_USE (context); + YY_USE (result); + YY_USE (last); + C + end + end + describe "#parse_param" do it "returns declaration of parse param without blanks" do - allow(grammar).to receive(:parse_param).and_return("struct parser_params *p") + allow(grammar).to receive(:parse_params).and_return(["struct parser_params *p"]) expect(output.parse_param).to eq("struct parser_params *p") - allow(grammar).to receive(:parse_param).and_return(" struct parser_params *p ") + allow(grammar).to receive(:parse_params).and_return([" struct parser_params *p "]) expect(output.parse_param).to eq("struct parser_params *p") - allow(grammar).to receive(:parse_param).and_return("int i") + allow(grammar).to receive(:parse_params).and_return(["int i"]) expect(output.parse_param).to eq("int i") - allow(grammar).to receive(:parse_param).and_return(" int i ") + allow(grammar).to receive(:parse_params).and_return([" int i "]) expect(output.parse_param).to eq("int i") - allow(grammar).to receive(:parse_param).and_return("int parse_param") + allow(grammar).to receive(:parse_params).and_return(["int parse_param"]) expect(output.parse_param).to eq("int parse_param") - allow(grammar).to receive(:parse_param).and_return(" int parse_param ") + allow(grammar).to receive(:parse_params).and_return([" int parse_param "]) expect(output.parse_param).to eq("int parse_param") end end @@ -54,29 +91,29 @@ describe "#user_formals" do context "when parse_param exists" do it "returns declaration of parse param without blanks with a leading comma" do - allow(grammar).to receive(:parse_param).and_return("struct parser_params *p") + allow(grammar).to receive(:parse_params).and_return(["struct parser_params *p"]) expect(output.user_formals).to eq(", struct parser_params *p") - allow(grammar).to receive(:parse_param).and_return(" struct parser_params *p ") + allow(grammar).to receive(:parse_params).and_return([" struct parser_params *p "]) expect(output.user_formals).to eq(", struct parser_params *p") - allow(grammar).to receive(:parse_param).and_return("int i") + allow(grammar).to receive(:parse_params).and_return(["int i"]) expect(output.user_formals).to eq(", int i") - allow(grammar).to receive(:parse_param).and_return(" int i ") + allow(grammar).to receive(:parse_params).and_return([" int i "]) expect(output.user_formals).to eq(", int i") - allow(grammar).to receive(:parse_param).and_return("int parse_param") + allow(grammar).to receive(:parse_params).and_return(["int parse_param"]) expect(output.user_formals).to eq(", int parse_param") - allow(grammar).to receive(:parse_param).and_return(" int parse_param ") + allow(grammar).to receive(:parse_params).and_return([" int parse_param "]) expect(output.user_formals).to eq(", int parse_param") end end context "when parse_param does not exist" do it "returns blank" do - allow(grammar).to receive(:parse_param).and_return(nil) + allow(grammar).to receive(:parse_params).and_return([]) expect(output.user_formals).to eq("") end end @@ -85,29 +122,29 @@ describe "#user_args" do context "when parse_param exists" do it "returns name of parse param without blanks with a leading comma" do - allow(grammar).to receive(:parse_param).and_return("struct parser_params *p") + allow(grammar).to receive(:parse_params).and_return(["struct parser_params *p"]) expect(output.user_args).to eq(", p") - allow(grammar).to receive(:parse_param).and_return(" struct parser_params *p ") + allow(grammar).to receive(:parse_params).and_return([" struct parser_params *p "]) expect(output.user_args).to eq(", p") - allow(grammar).to receive(:parse_param).and_return("int i") + allow(grammar).to receive(:parse_params).and_return(["int i"]) expect(output.user_args).to eq(", i") - allow(grammar).to receive(:parse_param).and_return(" int i ") + allow(grammar).to receive(:parse_params).and_return([" int i "]) expect(output.user_args).to eq(", i") - allow(grammar).to receive(:parse_param).and_return("int parse_param") + allow(grammar).to receive(:parse_params).and_return(["int parse_param"]) expect(output.user_args).to eq(", parse_param") - allow(grammar).to receive(:parse_param).and_return(" int parse_param ") + allow(grammar).to receive(:parse_params).and_return([" int parse_param "]) expect(output.user_args).to eq(", parse_param") end end context "when parse_param does not exist" do it "returns blank" do - allow(grammar).to receive(:parse_param).and_return(nil) + allow(grammar).to receive(:parse_params).and_return([]) expect(output.user_args).to eq("") end end @@ -115,44 +152,44 @@ describe "#parse_param_name" do it "returns name of parse param" do - allow(grammar).to receive(:parse_param).and_return("struct parser_params *p") + allow(grammar).to receive(:parse_params).and_return(["struct parser_params *p"]) expect(output.parse_param_name).to eq("p") - allow(grammar).to receive(:parse_param).and_return(" struct parser_params *p ") + allow(grammar).to receive(:parse_params).and_return([" struct parser_params *p "]) expect(output.parse_param_name).to eq("p") - allow(grammar).to receive(:parse_param).and_return("int i") + allow(grammar).to receive(:parse_params).and_return(["int i"]) expect(output.parse_param_name).to eq("i") - allow(grammar).to receive(:parse_param).and_return(" int i ") + allow(grammar).to receive(:parse_params).and_return([" int i "]) expect(output.parse_param_name).to eq("i") - allow(grammar).to receive(:parse_param).and_return("int parse_param") + allow(grammar).to receive(:parse_params).and_return(["int parse_param"]) expect(output.parse_param_name).to eq("parse_param") - allow(grammar).to receive(:parse_param).and_return(" int parse_param ") + allow(grammar).to receive(:parse_params).and_return([" int parse_param "]) expect(output.parse_param_name).to eq("parse_param") end end describe "#lex_param_name" do it "returns name of lex param" do - allow(grammar).to receive(:lex_param).and_return("struct parser_params *p") + allow(grammar).to receive(:lex_params).and_return(["struct parser_params *p"]) expect(output.lex_param_name).to eq("p") - allow(grammar).to receive(:lex_param).and_return(" struct parser_params *p ") + allow(grammar).to receive(:lex_params).and_return([" struct parser_params *p "]) expect(output.lex_param_name).to eq("p") - allow(grammar).to receive(:lex_param).and_return("int i") + allow(grammar).to receive(:lex_params).and_return(["int i"]) expect(output.lex_param_name).to eq("i") - allow(grammar).to receive(:lex_param).and_return(" int i ") + allow(grammar).to receive(:lex_params).and_return([" int i "]) expect(output.lex_param_name).to eq("i") - allow(grammar).to receive(:lex_param).and_return("int lex_param") + allow(grammar).to receive(:lex_params).and_return(["int lex_param"]) expect(output.lex_param_name).to eq("lex_param") - allow(grammar).to receive(:lex_param).and_return(" int lex_param ") + allow(grammar).to receive(:lex_params).and_return([" int lex_param "]) expect(output.lex_param_name).to eq("lex_param") end end diff --git a/spec/lrama/parser_spec.rb b/spec/lrama/parser_spec.rb index 2366cb32b..95874fd3c 100644 --- a/spec/lrama/parser_spec.rb +++ b/spec/lrama/parser_spec.rb @@ -127,8 +127,8 @@ grammar.prepare grammar.validate! - expect(grammar.lex_param).to eq("struct context *ctx") - expect(grammar.parse_param).to eq("struct context *ctx") + expect(grammar.lex_params).to eq(["struct context *ctx"]) + expect(grammar.parse_params).to eq(["struct context *ctx"]) end it "no_union" do @@ -191,8 +191,8 @@ lineno: 19 ), ]) - expect(grammar.lex_param).to eq("struct lex_params *p") - expect(grammar.parse_param).to eq("struct parse_params *p") + expect(grammar.lex_params).to eq(["struct lex_params *p"]) + expect(grammar.parse_params).to eq(["struct parse_params *p"]) expect(grammar.initial_action).to eq(Code::InitialActionCode.new(type: :initial_action, token_code: T::UserCode.new(s_value: "\n initial_action_func(@$);\n"))) expect(grammar.symbols.sort_by(&:number)).to match_symbols([ Sym.new(id: T::Ident.new(s_value: "EOI"), alias_name: "\"EOI\"", number: 0, tag: nil, term: true, token_id: 0, nullable: false, precedence: nil, printer: nil),