arpaka 1.0.0 → 2.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/CHANGELOG.md +46 -2
- data/README.md +104 -7
- data/Rakefile +15 -1
- data/docs/architecture.dot +30 -0
- data/docs/architecture.md +96 -0
- data/docs/architecture.webp +0 -0
- data/docs/contributes.md +115 -0
- data/docs/usage.md +497 -0
- data/lib/arpaka/cli.rb +5 -53
- data/lib/arpaka/plugins/cli.rb +61 -0
- data/lib/arpaka/plugins/sample.rb +14 -0
- data/lib/arpaka/plugins/sample_runner.rb +21 -0
- data/lib/arpaka/{actions.rb → ruby/actions.rb} +230 -151
- data/lib/arpaka/ruby/class_name_parser.rb +97 -0
- data/lib/arpaka/ruby/cli.rb +64 -0
- data/lib/arpaka/ruby/frontend_emitter.rb +59 -0
- data/lib/arpaka/ruby/generator.rb +41 -0
- data/lib/arpaka/{ruby_grammar.rb → ruby/grammar.rb} +5 -56
- data/lib/arpaka/ruby/grammar_emitter.rb +71 -0
- data/lib/arpaka/ruby/profile.rb +129 -0
- data/lib/arpaka/{profile → ruby/profiles}/ruby_3_3.rb +21 -21
- data/lib/arpaka/{profile → ruby/profiles}/ruby_3_4.rb +21 -21
- data/lib/arpaka/{runtime → ruby/runtime/detailed}/ast.rb.erb +41 -5
- data/lib/arpaka/ruby/runtime/detailed/builder.rb.erb +1177 -0
- data/lib/arpaka/{runtime → ruby/runtime}/frontend.rb.erb +17 -4
- data/lib/arpaka/{runtime → ruby/runtime}/lexer.rb.erb +576 -99
- data/lib/arpaka/ruby/runtime/structured.rb.erb +111 -0
- data/lib/arpaka/ruby.rb +23 -0
- data/lib/arpaka.rb +6 -9
- data/lib/lrama/ruby/backend.rb +4 -9
- data/lib/lrama/ruby/parser.rb.erb +35 -9
- data/lib/lrama/ruby/parser_emitter.rb +41 -0
- data/lib/lrama/ruby/table_codec.rb +50 -0
- data/lib/lrama/ruby/version.rb +1 -1
- data/lib/lrama/ruby.rb +1 -0
- data/sig/arpaka.rbs +8 -3
- metadata +32 -16
- data/lib/arpaka/generator.rb +0 -51
- data/lib/arpaka/profile/ruby.rb +0 -49
- data/lib/arpaka/runtime/builder.rb.erb +0 -323
- /data/lib/arpaka/{BSDL → ruby/BSDL} +0 -0
- /data/lib/arpaka/{COPYING → ruby/COPYING} +0 -0
- /data/lib/arpaka/{source.json → ruby/source.json} +0 -0
- /data/lib/arpaka/{upstream_rules.json → ruby/upstream_rules.json} +0 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
module AST
|
|
2
|
+
Source = Data.define(:filename, :children)
|
|
3
|
+
Class = Data.define(:name, :superclass, :children)
|
|
4
|
+
Module = Data.define(:name, :children)
|
|
5
|
+
Method = Data.define(:name, :kind, :receiver, :children)
|
|
6
|
+
|
|
7
|
+
def self.project(detailed_ast, filename:, modifier_nodes: nil)
|
|
8
|
+
Source.new(filename, definitions_in(detailed_ast, modifier_nodes: modifier_nodes).freeze)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.definitions_in(value, modifier_nodes:, method_kind: :instance, receiver: nil)
|
|
12
|
+
case value
|
|
13
|
+
when Array
|
|
14
|
+
value.flat_map do |item|
|
|
15
|
+
definitions_in(item, modifier_nodes: modifier_nodes,
|
|
16
|
+
method_kind: method_kind, receiver: receiver)
|
|
17
|
+
end
|
|
18
|
+
when AST::ClassDef
|
|
19
|
+
definitions_in([value.name, value.superclass], modifier_nodes: modifier_nodes,
|
|
20
|
+
method_kind: method_kind, receiver: receiver) +
|
|
21
|
+
[Class.new(static_name(value.name), static_name(value.superclass),
|
|
22
|
+
definitions_in(value.body, modifier_nodes: modifier_nodes).freeze)]
|
|
23
|
+
when AST::ModuleDef
|
|
24
|
+
definitions_in(value.name, modifier_nodes: modifier_nodes,
|
|
25
|
+
method_kind: method_kind, receiver: receiver) +
|
|
26
|
+
[Module.new(static_name(value.name),
|
|
27
|
+
definitions_in(value.body, modifier_nodes: modifier_nodes).freeze)]
|
|
28
|
+
when AST::Def
|
|
29
|
+
children = definitions_in(value.arguments, modifier_nodes: modifier_nodes,
|
|
30
|
+
method_kind: method_kind, receiver: receiver) +
|
|
31
|
+
definitions_in(value.body, modifier_nodes: modifier_nodes,
|
|
32
|
+
method_kind: method_kind, receiver: receiver)
|
|
33
|
+
[Method.new(value.name, method_kind, receiver, children.freeze)]
|
|
34
|
+
when AST::SingletonMethodDef
|
|
35
|
+
header = definitions_in(value.receiver, modifier_nodes: modifier_nodes,
|
|
36
|
+
method_kind: method_kind, receiver: receiver)
|
|
37
|
+
children = definitions_in(value.arguments, modifier_nodes: modifier_nodes,
|
|
38
|
+
method_kind: method_kind, receiver: receiver) +
|
|
39
|
+
definitions_in(value.body, modifier_nodes: modifier_nodes,
|
|
40
|
+
method_kind: method_kind, receiver: receiver)
|
|
41
|
+
header + [Method.new(value.name, :singleton, receiver_name(value.receiver),
|
|
42
|
+
children.freeze)]
|
|
43
|
+
when AST::SingletonClassDef
|
|
44
|
+
definitions_in(value.receiver, modifier_nodes: modifier_nodes,
|
|
45
|
+
method_kind: method_kind, receiver: receiver) +
|
|
46
|
+
definitions_in(value.body, modifier_nodes: modifier_nodes,
|
|
47
|
+
method_kind: :singleton, receiver: receiver_name(value.receiver))
|
|
48
|
+
when AST::If
|
|
49
|
+
members = if modifier_nodes&.key?(value)
|
|
50
|
+
[value.then_body, value.condition, value.else_body]
|
|
51
|
+
else
|
|
52
|
+
[value.condition, value.then_body, value.else_body]
|
|
53
|
+
end
|
|
54
|
+
members.flat_map do |member|
|
|
55
|
+
definitions_in(member, modifier_nodes: modifier_nodes,
|
|
56
|
+
method_kind: method_kind, receiver: receiver)
|
|
57
|
+
end
|
|
58
|
+
when AST::Loop
|
|
59
|
+
members = if modifier_nodes&.key?(value)
|
|
60
|
+
[value.body, value.condition]
|
|
61
|
+
else
|
|
62
|
+
[value.condition, value.body]
|
|
63
|
+
end
|
|
64
|
+
members.flat_map do |member|
|
|
65
|
+
definitions_in(member, modifier_nodes: modifier_nodes,
|
|
66
|
+
method_kind: method_kind, receiver: receiver)
|
|
67
|
+
end
|
|
68
|
+
else
|
|
69
|
+
return [] unless ast_node?(value)
|
|
70
|
+
|
|
71
|
+
value.members.flat_map do |member|
|
|
72
|
+
definitions_in(value.public_send(member), modifier_nodes: modifier_nodes,
|
|
73
|
+
method_kind: method_kind, receiver: receiver)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
private_class_method :definitions_in
|
|
78
|
+
|
|
79
|
+
def self.ast_node?(value)
|
|
80
|
+
value.is_a?(Data) && value.class.name&.start_with?("#{AST.name}::")
|
|
81
|
+
end
|
|
82
|
+
private_class_method :ast_node?
|
|
83
|
+
|
|
84
|
+
def self.static_name(value)
|
|
85
|
+
case value
|
|
86
|
+
when Symbol
|
|
87
|
+
value
|
|
88
|
+
when AST::Variable
|
|
89
|
+
value.name if value.kind == :constant
|
|
90
|
+
when AST::AbsoluteConstant
|
|
91
|
+
"::#{value.name}"
|
|
92
|
+
when AST::ConstantPath
|
|
93
|
+
receiver = static_name(value.receiver)
|
|
94
|
+
"#{receiver}::#{value.name}" if receiver
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
private_class_method :static_name
|
|
98
|
+
|
|
99
|
+
def self.receiver_name(value)
|
|
100
|
+
return :self if defined?(AST::Self) && value.is_a?(AST::Self)
|
|
101
|
+
return :self if value.is_a?(AST::Literal) && value.value == :self
|
|
102
|
+
|
|
103
|
+
case value
|
|
104
|
+
when AST::LocalRead, AST::BareCall
|
|
105
|
+
value.name
|
|
106
|
+
else
|
|
107
|
+
static_name(value)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
private_class_method :receiver_name
|
|
111
|
+
end
|
data/lib/arpaka/ruby.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "ruby/class_name_parser"
|
|
4
|
+
|
|
5
|
+
module Arpaka
|
|
6
|
+
module Ruby
|
|
7
|
+
def self.generate(ruby_source: nil, parse_y: nil, class_name: "RubyParser", ast_type: :detailed)
|
|
8
|
+
Generator.new.generate(ruby_source: ruby_source, parse_y: parse_y, class_name: class_name,
|
|
9
|
+
ast_type: ast_type)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.compile(ruby_source: nil, parse_y: nil, class_name: "RubyParser", ast_type: :detailed)
|
|
13
|
+
class_name = ClassNameParser.parse(class_name)
|
|
14
|
+
source = generate(ruby_source: ruby_source, parse_y: parse_y, class_name: class_name.to_s,
|
|
15
|
+
ast_type: ast_type)
|
|
16
|
+
box = ::Ruby::Box.new
|
|
17
|
+
box.eval(source)
|
|
18
|
+
class_name.resolve(box)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
require_relative "ruby/generator"
|
data/lib/arpaka.rb
CHANGED
|
@@ -5,16 +5,13 @@ require_relative "lrama/ruby"
|
|
|
5
5
|
module Arpaka
|
|
6
6
|
class Error < Lrama::Ruby::Error; end
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
require_relative "arpaka/ruby"
|
|
9
|
+
|
|
10
|
+
def self.generate(ruby_source: nil, parse_y: nil, class_name: "RubyParser", ast_type: :detailed)
|
|
11
|
+
Ruby.generate(ruby_source: ruby_source, parse_y: parse_y, class_name: class_name, ast_type: ast_type)
|
|
10
12
|
end
|
|
11
13
|
|
|
12
|
-
def self.compile(ruby_source: nil, parse_y: nil, class_name: "RubyParser")
|
|
13
|
-
|
|
14
|
-
box = ::Ruby::Box.new
|
|
15
|
-
box.eval(source)
|
|
16
|
-
box.const_get(class_name, false)
|
|
14
|
+
def self.compile(ruby_source: nil, parse_y: nil, class_name: "RubyParser", ast_type: :detailed)
|
|
15
|
+
Ruby.compile(ruby_source: ruby_source, parse_y: parse_y, class_name: class_name, ast_type: ast_type)
|
|
17
16
|
end
|
|
18
17
|
end
|
|
19
|
-
|
|
20
|
-
require_relative "arpaka/generator"
|
data/lib/lrama/ruby/backend.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "lrama"
|
|
4
|
-
|
|
4
|
+
require_relative "parser_emitter"
|
|
5
5
|
|
|
6
6
|
module Lrama
|
|
7
7
|
module Ruby
|
|
@@ -30,15 +30,10 @@ module Lrama
|
|
|
30
30
|
tokens = token_names(grammar)
|
|
31
31
|
actions = grammar.rules.filter_map do |rule|
|
|
32
32
|
next if recognizer || !rule.token_code
|
|
33
|
-
#
|
|
34
|
-
" when #{rule.id + 1} # #{rule.as_comment.gsub(/[\r\n]/, ' ')}\n#{translate_action(rule)}\n"
|
|
33
|
+
" when #{rule.id + 1}\n#{translate_action(rule)}\n"
|
|
35
34
|
end.join
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class_name: class_name, context: context, tokens: tokens, actions: actions,
|
|
39
|
-
recognizer: recognizer
|
|
40
|
-
)
|
|
41
|
-
result
|
|
35
|
+
ParserEmitter.new.emit(class_name: class_name, context: context, tokens: tokens,
|
|
36
|
+
actions: actions, recognizer: recognizer)
|
|
42
37
|
end
|
|
43
38
|
|
|
44
39
|
private
|
|
@@ -1,11 +1,37 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Generated by Arpaka/Lrama. Do not edit.
|
|
4
|
+
<% if compressed_tables -%>
|
|
5
|
+
require "zlib"
|
|
6
|
+
<% end -%>
|
|
4
7
|
<% if recognizer -%>
|
|
5
8
|
# Recognition only: C actions and support code are omitted.
|
|
6
9
|
# Supply pre-tokenized input; acceptance returns true. No error recovery.
|
|
7
10
|
<% end -%>
|
|
8
11
|
class <%= class_name %>
|
|
12
|
+
<% if compressed_tables -%>
|
|
13
|
+
def self.__decode_table(encoded, length)
|
|
14
|
+
bytes = Zlib::Inflate.inflate(encoded.unpack1("m0")).bytes
|
|
15
|
+
values = []
|
|
16
|
+
value = 0
|
|
17
|
+
shift = 0
|
|
18
|
+
bytes.each do |byte|
|
|
19
|
+
value |= (byte & 0x7f) << shift
|
|
20
|
+
if byte < 0x80
|
|
21
|
+
values << (value.even? ? value / 2 : -(value / 2) - 1)
|
|
22
|
+
value = 0
|
|
23
|
+
shift = 0
|
|
24
|
+
else
|
|
25
|
+
shift += 7
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
raise ArgumentError, "invalid parser table encoding" unless shift.zero? && values.length == length
|
|
29
|
+
|
|
30
|
+
values.freeze
|
|
31
|
+
end
|
|
32
|
+
private_class_method :__decode_table
|
|
33
|
+
<% end -%>
|
|
34
|
+
|
|
9
35
|
class ParseError < StandardError
|
|
10
36
|
attr_reader :token, :state
|
|
11
37
|
|
|
@@ -16,15 +42,6 @@ class <%= class_name %>
|
|
|
16
42
|
end
|
|
17
43
|
end
|
|
18
44
|
|
|
19
|
-
TOKENS = <%= tokens.inspect %>.freeze
|
|
20
|
-
<% %i[yytranslate yypact yydefact yypgoto yydefgoto yytable yycheck yyr1 yyr2].each do |name| -%>
|
|
21
|
-
<%= name.upcase %> = <%= context.public_send(name).inspect %>.freeze
|
|
22
|
-
<% end -%>
|
|
23
|
-
YYFINAL = <%= context.yyfinal %>
|
|
24
|
-
YYNTOKENS = <%= context.yyntokens %>
|
|
25
|
-
YYPACT_NINF = <%= context.yypact_ninf %>
|
|
26
|
-
YYTABLE_NINF = <%= context.yytable_ninf %>
|
|
27
|
-
|
|
28
45
|
attr_accessor :lexical_context
|
|
29
46
|
attr_reader :parse_trace, :lookahead_token, :lookahead_value, :lookahead_state
|
|
30
47
|
|
|
@@ -124,4 +141,13 @@ class <%= class_name %>
|
|
|
124
141
|
<% end -%>
|
|
125
142
|
yyval
|
|
126
143
|
end
|
|
144
|
+
|
|
145
|
+
TOKENS = <%= tokens.inspect %>.freeze
|
|
146
|
+
<% %i[yytranslate yypact yydefact yypgoto yydefgoto yytable yycheck yyr1 yyr2].each do |name| -%>
|
|
147
|
+
<%= name.upcase %> = <%= tables.fetch(name) %>
|
|
148
|
+
<% end -%>
|
|
149
|
+
YYFINAL = <%= context.yyfinal %>
|
|
150
|
+
YYNTOKENS = <%= context.yyntokens %>
|
|
151
|
+
YYPACT_NINF = <%= context.yypact_ninf %>
|
|
152
|
+
YYTABLE_NINF = <%= context.yytable_ninf %>
|
|
127
153
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
require_relative "table_codec"
|
|
5
|
+
|
|
6
|
+
module Lrama
|
|
7
|
+
module Ruby
|
|
8
|
+
class ParserEmitter
|
|
9
|
+
def emit(class_name:, context:, tokens:, actions:, recognizer:)
|
|
10
|
+
template = File.read(File.expand_path("parser.rb.erb", __dir__))
|
|
11
|
+
names = %i[yytranslate yypact yydefact yypgoto yydefgoto yytable yycheck yyr1 yyr2]
|
|
12
|
+
literal_tables = names.to_h do |name|
|
|
13
|
+
[name, "#{context.public_send(name).inspect}.freeze"]
|
|
14
|
+
end
|
|
15
|
+
candidates = names.to_h do |name|
|
|
16
|
+
values = context.public_send(name)
|
|
17
|
+
compressed = TableCodec.compress(values)
|
|
18
|
+
encoded = "__decode_table(#{compressed.inspect}, #{values.length})"
|
|
19
|
+
[name, encoded.bytesize < literal_tables.fetch(name).bytesize ? encoded : literal_tables.fetch(name)]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
literal_source = render(template, class_name:, context:, tokens:, actions:, recognizer:,
|
|
23
|
+
tables: literal_tables, compressed_tables: false)
|
|
24
|
+
return literal_source unless candidates.values.any? { |expression| expression.start_with?("__decode_table(") }
|
|
25
|
+
|
|
26
|
+
compressed_source = render(template, class_name:, context:, tokens:, actions:, recognizer:,
|
|
27
|
+
tables: candidates, compressed_tables: true)
|
|
28
|
+
compressed_source.bytesize < literal_source.bytesize ? compressed_source : literal_source
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def render(template, class_name:, context:, tokens:, actions:, recognizer:, tables:, compressed_tables:)
|
|
34
|
+
::ERB.new(template, trim_mode: "-").result_with_hash(
|
|
35
|
+
class_name: class_name, context: context, tokens: tokens, actions: actions,
|
|
36
|
+
recognizer: recognizer, tables: tables, compressed_tables: compressed_tables
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zlib"
|
|
4
|
+
|
|
5
|
+
module Lrama
|
|
6
|
+
module Ruby
|
|
7
|
+
module TableCodec
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def encode(values)
|
|
11
|
+
bytes = []
|
|
12
|
+
values.each do |value|
|
|
13
|
+
raise TypeError, "table values must be integers" unless value.is_a?(Integer)
|
|
14
|
+
|
|
15
|
+
unsigned = value.negative? ? (-value * 2) - 1 : value * 2
|
|
16
|
+
while unsigned >= 0x80
|
|
17
|
+
bytes << ((unsigned & 0x7f) | 0x80)
|
|
18
|
+
unsigned >>= 7
|
|
19
|
+
end
|
|
20
|
+
bytes << unsigned
|
|
21
|
+
end
|
|
22
|
+
bytes.pack("C*").force_encoding(Encoding::BINARY)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def compress(values)
|
|
26
|
+
[Zlib::Deflate.deflate(encode(values))].pack("m0")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def decode(encoded)
|
|
30
|
+
bytes = Zlib::Inflate.inflate(encoded.unpack1("m0")).bytes
|
|
31
|
+
values = []
|
|
32
|
+
value = 0
|
|
33
|
+
shift = 0
|
|
34
|
+
bytes.each do |byte|
|
|
35
|
+
value |= (byte & 0x7f) << shift
|
|
36
|
+
if byte < 0x80
|
|
37
|
+
values << (value.even? ? value / 2 : -(value / 2) - 1)
|
|
38
|
+
value = 0
|
|
39
|
+
shift = 0
|
|
40
|
+
else
|
|
41
|
+
shift += 7
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
raise ArgumentError, "invalid parser table encoding" unless shift.zero?
|
|
45
|
+
|
|
46
|
+
values.freeze
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/lrama/ruby/version.rb
CHANGED
data/lib/lrama/ruby.rb
CHANGED
data/sig/arpaka.rbs
CHANGED
|
@@ -3,12 +3,17 @@ module Arpaka
|
|
|
3
3
|
def statements: () -> Array[untyped]
|
|
4
4
|
end
|
|
5
5
|
|
|
6
|
+
interface _StructuredSource
|
|
7
|
+
def filename: () -> String
|
|
8
|
+
def children: () -> Array[untyped]
|
|
9
|
+
end
|
|
10
|
+
|
|
6
11
|
interface _Frontend
|
|
7
|
-
def parse: (String source, ?filename: String) -> _Program
|
|
12
|
+
def parse: (String source, ?filename: String) -> (_Program | _StructuredSource)
|
|
8
13
|
end
|
|
9
14
|
|
|
10
|
-
def self.generate: (ruby_source: String
|
|
11
|
-
def self.compile: (ruby_source: String
|
|
15
|
+
def self.generate: (?ruby_source: String?, ?parse_y: String?, ?class_name: String, ?ast_type: (:detailed | :structured | String)) -> String
|
|
16
|
+
def self.compile: (?ruby_source: String?, ?parse_y: String?, ?class_name: String, ?ast_type: (:detailed | :structured | String)) -> _Frontend
|
|
12
17
|
|
|
13
18
|
class Error < ::Lrama::Ruby::Error
|
|
14
19
|
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:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- MATSUMOTO, Katsuyoshi
|
|
@@ -50,31 +50,47 @@ files:
|
|
|
50
50
|
- README.md
|
|
51
51
|
- Rakefile
|
|
52
52
|
- assets/arpaka-logo.svg
|
|
53
|
+
- docs/architecture.dot
|
|
54
|
+
- docs/architecture.md
|
|
55
|
+
- docs/architecture.webp
|
|
56
|
+
- docs/contributes.md
|
|
57
|
+
- docs/usage.md
|
|
53
58
|
- examples/calculator.y
|
|
54
59
|
- examples/calculator_ast.y
|
|
55
60
|
- exe/arpaka
|
|
56
61
|
- lib/arpaka.rb
|
|
57
|
-
- lib/arpaka/BSDL
|
|
58
|
-
- lib/arpaka/COPYING
|
|
59
|
-
- lib/arpaka/actions.rb
|
|
60
62
|
- lib/arpaka/cli.rb
|
|
61
|
-
- lib/arpaka/
|
|
62
|
-
- lib/arpaka/
|
|
63
|
-
- lib/arpaka/
|
|
64
|
-
- lib/arpaka/
|
|
65
|
-
- lib/arpaka/
|
|
66
|
-
- lib/arpaka/
|
|
67
|
-
- lib/arpaka/
|
|
68
|
-
- lib/arpaka/
|
|
69
|
-
- lib/arpaka/
|
|
70
|
-
- lib/arpaka/
|
|
71
|
-
- lib/arpaka/
|
|
63
|
+
- lib/arpaka/plugins/cli.rb
|
|
64
|
+
- lib/arpaka/plugins/sample.rb
|
|
65
|
+
- lib/arpaka/plugins/sample_runner.rb
|
|
66
|
+
- lib/arpaka/ruby.rb
|
|
67
|
+
- lib/arpaka/ruby/BSDL
|
|
68
|
+
- lib/arpaka/ruby/COPYING
|
|
69
|
+
- lib/arpaka/ruby/actions.rb
|
|
70
|
+
- lib/arpaka/ruby/class_name_parser.rb
|
|
71
|
+
- lib/arpaka/ruby/cli.rb
|
|
72
|
+
- lib/arpaka/ruby/frontend_emitter.rb
|
|
73
|
+
- lib/arpaka/ruby/generator.rb
|
|
74
|
+
- lib/arpaka/ruby/grammar.rb
|
|
75
|
+
- lib/arpaka/ruby/grammar_emitter.rb
|
|
76
|
+
- lib/arpaka/ruby/profile.rb
|
|
77
|
+
- lib/arpaka/ruby/profiles/ruby_3_3.rb
|
|
78
|
+
- lib/arpaka/ruby/profiles/ruby_3_4.rb
|
|
79
|
+
- lib/arpaka/ruby/runtime/detailed/ast.rb.erb
|
|
80
|
+
- lib/arpaka/ruby/runtime/detailed/builder.rb.erb
|
|
81
|
+
- lib/arpaka/ruby/runtime/frontend.rb.erb
|
|
82
|
+
- lib/arpaka/ruby/runtime/lexer.rb.erb
|
|
83
|
+
- lib/arpaka/ruby/runtime/structured.rb.erb
|
|
84
|
+
- lib/arpaka/ruby/source.json
|
|
85
|
+
- lib/arpaka/ruby/upstream_rules.json
|
|
72
86
|
- lib/lrama/ruby.rb
|
|
73
87
|
- lib/lrama/ruby/action_code.rb
|
|
74
88
|
- lib/lrama/ruby/action_scanner.rb
|
|
75
89
|
- lib/lrama/ruby/backend.rb
|
|
76
90
|
- lib/lrama/ruby/generator.rb
|
|
77
91
|
- lib/lrama/ruby/parser.rb.erb
|
|
92
|
+
- lib/lrama/ruby/parser_emitter.rb
|
|
93
|
+
- lib/lrama/ruby/table_codec.rb
|
|
78
94
|
- lib/lrama/ruby/version.rb
|
|
79
95
|
- sig/arpaka.rbs
|
|
80
96
|
- sig/lrama/ruby.rbs
|
|
@@ -97,7 +113,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
97
113
|
- !ruby/object:Gem::Version
|
|
98
114
|
version: '0'
|
|
99
115
|
requirements: []
|
|
100
|
-
rubygems_version: 4.0.
|
|
116
|
+
rubygems_version: 4.0.24
|
|
101
117
|
specification_version: 4
|
|
102
118
|
summary: Ruby language parser frontend and Lrama output backend
|
|
103
119
|
test_files: []
|
data/lib/arpaka/generator.rb
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "erb"
|
|
4
|
-
require "json"
|
|
5
|
-
require_relative "ruby_grammar"
|
|
6
|
-
|
|
7
|
-
module Arpaka
|
|
8
|
-
class Generator
|
|
9
|
-
def generate(ruby_source: nil, parse_y: nil, class_name:)
|
|
10
|
-
if ruby_source.nil? == parse_y.nil?
|
|
11
|
-
raise Error, "specify exactly one of ruby_source or parse_y"
|
|
12
|
-
end
|
|
13
|
-
unless /\A[A-Z][a-zA-Z0-9_]*\z/.match?(class_name)
|
|
14
|
-
raise Error, "class_name must be a single Ruby constant name"
|
|
15
|
-
end
|
|
16
|
-
unless ::Ruby::Box.enabled?
|
|
17
|
-
raise Error, "Ruby Box is required for generation. Start Ruby with RUBY_BOX=1."
|
|
18
|
-
end
|
|
19
|
-
artifacts = RubyGrammar.artifacts(ruby_source: ruby_source, parse_y: parse_y)
|
|
20
|
-
grammar_filename = parse_y || File.join(ruby_source, "parse.y")
|
|
21
|
-
parser = Lrama::Ruby.generate(artifacts.fetch("parse.y"),
|
|
22
|
-
filename: grammar_filename, class_name: "Parser", allow_error_rules: true)
|
|
23
|
-
parser = parser.delete_prefix("# frozen_string_literal: true\n")
|
|
24
|
-
rules = JSON.parse(artifacts.fetch("rules.json"))
|
|
25
|
-
runtime_options = artifacts.fetch("runtime", {})
|
|
26
|
-
runtime = %w[frontend ast builder lexer].map do |name|
|
|
27
|
-
template = File.read(File.join(__dir__, "runtime", "#{name}.rb.erb"))
|
|
28
|
-
ERB.new(template, trim_mode: "-").result_with_hash(rules: rules, runtime: runtime_options)
|
|
29
|
-
end.join("\n")
|
|
30
|
-
notices = [File.read(File.expand_path("../../LICENSE.txt", __dir__))]
|
|
31
|
-
%w[COPYING BSDL].each do |name|
|
|
32
|
-
path = ruby_source ? File.join(ruby_source, name) : File.join(__dir__, name)
|
|
33
|
-
notices << File.read(path)
|
|
34
|
-
end
|
|
35
|
-
source = "# frozen_string_literal: true\n" \
|
|
36
|
-
"# Generated by Arpaka #{Lrama::Ruby::VERSION}. Do not edit.\n" \
|
|
37
|
-
"# Ruby source profile: #{ruby_source ? artifacts.fetch('source_profile') : 'parse.y only'}\n" \
|
|
38
|
-
"# parse.y SHA256: #{Digest::SHA256.file(grammar_filename).hexdigest}\n" \
|
|
39
|
-
"#{notices.join("\n").lines.map { |line| "# #{line}" }.join}\n" \
|
|
40
|
-
"class #{class_name}\n#{parser}\n#{runtime}\nend\n"
|
|
41
|
-
# Compile in an isolated box to validate syntax without executing actions.
|
|
42
|
-
box = ::Ruby::Box.new
|
|
43
|
-
box.eval(source)
|
|
44
|
-
source
|
|
45
|
-
rescue SyntaxError => error
|
|
46
|
-
raise Error, "Invalid generated frontend: #{error.message}"
|
|
47
|
-
rescue SystemCallError => error
|
|
48
|
-
raise Error, "Cannot read Ruby frontend inputs: #{error.message}"
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
data/lib/arpaka/profile/ruby.rb
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
|
|
5
|
-
module Arpaka
|
|
6
|
-
module Profile
|
|
7
|
-
module RUBY
|
|
8
|
-
CURRENT = {
|
|
9
|
-
"name" => "ruby-4.0",
|
|
10
|
-
"actions" => ::Arpaka::RubyGrammarActions::ACTIONS,
|
|
11
|
-
"additional_expected_conflicts" => 0,
|
|
12
|
-
"runtime" => {}.freeze
|
|
13
|
-
}.freeze
|
|
14
|
-
|
|
15
|
-
def self.select(grammar)
|
|
16
|
-
if grammar.rules.any? { |rule| rule.lhs.id.s_value == "value_expr_command" }
|
|
17
|
-
CURRENT
|
|
18
|
-
elsif grammar.rules.any? { |rule| rule.lhs.id.s_value == "top_compstmt" &&
|
|
19
|
-
rule.rhs.any? { |symbol| symbol.id.s_value == "option_terms" } }
|
|
20
|
-
legacy
|
|
21
|
-
elsif grammar.rules.any? { |rule| rule.lhs.id.s_value == "top_compstmt" &&
|
|
22
|
-
rule.rhs.any? { |symbol| symbol.id.s_value == "opt_terms" } }
|
|
23
|
-
legacy_3_3
|
|
24
|
-
else
|
|
25
|
-
raise ::Arpaka::Error, "Unsupported Ruby parse.y profile"
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def self.legacy
|
|
30
|
-
@legacy ||= load("ruby_3_4.rb", "ruby-3.4")
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def self.legacy_3_3
|
|
34
|
-
@legacy_3_3 ||= load("ruby_3_3.rb", "ruby-3.3")
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def self.load(filename, name)
|
|
38
|
-
box = ::Ruby::Box.new
|
|
39
|
-
profile = box.eval(File.read(File.join(__dir__, filename)))
|
|
40
|
-
unless profile.is_a?(Hash) && profile["name"] == name
|
|
41
|
-
raise ::Arpaka::Error, "Invalid #{name} profile"
|
|
42
|
-
end
|
|
43
|
-
profile.freeze
|
|
44
|
-
rescue SyntaxError, TypeError => error
|
|
45
|
-
raise ::Arpaka::Error, "Ruby profile loading failed: #{error.message}"
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|