lrama 0.6.11 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/gh-pages.yml +46 -0
- data/.github/workflows/test.yaml +35 -7
- data/.gitignore +1 -0
- data/.rdoc_options +2 -0
- data/Gemfile +4 -2
- data/NEWS.md +60 -0
- data/README.md +44 -15
- data/Rakefile +13 -1
- data/Steepfile +2 -0
- data/doc/Index.md +58 -0
- data/lib/lrama/bitmap.rb +3 -0
- data/lib/lrama/command.rb +2 -1
- data/lib/lrama/digraph.rb +30 -0
- data/lib/lrama/grammar/binding.rb +47 -15
- data/lib/lrama/grammar/rule.rb +8 -0
- data/lib/lrama/grammar/rule_builder.rb +3 -15
- data/lib/lrama/grammar.rb +8 -3
- data/lib/lrama/lexer/grammar_file.rb +8 -1
- data/lib/lrama/lexer/location.rb +17 -1
- data/lib/lrama/lexer/token/char.rb +1 -0
- data/lib/lrama/lexer/token/ident.rb +1 -0
- data/lib/lrama/lexer/token/instantiate_rule.rb +6 -1
- data/lib/lrama/lexer/token/tag.rb +3 -1
- data/lib/lrama/lexer/token/user_code.rb +5 -1
- data/lib/lrama/lexer/token.rb +14 -2
- data/lib/lrama/lexer.rb +4 -5
- data/lib/lrama/logger.rb +4 -0
- data/lib/lrama/option_parser.rb +10 -8
- data/lib/lrama/options.rb +2 -1
- data/lib/lrama/parser.rb +10 -4
- data/lib/lrama/state.rb +288 -1
- data/lib/lrama/states/item.rb +8 -0
- data/lib/lrama/states.rb +69 -2
- data/lib/lrama/trace_reporter.rb +17 -2
- data/lib/lrama/version.rb +1 -1
- data/lrama.gemspec +1 -1
- data/parser.y +4 -3
- data/rbs_collection.lock.yaml +3 -3
- data/sig/generated/lrama/bitmap.rbs +11 -0
- data/sig/generated/lrama/digraph.rbs +39 -0
- data/sig/generated/lrama/grammar/binding.rbs +34 -0
- data/sig/generated/lrama/lexer/grammar_file.rbs +28 -0
- data/sig/generated/lrama/lexer/location.rbs +52 -0
- data/sig/{lrama → generated/lrama}/lexer/token/char.rbs +2 -0
- data/sig/{lrama → generated/lrama}/lexer/token/ident.rbs +2 -0
- data/sig/{lrama → generated/lrama}/lexer/token/instantiate_rule.rbs +8 -0
- data/sig/{lrama → generated/lrama}/lexer/token/tag.rbs +3 -0
- data/sig/{lrama → generated/lrama}/lexer/token/user_code.rbs +6 -1
- data/sig/{lrama → generated/lrama}/lexer/token.rbs +26 -3
- data/sig/generated/lrama/logger.rbs +14 -0
- data/sig/generated/lrama/trace_reporter.rbs +25 -0
- data/sig/lrama/grammar/rule_builder.rbs +0 -1
- data/sig/lrama/options.rbs +1 -0
- metadata +19 -14
- data/sig/lrama/bitmap.rbs +0 -7
- data/sig/lrama/digraph.rbs +0 -23
- data/sig/lrama/grammar/binding.rbs +0 -19
- data/sig/lrama/lexer/grammar_file.rbs +0 -17
- data/sig/lrama/lexer/location.rbs +0 -26
data/lib/lrama/grammar.rb
CHANGED
@@ -28,14 +28,14 @@ module Lrama
|
|
28
28
|
attr_reader :percent_codes, :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol, :aux, :parameterizing_rule_resolver
|
29
29
|
attr_accessor :union, :expect, :printers, :error_tokens, :lex_param, :parse_param, :initial_action,
|
30
30
|
:after_shift, :before_reduce, :after_reduce, :after_shift_error_token, :after_pop_stack,
|
31
|
-
:symbols_resolver, :types, :rules, :rule_builders, :sym_to_rules, :no_stdlib, :locations
|
31
|
+
:symbols_resolver, :types, :rules, :rule_builders, :sym_to_rules, :no_stdlib, :locations, :define
|
32
32
|
|
33
33
|
def_delegators "@symbols_resolver", :symbols, :nterms, :terms, :add_nterm, :add_term, :find_term_by_s_value,
|
34
34
|
:find_symbol_by_number!, :find_symbol_by_id!, :token_to_symbol,
|
35
35
|
:find_symbol_by_s_value!, :fill_symbol_number, :fill_nterm_type,
|
36
36
|
:fill_printer, :fill_destructor, :fill_error_token, :sort_by_number!
|
37
37
|
|
38
|
-
def initialize(rule_counter)
|
38
|
+
def initialize(rule_counter, define = {})
|
39
39
|
@rule_counter = rule_counter
|
40
40
|
|
41
41
|
# Code defined by "%code"
|
@@ -57,6 +57,7 @@ module Lrama
|
|
57
57
|
@aux = Auxiliary.new
|
58
58
|
@no_stdlib = false
|
59
59
|
@locations = false
|
60
|
+
@define = define.map {|d| d.split('=') }.to_h
|
60
61
|
|
61
62
|
append_special_symbols
|
62
63
|
end
|
@@ -171,6 +172,10 @@ module Lrama
|
|
171
172
|
@sym_to_rules[sym.number]
|
172
173
|
end
|
173
174
|
|
175
|
+
def ielr_defined?
|
176
|
+
@define.key?('lr.type') && @define['lr.type'] == 'ielr'
|
177
|
+
end
|
178
|
+
|
174
179
|
private
|
175
180
|
|
176
181
|
def compute_nullable
|
@@ -294,7 +299,7 @@ module Lrama
|
|
294
299
|
end
|
295
300
|
|
296
301
|
def resolve_inline_rules
|
297
|
-
while @rule_builders.any?
|
302
|
+
while @rule_builders.any?(&:has_inline_rules?) do
|
298
303
|
@rule_builders = @rule_builders.flat_map do |builder|
|
299
304
|
if builder.has_inline_rules?
|
300
305
|
builder.resolve_inline_rules
|
@@ -1,30 +1,37 @@
|
|
1
|
+
# rbs_inline: enabled
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module Lrama
|
4
5
|
class Lexer
|
5
6
|
class GrammarFile
|
6
7
|
class Text < String
|
8
|
+
# @rbs () -> String
|
7
9
|
def inspect
|
8
10
|
length <= 50 ? super : "#{self[0..47]}...".inspect
|
9
11
|
end
|
10
12
|
end
|
11
13
|
|
12
|
-
attr_reader :path
|
14
|
+
attr_reader :path #: String
|
15
|
+
attr_reader :text #: String
|
13
16
|
|
17
|
+
# @rbs (String path, String text) -> void
|
14
18
|
def initialize(path, text)
|
15
19
|
@path = path
|
16
20
|
@text = Text.new(text).freeze
|
17
21
|
end
|
18
22
|
|
23
|
+
# @rbs () -> String
|
19
24
|
def inspect
|
20
25
|
"<#{self.class}: @path=#{path}, @text=#{text.inspect}>"
|
21
26
|
end
|
22
27
|
|
28
|
+
# @rbs (GrammarFile other) -> bool
|
23
29
|
def ==(other)
|
24
30
|
self.class == other.class &&
|
25
31
|
self.path == other.path
|
26
32
|
end
|
27
33
|
|
34
|
+
# @rbs () -> Array[String]
|
28
35
|
def lines
|
29
36
|
@lines ||= text.split("\n")
|
30
37
|
end
|
data/lib/lrama/lexer/location.rb
CHANGED
@@ -1,10 +1,16 @@
|
|
1
|
+
# rbs_inline: enabled
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module Lrama
|
4
5
|
class Lexer
|
5
6
|
class Location
|
6
|
-
attr_reader :grammar_file
|
7
|
+
attr_reader :grammar_file #: GrammarFile
|
8
|
+
attr_reader :first_line #: Integer
|
9
|
+
attr_reader :first_column #: Integer
|
10
|
+
attr_reader :last_line #: Integer
|
11
|
+
attr_reader :last_column #: Integer
|
7
12
|
|
13
|
+
# @rbs (grammar_file: GrammarFile, first_line: Integer, first_column: Integer, last_line: Integer, last_column: Integer) -> void
|
8
14
|
def initialize(grammar_file:, first_line:, first_column:, last_line:, last_column:)
|
9
15
|
@grammar_file = grammar_file
|
10
16
|
@first_line = first_line
|
@@ -13,6 +19,7 @@ module Lrama
|
|
13
19
|
@last_column = last_column
|
14
20
|
end
|
15
21
|
|
22
|
+
# @rbs (Location other) -> bool
|
16
23
|
def ==(other)
|
17
24
|
self.class == other.class &&
|
18
25
|
self.grammar_file == other.grammar_file &&
|
@@ -22,6 +29,7 @@ module Lrama
|
|
22
29
|
self.last_column == other.last_column
|
23
30
|
end
|
24
31
|
|
32
|
+
# @rbs (Integer left, Integer right) -> Location
|
25
33
|
def partial_location(left, right)
|
26
34
|
offset = -first_column
|
27
35
|
new_first_line = -1
|
@@ -52,10 +60,12 @@ module Lrama
|
|
52
60
|
)
|
53
61
|
end
|
54
62
|
|
63
|
+
# @rbs () -> String
|
55
64
|
def to_s
|
56
65
|
"#{path} (#{first_line},#{first_column})-(#{last_line},#{last_column})"
|
57
66
|
end
|
58
67
|
|
68
|
+
# @rbs (String error_message) -> String
|
59
69
|
def generate_error_message(error_message)
|
60
70
|
<<~ERROR.chomp
|
61
71
|
#{path}:#{first_line}:#{first_column}: #{error_message}
|
@@ -63,6 +73,7 @@ module Lrama
|
|
63
73
|
ERROR
|
64
74
|
end
|
65
75
|
|
76
|
+
# @rbs () -> String
|
66
77
|
def line_with_carets
|
67
78
|
<<~TEXT
|
68
79
|
#{text}
|
@@ -72,22 +83,27 @@ module Lrama
|
|
72
83
|
|
73
84
|
private
|
74
85
|
|
86
|
+
# @rbs () -> String
|
75
87
|
def path
|
76
88
|
grammar_file.path
|
77
89
|
end
|
78
90
|
|
91
|
+
# @rbs () -> String
|
79
92
|
def blanks
|
80
93
|
(text[0...first_column] or raise "#{first_column} is invalid").gsub(/[^\t]/, ' ')
|
81
94
|
end
|
82
95
|
|
96
|
+
# @rbs () -> String
|
83
97
|
def carets
|
84
98
|
blanks + '^' * (last_column - first_column)
|
85
99
|
end
|
86
100
|
|
101
|
+
# @rbs () -> String
|
87
102
|
def text
|
88
103
|
@text ||= _text.join("\n")
|
89
104
|
end
|
90
105
|
|
106
|
+
# @rbs () -> Array[String]
|
91
107
|
def _text
|
92
108
|
@_text ||=begin
|
93
109
|
range = (first_line - 1)...last_line
|
@@ -1,21 +1,26 @@
|
|
1
|
+
# rbs_inline: enabled
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module Lrama
|
4
5
|
class Lexer
|
5
6
|
class Token
|
6
7
|
class InstantiateRule < Token
|
7
|
-
attr_reader :args
|
8
|
+
attr_reader :args #: Array[Lexer::Token]
|
9
|
+
attr_reader :lhs_tag #: Lexer::Token::Tag?
|
8
10
|
|
11
|
+
# @rbs (s_value: String, ?alias_name: String, ?location: Location, ?args: Array[Lexer::Token], ?lhs_tag: Lexer::Token::Tag?) -> void
|
9
12
|
def initialize(s_value:, alias_name: nil, location: nil, args: [], lhs_tag: nil)
|
10
13
|
super s_value: s_value, alias_name: alias_name, location: location
|
11
14
|
@args = args
|
12
15
|
@lhs_tag = lhs_tag
|
13
16
|
end
|
14
17
|
|
18
|
+
# @rbs () -> String
|
15
19
|
def rule_name
|
16
20
|
s_value
|
17
21
|
end
|
18
22
|
|
23
|
+
# @rbs () -> Integer
|
19
24
|
def args_count
|
20
25
|
args.count
|
21
26
|
end
|
@@ -1,11 +1,13 @@
|
|
1
|
+
# rbs_inline: enabled
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module Lrama
|
4
5
|
class Lexer
|
5
6
|
class Token
|
6
7
|
class Tag < Token
|
7
|
-
#
|
8
|
+
# @rbs () -> String
|
8
9
|
def member
|
10
|
+
# Omit "<>"
|
9
11
|
s_value[1..-2] or raise "Unexpected Tag format (#{s_value})"
|
10
12
|
end
|
11
13
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# rbs_inline: enabled
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require "strscan"
|
@@ -6,14 +7,16 @@ module Lrama
|
|
6
7
|
class Lexer
|
7
8
|
class Token
|
8
9
|
class UserCode < Token
|
9
|
-
attr_accessor :tag
|
10
|
+
attr_accessor :tag #: Lexer::Token::Tag
|
10
11
|
|
12
|
+
# @rbs () -> Array[Lrama::Grammar::Reference]
|
11
13
|
def references
|
12
14
|
@references ||= _references
|
13
15
|
end
|
14
16
|
|
15
17
|
private
|
16
18
|
|
19
|
+
# @rbs () -> Array[Lrama::Grammar::Reference]
|
17
20
|
def _references
|
18
21
|
scanner = StringScanner.new(s_value)
|
19
22
|
references = [] #: Array[Grammar::Reference]
|
@@ -32,6 +35,7 @@ module Lrama
|
|
32
35
|
references
|
33
36
|
end
|
34
37
|
|
38
|
+
# @rbs (StringScanner scanner) -> Lrama::Grammar::Reference?
|
35
39
|
def scan_reference(scanner)
|
36
40
|
start = scanner.pos
|
37
41
|
case
|
data/lib/lrama/lexer/token.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# rbs_inline: enabled
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require_relative 'token/char'
|
@@ -9,9 +10,12 @@ require_relative 'token/user_code'
|
|
9
10
|
module Lrama
|
10
11
|
class Lexer
|
11
12
|
class Token
|
12
|
-
attr_reader :s_value
|
13
|
-
|
13
|
+
attr_reader :s_value #: String
|
14
|
+
attr_reader :location #: Location
|
15
|
+
attr_accessor :alias_name #: String
|
16
|
+
attr_accessor :referred #: bool
|
14
17
|
|
18
|
+
# @rbs (s_value: String, ?alias_name: String, ?location: Location) -> void
|
15
19
|
def initialize(s_value:, alias_name: nil, location: nil)
|
16
20
|
s_value.freeze
|
17
21
|
@s_value = s_value
|
@@ -19,36 +23,44 @@ module Lrama
|
|
19
23
|
@location = location
|
20
24
|
end
|
21
25
|
|
26
|
+
# @rbs () -> String
|
22
27
|
def to_s
|
23
28
|
"value: `#{s_value}`, location: #{location}"
|
24
29
|
end
|
25
30
|
|
31
|
+
# @rbs (String string) -> bool
|
26
32
|
def referred_by?(string)
|
27
33
|
[self.s_value, self.alias_name].compact.include?(string)
|
28
34
|
end
|
29
35
|
|
36
|
+
# @rbs (Token other) -> bool
|
30
37
|
def ==(other)
|
31
38
|
self.class == other.class && self.s_value == other.s_value
|
32
39
|
end
|
33
40
|
|
41
|
+
# @rbs () -> Integer
|
34
42
|
def first_line
|
35
43
|
location.first_line
|
36
44
|
end
|
37
45
|
alias :line :first_line
|
38
46
|
|
47
|
+
# @rbs () -> Integer
|
39
48
|
def first_column
|
40
49
|
location.first_column
|
41
50
|
end
|
42
51
|
alias :column :first_column
|
43
52
|
|
53
|
+
# @rbs () -> Integer
|
44
54
|
def last_line
|
45
55
|
location.last_line
|
46
56
|
end
|
47
57
|
|
58
|
+
# @rbs () -> Integer
|
48
59
|
def last_column
|
49
60
|
location.last_column
|
50
61
|
end
|
51
62
|
|
63
|
+
# @rbs (Lrama::Grammar::Reference ref, String message) -> bot
|
52
64
|
def invalid_ref(ref, message)
|
53
65
|
location = self.location.partial_location(ref.first_column, ref.last_column)
|
54
66
|
raise location.generate_error_message(message)
|
data/lib/lrama/lexer.rb
CHANGED
@@ -169,12 +169,11 @@ module Lrama
|
|
169
169
|
def lex_comment
|
170
170
|
until @scanner.eos? do
|
171
171
|
case
|
172
|
-
when @scanner.
|
173
|
-
newline
|
174
|
-
when @scanner.scan(/\*\//)
|
172
|
+
when @scanner.scan_until(/[\s\S]*?\*\//)
|
173
|
+
@scanner.matched.count("\n").times { newline }
|
175
174
|
return
|
176
|
-
|
177
|
-
|
175
|
+
when @scanner.scan_until(/\n/)
|
176
|
+
newline
|
178
177
|
end
|
179
178
|
end
|
180
179
|
end
|
data/lib/lrama/logger.rb
CHANGED
@@ -1,15 +1,19 @@
|
|
1
|
+
# rbs_inline: enabled
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module Lrama
|
4
5
|
class Logger
|
6
|
+
# @rbs (IO out) -> void
|
5
7
|
def initialize(out = STDERR)
|
6
8
|
@out = out
|
7
9
|
end
|
8
10
|
|
11
|
+
# @rbs (String message) -> void
|
9
12
|
def warn(message)
|
10
13
|
@out << message << "\n"
|
11
14
|
end
|
12
15
|
|
16
|
+
# @rbs (String message) -> void
|
13
17
|
def error(message)
|
14
18
|
@out << message << "\n"
|
15
19
|
end
|
data/lib/lrama/option_parser.rb
CHANGED
@@ -59,8 +59,8 @@ module Lrama
|
|
59
59
|
o.separator ''
|
60
60
|
o.separator 'Tuning the Parser:'
|
61
61
|
o.on('-S', '--skeleton=FILE', 'specify the skeleton to use') {|v| @options.skeleton = v }
|
62
|
-
o.on('-t', '
|
63
|
-
o.on('
|
62
|
+
o.on('-t', '--debug', 'display debugging outputs of internal parser') {|v| @options.debug = true }
|
63
|
+
o.on('-D', '--define=NAME[=VALUE]', Array, "similar to '%define NAME VALUE'") {|v| @options.define = v }
|
64
64
|
o.separator ''
|
65
65
|
o.separator 'Output:'
|
66
66
|
o.on('-H', '--header=[FILE]', 'also produce a header file named FILE') {|v| @options.header = true; @options.header_file = v }
|
@@ -86,6 +86,7 @@ module Lrama
|
|
86
86
|
o.on_tail ' automaton display states'
|
87
87
|
o.on_tail ' closure display states'
|
88
88
|
o.on_tail ' rules display grammar rules'
|
89
|
+
o.on_tail ' only-explicit-rules display only explicit grammar rules'
|
89
90
|
o.on_tail ' actions display grammar rules with actions'
|
90
91
|
o.on_tail ' time display generation time'
|
91
92
|
o.on_tail ' all include all the above traces'
|
@@ -136,26 +137,27 @@ module Lrama
|
|
136
137
|
|
137
138
|
VALID_TRACES = %w[
|
138
139
|
locations scan parse automaton bitsets closure
|
139
|
-
grammar rules actions resource
|
140
|
-
tools m4-early m4 skeleton time ielr cex
|
140
|
+
grammar rules only-explicit-rules actions resource
|
141
|
+
sets muscles tools m4-early m4 skeleton time ielr cex
|
141
142
|
].freeze
|
142
143
|
NOT_SUPPORTED_TRACES = %w[
|
143
144
|
locations scan parse bitsets grammar resource
|
144
145
|
sets muscles tools m4-early m4 skeleton ielr cex
|
145
146
|
].freeze
|
147
|
+
SUPPORTED_TRACES = VALID_TRACES - NOT_SUPPORTED_TRACES
|
146
148
|
|
147
149
|
def validate_trace(trace)
|
148
150
|
h = {}
|
149
151
|
return h if trace.empty? || trace == ['none']
|
150
|
-
|
152
|
+
all_traces = SUPPORTED_TRACES - %w[only-explicit-rules]
|
151
153
|
if trace == ['all']
|
152
|
-
|
154
|
+
all_traces.each { |t| h[t.gsub(/-/, '_').to_sym] = true }
|
153
155
|
return h
|
154
156
|
end
|
155
157
|
|
156
158
|
trace.each do |t|
|
157
|
-
if
|
158
|
-
h[t.to_sym] = true
|
159
|
+
if SUPPORTED_TRACES.include?(t)
|
160
|
+
h[t.gsub(/-/, '_').to_sym] = true
|
159
161
|
else
|
160
162
|
raise "Invalid trace option \"#{t}\"."
|
161
163
|
end
|
data/lib/lrama/options.rb
CHANGED
@@ -7,10 +7,11 @@ module Lrama
|
|
7
7
|
:report_file, :outfile,
|
8
8
|
:error_recovery, :grammar_file,
|
9
9
|
:trace_opts, :report_opts,
|
10
|
-
:diagnostic, :y, :debug
|
10
|
+
:diagnostic, :y, :debug, :define
|
11
11
|
|
12
12
|
def initialize
|
13
13
|
@skeleton = "bison/yacc.c"
|
14
|
+
@define = {}
|
14
15
|
@header = false
|
15
16
|
@header_file = nil
|
16
17
|
@report_file = nil
|
data/lib/lrama/parser.rb
CHANGED
@@ -658,17 +658,18 @@ module_eval(<<'...end parser.y/module_eval...', 'parser.y', 428)
|
|
658
658
|
|
659
659
|
include Lrama::Report::Duration
|
660
660
|
|
661
|
-
def initialize(text, path, debug = false)
|
661
|
+
def initialize(text, path, debug = false, define = {})
|
662
662
|
@grammar_file = Lrama::Lexer::GrammarFile.new(path, text)
|
663
663
|
@yydebug = debug
|
664
664
|
@rule_counter = Lrama::Grammar::Counter.new(0)
|
665
665
|
@midrule_action_counter = Lrama::Grammar::Counter.new(1)
|
666
|
+
@define = define
|
666
667
|
end
|
667
668
|
|
668
669
|
def parse
|
669
670
|
report_duration(:parse) do
|
670
671
|
@lexer = Lrama::Lexer.new(@grammar_file)
|
671
|
-
@grammar = Lrama::Grammar.new(@rule_counter)
|
672
|
+
@grammar = Lrama::Grammar.new(@rule_counter, @define)
|
672
673
|
@precedence_number = 0
|
673
674
|
reset_precs
|
674
675
|
do_parse
|
@@ -914,7 +915,7 @@ racc_reduce_table = [
|
|
914
915
|
2, 73, :_reduce_15,
|
915
916
|
1, 60, :_reduce_none,
|
916
917
|
2, 60, :_reduce_17,
|
917
|
-
3, 60, :
|
918
|
+
3, 60, :_reduce_18,
|
918
919
|
2, 60, :_reduce_none,
|
919
920
|
2, 60, :_reduce_20,
|
920
921
|
2, 60, :_reduce_21,
|
@@ -1328,7 +1329,12 @@ module_eval(<<'.,.,', 'parser.y', 26)
|
|
1328
1329
|
end
|
1329
1330
|
.,.,
|
1330
1331
|
|
1331
|
-
|
1332
|
+
module_eval(<<'.,.,', 'parser.y', 27)
|
1333
|
+
def _reduce_18(val, _values, result)
|
1334
|
+
@grammar.define[val[1].s_value] = val[2]&.s_value
|
1335
|
+
result
|
1336
|
+
end
|
1337
|
+
.,.,
|
1332
1338
|
|
1333
1339
|
# reduce 19 omitted
|
1334
1340
|
|