lrama 0.5.2 → 0.5.4

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yaml +10 -1
  3. data/.gitignore +1 -0
  4. data/Gemfile +1 -0
  5. data/LEGAL.md +1 -16
  6. data/README.md +11 -1
  7. data/Steepfile +2 -1
  8. data/doc/TODO.md +8 -3
  9. data/exe/lrama +1 -1
  10. data/lib/lrama/command.rb +91 -72
  11. data/lib/lrama/context.rb +11 -1
  12. data/lib/lrama/counterexamples/derivation.rb +63 -0
  13. data/lib/lrama/counterexamples/example.rb +124 -0
  14. data/lib/lrama/counterexamples/path.rb +69 -0
  15. data/lib/lrama/counterexamples/state_item.rb +6 -0
  16. data/lib/lrama/counterexamples/triple.rb +21 -0
  17. data/lib/lrama/counterexamples.rb +285 -0
  18. data/lib/lrama/digraph.rb +2 -3
  19. data/lib/lrama/grammar/auxiliary.rb +7 -0
  20. data/lib/lrama/grammar/code.rb +123 -0
  21. data/lib/lrama/grammar/error_token.rb +9 -0
  22. data/lib/lrama/grammar/precedence.rb +11 -0
  23. data/lib/lrama/grammar/printer.rb +9 -0
  24. data/lib/lrama/grammar/reference.rb +22 -0
  25. data/lib/lrama/grammar/rule.rb +39 -0
  26. data/lib/lrama/grammar/symbol.rb +87 -0
  27. data/lib/lrama/grammar/union.rb +10 -0
  28. data/lib/lrama/grammar.rb +89 -282
  29. data/lib/lrama/lexer/token/type.rb +8 -0
  30. data/lib/lrama/lexer/token.rb +77 -0
  31. data/lib/lrama/lexer.rb +4 -74
  32. data/lib/lrama/output.rb +32 -4
  33. data/lib/lrama/parser/token_scanner.rb +3 -6
  34. data/lib/lrama/parser.rb +9 -1
  35. data/lib/lrama/report/duration.rb +25 -0
  36. data/lib/lrama/report/profile.rb +25 -0
  37. data/lib/lrama/report.rb +2 -47
  38. data/lib/lrama/state/reduce_reduce_conflict.rb +9 -0
  39. data/lib/lrama/state/resolved_conflict.rb +29 -0
  40. data/lib/lrama/state/shift_reduce_conflict.rb +9 -0
  41. data/lib/lrama/state.rb +13 -30
  42. data/lib/lrama/states/item.rb +79 -0
  43. data/lib/lrama/states.rb +24 -73
  44. data/lib/lrama/states_reporter.rb +28 -3
  45. data/lib/lrama/type.rb +4 -0
  46. data/lib/lrama/version.rb +1 -1
  47. data/lib/lrama.rb +2 -0
  48. data/lrama.gemspec +1 -1
  49. data/sig/lrama/{report.rbs → report/duration.rbs} +0 -4
  50. data/sig/lrama/report/profile.rbs +7 -0
  51. data/template/bison/yacc.c +371 -0
  52. metadata +30 -5
@@ -0,0 +1,285 @@
1
+ require "set"
2
+
3
+ require "lrama/counterexamples/derivation"
4
+ require "lrama/counterexamples/example"
5
+ require "lrama/counterexamples/path"
6
+ require "lrama/counterexamples/state_item"
7
+ require "lrama/counterexamples/triple"
8
+
9
+ module Lrama
10
+ # See: https://www.cs.cornell.edu/andru/papers/cupex/cupex.pdf
11
+ # 4. Constructing Nonunifying Counterexamples
12
+ class Counterexamples
13
+ attr_reader :transitions, :productions
14
+
15
+ def initialize(states)
16
+ @states = states
17
+ setup_transitions
18
+ setup_productions
19
+ end
20
+
21
+ def to_s
22
+ "#<Counterexamples>"
23
+ end
24
+ alias :inspect :to_s
25
+
26
+ def compute(conflict_state)
27
+ conflict_state.conflicts.flat_map do |conflict|
28
+ case conflict.type
29
+ when :shift_reduce
30
+ shift_reduce_example(conflict_state, conflict)
31
+ when :reduce_reduce
32
+ reduce_reduce_examples(conflict_state, conflict)
33
+ end
34
+ end.compact
35
+ end
36
+
37
+ private
38
+
39
+ def setup_transitions
40
+ # Hash [StateItem, Symbol] => StateItem
41
+ @transitions = {}
42
+ # Hash [StateItem, Symbol] => Set(StateItem)
43
+ @reverse_transitions = {}
44
+
45
+ @states.states.each do |src_state|
46
+ trans = {}
47
+
48
+ src_state.transitions.each do |shift, next_state|
49
+ trans[shift.next_sym] = next_state
50
+ end
51
+
52
+ src_state.items.each do |src_item|
53
+ next if src_item.end_of_rule?
54
+ sym = src_item.next_sym
55
+ dest_state = trans[sym]
56
+
57
+ dest_state.kernels.each do |dest_item|
58
+ next unless (src_item.rule == dest_item.rule) && (src_item.position + 1 == dest_item.position)
59
+ src_state_item = StateItem.new(src_state, src_item)
60
+ dest_state_item = StateItem.new(dest_state, dest_item)
61
+
62
+ @transitions[[src_state_item, sym]] = dest_state_item
63
+
64
+ key = [dest_state_item, sym]
65
+ @reverse_transitions[key] ||= Set.new
66
+ @reverse_transitions[key] << src_state_item
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ def setup_productions
73
+ # Hash [StateItem] => Set(Item)
74
+ @productions = {}
75
+ # Hash [State, Symbol] => Set(Item). Symbol is nterm
76
+ @reverse_productions = {}
77
+
78
+ @states.states.each do |state|
79
+ # LHS => Set(Item)
80
+ h = {}
81
+
82
+ state.closure.each do |item|
83
+ sym = item.lhs
84
+
85
+ h[sym] ||= Set.new
86
+ h[sym] << item
87
+ end
88
+
89
+ state.items.each do |item|
90
+ next if item.end_of_rule?
91
+ next if item.next_sym.term?
92
+
93
+ sym = item.next_sym
94
+ state_item = StateItem.new(state, item)
95
+ key = [state, sym]
96
+
97
+ @productions[state_item] = h[sym]
98
+
99
+ @reverse_productions[key] ||= Set.new
100
+ @reverse_productions[key] << item
101
+ end
102
+ end
103
+ end
104
+
105
+ def shift_reduce_example(conflict_state, conflict)
106
+ conflict_symbol = conflict.symbols.first
107
+ shift_conflict_item = conflict_state.items.find { |item| item.next_sym == conflict_symbol }
108
+ path2 = shortest_path(conflict_state, conflict.reduce.item, conflict_symbol)
109
+ path1 = find_shift_conflict_shortest_path(path2, conflict_state, shift_conflict_item)
110
+
111
+ Example.new(path1, path2, conflict, conflict_symbol, self)
112
+ end
113
+
114
+ def reduce_reduce_examples(conflict_state, conflict)
115
+ conflict_symbol = conflict.symbols.first
116
+ path1 = shortest_path(conflict_state, conflict.reduce1.item, conflict_symbol)
117
+ path2 = shortest_path(conflict_state, conflict.reduce2.item, conflict_symbol)
118
+
119
+ Example.new(path1, path2, conflict, conflict_symbol, self)
120
+ end
121
+
122
+ def find_shift_conflict_shortest_path(reduce_path, conflict_state, conflict_item)
123
+ state_items = find_shift_conflict_shortest_state_items(reduce_path, conflict_state, conflict_item)
124
+ build_paths_from_state_items(state_items)
125
+ end
126
+
127
+ def find_shift_conflict_shortest_state_items(reduce_path, conflict_state, conflict_item)
128
+ target_state_item = StateItem.new(conflict_state, conflict_item)
129
+ result = [target_state_item]
130
+ reversed_reduce_path = reduce_path.to_a.reverse
131
+ # Index for state_item
132
+ i = 0
133
+
134
+ while (path = reversed_reduce_path[i])
135
+ # Index for prev_state_item
136
+ j = i + 1
137
+ _j = j
138
+
139
+ while (prev_path = reversed_reduce_path[j])
140
+ if prev_path.production?
141
+ j += 1
142
+ else
143
+ break
144
+ end
145
+ end
146
+
147
+ state_item = path.to
148
+ prev_state_item = prev_path&.to
149
+
150
+ if target_state_item == state_item || target_state_item.item.start_item?
151
+ result.concat(reversed_reduce_path[_j..-1].map(&:to))
152
+ break
153
+ end
154
+
155
+ if target_state_item.item.beginning_of_rule?
156
+ queue = []
157
+ queue << [target_state_item]
158
+
159
+ # Find reverse production
160
+ while (sis = queue.shift)
161
+ si = sis.last
162
+
163
+ # Reach to start state
164
+ if si.item.start_item?
165
+ sis.shift
166
+ result.concat(sis)
167
+ target_state_item = si
168
+ break
169
+ end
170
+
171
+ if !si.item.beginning_of_rule?
172
+ key = [si, si.item.previous_sym]
173
+ @reverse_transitions[key].each do |prev_target_state_item|
174
+ next if prev_target_state_item.state != prev_state_item.state
175
+ sis.shift
176
+ result.concat(sis)
177
+ result << prev_target_state_item
178
+ target_state_item = prev_target_state_item
179
+ i = j
180
+ queue.clear
181
+ break
182
+ end
183
+ else
184
+ key = [si.state, si.item.lhs]
185
+ @reverse_productions[key].each do |item|
186
+ state_item = StateItem.new(si.state, item)
187
+ queue << (sis + [state_item])
188
+ end
189
+ end
190
+ end
191
+ else
192
+ # Find reverse transition
193
+ key = [target_state_item, target_state_item.item.previous_sym]
194
+ @reverse_transitions[key].each do |prev_target_state_item|
195
+ next if prev_target_state_item.state != prev_state_item.state
196
+ result << prev_target_state_item
197
+ target_state_item = prev_target_state_item
198
+ i = j
199
+ break
200
+ end
201
+ end
202
+ end
203
+
204
+ result.reverse
205
+ end
206
+
207
+ def build_paths_from_state_items(state_items)
208
+ paths = state_items.zip([nil] + state_items).map do |si, prev_si|
209
+ case
210
+ when prev_si.nil?
211
+ StartPath.new(si)
212
+ when si.item.beginning_of_rule?
213
+ ProductionPath.new(prev_si, si)
214
+ else
215
+ TransitionPath.new(prev_si, si)
216
+ end
217
+ end
218
+
219
+ paths
220
+ end
221
+
222
+ def shortest_path(conflict_state, conflict_reduce_item, conflict_term)
223
+ # queue: is an array of [Triple, [Path]]
224
+ queue = []
225
+ visited = {}
226
+ start_state = @states.states.first
227
+ raise "BUG: Start state should be just one kernel." if start_state.kernels.count != 1
228
+
229
+ start = Triple.new(start_state, start_state.kernels.first, Set.new([@states.eof_symbol]))
230
+
231
+ queue << [start, [StartPath.new(start.state_item)]]
232
+
233
+ while true
234
+ triple, paths = queue.shift
235
+
236
+ next if visited[triple]
237
+ visited[triple] = true
238
+
239
+ # Found
240
+ if triple.state == conflict_state && triple.item == conflict_reduce_item && triple.l.include?(conflict_term)
241
+ return paths
242
+ end
243
+
244
+ # transition
245
+ triple.state.transitions.each do |shift, next_state|
246
+ next unless triple.item.next_sym && triple.item.next_sym == shift.next_sym
247
+ next_state.kernels.each do |kernel|
248
+ next if kernel.rule != triple.item.rule
249
+ t = Triple.new(next_state, kernel, triple.l)
250
+ queue << [t, paths + [TransitionPath.new(triple.state_item, t.state_item)]]
251
+ end
252
+ end
253
+
254
+ # production step
255
+ triple.state.closure.each do |item|
256
+ next unless triple.item.next_sym && triple.item.next_sym == item.lhs
257
+ l = follow_l(triple.item, triple.l)
258
+ t = Triple.new(triple.state, item, l)
259
+ queue << [t, paths + [ProductionPath.new(triple.state_item, t.state_item)]]
260
+ end
261
+
262
+ break if queue.empty?
263
+ end
264
+
265
+ return nil
266
+ end
267
+
268
+ def follow_l(item, current_l)
269
+ # 1. follow_L (A -> X1 ... Xn-1 • Xn) = L
270
+ # 2. follow_L (A -> X1 ... Xk • Xk+1 Xk+2 ... Xn) = {Xk+2} if Xk+2 is a terminal
271
+ # 3. follow_L (A -> X1 ... Xk • Xk+1 Xk+2 ... Xn) = FIRST(Xk+2) if Xk+2 is a nonnullable nonterminal
272
+ # 4. follow_L (A -> X1 ... Xk • Xk+1 Xk+2 ... Xn) = FIRST(Xk+2) + follow_L (A -> X1 ... Xk+1 • Xk+2 ... Xn) if Xk+2 is a nullable nonterminal
273
+ case
274
+ when item.number_of_rest_symbols == 1
275
+ current_l
276
+ when item.next_next_sym.term?
277
+ Set.new([item.next_next_sym])
278
+ when !item.next_next_sym.nullable
279
+ item.next_next_sym.first_set
280
+ else
281
+ item.next_next_sym.first_set + follow_l(item.new_by_next_position, current_l)
282
+ end
283
+ end
284
+ end
285
+ end
data/lib/lrama/digraph.rb CHANGED
@@ -33,7 +33,7 @@ module Lrama
33
33
  @h[x] = d
34
34
  @result[x] = @base_function[x] # F x = F' x
35
35
 
36
- @relation[x] && @relation[x].each do |y|
36
+ @relation[x]&.each do |y|
37
37
  traverse(y) if @h[y] == 0
38
38
  @h[x] = [@h[x], @h[y]].min
39
39
  @result[x] |= @result[y] # F x = F x + F y
@@ -43,9 +43,8 @@ module Lrama
43
43
  while true do
44
44
  z = @stack.pop
45
45
  @h[z] = Float::INFINITY
46
- @result[z] = @result[x] # F (Top of S) = F x
47
-
48
46
  break if z == x
47
+ @result[z] = @result[x] # F (Top of S) = F x
49
48
  end
50
49
  end
51
50
  end
@@ -0,0 +1,7 @@
1
+ module Lrama
2
+ class Grammar
3
+ # Grammar file information not used by States but by Output
4
+ class Auxiliary < Struct.new(:prologue_first_lineno, :prologue, :epilogue_first_lineno, :epilogue, keyword_init: true)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,123 @@
1
+ require "forwardable"
2
+
3
+ module Lrama
4
+ class Grammar
5
+ class Code < Struct.new(:type, :token_code, keyword_init: true)
6
+ extend Forwardable
7
+
8
+ def_delegators "token_code", :s_value, :line, :column, :references
9
+
10
+ # $$, $n, @$, @n is translated to C code
11
+ def translated_code
12
+ case type
13
+ when :user_code
14
+ translated_user_code
15
+ when :initial_action
16
+ translated_initial_action_code
17
+ end
18
+ end
19
+
20
+ # * ($1) error
21
+ # * ($$) *yyvaluep
22
+ # * (@1) error
23
+ # * (@$) *yylocationp
24
+ def translated_printer_code(tag)
25
+ t_code = s_value.dup
26
+
27
+ references.reverse.each do |ref|
28
+ first_column = ref.first_column
29
+ last_column = ref.last_column
30
+
31
+ case
32
+ when ref.value == "$" && ref.type == :dollar # $$
33
+ # Omit "<>"
34
+ member = tag.s_value[1..-2]
35
+ str = "((*yyvaluep).#{member})"
36
+ when ref.value == "$" && ref.type == :at # @$
37
+ str = "(*yylocationp)"
38
+ when ref.type == :dollar # $n
39
+ raise "$#{ref.value} can not be used in %printer."
40
+ when ref.type == :at # @n
41
+ raise "@#{ref.value} can not be used in %printer."
42
+ else
43
+ raise "Unexpected. #{self}, #{ref}"
44
+ end
45
+
46
+ t_code[first_column..last_column] = str
47
+ end
48
+
49
+ return t_code
50
+ end
51
+ alias :translated_error_token_code :translated_printer_code
52
+
53
+
54
+ private
55
+
56
+ # * ($1) yyvsp[i]
57
+ # * ($$) yyval
58
+ # * (@1) yylsp[i]
59
+ # * (@$) yyloc
60
+ def translated_user_code
61
+ t_code = s_value.dup
62
+
63
+ references.reverse.each do |ref|
64
+ first_column = ref.first_column
65
+ last_column = ref.last_column
66
+
67
+ case
68
+ when ref.value == "$" && ref.type == :dollar # $$
69
+ # Omit "<>"
70
+ member = ref.tag.s_value[1..-2]
71
+ str = "(yyval.#{member})"
72
+ when ref.value == "$" && ref.type == :at # @$
73
+ str = "(yyloc)"
74
+ when ref.type == :dollar # $n
75
+ i = -ref.position_in_rhs + ref.value
76
+ # Omit "<>"
77
+ member = ref.tag.s_value[1..-2]
78
+ str = "(yyvsp[#{i}].#{member})"
79
+ when ref.type == :at # @n
80
+ i = -ref.position_in_rhs + ref.value
81
+ str = "(yylsp[#{i}])"
82
+ else
83
+ raise "Unexpected. #{self}, #{ref}"
84
+ end
85
+
86
+ t_code[first_column..last_column] = str
87
+ end
88
+
89
+ return t_code
90
+ end
91
+
92
+ # * ($1) error
93
+ # * ($$) yylval
94
+ # * (@1) error
95
+ # * (@$) yylloc
96
+ def translated_initial_action_code
97
+ t_code = s_value.dup
98
+
99
+ references.reverse.each do |ref|
100
+ first_column = ref.first_column
101
+ last_column = ref.last_column
102
+
103
+ case
104
+ when ref.value == "$" && ref.type == :dollar # $$
105
+ str = "yylval"
106
+ when ref.value == "$" && ref.type == :at # @$
107
+ str = "yylloc"
108
+ when ref.type == :dollar # $n
109
+ raise "$#{ref.value} can not be used in initial_action."
110
+ when ref.type == :at # @n
111
+ raise "@#{ref.value} can not be used in initial_action."
112
+ else
113
+ raise "Unexpected. #{self}, #{ref}"
114
+ end
115
+
116
+ t_code[first_column..last_column] = str
117
+ end
118
+
119
+ return t_code
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,9 @@
1
+ module Lrama
2
+ class Grammar
3
+ class ErrorToken < Struct.new(:ident_or_tags, :code, :lineno, keyword_init: true)
4
+ def translated_code(member)
5
+ code.translated_error_token_code(member)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module Lrama
2
+ class Grammar
3
+ class Precedence < Struct.new(:type, :precedence, keyword_init: true)
4
+ include Comparable
5
+
6
+ def <=>(other)
7
+ self.precedence <=> other.precedence
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module Lrama
2
+ class Grammar
3
+ class Printer < Struct.new(:ident_or_tags, :code, :lineno, keyword_init: true)
4
+ def translated_code(member)
5
+ code.translated_printer_code(member)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,22 @@
1
+ # type: :dollar or :at
2
+ # ex_tag: "$<tag>1" (Optional)
3
+
4
+ module Lrama
5
+ class Grammar
6
+ class Reference < Struct.new(:type, :value, :ex_tag, :first_column, :last_column, :referring_symbol, :position_in_rhs, keyword_init: true)
7
+ def tag
8
+ if ex_tag
9
+ ex_tag
10
+ else
11
+ # FIXME: Remove this class check
12
+ if referring_symbol.is_a?(Symbol)
13
+ referring_symbol.tag
14
+ else
15
+ # Lrama::Lexer::Token (User_code) case
16
+ nil
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ module Lrama
2
+ class Grammar
3
+ class Rule < Struct.new(:id, :lhs, :rhs, :code, :nullable, :precedence_sym, :lineno, keyword_init: true)
4
+ # TODO: Change this to display_name
5
+ def to_s
6
+ l = lhs.id.s_value
7
+ r = rhs.empty? ? "ε" : rhs.map {|r| r.id.s_value }.join(", ")
8
+
9
+ "#{l} -> #{r}"
10
+ end
11
+
12
+ # Used by #user_actions
13
+ def as_comment
14
+ l = lhs.id.s_value
15
+ r = rhs.empty? ? "%empty" : rhs.map(&:display_name).join(" ")
16
+
17
+ "#{l}: #{r}"
18
+ end
19
+
20
+ # opt_nl: ε <-- empty_rule
21
+ # | '\n' <-- not empty_rule
22
+ def empty_rule?
23
+ rhs.empty?
24
+ end
25
+
26
+ def precedence
27
+ precedence_sym&.precedence
28
+ end
29
+
30
+ def initial_rule?
31
+ id == 0
32
+ end
33
+
34
+ def translated_code
35
+ code&.translated_code
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,87 @@
1
+ # Symbol is both of nterm and term
2
+ # `number` is both for nterm and term
3
+ # `token_id` is tokentype for term, internal sequence number for nterm
4
+ #
5
+ # TODO: Add validation for ASCII code range for Token::Char
6
+
7
+ module Lrama
8
+ class Grammar
9
+ class Symbol < Struct.new(:id, :alias_name, :number, :tag, :term, :token_id, :nullable, :precedence, :printer, :error_token, keyword_init: true)
10
+ attr_accessor :first_set, :first_set_bitmap
11
+ attr_writer :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol
12
+
13
+ def term?
14
+ term
15
+ end
16
+
17
+ def nterm?
18
+ !term
19
+ end
20
+
21
+ def eof_symbol?
22
+ !!@eof_symbol
23
+ end
24
+
25
+ def error_symbol?
26
+ !!@error_symbol
27
+ end
28
+
29
+ def undef_symbol?
30
+ !!@undef_symbol
31
+ end
32
+
33
+ def accept_symbol?
34
+ !!@accept_symbol
35
+ end
36
+
37
+ def display_name
38
+ alias_name || id.s_value
39
+ end
40
+
41
+ # name for yysymbol_kind_t
42
+ #
43
+ # See: b4_symbol_kind_base
44
+ def enum_name
45
+ case
46
+ when accept_symbol?
47
+ name = "YYACCEPT"
48
+ when eof_symbol?
49
+ name = "YYEOF"
50
+ when term? && id.type == Token::Char
51
+ name = number.to_s + display_name
52
+ when term? && id.type == Token::Ident
53
+ name = id.s_value
54
+ when nterm? && (id.s_value.include?("$") || id.s_value.include?("@"))
55
+ name = number.to_s + id.s_value
56
+ when nterm?
57
+ name = id.s_value
58
+ else
59
+ raise "Unexpected #{self}"
60
+ end
61
+
62
+ "YYSYMBOL_" + name.gsub(/\W+/, "_")
63
+ end
64
+
65
+ # comment for yysymbol_kind_t
66
+ def comment
67
+ case
68
+ when accept_symbol?
69
+ # YYSYMBOL_YYACCEPT
70
+ id.s_value
71
+ when eof_symbol?
72
+ # YYEOF
73
+ alias_name
74
+ when (term? && 0 < token_id && token_id < 128)
75
+ # YYSYMBOL_3_backslash_, YYSYMBOL_14_
76
+ alias_name || id.s_value
77
+ when id.s_value.include?("$") || id.s_value.include?("@")
78
+ # YYSYMBOL_21_1
79
+ id.s_value
80
+ else
81
+ # YYSYMBOL_keyword_class, YYSYMBOL_strings_1
82
+ alias_name || id.s_value
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,10 @@
1
+ module Lrama
2
+ class Grammar
3
+ class Union < Struct.new(:code, :lineno, keyword_init: true)
4
+ def braces_less_code
5
+ # Remove braces
6
+ code.s_value[1..-2]
7
+ end
8
+ end
9
+ end
10
+ end