lrama 0.5.2 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
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