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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -2
  3. data/README.md +104 -7
  4. data/Rakefile +15 -1
  5. data/docs/architecture.dot +30 -0
  6. data/docs/architecture.md +96 -0
  7. data/docs/architecture.webp +0 -0
  8. data/docs/contributes.md +115 -0
  9. data/docs/usage.md +497 -0
  10. data/lib/arpaka/cli.rb +5 -53
  11. data/lib/arpaka/plugins/cli.rb +61 -0
  12. data/lib/arpaka/plugins/sample.rb +14 -0
  13. data/lib/arpaka/plugins/sample_runner.rb +21 -0
  14. data/lib/arpaka/{actions.rb → ruby/actions.rb} +230 -151
  15. data/lib/arpaka/ruby/class_name_parser.rb +97 -0
  16. data/lib/arpaka/ruby/cli.rb +64 -0
  17. data/lib/arpaka/ruby/frontend_emitter.rb +59 -0
  18. data/lib/arpaka/ruby/generator.rb +41 -0
  19. data/lib/arpaka/{ruby_grammar.rb → ruby/grammar.rb} +5 -56
  20. data/lib/arpaka/ruby/grammar_emitter.rb +71 -0
  21. data/lib/arpaka/ruby/profile.rb +129 -0
  22. data/lib/arpaka/{profile → ruby/profiles}/ruby_3_3.rb +21 -21
  23. data/lib/arpaka/{profile → ruby/profiles}/ruby_3_4.rb +21 -21
  24. data/lib/arpaka/{runtime → ruby/runtime/detailed}/ast.rb.erb +41 -5
  25. data/lib/arpaka/ruby/runtime/detailed/builder.rb.erb +1177 -0
  26. data/lib/arpaka/{runtime → ruby/runtime}/frontend.rb.erb +17 -4
  27. data/lib/arpaka/{runtime → ruby/runtime}/lexer.rb.erb +576 -99
  28. data/lib/arpaka/ruby/runtime/structured.rb.erb +111 -0
  29. data/lib/arpaka/ruby.rb +23 -0
  30. data/lib/arpaka.rb +6 -9
  31. data/lib/lrama/ruby/backend.rb +4 -9
  32. data/lib/lrama/ruby/parser.rb.erb +35 -9
  33. data/lib/lrama/ruby/parser_emitter.rb +41 -0
  34. data/lib/lrama/ruby/table_codec.rb +50 -0
  35. data/lib/lrama/ruby/version.rb +1 -1
  36. data/lib/lrama/ruby.rb +1 -0
  37. data/sig/arpaka.rbs +8 -3
  38. metadata +32 -16
  39. data/lib/arpaka/generator.rb +0 -51
  40. data/lib/arpaka/profile/ruby.rb +0 -49
  41. data/lib/arpaka/runtime/builder.rb.erb +0 -323
  42. /data/lib/arpaka/{BSDL → ruby/BSDL} +0 -0
  43. /data/lib/arpaka/{COPYING → ruby/COPYING} +0 -0
  44. /data/lib/arpaka/{source.json → ruby/source.json} +0 -0
  45. /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
@@ -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
- 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)
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
- source = generate(ruby_source: ruby_source, parse_y: parse_y, class_name: class_name)
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"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "lrama"
4
- require "erb"
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
- # Preserve whitespace inside multiline strings and heredocs.
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
- template = File.read(File.expand_path("parser.rb.erb", __dir__))
37
- result = ::ERB.new(template, trim_mode: "-").result_with_hash(
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lrama
4
4
  module Ruby
5
- VERSION = "1.0.0"
5
+ VERSION = "2.0.0"
6
6
  end
7
7
  end
data/lib/lrama/ruby.rb CHANGED
@@ -26,3 +26,4 @@ module Lrama
26
26
  end
27
27
 
28
28
  require_relative "ruby/generator"
29
+
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, ?class_name: String) -> String
11
- def self.compile: (ruby_source: String, ?class_name: String) -> _Frontend
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: 1.0.0
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/generator.rb
62
- - lib/arpaka/profile/ruby.rb
63
- - lib/arpaka/profile/ruby_3_3.rb
64
- - lib/arpaka/profile/ruby_3_4.rb
65
- - lib/arpaka/ruby_grammar.rb
66
- - lib/arpaka/runtime/ast.rb.erb
67
- - lib/arpaka/runtime/builder.rb.erb
68
- - lib/arpaka/runtime/frontend.rb.erb
69
- - lib/arpaka/runtime/lexer.rb.erb
70
- - lib/arpaka/source.json
71
- - lib/arpaka/upstream_rules.json
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.20
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: []
@@ -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
@@ -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