diff --git a/README.md b/README.md index 3ca94a8..47f7967 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,20 @@ preserved unless `--force` is supplied. A failed generation leaves the previous file intact. The output includes its source profile, generator version and copyright/license notices. +When only `parse.y` is available, Arpaka can replace its `RUBY_TOKEN(NAME)` +declarations with token names and generate a frontend without the rest of the +Ruby source tree: + +```sh +RUBY_BOX=1 bundle exec arpaka generate \ + --parse-y /path/to/parse.y \ + --class-name MyRubyParser \ + --output lib/my_library/ruby_parser.rb +``` + +This mode embeds Arpaka's own license notices. The `--ruby-source` mode remains +the compatibility-preserving path when the Ruby source tree is available. + The consumer only needs the generated file and Ruby 4.0 or later: ```ruby diff --git a/lib/arpaka.rb b/lib/arpaka.rb index 65281f8..31b5817 100644 --- a/lib/arpaka.rb +++ b/lib/arpaka.rb @@ -5,12 +5,12 @@ module Arpaka class Error < Lrama::Ruby::Error; end - def self.generate(ruby_source:, class_name: "RubyParser") - Generator.new.generate(ruby_source: ruby_source, class_name: class_name) + def self.generate(ruby_source: nil, parse_y: nil, class_name: "RubyParser") + Generator.new.generate(ruby_source: ruby_source, parse_y: parse_y, class_name: class_name) end - def self.compile(ruby_source:, class_name: "RubyParser") - source = generate(ruby_source: ruby_source, class_name: class_name) + def self.compile(ruby_source: nil, parse_y: nil, class_name: "RubyParser") + source = generate(ruby_source: ruby_source, parse_y: parse_y, class_name: class_name) box = ::Ruby::Box.new box.eval(source) box.const_get(class_name, false) diff --git a/lib/arpaka/cli.rb b/lib/arpaka/cli.rb index 9f40132..a67138c 100644 --- a/lib/arpaka/cli.rb +++ b/lib/arpaka/cli.rb @@ -11,8 +11,9 @@ def self.run(argv, out: $stdout, err: $stderr) force = false help = false parser = OptionParser.new do |opts| - opts.banner = "Usage: arpaka generate --ruby-source DIR --output FILE [--class-name NAME] [--force]" + opts.banner = "Usage: arpaka generate (--ruby-source DIR | --parse-y FILE) --output FILE [--class-name NAME] [--force]" opts.on("--ruby-source DIR", "Ruby source tree (provided by the user)") { |v| options[:ruby_source] = v } + opts.on("--parse-y FILE", "parse.y file (use Arpaka's token definitions)") { |v| options[:parse_y] = v } opts.on("--output FILE", "Write a standalone Ruby frontend") { |v| options[:output] = v } opts.on("--class-name NAME", "Generated class name (default: RubyParser)") { |v| options[:class_name] = v } opts.on("--force", "Replace an existing output file") { force = true } @@ -31,7 +32,9 @@ def self.run(argv, out: $stdout, err: $stderr) return 0 end raise OptionParser::InvalidArgument, args.join(" ") unless args.empty? - %i[ruby_source output].each do |key| + raise OptionParser::InvalidArgument, "specify exactly one of --ruby-source or --parse-y" if options[:ruby_source] == options[:parse_y] + raise OptionParser::MissingArgument, "--output" unless options[:output] + %i[output].each do |key| raise OptionParser::MissingArgument, "--#{key.to_s.tr('_', '-')}" unless options[key] end output = File.expand_path(options.delete(:output)) diff --git a/lib/arpaka/generator.rb b/lib/arpaka/generator.rb index f65d574..1404d66 100644 --- a/lib/arpaka/generator.rb +++ b/lib/arpaka/generator.rb @@ -6,28 +6,36 @@ module Arpaka class Generator - def generate(ruby_source:, class_name:) + def generate(ruby_source: nil, parse_y: nil, class_name:) + if ruby_source.nil? == parse_y.nil? + raise Error, "specify exactly one of ruby_source or parse_y" + end unless /\A[A-Z][a-zA-Z0-9_]*\z/.match?(class_name) raise Error, "class_name must be a single Ruby constant name" end unless ::Ruby::Box.enabled? raise Error, "Ruby Box is required for generation. Start Ruby with RUBY_BOX=1." end - artifacts = RubyGrammar.artifacts(ruby_source: ruby_source) + artifacts = RubyGrammar.artifacts(ruby_source: ruby_source, parse_y: parse_y) + grammar_filename = parse_y || File.join(ruby_source, "parse.y") parser = Lrama::Ruby.generate(artifacts.fetch("parse.y"), - filename: File.join(ruby_source, "parse.y"), class_name: "Parser", allow_error_rules: true) + filename: grammar_filename, class_name: "Parser", allow_error_rules: true) parser = parser.delete_prefix("# frozen_string_literal: true\n") rules = JSON.parse(artifacts.fetch("rules.json")) + runtime_options = artifacts.fetch("runtime", {}) runtime = %w[frontend ast builder lexer].map do |name| template = File.read(File.join(__dir__, "runtime", "#{name}.rb.erb")) - name == "builder" ? ERB.new(template).result_with_hash(rules: rules) : template + ERB.new(template, trim_mode: "-").result_with_hash(rules: rules, runtime: runtime_options) end.join("\n") notices = [File.read(File.expand_path("../../LICENSE.txt", __dir__))] - %w[COPYING BSDL].each { |name| notices << File.read(File.join(ruby_source, name)) } + %w[COPYING BSDL].each do |name| + path = ruby_source ? File.join(ruby_source, name) : File.join(__dir__, name) + notices << File.read(path) + end source = "# frozen_string_literal: true\n" \ "# Generated by Arpaka #{Lrama::Ruby::VERSION}. Do not edit.\n" \ - "# Ruby source profile: #{RubyGrammar::REVISION}\n" \ - "# parse.y SHA256: #{Digest::SHA256.file(File.join(ruby_source, 'parse.y')).hexdigest}\n" \ + "# Ruby source profile: #{ruby_source ? RubyGrammar::REVISION : 'parse.y only'}\n" \ + "# parse.y SHA256: #{Digest::SHA256.file(grammar_filename).hexdigest}\n" \ "#{notices.join("\n").lines.map { |line| "# #{line}" }.join}\n" \ "class #{class_name}\n#{parser}\n#{runtime}\nend\n" # Compile in an isolated box to validate syntax without executing actions. diff --git a/lib/arpaka/profile/ruby.rb b/lib/arpaka/profile/ruby.rb new file mode 100644 index 0000000..c42d476 --- /dev/null +++ b/lib/arpaka/profile/ruby.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "json" + +module Arpaka + module Profile + module RUBY + CURRENT = { + "name" => "ruby-4.0", + "actions" => ::Arpaka::RubyGrammarActions::ACTIONS, + "additional_expected_conflicts" => 0, + "runtime" => {}.freeze + }.freeze + + def self.select(grammar) + if grammar.rules.any? { |rule| rule.lhs.id.s_value == "value_expr_command" } + CURRENT + elsif grammar.rules.any? { |rule| rule.lhs.id.s_value == "top_compstmt" } + legacy + else + raise ::Arpaka::Error, "Unsupported Ruby parse.y profile" + end + end + + def self.legacy + @legacy ||= begin + box = ::Ruby::Box.new + profile = box.eval(File.read(File.join(__dir__, "ruby_3_4.rb"))) + unless profile.is_a?(Hash) && profile["name"] == "ruby-3.4" + raise ::Arpaka::Error, "Invalid Ruby 3.4 profile" + end + profile.freeze + rescue SyntaxError, TypeError => error + raise ::Arpaka::Error, "Ruby legacy profile loading failed: #{error.message}" + end + end + end + end +end diff --git a/lib/arpaka/profile/ruby_3_4.rb b/lib/arpaka/profile/ruby_3_4.rb new file mode 100644 index 0000000..c70c804 --- /dev/null +++ b/lib/arpaka/profile/ruby_3_4.rb @@ -0,0 +1,802 @@ +{"name" => "ruby-3.4", + "source_revision" => "76cca827ab52ab1d346a728f068d5b8da3e2952b", + "source_sha256" => "c3b4893af679eaeb5688d615cbfff0459a42077776a49df5d9514187cb56b1ec", + "runtime" => { + "ast_nodes" => %w[assignment singleton_method singleton_class pattern].freeze, + "builder_methods" => %w[assignment singleton_method singleton_class rescue].freeze, + "lexer" => {"source_text_tokens" => ["tOP_ASGN"].freeze}.freeze + }.freeze, + "actions" => + {1 => ["empty", "nil"], + 2 => ["ported", "@builder.program($2)"], + 3 => ["empty", "nil"], + 4 => ["empty", "nil"], + 5 => ["pass_through", "$1"], + 6 => ["ported", "[].freeze"], + 7 => ["ported", "[$1].freeze"], + 8 => ["ported", "($1 + [$3]).freeze"], + 9 => ["pass_through", "$1"], + 10 => ["pass_through", "$2"], + 11 => ["empty", "nil"], + 12 => ["ported", "@builder.begin_block($2)"], + 13 => ["empty", "nil"], + 14 => ["empty", "nil"], + 15 => ["ported", "@builder.body_statement($1, $3, $6, $8)"], + 16 => ["empty", "nil"], + 17 => ["ported", "@builder.body_statement($1, $3, nil, $5)"], + 18 => ["ported", "@builder.body_nodes($1)"], + 19 => ["ported", "[].freeze"], + 20 => ["ported", "[$1].freeze"], + 21 => ["ported", "($1 + [$3]).freeze"], + 22 => ["pass_through", "$1"], + 23 => ["empty", "nil"], + 24 => ["pass_through", "$3"], + 25 => ["empty", "nil"], + 26 => ["pass_through", "$1"], + 27 => ["empty", "nil"], + 28 => ["ported", "@builder.alias_node($2, $4)"], + 29 => ["ported", "@builder.alias_node($2, $3)"], + 30 => ["ported", "@builder.alias_node($2, $3)"], + 31 => ["unsupported", "@builder.unsupported(31)"], + 32 => ["ported", "@builder.undef_node($2)"], + 33 => ["ported", "@builder.if_node($3, [$1], nil)"], + 34 => ["ported", "@builder.unless_node($3, [$1], nil)"], + 35 => ["ported", "@builder.loop(:while, $3, [$1])"], + 36 => ["ported", "@builder.loop(:until, $3, [$1])"], + 37 => ["ported", "@builder.rescue_modifier($1, $4)"], + 38 => ["ported", "@builder.end_block($4)"], + 39 => ["pass_through", "$1"], + 40 => ["ported", "@builder.multiple_assign($1, $4)"], + 41 => ["ported", "@builder.assignment($1, $4)"], + 42 => ["ported", "@builder.multiple_assign($1, @builder.rescue_modifier($4, $7))"], + 43 => ["ported", "@builder.multiple_assign($1, $4)"], + 44 => ["pass_through", "$1"], + 45 => ["empty", "nil"], + 46 => ["ported", "@builder.assignment($1, $4)"], + 47 => ["ported", "@builder.opassign($1, $2, $4)"], + 48 => ["ported", "@builder.opassign(@builder.index($1, $3 || []), $5, $7)"], + 49 => ["ported", "@builder.opassign(@builder.field($1, $2, $3), $4, $6)"], + 50 => ["ported", "@builder.opassign(@builder.field($1, $2, $3), $4, $6)"], + 51 => ["ported", "@builder.opassign(@builder.field($1, :\"::\", $3), $4, $6)"], + 52 => ["ported", "@builder.opassign(@builder.field($1, $2, $3), $4, $6)"], + 53 => ["ported", "@builder.opassign(@builder.field(nil, :\"::\", $2), $3, $5)"], + 54 => ["ported", "@builder.def_node($1, $2, $4)"], + 55 => ["ported", "@builder.singleton_method_node($1[0], $1[2], $2, $4)"], + 56 => ["unsupported", "@builder.unsupported(56)"], + 57 => ["pass_through", "$1"], + 58 => ["ported", "@builder.rescue_modifier($1, $4)"], + 59 => ["empty", "nil"], + 60 => ["pass_through", "nil"], + 61 => ["ported", "@builder.unary(:\"!\", $3)"], + 62 => ["pass_through", "$1"], + 63 => ["ported", "@builder.rescue_modifier($1, $4)"], + 64 => ["pass_through", "$1"], + 65 => ["pass_through", "$1"], + 66 => ["ported", "@builder.binary(:and, $1, $3)"], + 67 => ["ported", "@builder.binary(:or, $1, $3)"], + 68 => ["ported", "@builder.unary(:\"!\", $3)"], + 69 => ["ported", "@builder.unary(:\"!\", $2)"], + 70 => ["empty", "nil"], + 71 => ["ported", "@builder.case_node($1, [@builder.when_node($7, [])], nil)"], + 72 => ["empty", "nil"], + 73 => ["ported", "@builder.case_node($1, [@builder.when_node($7, [])], nil)"], + 74 => ["pass_through", "$1"], + 75 => ["pass_through", "$1"], + 76 => ["pass_through", "$2"], + 77 => ["empty", "nil"], + 78 => ["ported", "[$2, $3, $5].freeze"], + 79 => ["pass_through", "$1"], + 80 => ["empty", "nil"], + 81 => ["empty", "nil"], + 82 => ["empty", "nil"], + 83 => ["pass_through", "$2"], + 84 => ["pass_through", "$1"], + 85 => ["pass_through", "$1"], + 86 => ["pass_through", "$1"], + 87 => ["ported", "@builder.receiver_call($1, $2, $3, $4)"], + 88 => ["ported", "$2"], + 89 => ["pass_through", "$1"], + 90 => ["ported", "@builder.call($1, $2 || [])"], + 91 => ["ported", "@builder.block_call(@builder.call($1, $2 || []), $3)"], + 92 => ["ported", "@builder.receiver_call($1, $2, $3, $4)"], + 93 => ["ported", "@builder.block_call(@builder.receiver_call($1, $2, $3, $4), $5)"], + 94 => ["ported", "@builder.receiver_call($1, :\"::\", $3, $4)"], + 95 => ["ported", "@builder.block_call(@builder.receiver_call($1, :\"::\", $3, $4), $5)"], + 96 => ["ported", "@builder.block_call(@builder.receiver_call($1, :\"::\", $3, []), $5)"], + 97 => ["ported", "@builder.call(:super, $2 || [])"], + 98 => ["ported", "@builder.call(:yield, $2 || [])"], + 99 => ["ported", "@builder.call(:return, $2 || [])"], + 100 => ["ported", "@builder.call(:break, $2 || [])"], + 101 => ["ported", "@builder.call(:next, $2 || [])"], + 102 => ["pass_through", "$1"], + 103 => ["pass_through", "$2"], + 104 => ["pass_through", "$1"], + 105 => ["pass_through", "$2"], + 106 => ["pass_through", "$1"], + 107 => ["ported", "(Array($1) + [$2]).freeze"], + 108 => ["ported", "(Array($1) + [$3]).freeze"], + 109 => ["pass_through", "$1"], + 110 => ["pass_through", "$1"], + 111 => ["pass_through", "$1"], + 112 => ["pass_through", "$2"], + 113 => ["pass_through", "$2"], + 114 => ["empty", "nil"], + 115 => ["pass_through", "$2"], + 116 => ["pass_through", "$1"], + 117 => ["pass_through", "$2"], + 118 => ["ported", "[$1].freeze"], + 119 => ["ported", "($1 + [$2]).freeze"], + 120 => ["pass_through", "$1"], + 121 => ["pass_through", "$1"], + 122 => ["pass_through", "$1"], + 123 => ["pass_through", "$1"], + 124 => ["ported", "@builder.index($1, $3 || [])"], + 125 => ["pass_through", "$1"], + 126 => ["pass_through", "$1"], + 127 => ["pass_through", "$1"], + 128 => ["ported", "@builder.variable(:constant, $3)"], + 129 => ["ported", "@builder.variable(:constant, $2)"], + 130 => ["pass_through", "$1"], + 131 => ["ported", "@builder.declare_local($1)"], + 132 => ["pass_through", "$1"], + 133 => ["ported", "@builder.index($1, $3 || [])"], + 134 => ["ported", "@builder.field($1, $2, $3)"], + 135 => ["ported", "@builder.field($1, $2, $3)"], + 136 => ["ported", "@builder.field($1, $2, $3)"], + 137 => ["ported", "@builder.field($1, :\"::\", $3)"], + 138 => ["ported", "@builder.variable(:constant, $2)"], + 139 => ["pass_through", "$1"], + 140 => ["pass_through", "$1"], + 141 => ["pass_through", "$1"], + 142 => ["pass_through", "$2"], + 143 => ["pass_through", "$1"], + 144 => ["pass_through", "$3"], + 145 => ["pass_through", "$1"], + 146 => ["pass_through", "$1"], + 147 => ["pass_through", "$1"], + 148 => ["pass_through", "$1"], + 149 => ["pass_through", "$1"], + 150 => ["pass_through", "$1"], + 151 => ["pass_through", "$1"], + 152 => ["ported", "[$1].freeze"], + 153 => ["empty", "nil"], + 154 => ["ported", "($1 + [$4]).freeze"], + 155 => ["ported", "@builder.operator(\"|\")"], + 156 => ["ported", "@builder.operator(\"^\")"], + 157 => ["ported", "@builder.operator(\"&\")"], + 158 => ["ported", "@builder.operator(\"<=>\")"], + 159 => ["ported", "@builder.operator(\"==\")"], + 160 => ["ported", "@builder.operator(\"===\")"], + 161 => ["ported", "@builder.operator(\"=~\")"], + 162 => ["ported", "@builder.operator(\"!~\")"], + 163 => ["ported", "@builder.operator(\">\")"], + 164 => ["ported", "@builder.operator(\">=\")"], + 165 => ["ported", "@builder.operator(\"<\")"], + 166 => ["ported", "@builder.operator(\"<=\")"], + 167 => ["ported", "@builder.operator(\"!=\")"], + 168 => ["ported", "@builder.operator(\"<<\")"], + 169 => ["ported", "@builder.operator(\">>\")"], + 170 => ["ported", "@builder.operator(\"+\")"], + 171 => ["ported", "@builder.operator(\"-\")"], + 172 => ["ported", "@builder.operator(\"*\")"], + 173 => ["ported", "@builder.operator(\"*\")"], + 174 => ["ported", "@builder.operator(\"/\")"], + 175 => ["ported", "@builder.operator(\"%\")"], + 176 => ["ported", "@builder.operator(\"**\")"], + 177 => ["ported", "@builder.operator(\"**\")"], + 178 => ["ported", "@builder.operator(\"!\")"], + 179 => ["ported", "@builder.operator(\"~\")"], + 180 => ["ported", "@builder.operator(\"+@\")"], + 181 => ["ported", "@builder.operator(\"-@\")"], + 182 => ["ported", "@builder.operator(\"[]\")"], + 183 => ["ported", "@builder.operator(\"[]=\")"], + 184 => ["ported", "@builder.operator(\"`\")"], + 185 => ["ported", "@builder.reserved_word(\"__LINE__\")"], + 186 => ["ported", "@builder.reserved_word(\"__FILE__\")"], + 187 => ["ported", "@builder.reserved_word(\"__ENCODING__\")"], + 188 => ["ported", "@builder.reserved_word(\"BEGIN\")"], + 189 => ["ported", "@builder.reserved_word(\"END\")"], + 190 => ["ported", "@builder.reserved_word(\"alias\")"], + 191 => ["ported", "@builder.reserved_word(\"and\")"], + 192 => ["ported", "@builder.reserved_word(\"begin\")"], + 193 => ["ported", "@builder.reserved_word(\"break\")"], + 194 => ["ported", "@builder.reserved_word(\"case\")"], + 195 => ["ported", "@builder.reserved_word(\"class\")"], + 196 => ["ported", "@builder.reserved_word(\"def\")"], + 197 => ["ported", "@builder.reserved_word(\"defined?\")"], + 198 => ["ported", "@builder.reserved_word(\"do\")"], + 199 => ["ported", "@builder.reserved_word(\"else\")"], + 200 => ["ported", "@builder.reserved_word(\"elsif\")"], + 201 => ["ported", "@builder.reserved_word(\"end\")"], + 202 => ["ported", "@builder.reserved_word(\"ensure\")"], + 203 => ["ported", "@builder.reserved_word(\"false\")"], + 204 => ["ported", "@builder.reserved_word(\"for\")"], + 205 => ["ported", "@builder.reserved_word(\"in\")"], + 206 => ["ported", "@builder.reserved_word(\"module\")"], + 207 => ["ported", "@builder.reserved_word(\"next\")"], + 208 => ["ported", "@builder.reserved_word(\"nil\")"], + 209 => ["ported", "@builder.reserved_word(\"not\")"], + 210 => ["ported", "@builder.reserved_word(\"or\")"], + 211 => ["ported", "@builder.reserved_word(\"redo\")"], + 212 => ["ported", "@builder.reserved_word(\"rescue\")"], + 213 => ["ported", "@builder.reserved_word(\"retry\")"], + 214 => ["ported", "@builder.reserved_word(\"return\")"], + 215 => ["ported", "@builder.reserved_word(\"self\")"], + 216 => ["ported", "@builder.reserved_word(\"super\")"], + 217 => ["ported", "@builder.reserved_word(\"then\")"], + 218 => ["ported", "@builder.reserved_word(\"true\")"], + 219 => ["ported", "@builder.reserved_word(\"undef\")"], + 220 => ["ported", "@builder.reserved_word(\"when\")"], + 221 => ["ported", "@builder.reserved_word(\"yield\")"], + 222 => ["ported", "@builder.reserved_word(\"if\")"], + 223 => ["ported", "@builder.reserved_word(\"unless\")"], + 224 => ["ported", "@builder.reserved_word(\"while\")"], + 225 => ["ported", "@builder.reserved_word(\"until\")"], + 226 => ["ported", "@builder.assignment($1, $4)"], + 227 => ["ported", "@builder.opassign($1, $2, $4)"], + 228 => ["ported", "@builder.opassign(@builder.index($1, $3 || []), $5, $7)"], + 229 => ["ported", "@builder.opassign(@builder.field($1, $2, $3), $4, $6)"], + 230 => ["ported", "@builder.opassign(@builder.field($1, $2, $3), $4, $6)"], + 231 => ["ported", "@builder.opassign(@builder.field($1, $2, $3), $4, $6)"], + 232 => ["ported", "@builder.opassign(@builder.field($1, :\"::\", $3), $4, $6)"], + 233 => ["ported", "@builder.opassign(@builder.field(nil, :\"::\", $2), $3, $5)"], + 234 => ["unsupported", "@builder.unsupported(234)"], + 235 => ["ported", "@builder.range(:\"..\", $1, $3)"], + 236 => ["ported", "@builder.range(:\"...\", $1, $3)"], + 237 => ["ported", "@builder.range(:\"..\", $1, nil)"], + 238 => ["ported", "@builder.range(:\"...\", $1, nil)"], + 239 => ["ported", "@builder.range(:\"..\", nil, $2)"], + 240 => ["ported", "@builder.range(:\"...\", nil, $2)"], + 241 => ["ported", "@builder.binary(:+, $1, $3)"], + 242 => ["ported", "@builder.binary(:-, $1, $3)"], + 243 => ["ported", "@builder.binary(:*, $1, $3)"], + 244 => ["ported", "@builder.binary(:/, $1, $3)"], + 245 => ["ported", "@builder.binary(:%, $1, $3)"], + 246 => ["ported", "@builder.binary(:\"**\", $1, $3)"], + 247 => ["ported", "@builder.binary(:\"**\", @builder.unary(:-@, $2), $4)"], + 248 => ["ported", "@builder.unary(:+, $2)"], + 249 => ["ported", "@builder.unary(:-, $2)"], + 250 => ["ported", "@builder.binary(:\"|\", $1, $3)"], + 251 => ["ported", "@builder.binary(:\"^\", $1, $3)"], + 252 => ["ported", "@builder.binary(:\"&\", $1, $3)"], + 253 => ["ported", "@builder.binary(:\"<=>\", $1, $3)"], + 254 => ["pass_through", "$1"], + 255 => ["ported", "@builder.binary(:\"==\", $1, $3)"], + 256 => ["ported", "@builder.binary(:\"===\", $1, $3)"], + 257 => ["ported", "@builder.binary(:\"!=\", $1, $3)"], + 258 => ["ported", "@builder.binary(:\"=~\", $1, $3)"], + 259 => ["ported", "@builder.binary(:\"!~\", $1, $3)"], + 260 => ["ported", "@builder.unary(:\"!\", $2)"], + 261 => ["ported", "@builder.unary(:\"~\", $2)"], + 262 => ["ported", "@builder.binary(:\"<<\", $1, $3)"], + 263 => ["ported", "@builder.binary(:\">>\", $1, $3)"], + 264 => ["ported", "@builder.binary(:\"&&\", $1, $3)"], + 265 => ["ported", "@builder.binary(:\"||\", $1, $3)"], + 266 => ["ported", "@builder.call(:defined, [$4])"], + 267 => ["ported", "@builder.ternary($1, $3, $6)"], + 268 => ["ported", "@builder.def_node($1, $2, $4)"], + 269 => ["ported", "@builder.singleton_method_node($1[0], $1[2], $2, $4)"], + 270 => ["pass_through", "$1"], + 271 => ["pass_through", "$1"], + 272 => ["ported", "@builder.rescue_modifier($1, $4)"], + 273 => ["ported", "@builder.unary(:\"!\", $3)"], + 274 => ["ported", "@builder.operator(\">\")"], + 275 => ["ported", "@builder.operator(\"<\")"], + 276 => ["ported", "@builder.operator(\">=\")"], + 277 => ["ported", "@builder.operator(\"<=\")"], + 278 => ["ported", "@builder.binary($2, $1, $3)"], + 279 => ["ported", "@builder.binary($2, $1, $3)"], + 280 => ["empty", "nil"], + 281 => ["pass_through", "nil"], + 282 => ["pass_through", "nil"], + 283 => ["pass_through", "$1"], + 284 => ["ported", "[].freeze"], + 285 => ["pass_through", "$1"], + 286 => ["ported", "($1 + $3).freeze"], + 287 => ["pass_through", "$1"], + 288 => ["pass_through", "$1"], + 289 => ["ported", "@builder.rescue_modifier($1, $4)"], + 290 => ["pass_through", "$2"], + 291 => ["ported", "($2 + [$4]).freeze"], + 292 => ["pass_through", "$2"], + 293 => ["empty", "nil"], + 294 => ["pass_through", "$1"], + 295 => ["empty", "nil"], + 296 => ["pass_through", "$1"], + 297 => ["pass_through", "$1"], + 298 => ["pass_through", "$1"], + 299 => ["pass_through", "$1"], + 300 => ["ported", "[$1].freeze"], + 301 => ["pass_through", "$1"], + 302 => ["pass_through", "$1"], + 303 => ["ported", "(($1 || []) + ($3 || [])).freeze"], + 304 => ["pass_through", "$1"], + 305 => ["empty", "nil"], + 306 => ["pass_through", "$2"], + 307 => ["pass_through", "$2"], + 308 => ["empty", "nil"], + 309 => ["pass_through", "$2"], + 310 => ["empty", "nil"], + 311 => ["ported", "[$1].freeze"], + 312 => ["pass_through", "$1"], + 313 => ["ported", "@builder.append_arguments($1, $3)"], + 314 => ["ported", "@builder.append_arguments($1, $3)"], + 315 => ["ported", "[$2].freeze"], + 316 => ["empty", "nil"], + 317 => ["pass_through", "$1"], + 318 => ["pass_through", "$1"], + 319 => ["ported", "($1 + [$3]).freeze"], + 320 => ["ported", "($1 + [$4]).freeze"], + 321 => ["ported", "[$2].freeze"], + 322 => ["pass_through", "$1"], + 323 => ["pass_through", "$1"], + 324 => ["pass_through", "$1"], + 325 => ["pass_through", "$1"], + 326 => ["ported", "@builder.word_array($1)"], + 327 => ["ported", "@builder.word_array($1, symbols: true)"], + 328 => ["pass_through", "$1"], + 329 => ["pass_through", "$1"], + 330 => ["pass_through", "$1"], + 331 => ["pass_through", "$1"], + 332 => ["ported", "@builder.reserved_word(\"method\")"], + 333 => ["empty", "nil"], + 334 => ["ported", "@builder.body_nodes($3)"], + 335 => ["empty", "nil"], + 336 => ["ported", "@builder.parentheses($2, 336)"], + 337 => ["ported", "@builder.parentheses($2, 337)"], + 338 => ["ported", "@builder.variable(:constant, $3)"], + 339 => ["pass_through", "$2"], + 340 => ["ported", "@builder.array($2)"], + 341 => ["ported", "@builder.hash($2)"], + 342 => ["ported", "@builder.control(:return)"], + 343 => ["ported", "@builder.call(:yield, $3 || [])"], + 344 => ["ported", "@builder.call(:yield, [])"], + 345 => ["ported", "@builder.call(:yield, [])"], + 346 => ["ported", "@builder.call(:defined, [$5])"], + 347 => ["ported", "@builder.unary(:\"!\", $3)"], + 348 => ["ported", "@builder.reserved_word(\"not\")"], + 349 => ["ported", "@builder.block_call(@builder.call($1, []), $2)"], + 350 => ["pass_through", "$1"], + 351 => ["ported", "@builder.block_call($1, $2)"], + 352 => ["pass_through", "$1"], + 353 => ["ported", "@builder.if_node($2, $4, $5)"], + 354 => ["ported", "@builder.unless_node($2, $4, $5)"], + 355 => ["ported", "@builder.loop(:while, $2, $3)"], + 356 => ["ported", "@builder.loop(:until, $2, $3)"], + 357 => ["empty", "nil"], + 358 => ["ported", "@builder.case_node($2, @builder.case_clauses($5), nil)"], + 359 => ["empty", "nil"], + 360 => ["ported", "@builder.case_node(nil, @builder.case_clauses($4), nil)"], + 361 => ["ported", "@builder.case_node($2, @builder.case_clauses($4), nil)"], + 362 => ["ported", "@builder.for_node($2, $4, $5)"], + 363 => ["empty", "nil"], + 364 => ["ported", "@builder.class_node($2, $3, $5)"], + 365 => ["empty", "nil"], + 366 => ["ported", "@builder.singleton_class_node($3, $6)"], + 367 => ["empty", "nil"], + 368 => ["ported", "@builder.module_node($2, $4)"], + 369 => ["empty", "nil"], + 370 => ["ported", "@builder.def_node($1, $2, $4)"], + 371 => ["empty", "nil"], + 372 => ["ported", "@builder.singleton_method_node($1[0], $1[2], $2, $4)"], + 373 => ["ported", "@builder.control(:break)"], + 374 => ["ported", "@builder.control(:next)"], + 375 => ["ported", "@builder.control(:redo)"], + 376 => ["ported", "@builder.control(:retry)"], + 377 => ["pass_through", "$1"], + 378 => ["empty", "nil"], + 379 => ["pass_through", "nil"], + 380 => ["pass_through", "nil"], + 381 => ["pass_through", "nil"], + 382 => ["pass_through", "nil"], + 383 => ["pass_through", "nil"], + 384 => ["pass_through", "nil"], + 385 => ["empty", "nil"], + 386 => ["empty", "nil"], + 387 => ["empty", "nil"], + 388 => ["empty", "nil"], + 389 => ["empty", "nil"], + 390 => ["empty", "nil"], + 391 => ["empty", "nil"], + 392 => ["pass_through", "nil"], + 393 => ["pass_through", "nil"], + 394 => ["pass_through", "nil"], + 395 => ["pass_through", "nil"], + 396 => ["empty", "nil"], + 397 => ["pass_through", "nil"], + 398 => ["pass_through", "nil"], + 399 => ["pass_through", "nil"], + 400 => ["pass_through", "nil"], + 401 => ["pass_through", "nil"], + 402 => ["pass_through", "$1"], + 403 => ["pass_through", "nil"], + 404 => ["pass_through", "$1"], + 405 => ["ported", "@builder.elsif_node($2, $4, $5)"], + 406 => ["empty", "nil"], + 407 => ["pass_through", "$2"], + 408 => ["pass_through", "$1"], + 409 => ["pass_through", "$1"], + 410 => ["pass_through", "$1"], + 411 => ["pass_through", "$2"], + 412 => ["ported", "[$1].freeze"], + 413 => ["ported", "($1 + [$3]).freeze"], + 414 => ["pass_through", "$1"], + 415 => ["pass_through", "$1"], + 416 => ["pass_through", "$1"], + 417 => ["pass_through", "$1"], + 418 => ["pass_through", "$2"], + 419 => ["pass_through", "$2"], + 420 => ["empty", "nil"], + 421 => ["pass_through", "$1"], + 422 => ["pass_through", "$1"], + 423 => ["empty", "nil"], + 424 => ["empty", "nil"], + 425 => ["ported", "[$1].freeze"], + 426 => ["ported", "($1 + [$3]).freeze"], + 427 => ["ported", "[$1, $3, $4].freeze"], + 428 => ["ported", "[$1, nil, $2].freeze"], + 429 => ["ported", "[nil, $1, $2].freeze"], + 430 => ["ported", "[nil, nil, $1].freeze"], + 431 => ["empty", "nil"], + 432 => ["pass_through", "$1"], + 433 => ["ported", "[$1].freeze"], + 434 => ["ported", "($1 + [$3]).freeze"], + 435 => ["pass_through", "$2"], + 436 => ["empty", "nil"], + 437 => ["pass_through", "$1"], + 438 => ["pass_through", "$1"], + 439 => ["pass_through", "$1"], + 440 => ["pass_through", "$1"], + 441 => ["pass_through", "$1"], + 442 => ["pass_through", "$1"], + 443 => ["pass_through", "$1"], + 444 => ["pass_through", "$1"], + 445 => ["pass_through", "$1"], + 446 => ["pass_through", "$1"], + 447 => ["pass_through", "$1"], + 448 => ["pass_through", "$1"], + 449 => ["pass_through", "$1"], + 450 => ["pass_through", "$1"], + 451 => ["pass_through", "$1"], + 452 => ["empty", "nil"], + 453 => ["pass_through", "$1"], + 454 => ["pass_through", "$2"], + 455 => ["pass_through", "$2"], + 456 => ["pass_through", "$1"], + 457 => ["pass_through", "$3"], + 458 => ["pass_through", "$1"], + 459 => ["pass_through", "$1"], + 460 => ["pass_through", "$1"], + 461 => ["pass_through", "$1"], + 462 => ["empty", "nil"], + 463 => ["empty", "nil"], + 464 => ["empty", "nil"], + 465 => ["empty", "nil"], + 466 => ["empty", "nil"], + 467 => ["ported", "@builder.lambda_node($7, $9)"], + 468 => ["pass_through", "$2"], + 469 => ["pass_through", "$1"], + 470 => ["pass_through", "$2"], + 471 => ["empty", "nil"], + 472 => ["pass_through", "$3"], + 473 => ["pass_through", "$2"], + 474 => ["ported", "@builder.block_call($1, $2)"], + 475 => ["pass_through", "$1"], + 476 => ["pass_through", "$1"], + 477 => ["pass_through", "$1"], + 478 => ["ported", "@builder.call($1, $2 || [])"], + 479 => ["ported", "@builder.receiver_call($1, $2, $3, $4)"], + 480 => ["ported", "@builder.receiver_call($1, :\"::\", $3, $4)"], + 481 => ["ported", "@builder.receiver_call($1, :\"::\", $3, [])"], + 482 => ["pass_through", "$1"], + 483 => ["pass_through", "$1"], + 484 => ["ported", "@builder.call(:super, $2 || [])"], + 485 => ["ported", "@builder.call(:super, [])"], + 486 => ["ported", "@builder.index($1, $3 || [])"], + 487 => ["pass_through", "$2"], + 488 => ["pass_through", "$2"], + 489 => ["empty", "nil"], + 490 => ["pass_through", "$7"], + 491 => ["empty", "nil"], + 492 => ["pass_through", "$7"], + 493 => ["ported", "[$1].freeze"], + 494 => ["pass_through", "$2"], + 495 => ["ported", "($1 + [$3]).freeze"], + 496 => ["pass_through", "$1"], + 497 => ["ported", "@builder.case_chain(@builder.when_node($2, $4), $5)"], + 498 => ["pass_through", "$1"], + 499 => ["ported", "[$1].freeze"], + 500 => ["empty", "nil"], + 501 => ["empty", "nil"], + 502 => ["empty", "nil"], + 503 => ["empty", "nil"], + 504 => ["ported", "@builder.case_chain(@builder.when_node($5, $8), $9)"], + 505 => ["pass_through", "$1"], + 506 => ["pass_through", "$1"], + 507 => ["pass_through", "$1"], + 508 => ["ported", "@builder.if_node($3, [$1], nil)"], + 509 => ["ported", "@builder.unless_node($3, [$1], nil)"], + 510 => ["pass_through", "$1"], + 511 => ["ported", "@builder.pattern(:array, [nil, [$1], nil, nil])"], + 512 => ["ported", "@builder.pattern(:array, [[$1] + Array($3[0]), $3[1], $3[2]])"], + 513 => ["ported", "@builder.pattern(:find, $1)"], + 514 => ["ported", "@builder.pattern(:array, $1)"], + 515 => ["ported", "@builder.pattern(:hash, $1)"], + 516 => ["pass_through", "$1"], + 517 => ["ported", "@builder.pattern(:as, [$1, $3])"], + 518 => ["pass_through", "$1"], + 519 => ["ported", "@builder.pattern(:or, [$1, $3])"], + 520 => ["pass_through", "$1"], + 521 => ["pass_through", "$2"], + 522 => ["pass_through", "$2"], + 523 => ["pass_through", "$1"], + 524 => ["pass_through", "$1"], + 525 => ["ported", "@builder.pattern(:array, [$1, $3])"], + 526 => ["ported", "@builder.pattern(:find, [$1, $3])"], + 527 => ["ported", "@builder.pattern(:hash, [$1, $3])"], + 528 => ["ported", "@builder.pattern(:array, [$1, nil, nil, nil])"], + 529 => ["ported", "@builder.pattern(:array, [$1, $3])"], + 530 => ["ported", "@builder.pattern(:find, [$1, $3])"], + 531 => ["ported", "@builder.pattern(:hash, [$1, $3])"], + 532 => ["ported", "@builder.pattern(:array, [$1, nil, nil, nil])"], + 533 => ["ported", "@builder.pattern(:array, $2)"], + 534 => ["ported", "@builder.pattern(:find, $2)"], + 535 => ["ported", "@builder.pattern(:array, [nil, nil, nil, nil])"], + 536 => ["empty", "nil"], + 537 => ["ported", "@builder.pattern(:hash, $5)"], + 538 => ["ported", "@builder.pattern(:hash, [nil, nil, nil])"], + 539 => ["pass_through", "$3"], + 540 => ["ported", "[[$1], nil, nil].freeze"], + 541 => ["ported", "[$1, nil, nil].freeze"], + 542 => ["ported", "[($1 + $2), nil, nil].freeze"], + 543 => ["ported", "[$1, $2, nil].freeze"], + 544 => ["ported", "[$1, $2, $4].freeze"], + 545 => ["pass_through", "$1"], + 546 => ["pass_through", "$1"], + 547 => ["ported", "($1 + $2).freeze"], + 548 => ["ported", "[nil, $1, nil].freeze"], + 549 => ["ported", "[nil, $1, $3].freeze"], + 550 => ["ported", "[$1, $3, $5].freeze"], + 551 => ["ported", "@builder.pattern(:rest, [$2])"], + 552 => ["ported", "@builder.pattern(:rest, [nil])"], + 553 => ["pass_through", "$1"], + 554 => ["ported", "($1 + $3).freeze"], + 555 => ["ported", "[$1].freeze"], + 556 => ["ported", "[$1, $3].freeze"], + 557 => ["ported", "[$1, nil].freeze"], + 558 => ["ported", "[$1, nil].freeze"], + 559 => ["ported", "[[], $1].freeze"], + 560 => ["pass_through", "$1"], + 561 => ["ported", "($1 + [$3]).freeze"], + 562 => ["ported", "[$1, $2].freeze"], + 563 => ["ported", "[$1, nil].freeze"], + 564 => ["pass_through", "$1"], + 565 => ["pass_through", "$2"], + 566 => ["ported", "@builder.pattern(:rest, [$2])"], + 567 => ["ported", "@builder.pattern(:rest, [nil])"], + 568 => ["pass_through", "$1"], + 569 => ["pass_through", "$1"], + 570 => ["pass_through", "$1"], + 571 => ["pass_through", "$1"], + 572 => ["ported", "@builder.range(:\"..\", $1, $3)"], + 573 => ["ported", "@builder.range(:\"...\", $1, $3)"], + 574 => ["ported", "@builder.range(:\"..\", $1, nil)"], + 575 => ["ported", "@builder.range(:\"...\", $1, nil)"], + 576 => ["pass_through", "$1"], + 577 => ["pass_through", "$1"], + 578 => ["pass_through", "$1"], + 579 => ["ported", "@builder.range(:\"..\", nil, $2)"], + 580 => ["ported", "@builder.range(:\"...\", nil, $2)"], + 581 => ["pass_through", "$1"], + 582 => ["pass_through", "$1"], + 583 => ["pass_through", "$1"], + 584 => ["pass_through", "$1"], + 585 => ["pass_through", "$1"], + 586 => ["pass_through", "$1"], + 587 => ["pass_through", "$1"], + 588 => ["pass_through", "$1"], + 589 => ["pass_through", "$1"], + 590 => ["pass_through", "$1"], + 591 => ["ported", "@builder.pattern(:variable, [$1])"], + 592 => ["ported", "@builder.pattern(:pin, [$2])"], + 593 => ["ported", "@builder.pattern(:pin, [$2])"], + 594 => ["ported", "@builder.pattern(:begin, [$3])"], + 595 => ["ported", "@builder.variable(:constant, $2)"], + 596 => ["ported", "@builder.field($1, :\"::\", $3)"], + 597 => ["ported", "@builder.variable(:constant, $1)"], + 598 => ["ported", "@builder.rescue_chain(@builder.rescue_clause($2, $3, $5), $6)"], + 599 => ["empty", "nil"], + 600 => ["pass_through", "$1"], + 601 => ["pass_through", "$1"], + 602 => ["empty", "nil"], + 603 => ["pass_through", "$2"], + 604 => ["empty", "nil"], + 605 => ["ported", "@builder.body_nodes($2)"], + 606 => ["empty", "nil"], + 607 => ["pass_through", "$1"], + 608 => ["pass_through", "$1"], + 609 => ["pass_through", "$1"], + 610 => ["ported", "@builder.literal($1)"], + 611 => ["pass_through", "$1"], + 612 => ["ported", "@builder.concat_strings($1, $2)"], + 613 => ["ported", "@builder.string(@builder.join_strings($2))"], + 614 => ["ported", "@builder.xstring(@builder.join_strings($2))"], + 615 => ["ported", "@builder.regexp(@builder.join_strings($2))"], + 616 => ["pass_through", "nil"], + 617 => ["pass_through", "nil"], + 618 => ["pass_through", "$3"], + 619 => ["pass_through", "$1"], + 620 => ["empty", "[]"], + 621 => ["ported", "($1 + [$2]).freeze"], + 622 => ["pass_through", "$1"], + 623 => ["ported", "$1.to_s + $2.to_s"], + 624 => ["pass_through", "$3"], + 625 => ["ported", "@builder.word_array($1, symbols: true)"], + 626 => ["empty", "[]"], + 627 => ["ported", "($1 + [$2]).freeze"], + 628 => ["pass_through", "$3"], + 629 => ["ported", "@builder.word_array($1)"], + 630 => ["pass_through", "$3"], + 631 => ["ported", "@builder.word_array($1, symbols: true)"], + 632 => ["empty", "[]"], + 633 => ["ported", "($1 + [$2]).freeze"], + 634 => ["empty", "nil"], + 635 => ["pass_through", "$1"], + 636 => ["empty", "[]"], + 637 => ["ported", "($1 + [$2]).freeze"], + 638 => ["empty", "[]"], + 639 => ["ported", "($1 + [$2]).freeze"], + 640 => ["empty", "[]"], + 641 => ["ported", "($1 + [$2]).freeze"], + 642 => ["pass_through", "$1"], + 643 => ["empty", "nil"], + 644 => ["pass_through", "$3"], + 645 => ["empty", "nil"], + 646 => ["empty", "nil"], + 647 => ["empty", "nil"], + 648 => ["ported", "@builder.interpolation($5)"], + 649 => ["empty", "nil"], + 650 => ["empty", "nil"], + 651 => ["pass_through", "$1"], + 652 => ["pass_through", "$1"], + 653 => ["pass_through", "$1"], + 654 => ["pass_through", "$1"], + 655 => ["ported", "@builder.symbol($2)"], + 656 => ["pass_through", "$1"], + 657 => ["pass_through", "$1"], + 658 => ["ported", "@builder.symbol(@builder.join_strings($2))"], + 659 => ["pass_through", "$1"], + 660 => ["ported", "@builder.unary(:-, $2)"], + 661 => ["ported", "@builder.integer($1)"], + 662 => ["ported", "@builder.float($1)"], + 663 => ["ported", "@builder.literal($1)"], + 664 => ["ported", "@builder.literal($1)"], + 665 => ["ported", "@builder.variable(:instance, $1)"], + 666 => ["ported", "@builder.variable(:global, $1)"], + 667 => ["ported", "@builder.variable(:class, $1)"], + 668 => ["ported", "@builder.identifier($1)"], + 669 => ["ported", "@builder.variable(:constant, $1)"], + 670 => ["pass_through", "$1"], + 671 => ["ported", "@builder.literal(nil)"], + 672 => ["ported", "@builder.literal(:self)"], + 673 => ["ported", "@builder.literal(true)"], + 674 => ["ported", "@builder.literal(false)"], + 675 => ["ported", "@builder.literal(:__FILE__)"], + 676 => ["ported", "@builder.literal(:__LINE__)"], + 677 => ["ported", "@builder.literal(:__ENCODING__)"], + 678 => ["ported", "@builder.read_local($1)"], + 679 => ["pass_through", "$1"], + 680 => ["pass_through", "$1"], + 681 => ["pass_through", "$1"], + 682 => ["ported", "@builder.variable(:backref, $1)"], + 683 => ["ported", "@builder.variable(:backref, $1)"], + 684 => ["empty", "nil"], + 685 => ["pass_through", "$3"], + 686 => ["empty", "nil"], + 687 => ["pass_through", "$1"], + 688 => ["empty", "nil"], + 689 => ["pass_through", "$2"], + 690 => ["pass_through", "$1"], + 691 => ["empty", "nil"], + 692 => ["pass_through", "$2"], + 693 => ["ported", "[$1].freeze"], + 694 => ["ported", "($1 + [$3]).freeze"], + 695 => ["ported", "[$1, $3, $4].freeze"], + 696 => ["ported", "[$1, nil, $2].freeze"], + 697 => ["ported", "[nil, $1, $2].freeze"], + 698 => ["pass_through", "$1"], + 699 => ["pass_through", "$1"], + 700 => ["pass_through", "$1"], + 701 => ["ported", "[$1].freeze"], + 702 => ["ported", "($1 + [$3]).freeze"], + 703 => ["pass_through", "$2"], + 704 => ["ported", "[nil, nil, nil].freeze"], + 705 => ["pass_through", "$1"], + 706 => ["pass_through", "$1"], + 707 => ["pass_through", "$1"], + 708 => ["pass_through", "$1"], + 709 => ["pass_through", "$1"], + 710 => ["pass_through", "$1"], + 711 => ["pass_through", "$1"], + 712 => ["pass_through", "$1"], + 713 => ["pass_through", "$1"], + 714 => ["pass_through", "$1"], + 715 => ["pass_through", "$1"], + 716 => ["pass_through", "$1"], + 717 => ["pass_through", "$1"], + 718 => ["pass_through", "$1"], + 719 => ["empty", "nil"], + 720 => ["empty", "nil"], + 721 => ["pass_through", "$1"], + 722 => ["empty", "nil"], + 723 => ["empty", "nil"], + 724 => ["empty", "nil"], + 725 => ["pass_through", "$1"], + 726 => ["pass_through", "$1"], + 727 => ["pass_through", "$1"], + 728 => ["pass_through", "$1"], + 729 => ["pass_through", "$1"], + 730 => ["pass_through", "$1"], + 731 => ["pass_through", "$1"], + 732 => ["pass_through", "$1"], + 733 => ["ported", "[$1, $2].freeze"], + 734 => ["ported", "[$1, 0].freeze"], + 735 => ["ported", "[$1, $2].freeze"], + 736 => ["ported", "[$1, 0].freeze"], + 737 => ["pass_through", "$1"], + 738 => ["pass_through", "$1"], + 739 => ["pass_through", "$1"], + 740 => ["pass_through", "$2"], + 741 => ["pass_through", "$1"], + 742 => ["empty", "nil"], + 743 => ["pass_through", "$1"], + 744 => ["ported", "[$2].freeze"], + 745 => ["pass_through", "$1"], + 746 => ["empty", "nil"], + 747 => ["empty", "nil"], + 748 => ["pass_through", "$1"], + 749 => ["pass_through", "$1"], + 750 => ["pass_through", "$2"], + 751 => ["empty", "nil"], + 752 => ["pass_through", "$1"], + 753 => ["empty", "nil"], + 754 => ["pass_through", "$1"], + 755 => ["pass_through", "$1"], + 756 => ["pass_through", "$1"], + 757 => ["ported", "[$1].freeze"], + 758 => ["ported", "($1 + [$3]).freeze"], + 759 => ["pass_through", "$1"], + 760 => ["ported", "@builder.pair(@builder.symbol($1), $2)"], + 761 => ["pass_through", "$1"], + 762 => ["pass_through", "$4"], + 763 => ["pass_through", "$2"], + 764 => ["empty", "nil"], + 765 => ["pass_through", "$1"], + 766 => ["pass_through", "$1"], + 767 => ["pass_through", "$1"], + 768 => ["pass_through", "$1"], + 769 => ["pass_through", "$1"], + 770 => ["pass_through", "$1"], + 771 => ["pass_through", "$1"], + 772 => ["pass_through", "$1"], + 773 => ["ported", "@builder.operator(\".\")"], + 774 => ["ported", "@builder.operator(\"::\")"], + 775 => ["ported", "@builder.operator(\".\")"], + 776 => ["ported", "@builder.operator(\"&.\")"], + 777 => ["pass_through", "$1"], + 778 => ["ported", "@builder.operator(\"::\")"], + 779 => ["pass_through", "$1"], + 780 => ["pass_through", "nil"], + 781 => ["pass_through", "$1"], + 782 => ["pass_through", "nil"], + 783 => ["pass_through", "nil"], + 784 => ["empty", "nil"], + 785 => ["empty", "nil"], + 786 => ["empty", "nil"], + 787 => ["empty", "nil"], + 788 => ["empty", "nil"], + 792 => ["pass_through", "$1"], + 795 => ["pass_through", "$1"], + 797 => ["pass_through", "$1"]}, + "compatibility_actions" => {}, + "additional_expected_conflicts" => 1} diff --git a/lib/arpaka/ruby_grammar.rb b/lib/arpaka/ruby_grammar.rb index 13c017f..6920ecc 100644 --- a/lib/arpaka/ruby_grammar.rb +++ b/lib/arpaka/ruby_grammar.rb @@ -4,13 +4,13 @@ require "json" require "digest" require_relative "actions" +require_relative "profile/ruby" module Arpaka::RubyGrammar REVISION = "37d60dd3241d5fdb10ca43f36077f5d556ae1b8d" EXTENDED_RULES = { ["value_expr_command", ["command"]] => "tLBRACE_ARG" }.freeze - def self.parse(source, filename) grammar = Lrama::Parser.new(source, filename).parse unless grammar.no_stdlib @@ -36,6 +36,17 @@ def self.load_upstream(ruby_source) source = preprocess(File.read(File.join(vendor, "parse.y")), File.join(vendor, "defs/id.def")) grammar = parse(source, File.join(vendor, "parse.y")) + [grammar, inventory(grammar)] + end + + def self.load_parse_y(parse_y) + path = File.expand_path(parse_y) + source = preprocess_names(File.read(path)) + grammar = parse(source, path) + [grammar, inventory(grammar)] + end + + def self.inventory(grammar) inventory = grammar.rules.reject(&:initial_rule?).map do |rule| { "id" => rule.id, "lhs" => rule.lhs.id.s_value, "rhs" => rule.rhs.map { |symbol| symbol.id.s_value }, @@ -44,9 +55,7 @@ def self.load_upstream(ruby_source) "midrule_position" => rule.position_in_original_rule_rhs, "precedence" => rule.precedence_sym&.id&.s_value } end - unknown = ::Arpaka::RubyGrammarActions::ACTIONS.keys - inventory.map { |rule| rule.fetch("id") } - warn "Arpaka: Unknown action IDs: #{unknown.inspect}" unless unknown.empty? - [grammar, inventory] + inventory end # Ruby's id.def is Ruby code. Evaluate it in a Box so its temporary locals, @@ -72,6 +81,10 @@ def self.preprocess(source, id_def) raise ::Arpaka::Error, "Ruby id.def preprocessing failed: #{error.message}" end + def self.preprocess_names(source) + source.gsub(/\bRUBY_TOKEN\(([A-Za-z_][A-Za-z0-9_]*)\)/, '\\1') + end + def self.symbol_names(grammar) used = grammar.terms.map { |symbol| symbol.id.s_value } grammar.nterms.to_h do |symbol| @@ -89,12 +102,18 @@ def self.symbol_names(grammar) end end - def self.artifacts(ruby_source:) - grammar, inventory = load_upstream(ruby_source) + def self.artifacts(ruby_source: nil, parse_y: nil) + grammar, inventory = if ruby_source + load_upstream(ruby_source) + else + load_parse_y(parse_y) + end names = symbol_names(grammar) name = ->(symbol) { symbol.term ? symbol.id.s_value : names.fetch(symbol.id.s_value) } + profile = ::Arpaka::Profile::RUBY.select(grammar) + expected_conflicts = (grammar.expect || 0) + profile.fetch("additional_expected_conflicts", 0) lines = ["/* Generated from ruby/ruby #{REVISION}. See Arpaka Action mappings. */", - "%no-stdlib", "%expect #{grammar.expect || 0}"] + "%no-stdlib", "%expect #{expected_conflicts}"] lines << "%define lr.type ielr" if grammar.ielr_defined? grammar.terms.each do |symbol| next if symbol.error_symbol? || symbol.undef_symbol? @@ -120,7 +139,9 @@ def self.artifacts(ruby_source:) else "" end - expression = ::Arpaka::RubyGrammarActions::ACTIONS[rule.id]&.last || "@builder.unsupported(#{rule.id})" + key = [rule.lhs.id.s_value, rule.rhs.map { |symbol| symbol.id.s_value }] + action = profile.fetch("actions")[rule.id] || profile.fetch("compatibility_actions", {})[key] + expression = action&.last || "@builder.unsupported(#{rule.id})" lines << "/* upstream parse.y:#{metadata.fetch(rule.id).fetch('line')}: #{rule.as_comment} */" lines << "#{name.call(rule.lhs)}: #{rhs}#{precedence} { $$ = #{expression} };" end @@ -132,9 +153,12 @@ def self.artifacts(ruby_source:) compact = rules.map do |rule| { id: rule.id, rule: rule.as_comment, line: metadata.fetch(rule.id).fetch("line"), - status: ::Arpaka::RubyGrammarActions::ACTIONS[rule.id]&.first || "unsupported" } + status: (profile.fetch("actions")[rule.id] || + profile.fetch("compatibility_actions", {})[[rule.lhs.id.s_value, + rule.rhs.map { |symbol| symbol.id.s_value }]])&.first || "unsupported" } end - { "parse.y" => source, "rules.json" => JSON.pretty_generate(compact) + "\n" } + { "parse.y" => source, "rules.json" => JSON.pretty_generate(compact) + "\n", + "runtime" => profile.fetch("runtime", {}) } end def self.verify_structure(original, source, names) @@ -147,7 +171,7 @@ def self.verify_structure(original, source, names) end.sort_by(&:inspect) end expected_rules = signature.call(original, names).map do |rule| - if rule[0] == names.fetch("value_expr_command") && rule[1] == [names.fetch("command")] + if names.key?("value_expr_command") && rule[0] == names.fetch("value_expr_command") && rule[1] == [names.fetch("command")] rule[0, 2] + ["tLBRACE_ARG"] else rule diff --git a/lib/arpaka/runtime/ast.rb.erb b/lib/arpaka/runtime/ast.rb.erb index 6d4962f..cae32df 100644 --- a/lib/arpaka/runtime/ast.rb.erb +++ b/lib/arpaka/runtime/ast.rb.erb @@ -6,6 +6,14 @@ module AST If = Data.define(:condition, :then_body, :else_body) LocalRead = Data.define(:name) LocalWrite = Data.define(:name, :value) +<% if runtime.fetch("ast_nodes", []).include?("assignment") -%> + Assignment = Data.define(:target, :value) + OpAssign = Data.define(:target, :operator, :value) + MultipleAssign = Data.define(:targets, :value) + Field = Data.define(:receiver, :operator, :name) + Alias = Data.define(:new_name, :old_name) + Undef = Data.define(:names) +<% end -%> BareCall = Data.define(:name) ArrayLiteral = Data.define(:elements) HashLiteral = Data.define(:pairs) @@ -26,10 +34,23 @@ module AST XStringLiteral = Data.define(:value) For = Data.define(:variable, :enumerable, :body) Def = Data.define(:name, :arguments, :body) +<% if runtime.fetch("ast_nodes", []).include?("singleton_method") -%> + SingletonMethodDef = Data.define(:receiver, :name, :arguments, :body) +<% end -%> ClassDef = Data.define(:name, :superclass, :body) ModuleDef = Data.define(:name, :body) +<% if runtime.fetch("ast_nodes", []).include?("singleton_class") -%> + SingletonClassDef = Data.define(:receiver, :body) +<% end -%> When = Data.define(:patterns, :body) Case = Data.define(:expression, :whens, :else_body) +<% if runtime.fetch("ast_nodes", []).include?("pattern") -%> + Pattern = Data.define(:kind, :values) + RescueClause = Data.define(:exceptions, :variable, :body) + FullRescue = Data.define(:body, :clauses, :else_body, :ensure_body) + BeginBlock = Data.define(:body) + EndBlock = Data.define(:body) +<% end -%> Lambda = Data.define(:arguments, :body) Sequence = Data.define(:statements) end diff --git a/lib/arpaka/runtime/builder.rb.erb b/lib/arpaka/runtime/builder.rb.erb index c8979cf..84118b8 100644 --- a/lib/arpaka/runtime/builder.rb.erb +++ b/lib/arpaka/runtime/builder.rb.erb @@ -84,6 +84,12 @@ class Builder def def_node(name, arguments, body) AST::Def.new(name.to_sym, (arguments || []).freeze, body || [].freeze) end +<% if runtime.fetch("builder_methods", []).include?("singleton_method") -%> + def singleton_method_node(receiver, name, arguments, body) + AST::SingletonMethodDef.new(receiver, name.to_sym, (arguments || []).freeze, + body_nodes(body)) + end +<% end -%> def class_node(name, superclass, body) AST::ClassDef.new(name.to_sym, superclass, body_nodes(body)) @@ -92,6 +98,11 @@ class Builder def module_node(name, body) AST::ModuleDef.new(name.to_sym, body_nodes(body)) end +<% if runtime.fetch("builder_methods", []).include?("singleton_class") -%> + def singleton_class_node(receiver, body) + AST::SingletonClassDef.new(receiver, body_nodes(body)) + end +<% end -%> def body_nodes(body) value = body @@ -170,6 +181,42 @@ class Builder def lambda_node(arguments, body) AST::Lambda.new((arguments || []).freeze, body_nodes(body)) end +<% if runtime.fetch("builder_methods", []).include?("rescue") -%> + def body_statement(body, clauses, else_body, ensure_body) + clauses = clauses.nil? ? [] : Array(clauses) + if clauses.empty? && else_body.nil? && ensure_body.nil? + body_nodes(body) + else + full_rescue(body, clauses, else_body, ensure_body) + end + end + + def rescue_chain(clause, rest) + [clause] + (rest.nil? ? [] : Array(rest)) + end + + def pattern(kind, values) + AST::Pattern.new(kind.to_sym, Array(values).freeze) + end + + def rescue_clause(exceptions, variable, body) + AST::RescueClause.new(Array(exceptions).freeze, variable, body_nodes(body)) + end + + def full_rescue(body, clauses, else_body, ensure_body) + AST::FullRescue.new(body_nodes(body), Array(clauses).freeze, + else_body.nil? ? nil : body_nodes(else_body), + ensure_body.nil? ? nil : body_nodes(ensure_body)) + end + + def begin_block(body) + AST::BeginBlock.new(body_nodes(body)) + end + + def end_block(body) + AST::EndBlock.new(body_nodes(body)) + end +<% end -%> def unary(operator, operand) AST::Unary.new(operator, operand) @@ -212,6 +259,35 @@ class Builder def assign(name, value) AST::LocalWrite.new(name, value) end +<% if runtime.fetch("builder_methods", []).include?("assignment") -%> + def assignment(name, value) + if name.is_a?(AST::Field) || name.is_a?(AST::Index) + AST::Assignment.new(name, value) + else + AST::LocalWrite.new(name, value) + end + end + + def opassign(target, operator, value) + AST::OpAssign.new(target, operator.to_sym, value) + end + + def multiple_assign(targets, value) + AST::MultipleAssign.new(Array(targets).freeze, value) + end + + def field(receiver, operator, name) + AST::Field.new(receiver, operator.to_sym, name.to_sym) + end + + def alias_node(new_name, old_name) + AST::Alias.new(new_name, old_name) + end + + def undef_node(names) + AST::Undef.new(Array(names).freeze) + end +<% end -%> def array(elements) AST::ArrayLiteral.new(elements.freeze) diff --git a/lib/arpaka/runtime/lexer.rb.erb b/lib/arpaka/runtime/lexer.rb.erb index 0018687..e9de66b 100644 --- a/lib/arpaka/runtime/lexer.rb.erb +++ b/lib/arpaka/runtime/lexer.rb.erb @@ -1287,7 +1287,14 @@ class Lexer OP_TOKENS.fetch(text, :tOP_ASGN) end @context.lambda_pending = true if token == :tLAMBDA +<% source_text_tokens = runtime.fetch("lexer", {}).fetch("source_text_tokens", []) -%> +<% if source_text_tokens.empty? -%> return [token, nil] +<% elsif source_text_tokens.one? -%> + return [token, token == <%= source_text_tokens.first.to_sym.inspect %> ? text : nil] +<% else -%> + return [token, <%= source_text_tokens.map { |value| value.to_sym }.inspect %>.include?(token) ? text : nil] +<% end -%> end value = byte.chr advance diff --git a/test/lrama/frontend_generator_test.rb b/test/lrama/frontend_generator_test.rb index 92fd3e6..69c8890 100644 --- a/test/lrama/frontend_generator_test.rb +++ b/test/lrama/frontend_generator_test.rb @@ -86,6 +86,13 @@ def generated_source end end + test "generates from parse.y by preserving token names" do + source = Arpaka.generate(parse_y: File.join(RUBY_SOURCE, "parse.y"), class_name: "StandaloneRuby") + assert_include(source, "class StandaloneRuby") + assert_include(source, "Ruby source profile: parse.y only") + assert_include(source, "Redistribution and use in source and binary forms") + end + test "CLI requires output and handles help" do out, err = StringIO.new, StringIO.new assert_equal(0, Arpaka::CLI.run(["--help"], out: out, err: err)) @@ -120,4 +127,14 @@ def generated_source assert_false(Arpaka.respond_to?(:parse)) assert_false(Lrama::Ruby.respond_to?(:parse)) end + + test "CLI generates from parse.y" do + Dir.mktmpdir do |directory| + path = File.join(directory, "frontend.rb") + out, err = StringIO.new, StringIO.new + args = ["generate", "--parse-y", File.join(RUBY_SOURCE, "parse.y"), "--output", path] + assert_equal(0, Arpaka::CLI.run(args, out: out, err: err), err.string) + assert_include(File.read(path), "class RubyParser") + end + end end diff --git a/tool/check_package.rb b/tool/check_package.rb index 28bfe3b..60e7ff3 100644 --- a/tool/check_package.rb +++ b/tool/check_package.rb @@ -51,6 +51,7 @@ abort "Ruby grammar packaged" if files.any? { |file| file.end_with?("/parse.y", "/rules.json") } %w[exe/arpaka lib/arpaka/COPYING lib/arpaka/BSDL lib/arpaka/actions.rb lib/arpaka/upstream_rules.json lib/arpaka/source.json + lib/arpaka/profile/ruby.rb lib/arpaka/profile/ruby_3_4.rb lib/arpaka/runtime/lexer.rb.erb].each do |file| unless files.include?(file) && File.file?(File.join(spec.full_gem_path, file)) abort "Missing generation support: #{file}"