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.
- 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 +61 -16
- 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
|
|
@@ -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
|
|
39
|
-
File.join(vendor, "
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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 =
|
|
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 #{
|
|
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
|
-
|
|
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:
|
|
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
|
|
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: 0.
|
|
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
|