arpaka 0.1.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,16 +3,14 @@
3
3
  require "lrama"
4
4
  require "json"
5
5
  require "digest"
6
- require "open3"
7
- require "rbconfig"
8
6
  require_relative "actions"
7
+ require_relative "profile/ruby"
9
8
 
10
9
  module Arpaka::RubyGrammar
11
10
  REVISION = "37d60dd3241d5fdb10ca43f36077f5d556ae1b8d"
12
11
  EXTENDED_RULES = {
13
12
  ["value_expr_command", ["command"]] => "tLBRACE_ARG"
14
13
  }.freeze
15
-
16
14
  def self.parse(source, filename)
17
15
  grammar = Lrama::Parser.new(source, filename).parse
18
16
  unless grammar.no_stdlib
@@ -35,10 +33,20 @@ module Arpaka::RubyGrammar
35
33
  warn "Arpaka: Ruby source differs from the recorded profile: #{path}; " \
36
34
  "expected SHA256 #{digest}, got #{actual}."
37
35
  end
38
- source, error, result = Open3.capture3(RbConfig.ruby,
39
- File.join(vendor, "tool/id2token.rb"), File.join(vendor, "parse.y"))
40
- raise ::Arpaka::Error, "Ruby preprocessing failed: #{error}" unless result.success?
36
+ source = preprocess(File.read(File.join(vendor, "parse.y")),
37
+ File.join(vendor, "defs/id.def"))
41
38
  grammar = parse(source, File.join(vendor, "parse.y"))
39
+ [grammar, inventory(grammar)]
40
+ end
41
+
42
+ def self.load_parse_y(parse_y)
43
+ path = File.expand_path(parse_y)
44
+ source = preprocess_names(File.read(path))
45
+ grammar = parse(source, path)
46
+ [grammar, inventory(grammar)]
47
+ end
48
+
49
+ def self.inventory(grammar)
42
50
  inventory = grammar.rules.reject(&:initial_rule?).map do |rule|
43
51
  { "id" => rule.id, "lhs" => rule.lhs.id.s_value,
44
52
  "rhs" => rule.rhs.map { |symbol| symbol.id.s_value },
@@ -47,9 +55,34 @@ module Arpaka::RubyGrammar
47
55
  "midrule_position" => rule.position_in_original_rule_rhs,
48
56
  "precedence" => rule.precedence_sym&.id&.s_value }
49
57
  end
50
- unknown = ::Arpaka::RubyGrammarActions::ACTIONS.keys - inventory.map { |rule| rule.fetch("id") }
51
- warn "Arpaka: Unknown action IDs: #{unknown.inspect}" unless unknown.empty?
52
- [grammar, inventory]
58
+ inventory
59
+ end
60
+
61
+ # Ruby's id.def is Ruby code. Evaluate it in a Box so its temporary locals,
62
+ # methods and constants cannot affect Arpaka or Lrama's process.
63
+ def self.preprocess(source, id_def)
64
+ box = ::Ruby::Box.new
65
+ ids = box.eval(File.read(id_def))
66
+ tokens = ids.fetch(:token_op).each_with_object({}) do |(_id, _op, token, id), result|
67
+ result[token] = id if token
68
+ end
69
+ return source unless tokens.any?
70
+
71
+ names = tokens.keys.map { |name| Regexp.escape(name) }.join("|")
72
+ token_pattern = /\bRUBY_TOKEN\((#{names})\)\s*(?=\s)/
73
+ source.lines.map do |line|
74
+ if line.start_with?("%token")
75
+ line.gsub(token_pattern) { tokens.fetch(Regexp.last_match(1)).to_s }
76
+ else
77
+ line
78
+ end
79
+ end.join
80
+ rescue KeyError, TypeError, SyntaxError => error
81
+ raise ::Arpaka::Error, "Ruby id.def preprocessing failed: #{error.message}"
82
+ end
83
+
84
+ def self.preprocess_names(source)
85
+ source.gsub(/\bRUBY_TOKEN\(([A-Za-z_][A-Za-z0-9_]*)\)/, '\\1')
53
86
  end
54
87
 
55
88
  def self.symbol_names(grammar)
@@ -69,12 +102,18 @@ module Arpaka::RubyGrammar
69
102
  end
70
103
  end
71
104
 
72
- def self.artifacts(ruby_source:)
73
- grammar, inventory = load_upstream(ruby_source)
105
+ def self.artifacts(ruby_source: nil, parse_y: nil)
106
+ grammar, inventory = if ruby_source
107
+ load_upstream(ruby_source)
108
+ else
109
+ load_parse_y(parse_y)
110
+ end
74
111
  names = symbol_names(grammar)
75
112
  name = ->(symbol) { symbol.term ? symbol.id.s_value : names.fetch(symbol.id.s_value) }
113
+ profile = ::Arpaka::Profile::RUBY.select(grammar)
114
+ expected_conflicts = (grammar.expect || 0) + profile.fetch("additional_expected_conflicts", 0)
76
115
  lines = ["/* Generated from ruby/ruby #{REVISION}. See Arpaka Action mappings. */",
77
- "%no-stdlib", "%expect #{grammar.expect || 0}"]
116
+ "%no-stdlib", "%expect #{expected_conflicts}"]
78
117
  lines << "%define lr.type ielr" if grammar.ielr_defined?
79
118
  grammar.terms.each do |symbol|
80
119
  next if symbol.error_symbol? || symbol.undef_symbol?
@@ -100,7 +139,9 @@ module Arpaka::RubyGrammar
100
139
  else
101
140
  ""
102
141
  end
103
- expression = ::Arpaka::RubyGrammarActions::ACTIONS[rule.id]&.last || "@builder.unsupported(#{rule.id})"
142
+ key = [rule.lhs.id.s_value, rule.rhs.map { |symbol| symbol.id.s_value }]
143
+ action = profile.fetch("actions")[rule.id] || profile.fetch("compatibility_actions", {})[key]
144
+ expression = action&.last || "@builder.unsupported(#{rule.id})"
104
145
  lines << "/* upstream parse.y:#{metadata.fetch(rule.id).fetch('line')}: #{rule.as_comment} */"
105
146
  lines << "#{name.call(rule.lhs)}: #{rhs}#{precedence} { $$ = #{expression} };"
106
147
  end
@@ -112,9 +153,13 @@ module Arpaka::RubyGrammar
112
153
  compact = rules.map do |rule|
113
154
  { id: rule.id, rule: rule.as_comment,
114
155
  line: metadata.fetch(rule.id).fetch("line"),
115
- status: ::Arpaka::RubyGrammarActions::ACTIONS[rule.id]&.first || "unsupported" }
156
+ status: (profile.fetch("actions")[rule.id] ||
157
+ profile.fetch("compatibility_actions", {})[[rule.lhs.id.s_value,
158
+ rule.rhs.map { |symbol| symbol.id.s_value }]])&.first || "unsupported" }
116
159
  end
117
- { "parse.y" => source, "rules.json" => JSON.pretty_generate(compact) + "\n" }
160
+ { "parse.y" => source, "rules.json" => JSON.pretty_generate(compact) + "\n",
161
+ "runtime" => profile.fetch("runtime", {}),
162
+ "source_profile" => profile.fetch("source_tag", REVISION) }
118
163
  end
119
164
 
120
165
  def self.verify_structure(original, source, names)
@@ -127,7 +172,7 @@ module Arpaka::RubyGrammar
127
172
  end.sort_by(&:inspect)
128
173
  end
129
174
  expected_rules = signature.call(original, names).map do |rule|
130
- if rule[0] == names.fetch("value_expr_command") && rule[1] == [names.fetch("command")]
175
+ if names.key?("value_expr_command") && rule[0] == names.fetch("value_expr_command") && rule[1] == [names.fetch("command")]
131
176
  rule[0, 2] + ["tLBRACE_ARG"]
132
177
  else
133
178
  rule
@@ -6,6 +6,14 @@ module AST
6
6
  If = Data.define(:condition, :then_body, :else_body)
7
7
  LocalRead = Data.define(:name)
8
8
  LocalWrite = Data.define(:name, :value)
9
+ <% if runtime.fetch("ast_nodes", []).include?("assignment") -%>
10
+ Assignment = Data.define(:target, :value)
11
+ OpAssign = Data.define(:target, :operator, :value)
12
+ MultipleAssign = Data.define(:targets, :value)
13
+ Field = Data.define(:receiver, :operator, :name)
14
+ Alias = Data.define(:new_name, :old_name)
15
+ Undef = Data.define(:names)
16
+ <% end -%>
9
17
  BareCall = Data.define(:name)
10
18
  ArrayLiteral = Data.define(:elements)
11
19
  HashLiteral = Data.define(:pairs)
@@ -26,10 +34,23 @@ module AST
26
34
  XStringLiteral = Data.define(:value)
27
35
  For = Data.define(:variable, :enumerable, :body)
28
36
  Def = Data.define(:name, :arguments, :body)
37
+ <% if runtime.fetch("ast_nodes", []).include?("singleton_method") -%>
38
+ SingletonMethodDef = Data.define(:receiver, :name, :arguments, :body)
39
+ <% end -%>
29
40
  ClassDef = Data.define(:name, :superclass, :body)
30
41
  ModuleDef = Data.define(:name, :body)
42
+ <% if runtime.fetch("ast_nodes", []).include?("singleton_class") -%>
43
+ SingletonClassDef = Data.define(:receiver, :body)
44
+ <% end -%>
31
45
  When = Data.define(:patterns, :body)
32
46
  Case = Data.define(:expression, :whens, :else_body)
47
+ <% if runtime.fetch("ast_nodes", []).include?("pattern") -%>
48
+ Pattern = Data.define(:kind, :values)
49
+ RescueClause = Data.define(:exceptions, :variable, :body)
50
+ FullRescue = Data.define(:body, :clauses, :else_body, :ensure_body)
51
+ BeginBlock = Data.define(:body)
52
+ EndBlock = Data.define(:body)
53
+ <% end -%>
33
54
  Lambda = Data.define(:arguments, :body)
34
55
  Sequence = Data.define(:statements)
35
56
  end
@@ -84,6 +84,12 @@ class Builder
84
84
  def def_node(name, arguments, body)
85
85
  AST::Def.new(name.to_sym, (arguments || []).freeze, body || [].freeze)
86
86
  end
87
+ <% if runtime.fetch("builder_methods", []).include?("singleton_method") -%>
88
+ def singleton_method_node(receiver, name, arguments, body)
89
+ AST::SingletonMethodDef.new(receiver, name.to_sym, (arguments || []).freeze,
90
+ body_nodes(body))
91
+ end
92
+ <% end -%>
87
93
 
88
94
  def class_node(name, superclass, body)
89
95
  AST::ClassDef.new(name.to_sym, superclass, body_nodes(body))
@@ -92,6 +98,11 @@ class Builder
92
98
  def module_node(name, body)
93
99
  AST::ModuleDef.new(name.to_sym, body_nodes(body))
94
100
  end
101
+ <% if runtime.fetch("builder_methods", []).include?("singleton_class") -%>
102
+ def singleton_class_node(receiver, body)
103
+ AST::SingletonClassDef.new(receiver, body_nodes(body))
104
+ end
105
+ <% end -%>
95
106
 
96
107
  def body_nodes(body)
97
108
  value = body
@@ -170,6 +181,42 @@ class Builder
170
181
  def lambda_node(arguments, body)
171
182
  AST::Lambda.new((arguments || []).freeze, body_nodes(body))
172
183
  end
184
+ <% if runtime.fetch("builder_methods", []).include?("rescue") -%>
185
+ def body_statement(body, clauses, else_body, ensure_body)
186
+ clauses = clauses.nil? ? [] : Array(clauses)
187
+ if clauses.empty? && else_body.nil? && ensure_body.nil?
188
+ body_nodes(body)
189
+ else
190
+ full_rescue(body, clauses, else_body, ensure_body)
191
+ end
192
+ end
193
+
194
+ def rescue_chain(clause, rest)
195
+ [clause] + (rest.nil? ? [] : Array(rest))
196
+ end
197
+
198
+ def pattern(kind, values)
199
+ AST::Pattern.new(kind.to_sym, Array(values).freeze)
200
+ end
201
+
202
+ def rescue_clause(exceptions, variable, body)
203
+ AST::RescueClause.new(Array(exceptions).freeze, variable, body_nodes(body))
204
+ end
205
+
206
+ def full_rescue(body, clauses, else_body, ensure_body)
207
+ AST::FullRescue.new(body_nodes(body), Array(clauses).freeze,
208
+ else_body.nil? ? nil : body_nodes(else_body),
209
+ ensure_body.nil? ? nil : body_nodes(ensure_body))
210
+ end
211
+
212
+ def begin_block(body)
213
+ AST::BeginBlock.new(body_nodes(body))
214
+ end
215
+
216
+ def end_block(body)
217
+ AST::EndBlock.new(body_nodes(body))
218
+ end
219
+ <% end -%>
173
220
 
174
221
  def unary(operator, operand)
175
222
  AST::Unary.new(operator, operand)
@@ -212,6 +259,35 @@ class Builder
212
259
  def assign(name, value)
213
260
  AST::LocalWrite.new(name, value)
214
261
  end
262
+ <% if runtime.fetch("builder_methods", []).include?("assignment") -%>
263
+ def assignment(name, value)
264
+ if name.is_a?(AST::Field) || name.is_a?(AST::Index)
265
+ AST::Assignment.new(name, value)
266
+ else
267
+ AST::LocalWrite.new(name, value)
268
+ end
269
+ end
270
+
271
+ def opassign(target, operator, value)
272
+ AST::OpAssign.new(target, operator.to_sym, value)
273
+ end
274
+
275
+ def multiple_assign(targets, value)
276
+ AST::MultipleAssign.new(Array(targets).freeze, value)
277
+ end
278
+
279
+ def field(receiver, operator, name)
280
+ AST::Field.new(receiver, operator.to_sym, name.to_sym)
281
+ end
282
+
283
+ def alias_node(new_name, old_name)
284
+ AST::Alias.new(new_name, old_name)
285
+ end
286
+
287
+ def undef_node(names)
288
+ AST::Undef.new(Array(names).freeze)
289
+ end
290
+ <% end -%>
215
291
 
216
292
  def array(elements)
217
293
  AST::ArrayLiteral.new(elements.freeze)
@@ -1287,7 +1287,14 @@ class Lexer
1287
1287
  OP_TOKENS.fetch(text, :tOP_ASGN)
1288
1288
  end
1289
1289
  @context.lambda_pending = true if token == :tLAMBDA
1290
+ <% source_text_tokens = runtime.fetch("lexer", {}).fetch("source_text_tokens", []) -%>
1291
+ <% if source_text_tokens.empty? -%>
1290
1292
  return [token, nil]
1293
+ <% elsif source_text_tokens.one? -%>
1294
+ return [token, token == <%= source_text_tokens.first.to_sym.inspect %> ? text : nil]
1295
+ <% else -%>
1296
+ return [token, <%= source_text_tokens.map { |value| value.to_sym }.inspect %>.include?(token) ? text : nil]
1297
+ <% end -%>
1291
1298
  end
1292
1299
  value = byte.chr
1293
1300
  advance
data/lib/arpaka.rb CHANGED
@@ -5,12 +5,12 @@ require_relative "lrama/ruby"
5
5
  module Arpaka
6
6
  class Error < Lrama::Ruby::Error; end
7
7
 
8
- def self.generate(ruby_source:, class_name: "RubyParser")
9
- Generator.new.generate(ruby_source: ruby_source, class_name: class_name)
8
+ def self.generate(ruby_source: nil, parse_y: nil, class_name: "RubyParser")
9
+ Generator.new.generate(ruby_source: ruby_source, parse_y: parse_y, class_name: class_name)
10
10
  end
11
11
 
12
- def self.compile(ruby_source:, class_name: "RubyParser")
13
- source = generate(ruby_source: ruby_source, class_name: class_name)
12
+ def self.compile(ruby_source: nil, parse_y: nil, class_name: "RubyParser")
13
+ source = generate(ruby_source: ruby_source, parse_y: parse_y, class_name: class_name)
14
14
  box = ::Ruby::Box.new
15
15
  box.eval(source)
16
16
  box.const_get(class_name, false)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lrama
4
4
  module Ruby
5
- VERSION = "0.1.1"
5
+ VERSION = "1.0.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arpaka
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - MATSUMOTO, Katsuyoshi
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: 0.8.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: racc
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
26
40
  description: A Ruby parser frontend and Ruby output backend for Lrama.
27
41
  email:
28
42
  - github@katsyoshi.org
@@ -45,6 +59,9 @@ files:
45
59
  - lib/arpaka/actions.rb
46
60
  - lib/arpaka/cli.rb
47
61
  - lib/arpaka/generator.rb
62
+ - lib/arpaka/profile/ruby.rb
63
+ - lib/arpaka/profile/ruby_3_3.rb
64
+ - lib/arpaka/profile/ruby_3_4.rb
48
65
  - lib/arpaka/ruby_grammar.rb
49
66
  - lib/arpaka/runtime/ast.rb.erb
50
67
  - lib/arpaka/runtime/builder.rb.erb