lrama 0.5.1 → 0.5.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20a0927bedeb14281abb8a5c59daf643fdfe556b5899cbe4872878b022804da2
4
- data.tar.gz: 731fdd0201266db9724a2ebcc1162427a2cfaa9d0d94543e1298b3f2683eb14f
3
+ metadata.gz: 32eaf486545ca4143a227a34cee2bfdb94928e002a85fcb2e098dba3dedb71c7
4
+ data.tar.gz: b80a7402d9f6caf9f0e8b5396e4bd6b80d84487a35c8cd4b4d711e51ffb56601
5
5
  SHA512:
6
- metadata.gz: 3a6e7c0ef4e7266dae41dba15e3a5d7609cb7171b82e0dbd5b75595e8e3cf8a4a402c5b710cc0f714c54357ef9fc20fd0935e55b8c529c99cd3c09ea173efb87
7
- data.tar.gz: fa867c9be43384c7f282bf22ecd2b0939cd0dca263a51914871dba42f409ddc01fb00c34d8696288c3668177fdc450ff1bc9fb5e90fdf053e2d56a5bf31bbe04
6
+ metadata.gz: 1d118074af8984d85e27485a0c9fb2f8c7eab9a55b771ea3eff17e26d84fe56316404f6316ba5a9c83e61591c83ff9048c6625a904529aa686417f6f295499d9
7
+ data.tar.gz: e3591cc5f70e6fe8a04af5070a7b5d6ee60e98718c4c6dc6db1fbef2b3be9f5239155da91bb51ba10611b5017925f1bf71aa1259fd5550b5af6d0ba458e2ab96
@@ -35,6 +35,7 @@ jobs:
35
35
  ruby-version: ${{ matrix.ruby }}
36
36
  bundler-cache: true
37
37
  - run: bundle install
38
+ - run: bundle exec rbs collection install
38
39
  - run: bundle exec steep check
39
40
  test-ruby:
40
41
  runs-on: ubuntu-20.04
@@ -57,9 +58,7 @@ jobs:
57
58
  - run: mkdir -p tool/lrama
58
59
  working-directory: ../ruby
59
60
  - name: Copy Lrama to ruby/tool
60
- run: cp -r exe lib ../ruby/tool/lrama
61
- # TODO: Consider how to manage changes on ruby/ruby master and ruby/lrama
62
- # run: cp -r exe lib template ../ruby/tool/lrama
61
+ run: cp -r exe lib template ../ruby/tool/lrama
63
62
  working-directory:
64
63
  - run: tree tool/lrama
65
64
  working-directory: ../ruby
data/.gitignore CHANGED
@@ -1,4 +1,6 @@
1
+ /.gem_rbs_collection/
1
2
  /tmp
2
3
  .irbrc
3
4
  /Gemfile.lock
4
5
  /pkg/
6
+ coverage/
data/Gemfile CHANGED
@@ -8,3 +8,4 @@ gem "stackprof"
8
8
  gem "rake"
9
9
  gem "rbs", require: false
10
10
  gem "steep", require: false
11
+ gem "simplecov", require: false
data/LEGAL.md CHANGED
@@ -5,22 +5,7 @@ mentioned below.
5
5
 
6
6
  ## GNU General Public License version 3
7
7
 
8
- These files are licensed under the GNU General Public License version 3. See these files for more information.
8
+ These files are licensed under the GNU General Public License version 3 or later. See these files for more information.
9
9
 
10
10
  * template/bison/yacc.c
11
11
  * template/bison/yacc.h
12
-
13
- ## Same with Ruby
14
-
15
- These files are licensed same with Ruby. See https://github.com/ruby/ruby/blob/master/COPYING for more information.
16
-
17
- * spec/fixtures/integration/ruby_3_0_5/parse.tmp.y
18
- * spec/fixtures/integration/ruby_3_0_5/y.tab.c
19
- * spec/fixtures/integration/ruby_3_0_5/y.tab.h
20
- * spec/fixtures/integration/ruby_3_1_0/parse.tmp.y
21
- * spec/fixtures/integration/ruby_3_1_0/y.tab.c
22
- * spec/fixtures/integration/ruby_3_1_0/y.tab.h
23
- * spec/fixtures/integration/ruby_3_2_0/parse.tmp.y
24
- * spec/fixtures/integration/ruby_3_2_0/y.tab.c
25
- * spec/fixtures/integration/ruby_3_2_0/y.tab.h
26
-
data/README.md CHANGED
@@ -62,7 +62,7 @@ This branch generates "parse.c" compatible with Bison 3.8.2 for ruby 3.0, 3.1, 3
62
62
 
63
63
  1. Update `Lrama::VERSION`
64
64
  2. Release as a gem by `rake release`
65
- 3. Update Lrama in ruby/ruby by `cp -r exe lib ruby/tool/lrama`
65
+ 3. Update Lrama in ruby/ruby by `cp -r LEGAL.md MIT exe lib ruby/tool/lrama`
66
66
  4. Create new release on [GitHub](https://github.com/ruby/lrama/releases)
67
67
 
68
68
  ## License
data/Steepfile CHANGED
@@ -4,4 +4,7 @@ target :lib do
4
4
  signature "sig"
5
5
 
6
6
  check "lib/lrama/bitmap.rb"
7
+ check "lib/lrama/report/duration.rb"
8
+ check "lib/lrama/report/profile.rb"
9
+ check "lib/lrama/warning.rb"
7
10
  end
data/doc/TODO.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # TODO
2
2
 
3
3
  * command
4
- * [ ] Add "--bison" option
5
4
  * lexer
6
5
  * [x] Basic functionalities
7
6
  * parser
@@ -27,7 +26,7 @@
27
26
  * [x] Table compaction
28
27
  * [x] -d option
29
28
  * yacc.c
30
- * [ ] %lex-param
29
+ * [x] %lex-param
31
30
  * [x] %parse-param
32
31
  * [x] %printer
33
32
  * [x] Replace $, @ in user codes
@@ -46,7 +45,9 @@
46
45
  * [ ] Bison style
47
46
  * [ ] Wrap not selected reduce with "[]". See basic.output file generated by Bison.
48
47
  * Error Tolerance
49
- * [x] Subset of Corchuelo et al.
48
+ * [x] Corchuelo et al. algorithm with N = 1 (this means the next token when error is raised)
49
+ * [x] Add new decl for error token semantic value initialization (%error-token)
50
+ * [ ] Use YYMALLOC & YYFREE
50
51
  * Lex state
51
52
  * CI
52
53
  * [x] Setup CI
data/exe/lrama CHANGED
@@ -4,4 +4,4 @@
4
4
  $LOAD_PATH << File.join(__dir__, "../lib")
5
5
  require "lrama"
6
6
 
7
- Lrama::Command.new.run(ARGV.dup)
7
+ Lrama::Command.new(ARGV.dup).run
data/lib/lrama/command.rb CHANGED
@@ -2,94 +2,57 @@ require 'optparse'
2
2
 
3
3
  module Lrama
4
4
  class Command
5
- def run(argv)
6
- opt = OptionParser.new
7
-
8
- # opt.on('-h') {|v| p v }
9
- opt.on('-V', '--version') {|v| puts Lrama::VERSION ; exit 0 }
10
-
11
- # Tuning the Parser
12
- skeleton = "bison/yacc.c"
13
-
14
- opt.on('-S', '--skeleton=FILE') {|v| skeleton = v }
15
- opt.on('-t') { } # Do nothing
16
-
17
- # Output Files:
18
- header = false
19
- header_file = nil
20
- report = []
21
- report_file = nil
22
- outfile = "y.tab.c"
23
-
24
- opt.on('-h', '--header=[FILE]') {|v| header = true; header_file = v }
25
- opt.on('-d') { header = true }
26
- opt.on('-r', '--report=THINGS') {|v| report = v.split(',') }
27
- opt.on('--report-file=FILE') {|v| report_file = v }
28
- opt.on('-v') { } # Do nothing
29
- opt.on('-o', '--output=FILE') {|v| outfile = v }
30
-
31
- # Hidden
32
- trace = []
33
- opt.on('--trace=THINGS') {|v| trace = v.split(',') }
34
-
35
- # Error Recovery
36
- error_recovery = false
37
- opt.on('-e') {|v| error_recovery = true }
38
-
39
- opt.parse!(argv)
40
-
41
- trace_opts = validate_trace(trace)
42
- report_opts = validate_report(report)
43
-
44
- grammar_file = argv.shift
45
-
46
- if !report.empty? && report_file.nil? && grammar_file
47
- report_file = File.dirname(grammar_file) + "/" + File.basename(grammar_file, ".*") + ".output"
48
- end
5
+ def initialize(argv)
6
+ @argv = argv
7
+
8
+ @version = nil
9
+ @skeleton = "bison/yacc.c"
10
+ @header = false
11
+ @header_file = nil
12
+ @report = []
13
+ @report_file = nil
14
+ @outfile = "y.tab.c"
15
+ @trace = []
16
+ @error_recovery = false
17
+ @grammar_file = nil
18
+ @report_file = nil
19
+ @trace_opts = nil
20
+ @report_opts = nil
21
+ end
49
22
 
50
- if !header_file && header
51
- case
52
- when outfile
53
- header_file = File.dirname(outfile) + "/" + File.basename(outfile, ".*") + ".h"
54
- when grammar_file
55
- header_file = File.dirname(grammar_file) + "/" + File.basename(grammar_file, ".*") + ".h"
56
- end
57
- end
23
+ def run
24
+ parse_option
58
25
 
59
- if !grammar_file
60
- abort "File should be specified\n"
26
+ if @version
27
+ puts Lrama::VERSION
28
+ exit 0
61
29
  end
62
30
 
63
- Report::Duration.enable if trace_opts[:time]
31
+ Report::Duration.enable if @trace_opts[:time]
64
32
 
65
33
  warning = Lrama::Warning.new
66
- if grammar_file == '-'
67
- grammar_file = argv.shift or abort "File name for STDIN should be specified\n"
68
- y = STDIN.read
69
- else
70
- y = File.read(grammar_file)
71
- end
72
- grammar = Lrama::Parser.new(y).parse
73
- states = Lrama::States.new(grammar, warning, trace_state: (trace_opts[:automaton] || trace_opts[:closure]))
34
+ grammar = Lrama::Parser.new(@y.read).parse
35
+ states = Lrama::States.new(grammar, warning, trace_state: (@trace_opts[:automaton] || @trace_opts[:closure]))
74
36
  states.compute
75
37
  context = Lrama::Context.new(states)
76
38
 
77
- if report_file
39
+ if @report_file
78
40
  reporter = Lrama::StatesReporter.new(states)
79
- File.open(report_file, "w+") do |f|
80
- reporter.report(f, **report_opts)
41
+ File.open(@report_file, "w+") do |f|
42
+ reporter.report(f, **@report_opts)
81
43
  end
82
44
  end
83
45
 
84
- File.open(outfile, "w+") do |f|
46
+ File.open(@outfile, "w+") do |f|
85
47
  Lrama::Output.new(
86
48
  out: f,
87
- output_file_path: outfile,
88
- template_name: skeleton,
89
- grammar_file_path: grammar_file,
90
- header_file_path: header_file,
49
+ output_file_path: @outfile,
50
+ template_name: @skeleton,
51
+ grammar_file_path: @grammar_file,
52
+ header_file_path: @header_file,
91
53
  context: context,
92
54
  grammar: grammar,
55
+ error_recovery: @error_recovery,
93
56
  ).render
94
57
  end
95
58
 
@@ -144,5 +107,61 @@ module Lrama
144
107
 
145
108
  return h
146
109
  end
110
+
111
+ def parse_option
112
+ opt = OptionParser.new
113
+
114
+ # opt.on('-h') {|v| p v }
115
+ opt.on('-V', '--version') {|v| @version = true }
116
+
117
+ # Tuning the Parser
118
+ opt.on('-S', '--skeleton=FILE') {|v| @skeleton = v }
119
+ opt.on('-t') { } # Do nothing
120
+
121
+ # Output Files:
122
+ opt.on('-h', '--header=[FILE]') {|v| @header = true; @header_file = v }
123
+ opt.on('-d') { @header = true }
124
+ opt.on('-r', '--report=THINGS') {|v| @report = v.split(',') }
125
+ opt.on('--report-file=FILE') {|v| @report_file = v }
126
+ opt.on('-v') { } # Do nothing
127
+ opt.on('-o', '--output=FILE') {|v| @outfile = v }
128
+
129
+ # Hidden
130
+ opt.on('--trace=THINGS') {|v| @trace = v.split(',') }
131
+
132
+ # Error Recovery
133
+ opt.on('-e') {|v| @error_recovery = true }
134
+
135
+ opt.parse!(@argv)
136
+
137
+ @trace_opts = validate_trace(@trace)
138
+ @report_opts = validate_report(@report)
139
+
140
+ @grammar_file = @argv.shift
141
+
142
+ if !@grammar_file
143
+ abort "File should be specified\n"
144
+ end
145
+
146
+ if @grammar_file == '-'
147
+ @grammar_file = @argv.shift or abort "File name for STDIN should be specified\n"
148
+ @y = STDIN
149
+ else
150
+ @y = File.open(@grammar_file, 'r')
151
+ end
152
+
153
+ if !@report.empty? && @report_file.nil? && @grammar_file
154
+ @report_file = File.dirname(@grammar_file) + "/" + File.basename(@grammar_file, ".*") + ".output"
155
+ end
156
+
157
+ if !@header_file && @header
158
+ case
159
+ when @outfile
160
+ @header_file = File.dirname(@outfile) + "/" + File.basename(@outfile, ".*") + ".h"
161
+ when @grammar_file
162
+ @header_file = File.dirname(@grammar_file) + "/" + File.basename(@grammar_file, ".*") + ".h"
163
+ end
164
+ end
165
+ end
147
166
  end
148
167
  end
data/lib/lrama/context.rb CHANGED
@@ -1,4 +1,4 @@
1
- require "lrama/report"
1
+ require "lrama/report/duration"
2
2
 
3
3
  module Lrama
4
4
  # This is passed to a template
@@ -89,6 +89,16 @@ module Lrama
89
89
  return a
90
90
  end
91
91
 
92
+ def yytranslate_inverted
93
+ a = Array.new(@states.symbols.count, @states.undef_symbol.token_id)
94
+
95
+ @states.terms.each do |term|
96
+ a[term.number] = term.token_id
97
+ end
98
+
99
+ return a
100
+ end
101
+
92
102
  # Mapping from rule number to line number of the rule is defined.
93
103
  # Dummy rule is appended as the first element whose value is 0
94
104
  # because 0 means error in yydefact.
@@ -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,33 @@
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
+ def precedence
21
+ precedence_sym&.precedence
22
+ end
23
+
24
+ def initial_rule?
25
+ id == 0
26
+ end
27
+
28
+ def translated_code
29
+ code&.translated_code
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,94 @@
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_writer :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol
11
+
12
+ def term?
13
+ term
14
+ end
15
+
16
+ def nterm?
17
+ !term
18
+ end
19
+
20
+ def eof_symbol?
21
+ !!@eof_symbol
22
+ end
23
+
24
+ def error_symbol?
25
+ !!@error_symbol
26
+ end
27
+
28
+ def undef_symbol?
29
+ !!@undef_symbol
30
+ end
31
+
32
+ def accept_symbol?
33
+ !!@accept_symbol
34
+ end
35
+
36
+ def display_name
37
+ if alias_name
38
+ alias_name
39
+ else
40
+ id.s_value
41
+ end
42
+ end
43
+
44
+ # name for yysymbol_kind_t
45
+ #
46
+ # See: b4_symbol_kind_base
47
+ def enum_name
48
+ case
49
+ when accept_symbol?
50
+ name = "YYACCEPT"
51
+ when eof_symbol?
52
+ name = "YYEOF"
53
+ when term? && id.type == Token::Char
54
+ if alias_name
55
+ name = number.to_s + alias_name
56
+ else
57
+ name = number.to_s + id.s_value
58
+ end
59
+ when term? && id.type == Token::Ident
60
+ name = id.s_value
61
+ when nterm? && (id.s_value.include?("$") || id.s_value.include?("@"))
62
+ name = number.to_s + id.s_value
63
+ when nterm?
64
+ name = id.s_value
65
+ else
66
+ raise "Unexpected #{self}"
67
+ end
68
+
69
+ "YYSYMBOL_" + name.gsub(/[^a-zA-Z_0-9]+/, "_")
70
+ end
71
+
72
+ # comment for yysymbol_kind_t
73
+ def comment
74
+ case
75
+ when accept_symbol?
76
+ # YYSYMBOL_YYACCEPT
77
+ id.s_value
78
+ when eof_symbol?
79
+ # YYEOF
80
+ alias_name
81
+ when (term? && 0 < token_id && token_id < 128)
82
+ # YYSYMBOL_3_backslash_, YYSYMBOL_14_
83
+ alias_name || id.s_value
84
+ when id.s_value.include?("$") || id.s_value.include?("@")
85
+ # YYSYMBOL_21_1
86
+ id.s_value
87
+ else
88
+ # YYSYMBOL_keyword_class, YYSYMBOL_strings_1
89
+ alias_name || id.s_value
90
+ end
91
+ end
92
+ end
93
+ end
94
+ 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