at_coder_friends 0.6.2 → 0.6.7

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -2
  3. data/.travis.yml +2 -2
  4. data/CHANGELOG.md +53 -0
  5. data/Gemfile.lock +32 -36
  6. data/README.md +1 -1
  7. data/at_coder_friends.gemspec +5 -7
  8. data/config/default.yml +146 -72
  9. data/docs/CONFIGURATION.md +224 -140
  10. data/lib/at_coder_friends.rb +3 -0
  11. data/lib/at_coder_friends/cli.rb +8 -0
  12. data/lib/at_coder_friends/config_loader.rb +11 -3
  13. data/lib/at_coder_friends/context.rb +10 -6
  14. data/lib/at_coder_friends/emitter.rb +2 -2
  15. data/lib/at_coder_friends/generator/base.rb +42 -0
  16. data/lib/at_coder_friends/generator/cxx_builtin.rb +196 -143
  17. data/lib/at_coder_friends/generator/main.rb +8 -2
  18. data/lib/at_coder_friends/generator/ruby_builtin.rb +97 -51
  19. data/lib/at_coder_friends/parser/constraints.rb +1 -0
  20. data/lib/at_coder_friends/parser/input_format.rb +389 -188
  21. data/lib/at_coder_friends/parser/input_type.rb +92 -0
  22. data/lib/at_coder_friends/parser/main.rb +1 -0
  23. data/lib/at_coder_friends/parser/modulo.rb +1 -1
  24. data/lib/at_coder_friends/parser/sections.rb +3 -3
  25. data/lib/at_coder_friends/path_info.rb +51 -0
  26. data/lib/at_coder_friends/path_util.rb +0 -31
  27. data/lib/at_coder_friends/problem.rb +81 -6
  28. data/lib/at_coder_friends/scraping/agent.rb +11 -2
  29. data/lib/at_coder_friends/scraping/custom_test.rb +5 -6
  30. data/lib/at_coder_friends/scraping/submission.rb +6 -7
  31. data/lib/at_coder_friends/scraping/tasks.rb +8 -3
  32. data/lib/at_coder_friends/test_runner/base.rb +17 -4
  33. data/lib/at_coder_friends/test_runner/judge.rb +7 -9
  34. data/lib/at_coder_friends/test_runner/sample.rb +2 -4
  35. data/lib/at_coder_friends/verifier.rb +2 -3
  36. data/lib/at_coder_friends/version.rb +1 -1
  37. data/templates/{cxx_builtin_interactive.cxx → cxx_builtin.cxx.erb} +26 -4
  38. data/templates/{ruby_builtin_interactive.rb → ruby_builtin.rb.erb} +17 -3
  39. metadata +11 -17
  40. data/tasks/regression/check_const.rake +0 -136
  41. data/tasks/regression/check_diff.rake +0 -30
  42. data/tasks/regression/check_fmt.rake +0 -42
  43. data/tasks/regression/check_parse.rake +0 -70
  44. data/tasks/regression/regression.rb +0 -72
  45. data/tasks/regression/section_list.rake +0 -53
  46. data/tasks/regression/setup.rake +0 -48
  47. data/templates/cxx_builtin_default.cxx +0 -26
  48. data/templates/ruby_builtin_default.rb +0 -7
@@ -14,8 +14,14 @@ module AtCoderFriends
14
14
  def process(pbm)
15
15
  generators = ctx.config.dig('generators') || []
16
16
  generators.each do |gen_name|
17
- gen_obj = load_obj(gen_name)
18
- gen_obj.process(pbm)
17
+ begin
18
+ gen_obj = load_obj(gen_name)
19
+ gen_obj.process(pbm)
20
+ rescue StandardError => e
21
+ puts "an error occurred in generator:#{gen_name}."
22
+ puts e.to_s
23
+ puts e.backtrace
24
+ end
19
25
  end
20
26
  end
21
27
 
@@ -3,43 +3,20 @@
3
3
  module AtCoderFriends
4
4
  module Generator
5
5
  # generates Ruby source from problem description
6
- class RubyBuiltin
6
+ class RubyBuiltin < Base
7
7
  ACF_HOME = File.realpath(File.join(__dir__, '..', '..', '..'))
8
8
  TMPL_DIR = File.join(ACF_HOME, 'templates')
9
- DEFAULT_TMPL = File.join(TMPL_DIR, 'ruby_builtin_default.rb')
10
- INTERACTIVE_TMPL = File.join(TMPL_DIR, 'ruby_builtin_interactive.rb')
9
+ DEFAULT_TMPL = File.join(TMPL_DIR, 'ruby_builtin.rb.erb')
10
+ ATTRS = Attributes.new(:rb, DEFAULT_TMPL)
11
11
 
12
- attr_reader :cfg, :pbm
13
-
14
- def initialize(cfg = nil)
15
- @cfg = cfg || {}
16
- end
17
-
18
- def process(pbm)
19
- src = generate(pbm)
20
- pbm.add_src(:rb, src)
21
- end
22
-
23
- def generate(pbm)
24
- @pbm = pbm
25
- File
26
- .read(select_template)
27
- .gsub('### URL ###', pbm.url)
28
- .gsub('### CONSTS ###', gen_consts.join("\n"))
29
- .gsub('### DCLS ###', gen_decls.join("\n"))
30
- .gsub('### OUTPUT ###', gen_output)
12
+ def attrs
13
+ ATTRS
31
14
  end
32
15
 
33
- def select_template(interactive = pbm.options.interactive)
34
- interactive ? interactive_template : default_template
35
- end
36
-
37
- def default_template
38
- cfg['default_template'] || DEFAULT_TMPL
39
- end
40
-
41
- def interactive_template
42
- cfg['interactive_template'] || INTERACTIVE_TMPL
16
+ def render(src)
17
+ src = embed_lines(src, '### CONSTS ###', gen_consts)
18
+ src = embed_lines(src, '### DCLS ###', gen_decls)
19
+ src
43
20
  end
44
21
 
45
22
  def gen_consts(constants = pbm.constants)
@@ -64,35 +41,45 @@ module AtCoderFriends
64
41
  when :harray
65
42
  gen_harray_decl(inpdef)
66
43
  when :varray
67
- if inpdef.names.size == 1
68
- gen_varray_1_decl(inpdef)
69
- else
70
- gen_varray_n_decl(inpdef)
71
- end
44
+ gen_varray_decl(inpdef)
72
45
  when :matrix
73
46
  gen_matrix_decl(inpdef)
47
+ when :varray_matrix, :matrix_varray
48
+ gen_cmb_decl(inpdef)
49
+ when :vmatrix
50
+ gen_vmatrix_decl(inpdef)
51
+ when :hmatrix
52
+ gen_hmatrix_decl(inpdef)
74
53
  end
75
54
  end
76
55
 
77
56
  def gen_single_decl(inpdef)
78
57
  names = inpdef.names
79
58
  dcl = names.join(', ')
80
- expr = gen_expr(inpdef.item, names.size > 1)
59
+ expr = gen_expr(inpdef, names.size > 1)
81
60
  "#{dcl} = #{expr}"
82
61
  end
83
62
 
84
63
  def gen_harray_decl(inpdef)
85
64
  v = inpdef.names[0]
86
65
  dcl = "#{v}s"
87
- expr = gen_expr(inpdef.item, true)
66
+ expr = gen_expr(inpdef, true)
88
67
  "#{dcl} = #{expr}"
89
68
  end
90
69
 
70
+ def gen_varray_decl(inpdef)
71
+ if inpdef.names.size == 1
72
+ gen_varray_1_decl(inpdef)
73
+ else
74
+ gen_varray_n_decl(inpdef)
75
+ end
76
+ end
77
+
91
78
  def gen_varray_1_decl(inpdef)
92
79
  v = inpdef.names[0]
93
80
  sz = inpdef.size[0]
94
81
  dcl = "#{v}s"
95
- expr = gen_expr(inpdef.item, false)
82
+ expr = gen_expr(inpdef, false)
96
83
  "#{dcl} = Array.new(#{sz}) { #{expr} }"
97
84
  end
98
85
 
@@ -100,7 +87,7 @@ module AtCoderFriends
100
87
  names = inpdef.names
101
88
  sz = inpdef.size[0]
102
89
  dcl = names.map { |v| "#{v}s[i]" }.join(', ')
103
- expr = gen_expr(inpdef.item, true)
90
+ expr = gen_expr(inpdef, true)
104
91
  ret = []
105
92
  ret += names.map { |v| "#{v}s = Array.new(#{sz})" }
106
93
  ret << "#{sz}.times do |i|"
@@ -113,28 +100,87 @@ module AtCoderFriends
113
100
  v = inpdef.names[0]
114
101
  sz = inpdef.size[0]
115
102
  decl = "#{v}ss"
116
- expr = gen_expr(inpdef.item, true)
103
+ expr = gen_expr(inpdef, true)
117
104
  "#{decl} = Array.new(#{sz}) { #{expr} }"
118
105
  end
119
106
 
120
- def gen_expr(item, split)
121
- case item
107
+ def gen_cmb_decl(inpdef)
108
+ mx = inpdef.container == :varray_matrix ? -1 : 0
109
+ vs = inpdef.names.map { |v| "#{v}s" }
110
+ vs[mx] += 's'
111
+ sz = inpdef.size[0]
112
+ dcls = vs.map { |v| "#{v}[i]" }
113
+ dcls[mx] = '*' + dcls[mx] unless inpdef.item == :char
114
+ dcl = dcls.join(', ')
115
+ expr = gen_cmb_expr(inpdef)
116
+ ret = []
117
+ ret += vs.map { |v| "#{v} = Array.new(#{sz})" }
118
+ ret << "#{sz}.times do |i|"
119
+ ret << " #{dcl} = #{expr}"
120
+ ret << 'end'
121
+ ret
122
+ end
123
+
124
+ def gen_vmatrix_decl(inpdef)
125
+ names = inpdef.names
126
+ sz1, sz2 = inpdef.size
127
+ dcl = names.map { |v| "#{v}ss[i][j]" }.join(', ')
128
+ expr = gen_expr(inpdef, true)
129
+ ret = []
130
+ ret += names.map do |v|
131
+ "#{v}ss = Array.new(#{sz1}) { Array.new(#{sz2}) }"
132
+ end
133
+ ret << "#{sz1}.times do |i|"
134
+ ret << " #{sz2}.times do |j|"
135
+ ret << " #{dcl} = #{expr}"
136
+ ret << ' end'
137
+ ret << 'end'
138
+ ret
139
+ end
140
+
141
+ def gen_hmatrix_decl(inpdef)
142
+ names = inpdef.names
143
+ sz = inpdef.size[0]
144
+ dcl = names.map { |v| "#{v}ss[i]" }.join(', ')
145
+ expr = gen_expr(inpdef, true)
146
+ ret = []
147
+ ret += names.map { |v| "#{v}ss = Array.new(#{sz})" }
148
+ ret << "#{sz}.times do |i|"
149
+ ret << " #{dcl} = #{expr}.each_slice(#{names.size}).to_a.transpose"
150
+ ret << 'end'
151
+ ret
152
+ end
153
+
154
+ def gen_expr(inpdef, split)
155
+ read = gen_read(inpdef.delim)
156
+ case inpdef.item
122
157
  when :number
123
- split ? 'gets.split.map(&:to_i)' : 'gets.to_i'
158
+ split ? "#{read}.split.map(&:to_i)" : "#{read}.to_i"
159
+ when :decimal
160
+ split ? "#{read}.split.map(&:to_f)" : "#{read}.to_f"
124
161
  when :string
125
- split ? 'gets.chomp.split' : 'gets.chomp'
162
+ split ? "#{read}.chomp.split" : "#{read}.chomp"
126
163
  when :char
127
164
  'gets.chomp'
128
165
  end
129
166
  end
130
167
 
131
- def gen_output(vs = pbm.options.binary_values)
132
- if vs
133
- "puts cond ? '#{vs[0]}' : '#{vs[1]}'"
134
- else
135
- 'puts ans'
168
+ def gen_cmb_expr(inpdef)
169
+ read = gen_read(inpdef.delim)
170
+ case inpdef.item
171
+ when :number
172
+ "#{read}.split.map(&:to_i)"
173
+ when :decimal
174
+ "#{read}.split.map(&:to_f)"
175
+ when :string, :char
176
+ "#{read}.chomp.split"
136
177
  end
137
178
  end
179
+
180
+ def gen_read(delim)
181
+ sub = delim.chars.map { |d| ".gsub('#{d}', ' ')" }.join
182
+ "gets#{sub}"
183
+ end
138
184
  end
139
185
  end
140
186
  end
@@ -64,6 +64,7 @@ module AtCoderFriends
64
64
  .gsub('\\lvert', '|')
65
65
  .gsub('\\rvert', '|')
66
66
  .gsub('\\mathit', '')
67
+ .gsub('\\mathrm', '')
67
68
  .gsub('\\times', '*')
68
69
  .gsub(/\\begin(\{[^{}]*\})*/, '')
69
70
  .gsub(/\\end(\{[^{}]*\})*/, '')
@@ -2,195 +2,463 @@
2
2
 
3
3
  module AtCoderFriends
4
4
  module Parser
5
- InputFormatMatcher = Struct.new(:container, :item, :pat, :gen_names, :gen_pat2) do
6
- attr_reader :names, :size
5
+ module InputFormatConstants
6
+ SECTIONS = [Problem::SECTION_IN_FMT, Problem::SECTION_IO_FMT].freeze
7
+ DELIMS = %w[- / :].freeze
8
+ RE_SINGLE = /[A-Za-z{][A-Za-z_0-9{}]*/.freeze
9
+ RE_ITEM = /\{*[A-Za-z]+(?:_[A-Za-z]+)*\}*/.freeze
10
+ RE_0 = /[0-9]+/.freeze
11
+ RE_00 = /[01][,_]?[01]/.freeze
12
+ RE_99 = /[0-9]+[,_]?[0-9]+/.freeze
13
+ ADD_TAG = ->(tag, re) { /(?<#{tag}>#{re})/ }
14
+ TO_SUFFIX = ->(re) { /(_\{*#{re}\}*|\{+#{re}\}+)/ }
15
+ TO_SUFFIX_STR = ->(str) { "(_\\{*#{str}\\}*|\\{+#{str}\\}+)" }
16
+ RE_IX = TO_SUFFIX[/\S+?/]
17
+ RE_IX_0 = TO_SUFFIX[ADD_TAG['ix0', RE_0]]
18
+ RE_IX_00 = TO_SUFFIX[ADD_TAG['ix0', RE_00]]
19
+ RE_IX_99 = TO_SUFFIX[RE_99]
20
+ RE_SZ = TO_SUFFIX[ADD_TAG['sz', /\S+?/]]
21
+ RE_SZ_0 = TO_SUFFIX[ADD_TAG['sz', RE_0]]
22
+ RE_SZ_00 = TO_SUFFIX[ADD_TAG['sz', RE_00]]
23
+ RE_SZ_99 = TO_SUFFIX[ADD_TAG['sz', RE_99]]
24
+ RE_SZ_REF = TO_SUFFIX_STR['\k<sz>']
25
+ RE_SZ2_0 = TO_SUFFIX[ADD_TAG['sz2', RE_0]]
26
+ RE_SZ2_REF = TO_SUFFIX_STR['\k<sz2>']
27
+ RE_BLOCK = /(?<bl>\{(?:[^{}]|\g<bl>)*\})/.freeze
28
+ DIMENSION_TBL = {
29
+ single: 0,
30
+ varray: 1,
31
+ harray: 1,
32
+ matrix: 2,
33
+ varray_matrix: 2,
34
+ matrix_varray: 2,
35
+ vmatrix: 2,
36
+ hmatrix: 2
37
+ }.freeze
38
+ end
39
+
40
+ # utilities for input format parser
41
+ module InputFormatUtils
42
+ include InputFormatConstants
43
+
44
+ # 1) &npsp;, fill-width space -> half width space
45
+ # 2) {i, j}->{i,j} for nested {}
46
+ def normalize_fmt(str)
47
+ str
48
+ .tr('0-9A-Za-z', '0-9A-Za-z')
49
+ .gsub(/[[:space:]]/) { |c| c.gsub(/[^\n]/, ' ') } # 1)
50
+ .gsub(%r{<var>([^<>]+)</var>}i, '\1') # <sub><var>N</var></sub>
51
+ .gsub(%r{<sup>([^<>]+)</sup>}i, '^\1')
52
+ .gsub(%r{<sub>([^<>]+)</sub>}i, '_{\1}')
53
+ .gsub(%r{<sub>([^<>]+)</sub>}i, '_{\1}') # for nested<sub>
54
+ .gsub(/<("[^"]*"|'[^']*'|[^'"<>])*>/, '')
55
+ .gsub('&amp;', '&')
56
+ .gsub('&gt;', '>')
57
+ .gsub('&lt;', '<')
58
+ .gsub('\\ ', ' ')
59
+ .gsub(/\\hspace\{\d+pt\}/, ' ')
60
+ .gsub('\\(', '')
61
+ .gsub('\\)', '')
62
+ .gsub('\\lvert', '|')
63
+ .gsub('\\rvert', '|')
64
+ .gsub('\\mathit', '')
65
+ .gsub('\\mathrm', '')
66
+ .gsub('\\times', '*')
67
+ .gsub(/\\begin(\{[^{}]*\})*/, '')
68
+ .gsub(/\\end(\{[^{}]*\})*/, '')
69
+ .gsub(/\\[cdlv]?dots/, '..')
70
+ .gsub(/\{\}/, ' ')
71
+ .gsub('−', '-') # full width hyphen
72
+ .gsub(/[・.:‥⋮︙…]+/, '..')
73
+ .gsub(/[\\$']/, '') # s' -> s
74
+ .gsub(/[&~|]/, ' ') # |S| -> S
75
+ .gsub(/^\s*[.:][\s.:]*$/, '..')
76
+ .tr('()', '{}')
77
+ .gsub(/#{RE_BLOCK}/) { |w| w.delete(' ') } # 2)
78
+ .split("\n")
79
+ .map(&:strip)
80
+ end
81
+
82
+ def extract_delim(str)
83
+ # a-b, a/b, a:b -> a b
84
+ str = str.dup
85
+ dlms =
86
+ DELIMS.select { |c| str.gsub!(/#{c}(#{RE_SINGLE})/, ' \1') }.join
87
+ [str, dlms]
88
+ end
89
+
90
+ def normalize_name(s)
91
+ s.delete('{},').gsub(/(\A_+|_+\z)/, '')
92
+ end
93
+
94
+ def normalize_names(names)
95
+ names.map { |nm| normalize_name(nm) }
96
+ end
97
+
98
+ def normalize_size(container, size, ix0)
99
+ sz = size_array(container, size)
100
+ sz0 = size_array(container, ix0)
101
+
102
+ sz.map.with_index do |s, i|
103
+ if sz0[i] == '0'
104
+ # 0 -> 1, N-1 -> N, N-2 -> N-1 if 0 origin
105
+ s.gsub(/\A0\z/, '1').gsub(/-1\z/, '').gsub(/-2\z/, '-1')
106
+ else
107
+ s
108
+ end
109
+ end
110
+ end
111
+
112
+ # split size by container dimension
113
+ def size_array(container, size)
114
+ (
115
+ case DIMENSION_TBL[container]
116
+ when 2
117
+ split_size(size)
118
+ when 1
119
+ [size]
120
+ when 0
121
+ []
122
+ end
123
+ ).map { |s| normalize_name(s) }
124
+ end
125
+
126
+ def split_size(str)
127
+ str = str.gsub(/(\A\{|\}\z)/, '') while str =~ /\A#{RE_BLOCK}\z/
128
+
129
+ sz = str.split(',')
130
+ return sz if sz.size == 2
131
+
132
+ sz = str.scan(/(?<nbl>[^{}]+)|#{RE_BLOCK}/).flatten.compact
133
+ return sz if sz.size == 2
134
+
135
+ str = str.delete('{},')
136
+
137
+ sz = str.scan(/[^_](?:_[^_])?/)
138
+ return sz if sz.size == 2
139
+
140
+ sz = str.split('_')
141
+ return sz if sz.size == 2
142
+
143
+ [str[0] || '_', str[1..-1] || '_']
144
+ end
145
+ end
146
+
147
+ # holds regular expressions and matches it with input format string
148
+ class InputFormatMatcher
149
+ include InputFormatUtils
150
+
151
+ attr_reader :container, :item, :pat, :gen_names, :gen_pat2
152
+ attr_reader :names, :pat2, :size, :delim, :ix0
153
+
154
+ def initialize(
155
+ container: nil, item: nil,
156
+ pat: nil, gen_names: nil, gen_pat2: nil
157
+ )
158
+ @container = container
159
+ @item = item
160
+ @pat = pat
161
+ @gen_names = gen_names
162
+ @gen_pat2 = gen_pat2
163
+ end
7
164
 
8
165
  def match(str)
166
+ str, dlm = extract_delim(str)
9
167
  return false unless (m1 = pat.match(str))
10
168
 
11
169
  @names = gen_names.call(m1)
12
170
  @pat2 = gen_pat2&.call(names)
13
171
  @size = m1.names.include?('sz') && m1['sz'] || ''
172
+ @ix0 = m1.names.include?('ix0') && m1['ix0'] || size
173
+ @delim = dlm
14
174
  true
15
175
  end
16
176
 
17
177
  def match2(str)
18
- return false unless @pat2
178
+ return false unless pat2
179
+
180
+ str, _dlm = extract_delim(str)
19
181
  return true if /\A\.+\z/ =~ str
20
- return false unless (m2 = @pat2.match(str))
182
+ return false unless (m2 = pat2.match(str))
21
183
 
22
184
  m2.names.include?('sz') && @size = m2['sz']
23
185
  true
24
186
  end
187
+
188
+ def to_inpdef
189
+ Problem::InputFormat.new(
190
+ container: container, item: item,
191
+ names: normalize_names(names),
192
+ size: normalize_size(container, size, ix0),
193
+ delim: delim
194
+ )
195
+ end
25
196
  end
26
197
 
27
- module InputFormatConstants
28
- SECTIONS = [
29
- Problem::SECTION_IN_FMT,
30
- Problem::SECTION_IO_FMT
31
- ].freeze
32
- ITEM_PAT = /\{*[A-Za-z]+(?:_[A-Za-z]+)*\}*/.freeze
33
- SZ = '(_(?<sz>\S+)|{(?<sz>\S+)})'
34
- SINGLE_PAT = /([A-Za-z{][A-Za-z_0-9{}]*)/.freeze
35
- MATCHERS = [
36
- InputFormatMatcher.new(
37
- :matrix, :number,
198
+ # matcher constants
199
+ module InputFormatMatcherConstants
200
+ include InputFormatConstants
201
+
202
+ MATRIX_MATCHER = InputFormatMatcher.new(
203
+ container: :matrix,
204
+ pat:
38
205
  /
39
206
  \A
40
- (?<v>#{ITEM_PAT})[_{]\{*[01][,_]?[01]\}*
41
- (\s+\k<v>[_{]\S+)*
42
- (\s+\.+)?
43
- (\s+\k<v>#{SZ})+
207
+ (?<v>#{RE_ITEM})#{RE_IX_00}
208
+ (\s+(\.+|\k<v>#{RE_IX}))*
209
+ \s+\k<v>#{RE_SZ}
44
210
  \z
45
211
  /x,
46
- ->(m) { [m[:v]] },
47
- lambda { |((v))|
212
+ gen_names: ->(m) { [m[:v]] },
213
+ gen_pat2:
214
+ lambda { |(v)|
48
215
  /
49
216
  \A
50
- #{v}[_{]\S+
51
- (\s+#{v}#{SZ})*
52
- (\s+\.+)?
53
- (\s+#{v}#{SZ})*
217
+ #{v}#{RE_IX}
218
+ (\s+(\.+|#{v}#{RE_IX}))*
219
+ \s+(\.+|#{v}#{RE_SZ})
54
220
  \z
55
221
  /x
56
222
  }
57
- ),
58
- InputFormatMatcher.new(
59
- :matrix, :char,
223
+ )
224
+ MATRIX_CHAR_MATCHER = InputFormatMatcher.new(
225
+ container: :matrix,
226
+ item: :char,
227
+ pat:
60
228
  /
61
229
  \A
62
- (?<v>#{ITEM_PAT})[_{]\{*[01][,_]?[01]\}*
63
- (\k<v>[_{]\S+)*
64
- (\s*\.+\s*)?
65
- (\k<v>#{SZ})+
230
+ (?<v>#{RE_ITEM})#{RE_IX_00}
231
+ (\s*\.+\s*|\k<v>#{RE_IX})*
232
+ \k<v>#{RE_SZ}
66
233
  \z
67
234
  /x,
68
- ->(m) { [m[:v]] },
69
- lambda { |((v))|
235
+ gen_names: ->(m) { [m[:v]] },
236
+ gen_pat2:
237
+ lambda { |(v)|
70
238
  /
71
239
  \A
72
- (#{v}[_{]\S+)+
73
- (\s*\.+\s*)?
74
- (#{v}#{SZ})+
240
+ (#{v}#{RE_IX})+
241
+ (\s*\.+\s*|#{v}#{RE_IX})*
242
+ (\s*\.+\s*|#{v}#{RE_SZ})
75
243
  \z
76
244
  /x
77
245
  }
78
- ),
79
- InputFormatMatcher.new(
80
- :harray, :number,
246
+ )
247
+ HARRAY_MATCHER = InputFormatMatcher.new(
248
+ container: :harray,
249
+ pat:
81
250
  /
82
251
  \A
83
- (?<v>#{ITEM_PAT})[_{]\{*[0-9]\}*
84
- (\s+\k<v>[_{]\S+)*
85
- (\s+\.+)?
86
- (\s+\k<v>#{SZ})+
252
+ (?<v>#{RE_ITEM})#{RE_IX_0}
253
+ (\s+(\.+|\k<v>#{RE_IX}))*
254
+ \s+\k<v>#{RE_SZ}
87
255
  \z
88
256
  /x,
89
- ->(m) { [m[:v]] },
90
- nil
91
- ),
92
- InputFormatMatcher.new(
93
- :harray, :char,
257
+ gen_names: ->(m) { [m[:v]] }
258
+ )
259
+ HARRAY_CHAR_MATCHER = InputFormatMatcher.new(
260
+ container: :harray,
261
+ item: :char,
262
+ pat:
94
263
  /
95
264
  \A
96
- (?<v>#{ITEM_PAT})[_{]\{*[0-9]\}*
97
- (\k<v>[_{]\S+)*
98
- (\s*\.+\s*)?
99
- (\k<v>#{SZ})+
265
+ (?<v>#{RE_ITEM})#{RE_IX_0}
266
+ (\s*\.+\s*|\k<v>#{RE_IX})*
267
+ \k<v>#{RE_SZ}
100
268
  \z
101
269
  /x,
102
- ->(m) { [m[:v]] },
103
- nil
104
- ),
105
- InputFormatMatcher.new(
106
- :varray, :number,
270
+ gen_names: ->(m) { [m[:v]] }
271
+ )
272
+ VARRAY_MATRIX_MATCHER = InputFormatMatcher.new(
273
+ container: :varray_matrix,
274
+ pat:
107
275
  /
108
276
  \A
109
- #{ITEM_PAT}[_{]\{*(?<sz>[0-9]+)\}*
110
- (\s+#{ITEM_PAT}[_{]\{*\k<sz>\}*)*
277
+ (?<vs>#{RE_ITEM}#{RE_SZ2_0} (\s+#{RE_ITEM}#{RE_SZ2_REF})*)
278
+ \s+(?<m>#{RE_ITEM})#{RE_IX_00}
279
+ (\s+(\.+|\k<m>#{RE_IX}))*
280
+ \s+\k<m>#{RE_SZ}
111
281
  \z
112
282
  /x,
113
- ->(m) { m[0].split.map { |w| w.scan(ITEM_PAT)[0] } },
283
+ gen_names:
284
+ ->(m) { [*m[:vs].split.map { |w| w.scan(RE_ITEM)[0] }, m[:m]] },
285
+ gen_pat2:
114
286
  lambda { |vs|
115
- pat2 = vs.map { |v| v + SZ }.join('\s+')
116
- /\A#{pat2}\z/
287
+ ws = vs[0..-2].map { |v| v + RE_IX.source }.join('\s+')
288
+ m = vs[-1]
289
+ /
290
+ \A
291
+ #{ws}
292
+ \s+#{m}#{RE_IX}
293
+ (\s+(\.+|#{m}#{RE_IX}))*
294
+ \s+(\.+|#{m}#{RE_SZ})
295
+ \z
296
+ /x
117
297
  }
118
- ),
119
- InputFormatMatcher.new(
120
- :single, :number,
121
- /\A(.*\s)?#{SINGLE_PAT}(\s.*)?\z/,
122
- ->(m) { m[0].split.select { |w| w =~ /\A#{SINGLE_PAT}\z/ } },
123
- nil
124
- )
298
+ )
299
+ VARRAY_MATRIX_CHAR_MATCHER = InputFormatMatcher.new(
300
+ container: :varray_matrix,
301
+ item: :char,
302
+ pat:
303
+ /
304
+ \A
305
+ (?<vs>#{RE_ITEM}#{RE_SZ2_0} (\s+#{RE_ITEM}#{RE_SZ2_REF})*)
306
+ \s+(?<m>#{RE_ITEM})#{RE_IX_00}
307
+ (\s*\.+\s*|\k<m>#{RE_IX})*
308
+ \k<m>#{RE_SZ} \z
309
+ /x,
310
+ gen_names:
311
+ ->(m) { [*m[:vs].split.map { |w| w.scan(RE_ITEM)[0] }, m[:m]] },
312
+ gen_pat2:
313
+ lambda { |vs|
314
+ ws = vs[0..-2].map { |v| v + RE_IX.source }.join('\s+')
315
+ m = vs[-1]
316
+ /
317
+ \A
318
+ #{ws}
319
+ \s+#{m}#{RE_IX}
320
+ (\s*\.+\s*|#{m}#{RE_IX})*
321
+ (\s*\.+\s*|#{m}#{RE_SZ})
322
+ \z
323
+ /x
324
+ }
325
+ )
326
+ MATRIX_VARRAY_MATCHER = InputFormatMatcher.new(
327
+ container: :matrix_varray,
328
+ pat:
329
+ /
330
+ \A
331
+ (?<m>#{RE_ITEM})#{RE_IX_00}
332
+ (\s+(\.+|\k<m>#{RE_IX}))*
333
+ \s+\k<m>#{RE_SZ}
334
+ \s+(?<vs>#{RE_ITEM}#{RE_SZ2_0} (\s+#{RE_ITEM}#{RE_SZ2_REF})*)
335
+ \z
336
+ /x,
337
+ gen_names:
338
+ ->(m) { [m[:m], *m[:vs].split.map { |w| w.scan(RE_ITEM)[0] }] },
339
+ gen_pat2:
340
+ lambda { |vs|
341
+ m = vs[0]
342
+ ws = vs[1..-1].map { |v| v + RE_IX.source }.join('\s+')
343
+ /
344
+ \A
345
+ #{m}#{RE_IX}
346
+ (\s+(\.+|#{m}#{RE_IX}))*
347
+ \s+(\.+|#{m}#{RE_SZ})
348
+ \s+#{ws}
349
+ \z
350
+ /x
351
+ }
352
+ )
353
+ VMATRIX_MATCHER = InputFormatMatcher.new(
354
+ container: :vmatrix,
355
+ pat:
356
+ /
357
+ \A
358
+ #{RE_ITEM}#{RE_SZ_00} (\s+#{RE_ITEM}#{RE_SZ_REF})*
359
+ \z
360
+ /x,
361
+ gen_names: ->(m) { m[0].split.map { |w| w.scan(RE_ITEM)[0] } },
362
+ gen_pat2:
363
+ lambda { |vs|
364
+ ws = [
365
+ vs[0] + RE_SZ.source,
366
+ *vs[1..-1]&.map { |v| v + RE_IX.source }
367
+ ].join('\s+')
368
+ /\A#{ws}\z/
369
+ }
370
+ )
371
+ HMATRIX_MATCHER = InputFormatMatcher.new(
372
+ container: :hmatrix,
373
+ pat:
374
+ /
375
+ \A
376
+ #{RE_ITEM}#{RE_IX_00}
377
+ (\s+(\.+|#{RE_ITEM}#{RE_IX_99}))*
378
+ \s+#{RE_ITEM}#{RE_SZ_99}
379
+ \z
380
+ /x,
381
+ gen_names:
382
+ ->(m) { m[0].split.map { |w| w.scan(RE_ITEM)[0] }.uniq },
383
+ gen_pat2:
384
+ lambda { |vs|
385
+ ws1 = vs.map { |v| v + RE_IX.source }.join('\s+')
386
+ ws2 = [
387
+ vs[0] + RE_SZ.source,
388
+ *vs[1..-1]&.map { |v| v + RE_IX.source }
389
+ ].join('\s+')
390
+ /
391
+ \A
392
+ #{ws1} (\s+(\.+|#{ws1}))* \s+(\.+|#{ws2})
393
+ \z
394
+ /x
395
+ }
396
+ )
397
+ VARRAY_MATCHER = InputFormatMatcher.new(
398
+ container: :varray,
399
+ pat:
400
+ /
401
+ \A
402
+ #{RE_ITEM}#{RE_SZ_0} (\s+#{RE_ITEM}#{RE_SZ_REF})*
403
+ \z
404
+ /x,
405
+ gen_names:
406
+ ->(m) { m[0].split.map { |w| w.scan(RE_ITEM)[0] } },
407
+ gen_pat2:
408
+ lambda { |vs|
409
+ ws = [
410
+ vs[0] + RE_SZ.source,
411
+ *vs[1..-1]&.map { |v| v + RE_IX.source }
412
+ ].join('\s+')
413
+ /\A#{ws}\z/
414
+ }
415
+ )
416
+ SINGLE_MATCHER = InputFormatMatcher.new(
417
+ container: :single,
418
+ pat: /\A(.*\s)?#{RE_SINGLE}(\s.*)?\z/,
419
+ gen_names: ->(m) { m[0].split.select { |w| w =~ /\A#{RE_SINGLE}\z/ } }
420
+ )
421
+ MATCHERS = [
422
+ MATRIX_MATCHER,
423
+ MATRIX_CHAR_MATCHER,
424
+ HARRAY_MATCHER,
425
+ HARRAY_CHAR_MATCHER,
426
+ VARRAY_MATRIX_MATCHER,
427
+ VARRAY_MATRIX_CHAR_MATCHER,
428
+ MATRIX_VARRAY_MATCHER,
429
+ VMATRIX_MATCHER,
430
+ HMATRIX_MATCHER,
431
+ VARRAY_MATCHER,
432
+ SINGLE_MATCHER
125
433
  ].freeze
126
434
  end
127
435
 
128
436
  # parses input data format and generates input definitons
129
437
  module InputFormat
438
+ extend InputFormatUtils
130
439
  include InputFormatConstants
440
+ include InputFormatMatcherConstants
131
441
 
132
442
  module_function
133
443
 
134
444
  def process(pbm)
135
445
  return unless (str = find_fmt(pbm))
136
446
 
137
- inpdefs = parse(str, pbm.samples)
138
- pbm.formats = inpdefs
447
+ inpdefs = parse(str)
448
+ pbm.formats_src = inpdefs
139
449
  end
140
450
 
141
451
  def find_fmt(pbm)
142
452
  str = nil
143
453
  SECTIONS.any? do |key|
144
- str = pbm.sections[key]&.code_block_html
145
- str && !str.empty?
454
+ (str = pbm.sections[key]&.code_block_html) && !str.empty?
146
455
  end
147
456
  str
148
457
  end
149
458
 
150
- def parse(str, smps)
459
+ def parse(str)
151
460
  lines = normalize_fmt(str)
152
- inpdefs = parse_fmt(lines)
153
- normalize_defs!(inpdefs)
154
- smpx = max_smp(smps)
155
- smpx && match_smp!(inpdefs, smpx)
156
- inpdefs
157
- end
158
-
159
- def normalize_fmt(fmt)
160
- # 1) &npsp; , fill-width space -> half width space
161
- # 2) {i, j}->{i,j} for nested {}
162
- fmt
163
- .tr('0-9A-Za-z', '0-9A-Za-z')
164
- .gsub(/[[:space:]]/) { |c| c.gsub(/[^\n]/, ' ') } # 1)
165
- .gsub(%r{<var>([^<>]+)</var>}i, '\1') # <sub><var>N</var></sub>
166
- .gsub(%r{<sup>([^<>]+)</sup>}i, '^\1')
167
- .gsub(%r{<sub>([^<>]+)</sub>}i, '_{\1}')
168
- .gsub(%r{<sub>([^<>]+)</sub>}i, '_{\1}') # for nested<sub>
169
- .gsub(/<("[^"]*"|'[^']*'|[^'"<>])*>/, '')
170
- .gsub('&amp;', '&')
171
- .gsub('&gt;', '>')
172
- .gsub('&lt;', '<')
173
- .gsub('\\ ', ' ')
174
- .gsub('\\(', '')
175
- .gsub('\\)', '')
176
- .gsub('\\lvert', '|')
177
- .gsub('\\rvert', '|')
178
- .gsub('\\mathit', '')
179
- .gsub('\\times', '*')
180
- .gsub(/\\begin(\{[^{}]*\})*/, '')
181
- .gsub(/\\end(\{[^{}]*\})*/, '')
182
- .gsub(/\\[cdlv]?dots/, '..')
183
- .gsub(/\{\}/, ' ')
184
- .gsub('−', '-') # fill width hyphen
185
- .gsub(/[・:‥⋮︙…]+/, '..')
186
- .gsub(/[\\$']/, '') # s' -> s
187
- .gsub(/[&~|]/, ' ') # |S| -> S
188
- .gsub(%r{[-/:](#{SINGLE_PAT})}, ' \1') # a-b, a/b, a:b -> a b
189
- .gsub(/^\s*[.:][\s.:]*$/, '..')
190
- .tr('()', '{}')
191
- .gsub(/(?<bl>\{(?:[^{}]|\g<bl>)*\})/) { |w| w.delete(' ') } # 2)
192
- .split("\n")
193
- .map(&:strip)
461
+ parse_fmt(lines)
194
462
  end
195
463
 
196
464
  def parse_fmt(lines)
@@ -199,85 +467,18 @@ module AtCoderFriends
199
467
  if matcher
200
468
  next if matcher.match2(line)
201
469
 
202
- ret.last.size = matcher.size
470
+ ret << matcher.to_inpdef
203
471
  end
204
472
  if (matcher = MATCHERS.find { |m| m.match(line) })
205
- ret << Problem::InputFormat.new(
206
- matcher.container, matcher.item, matcher.names, ''
207
- )
208
473
  elsif !line.empty?
209
474
  puts "unknown format: #{line}"
210
- # ret << Problem::InputFormat.new(:unknown, nil, line)
475
+ ret << unknown_fmt(line)
211
476
  end
212
477
  end
213
478
  end
214
479
 
215
- def normalize_defs!(inpdefs)
216
- inpdefs.each do |inpdef|
217
- inpdef.names = normalize_names(inpdef.names)
218
- inpdef.size = normalize_size(inpdef.container, inpdef.size)
219
- end
220
- end
221
-
222
- def normalize_names(names)
223
- return names unless names.is_a?(Array)
224
-
225
- names.map { |nm| nm.delete('{}').gsub(/(\A_+|_+\z)/, '') }
226
- end
227
-
228
- def normalize_size(container, size)
229
- sz =
230
- case container
231
- when :matrix
232
- matrix_size(size)
233
- when :harray, :varray
234
- [size]
235
- else
236
- []
237
- end
238
- sz.map do |w|
239
- w
240
- .delete('{},')
241
- .gsub(/(\A_+|(_|-1)+\z)/, '') # extra underscores, N-1 -> N
242
- end
243
- end
244
-
245
- def matrix_size(str)
246
- sz = str.scan(/([^{}]+|\{[^{}]+\}})/).flatten
247
- return sz if sz.size == 2
248
-
249
- sz = str.split(',')
250
- return sz if sz.size == 2
251
-
252
- sz = str.split('_')
253
- return sz if sz.size == 2
254
-
255
- str = str.delete('{},')
256
- len = str.size
257
- if len.positive? && len.even?
258
- return str.chars.each_slice(len / 2).map(&:join)
259
- end
260
-
261
- [str[0] || '_', str[1..-1] || '_']
262
- end
263
-
264
- def max_smp(smps)
265
- smps
266
- .select { |smp| smp.ext == :in }
267
- .max_by { |smp| smp.txt.size }
268
- &.txt
269
- end
270
-
271
- def match_smp!(inpdefs, smp)
272
- lines = smp.split("\n")
273
- inpdefs.each_with_index do |inpdef, i|
274
- break if i >= lines.size
275
- next if inpdef.item != :number
276
-
277
- inpdef.item = :string if lines[i].split[0] =~ /[^\-0-9]/
278
- break if %i[varray matrix].include?(inpdef.container)
279
- end
280
- inpdefs
480
+ def unknown_fmt(line)
481
+ Problem::InputFormat.new(container: :unknown, item: line)
281
482
  end
282
483
  end
283
484
  end