arpaka 0.1.0 → 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.
- checksums.yaml +4 -4
- data/README.md +14 -0
- data/lib/arpaka/cli.rb +5 -2
- data/lib/arpaka/generator.rb +15 -7
- data/lib/arpaka/profile/ruby.rb +49 -0
- data/lib/arpaka/profile/ruby_3_3.rb +800 -0
- data/lib/arpaka/profile/ruby_3_4.rb +802 -0
- data/lib/arpaka/ruby_grammar.rb +62 -29
- data/lib/arpaka/runtime/ast.rb.erb +21 -0
- data/lib/arpaka/runtime/builder.rb.erb +76 -0
- data/lib/arpaka/runtime/lexer.rb.erb +7 -0
- data/lib/arpaka.rb +4 -4
- data/lib/lrama/ruby/version.rb +1 -1
- metadata +18 -1
data/lib/arpaka/ruby_grammar.rb
CHANGED
|
@@ -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
|
|
@@ -32,14 +30,23 @@ module Arpaka::RubyGrammar
|
|
|
32
30
|
actual = Digest::SHA256.file(File.join(vendor, path)).hexdigest
|
|
33
31
|
next if actual == digest
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
"expected SHA256 #{digest}, got #{actual}.
|
|
37
|
-
"Update the Action mappings and source profile before regenerating."
|
|
33
|
+
warn "Arpaka: Ruby source differs from the recorded profile: #{path}; " \
|
|
34
|
+
"expected SHA256 #{digest}, got #{actual}."
|
|
38
35
|
end
|
|
39
|
-
source
|
|
40
|
-
File.join(vendor, "
|
|
41
|
-
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"))
|
|
42
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)
|
|
43
50
|
inventory = grammar.rules.reject(&:initial_rule?).map do |rule|
|
|
44
51
|
{ "id" => rule.id, "lhs" => rule.lhs.id.s_value,
|
|
45
52
|
"rhs" => rule.rhs.map { |symbol| symbol.id.s_value },
|
|
@@ -48,20 +55,34 @@ module Arpaka::RubyGrammar
|
|
|
48
55
|
"midrule_position" => rule.position_in_original_rule_rhs,
|
|
49
56
|
"precedence" => rule.precedence_sym&.id&.s_value }
|
|
50
57
|
end
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
61
68
|
end
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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')
|
|
65
86
|
end
|
|
66
87
|
|
|
67
88
|
def self.symbol_names(grammar)
|
|
@@ -81,12 +102,18 @@ module Arpaka::RubyGrammar
|
|
|
81
102
|
end
|
|
82
103
|
end
|
|
83
104
|
|
|
84
|
-
def self.artifacts(ruby_source:)
|
|
85
|
-
grammar, inventory =
|
|
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
|
|
86
111
|
names = symbol_names(grammar)
|
|
87
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)
|
|
88
115
|
lines = ["/* Generated from ruby/ruby #{REVISION}. See Arpaka Action mappings. */",
|
|
89
|
-
"%no-stdlib", "%expect #{
|
|
116
|
+
"%no-stdlib", "%expect #{expected_conflicts}"]
|
|
90
117
|
lines << "%define lr.type ielr" if grammar.ielr_defined?
|
|
91
118
|
grammar.terms.each do |symbol|
|
|
92
119
|
next if symbol.error_symbol? || symbol.undef_symbol?
|
|
@@ -112,7 +139,9 @@ module Arpaka::RubyGrammar
|
|
|
112
139
|
else
|
|
113
140
|
""
|
|
114
141
|
end
|
|
115
|
-
|
|
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})"
|
|
116
145
|
lines << "/* upstream parse.y:#{metadata.fetch(rule.id).fetch('line')}: #{rule.as_comment} */"
|
|
117
146
|
lines << "#{name.call(rule.lhs)}: #{rhs}#{precedence} { $$ = #{expression} };"
|
|
118
147
|
end
|
|
@@ -124,9 +153,13 @@ module Arpaka::RubyGrammar
|
|
|
124
153
|
compact = rules.map do |rule|
|
|
125
154
|
{ id: rule.id, rule: rule.as_comment,
|
|
126
155
|
line: metadata.fetch(rule.id).fetch("line"),
|
|
127
|
-
status:
|
|
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" }
|
|
128
159
|
end
|
|
129
|
-
{ "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) }
|
|
130
163
|
end
|
|
131
164
|
|
|
132
165
|
def self.verify_structure(original, source, names)
|
|
@@ -139,7 +172,7 @@ module Arpaka::RubyGrammar
|
|
|
139
172
|
end.sort_by(&:inspect)
|
|
140
173
|
end
|
|
141
174
|
expected_rules = signature.call(original, names).map do |rule|
|
|
142
|
-
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")]
|
|
143
176
|
rule[0, 2] + ["tLBRACE_ARG"]
|
|
144
177
|
else
|
|
145
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
|
|
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
|
|
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)
|
data/lib/lrama/ruby/version.rb
CHANGED
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:
|
|
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
|