at_coder_friends 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/.rubocop_todo.yml +1 -4
  4. data/Gemfile.lock +1 -1
  5. data/README.md +1 -1
  6. data/config/default.yml +3 -0
  7. data/docs/CONFIGURATION.md +74 -9
  8. data/lib/at_coder_friends.rb +10 -5
  9. data/lib/at_coder_friends/cli.rb +2 -5
  10. data/lib/at_coder_friends/context.rb +4 -0
  11. data/lib/at_coder_friends/emitter.rb +2 -2
  12. data/lib/at_coder_friends/generator/cxx_builtin.rb +191 -0
  13. data/lib/at_coder_friends/generator/main.rb +53 -0
  14. data/lib/at_coder_friends/generator/ruby_builtin.rb +128 -0
  15. data/lib/at_coder_friends/parser/binary.rb +39 -0
  16. data/lib/at_coder_friends/parser/constraints.rb +36 -0
  17. data/lib/at_coder_friends/parser/{format_parser.rb → input_format.rb} +42 -30
  18. data/lib/at_coder_friends/parser/interactive.rb +29 -0
  19. data/lib/at_coder_friends/parser/main.rb +6 -3
  20. data/lib/at_coder_friends/parser/sample_data.rb +24 -0
  21. data/lib/at_coder_friends/parser/section_wrapper.rb +49 -0
  22. data/lib/at_coder_friends/parser/{page_parser.rb → sections.rb} +44 -50
  23. data/lib/at_coder_friends/problem.rb +40 -24
  24. data/lib/at_coder_friends/scraping/agent.rb +1 -5
  25. data/lib/at_coder_friends/scraping/authentication.rb +2 -2
  26. data/lib/at_coder_friends/scraping/session.rb +1 -1
  27. data/lib/at_coder_friends/scraping/tasks.rb +2 -6
  28. data/lib/at_coder_friends/test_runner/base.rb +36 -31
  29. data/lib/at_coder_friends/test_runner/judge.rb +2 -6
  30. data/lib/at_coder_friends/test_runner/sample.rb +8 -6
  31. data/lib/at_coder_friends/version.rb +1 -1
  32. data/tasks/regression/check_diff.rake +29 -0
  33. data/tasks/regression/check_parse.rake +56 -0
  34. data/tasks/regression/regression.rb +67 -0
  35. data/tasks/regression/section_list.rake +41 -0
  36. data/tasks/regression/setup.rake +48 -0
  37. data/templates/cxx_builtin_default.cxx +26 -0
  38. data/templates/cxx_builtin_interactive.cxx +61 -0
  39. data/templates/ruby_builtin_default.rb +5 -0
  40. data/templates/ruby_builtin_interactive.rb +32 -0
  41. metadata +21 -8
  42. data/lib/at_coder_friends/cxx_generator.rb +0 -169
  43. data/lib/at_coder_friends/parser/constraints_parser.rb +0 -26
  44. data/lib/at_coder_friends/ruby_generator.rb +0 -97
  45. data/tasks/regression.rake +0 -163
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtCoderFriends
4
+ module Generator
5
+ # generates C++ source code from problem description
6
+ class RubyBuiltin
7
+ ACF_HOME = File.realpath(File.join(__dir__, '..', '..', '..'))
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')
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('### DCLS ###', gen_decls.join("\n"))
29
+ .gsub('### OUTPUT ###', gen_output)
30
+ end
31
+
32
+ def select_template(interactive = pbm.options.interactive)
33
+ interactive ? interactive_template : default_template
34
+ end
35
+
36
+ def default_template
37
+ cfg['default_template'] || DEFAULT_TMPL
38
+ end
39
+
40
+ def interactive_template
41
+ cfg['interactive_template'] || INTERACTIVE_TMPL
42
+ end
43
+
44
+ def gen_decls(inpdefs = pbm.formats)
45
+ inpdefs.map { |inpdef| gen_decl(inpdef) }.flatten
46
+ end
47
+
48
+ def gen_decl(inpdef)
49
+ case inpdef.container
50
+ when :single
51
+ gen_single_decl(inpdef)
52
+ when :harray
53
+ gen_harray_decl(inpdef)
54
+ when :varray
55
+ if inpdef.names.size == 1
56
+ gen_varray_1_decl(inpdef)
57
+ else
58
+ gen_varray_n_decl(inpdef)
59
+ end
60
+ when :matrix
61
+ gen_matrix_decl(inpdef)
62
+ end
63
+ end
64
+
65
+ def gen_single_decl(inpdef)
66
+ names = inpdef.names
67
+ dcl = names.join(', ')
68
+ expr = gen_expr(inpdef.item, names.size > 1)
69
+ "#{dcl} = #{expr}"
70
+ end
71
+
72
+ def gen_harray_decl(inpdef)
73
+ v = inpdef.names[0]
74
+ dcl = "#{v}s"
75
+ expr = gen_expr(inpdef.item, true)
76
+ "#{dcl} = #{expr}"
77
+ end
78
+
79
+ def gen_varray_1_decl(inpdef)
80
+ v = inpdef.names[0]
81
+ sz = inpdef.size[0]
82
+ dcl = "#{v}s"
83
+ expr = gen_expr(inpdef.item, false)
84
+ "#{dcl} = Array.new(#{sz}) { #{expr} }"
85
+ end
86
+
87
+ def gen_varray_n_decl(inpdef)
88
+ names = inpdef.names
89
+ sz = inpdef.size[0]
90
+ dcl = names.map { |v| "#{v}s[i]" }.join(', ')
91
+ expr = gen_expr(inpdef.item, true)
92
+ ret = []
93
+ ret += names.map { |v| "#{v}s = Array.new(#{sz})" }
94
+ ret << "#{sz}.times do |i|"
95
+ ret << " #{dcl} = #{expr}"
96
+ ret << 'end'
97
+ ret
98
+ end
99
+
100
+ def gen_matrix_decl(inpdef)
101
+ v = inpdef.names[0]
102
+ sz = inpdef.size[0]
103
+ decl = "#{v}ss"
104
+ expr = gen_expr(inpdef.item, true)
105
+ "#{decl} = Array.new(#{sz}) { #{expr} }"
106
+ end
107
+
108
+ def gen_expr(item, split)
109
+ case item
110
+ when :number
111
+ split ? 'gets.split.map(&:to_i)' : 'gets.to_i'
112
+ when :string
113
+ split ? 'gets.chomp.split' : 'gets.chomp'
114
+ when :char
115
+ 'gets.chomp'
116
+ end
117
+ end
118
+
119
+ def gen_output(vs = pbm.options.binary_values)
120
+ if vs
121
+ "puts cond ? '#{vs[0]}' : '#{vs[1]}'"
122
+ else
123
+ 'puts ans'
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtCoderFriends
4
+ module Parser
5
+ # detect binary problem
6
+ module Binary
7
+ module_function
8
+
9
+ def process(pbm)
10
+ vs = exp_values(pbm)
11
+ return unless vs.size == 2
12
+ return if vs.any? { |v| v.include?("\n") }
13
+ return if vs.any? { |v| v =~ /^[0-9\s]*$/ }
14
+
15
+ out_fmt = ouput_format(pbm)
16
+ re1, re2 = vs.map { |v| Regexp.escape(v) }
17
+
18
+ pbm.options.binary_values =
19
+ if out_fmt =~ /#{re1}.+#{re2}/m
20
+ vs
21
+ elsif out_fmt =~ /#{re2}.+#{re1}/m
22
+ vs.reverse
23
+ end
24
+ end
25
+
26
+ def exp_values(pbm)
27
+ pbm
28
+ .samples
29
+ .select { |smp| smp.ext == :exp }
30
+ .map { |smp| smp.txt.chomp }
31
+ .uniq
32
+ end
33
+
34
+ def ouput_format(pbm)
35
+ pbm.sections[Problem::SECTION_OUT_FMT]&.content || ''
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtCoderFriends
4
+ module Parser
5
+ # parses constraints
6
+ module Constraints
7
+ module_function
8
+
9
+ SECTIONS = [
10
+ Problem::SECTION_IN_FMT,
11
+ Problem::SECTION_IO_FMT,
12
+ Problem::SECTION_CONSTRAINTS
13
+ ].freeze
14
+
15
+ def process(pbm)
16
+ str = SECTIONS.reduce('') do |m, key|
17
+ m + (pbm.sections[key]&.content || '')
18
+ end
19
+ constraints = parse(str)
20
+ pbm.constraints = constraints
21
+ end
22
+
23
+ def parse(str)
24
+ str
25
+ .gsub(/[,\\(){}|]/, '')
26
+ .gsub(/(≤|leq?)/i, '≦')
27
+ .scan(/([\da-z_]+)\s*≦\s*(\d+)(?:\^(\d+))?/i)
28
+ .map do |v, sz, k|
29
+ sz = sz.to_i
30
+ sz **= k.to_i if k
31
+ Problem::Constraint.new(v, :max, sz)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -2,28 +2,11 @@
2
2
 
3
3
  module AtCoderFriends
4
4
  module Parser
5
- # parses input data format and generates input definitons
6
- module FormatParser
7
- module_function
8
-
9
- # Iterates through elements of an array
10
- class Iterator
11
- def initialize(array)
12
- @array = array
13
- @i = 0
14
- end
15
-
16
- def next?
17
- @i < @array.size
18
- end
19
-
20
- def next
21
- ret = @array[@i]
22
- @i += 1
23
- ret
24
- end
25
- end
26
-
5
+ module InputFormatConstants
6
+ SECTIONS = [
7
+ Problem::SECTION_IN_FMT,
8
+ Problem::SECTION_IO_FMT
9
+ ].freeze
27
10
  PARSERS = [
28
11
  {
29
12
  container: :harray,
@@ -77,18 +60,47 @@ module AtCoderFriends
77
60
  size: ->(_) { [] }
78
61
  }
79
62
  ].freeze
63
+ end
64
+
65
+ # parses input data format and generates input definitons
66
+ module InputFormat
67
+ include InputFormatConstants
68
+
69
+ module_function
70
+
71
+ # Iterates through elements of an array
72
+ class Iterator
73
+ def initialize(array)
74
+ @array = array
75
+ @i = 0
76
+ end
77
+
78
+ def next?
79
+ @i < @array.size
80
+ end
81
+
82
+ def next
83
+ ret = @array[@i]
84
+ @i += 1
85
+ ret
86
+ end
87
+ end
80
88
 
81
89
  def process(pbm)
82
- defs = parse(pbm.fmt, pbm.smps)
83
- pbm.defs = defs
90
+ str =
91
+ SECTIONS
92
+ .map { |key| pbm.sections[key]&.code_block }
93
+ .find(&:itself) || ''
94
+ inpdefs = parse(str, pbm.samples)
95
+ pbm.formats = inpdefs
84
96
  end
85
97
 
86
- def parse(fmt, smps)
87
- lines = normalize(fmt)
88
- defs = parse_fmt(lines)
98
+ def parse(str, smps)
99
+ lines = normalize(str)
100
+ inpdefs = parse_fmt(lines)
89
101
  smpx = max_smp(smps)
90
- smpx && match_smp!(defs, smpx)
91
- defs
102
+ smpx && match_smp!(inpdefs, smpx)
103
+ inpdefs
92
104
  end
93
105
 
94
106
  def normalize(fmt)
@@ -125,7 +137,7 @@ module AtCoderFriends
125
137
  break unless pat2 && pat2 =~ cur
126
138
  end
127
139
  size = parser[:size].call(prv)
128
- y << InputDef.new(container, item, names, size)
140
+ y << Problem::InputFormat.new(container, item, names, size)
129
141
  end
130
142
  end.to_a
131
143
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtCoderFriends
4
+ module Parser
5
+ # detect interactive problem
6
+ module Interactive
7
+ module_function
8
+
9
+ INTERACTIVE_PAT = '(インタラクティブ|interactive|リアクティブ|reactive)'
10
+ FLUSH_PAT = 'flush'
11
+
12
+ def process(pbm)
13
+ pbm.options.interactive = false
14
+
15
+ body = pbm.page_body
16
+ f_int = body =~ /#{INTERACTIVE_PAT}/i
17
+ f_flush = body =~ /#{FLUSH_PAT}/i
18
+ f_io = pbm.sections[Problem::SECTION_IO_FMT]
19
+ f_tbl =
20
+ pbm
21
+ .sections[Problem::SECTION_IO_SMP]
22
+ &.find_element(%w[table])
23
+ return unless [f_int, f_flush, f_io, f_tbl].count(&:itself) > 1
24
+
25
+ pbm.options.interactive = true
26
+ end
27
+ end
28
+ end
29
+ end
@@ -7,9 +7,12 @@ module AtCoderFriends
7
7
  module_function
8
8
 
9
9
  def process(pbm)
10
- PageParser.process(pbm)
11
- FormatParser.process(pbm)
12
- ConstraintsParser.process(pbm)
10
+ Sections.process(pbm)
11
+ SampleData.process(pbm)
12
+ InputFormat.process(pbm)
13
+ Constraints.process(pbm)
14
+ Interactive.process(pbm)
15
+ Binary.process(pbm)
13
16
  end
14
17
  end
15
18
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+
5
+ module AtCoderFriends
6
+ module Parser
7
+ # parses sample data and sets to problem
8
+ module SampleData
9
+ module_function
10
+
11
+ def process(pbm)
12
+ pbm.sections.each do |key, section|
13
+ ext =
14
+ if key =~ Problem::SECTION_IN_SMP_PAT
15
+ :in
16
+ elsif key =~ Problem::SECTION_OUT_SMP_PAT
17
+ :exp
18
+ end
19
+ ext && pbm.add_smp($LAST_MATCH_INFO[:no], ext, section.code_block)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtCoderFriends
4
+ module Parser
5
+ # holds section in problrem page
6
+ class SectionWrapper
7
+ attr_reader :h
8
+
9
+ def initialize(h)
10
+ @h = h
11
+ end
12
+
13
+ def siblings
14
+ @siblings ||= begin
15
+ ret = []
16
+ nx = h.next
17
+ while nx && nx.name != h.name
18
+ ret << nx
19
+ nx = nx.next
20
+ end
21
+ ret
22
+ end
23
+ end
24
+
25
+ def content
26
+ @content ||= begin
27
+ siblings.reduce('') { |m, node| m + node.content.gsub("\r\n", "\n") }
28
+ end
29
+ end
30
+
31
+ def find_element(tags)
32
+ siblings.each do |node|
33
+ tags.each do |tag|
34
+ elem = node.name == tag ? node : node.search(tag)[0]
35
+ return elem if elem
36
+ end
37
+ end
38
+ nil
39
+ end
40
+
41
+ def code_block
42
+ @code_block ||= begin
43
+ elem = find_element(%w[pre blockquote])
44
+ (elem&.content || '').lstrip.gsub("\r\n", "\n")
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -2,27 +2,39 @@
2
2
 
3
3
  module AtCoderFriends
4
4
  module Parser
5
- # parses problem page and collect problem information
6
- module PageParser
7
- module_function
8
-
9
- SECTION_TYPES = [
5
+ module SectionsConstants
6
+ SECTION_DEFS = [
10
7
  {
11
- key: 'constraints',
8
+ key: Problem::SECTION_CONSTRAINTS,
12
9
  patterns: [
13
10
  '^制約$',
11
+ '^入力制限$',
14
12
  '^Constraints$'
15
13
  ]
16
14
  },
17
15
  {
18
- key: 'input format',
16
+ key: Problem::SECTION_IN_FMT,
19
17
  patterns: [
20
- '^入出?力(形式)?$',
21
- '^Inputs?\s*(,|and)?\s*(Outputs?)?\s*(Format)?$'
18
+ '^入力(形式)?$',
19
+ '^Inputs?\s*(Format)?$'
22
20
  ]
23
21
  },
24
22
  {
25
- key: 'sample input %<no>s',
23
+ key: Problem::SECTION_OUT_FMT,
24
+ patterns: [
25
+ '^出力(形式)?$',
26
+ '^Outputs?\s*(Format)?$'
27
+ ]
28
+ },
29
+ {
30
+ key: Problem::SECTION_IO_FMT,
31
+ patterns: [
32
+ '^入出力(形式)?$',
33
+ '^Input\s*(and)?\s*Output\s*(Format)?$'
34
+ ]
35
+ },
36
+ {
37
+ key: Problem::SECTION_IN_SMP,
26
38
  patterns: [
27
39
  '^入力例\s*(?<no>\d+)?$',
28
40
  '^入力\s*(?<no>\d+)$',
@@ -32,7 +44,7 @@ module AtCoderFriends
32
44
  ]
33
45
  },
34
46
  {
35
- key: 'sample output %<no>s',
47
+ key: Problem::SECTION_OUT_SMP,
36
48
  patterns: [
37
49
  '^出力例\s*(?<no>\d+)?$',
38
50
  '^出力\s*(?<no>\d+)$',
@@ -42,33 +54,46 @@ module AtCoderFriends
42
54
  '^Output\s*(?<no>\d+)$',
43
55
  '^Output\s*for\s*(the)?\s*Sample\s*Input\s*(?<no>\d+)?$'
44
56
  ]
57
+ },
58
+ {
59
+ key: Problem::SECTION_IO_SMP,
60
+ patterns: [
61
+ '^入出力の?例\s*(\d+)?$',
62
+ '^サンプル\s*(\d+)?$',
63
+ '^Sample\s*Input\s*(and)?\s*Output\s*(\d+)?$',
64
+ '^Samples?\s*(\d+)?$'
65
+ ]
45
66
  }
46
67
  ].freeze
68
+ end
69
+
70
+ # parses problem page and builds section table
71
+ module Sections
72
+ include SectionsConstants
73
+
74
+ module_function
47
75
 
48
76
  def process(pbm)
49
77
  sections = collect_sections(pbm.page)
50
- apply_sections(pbm, sections)
78
+ pbm.sections = sections
51
79
  end
52
80
 
53
81
  def collect_sections(page)
54
- sections = {}
55
- %w[h2 h3].each do |tag|
82
+ %w[h2 h3].each_with_object({}) do |tag, sections|
56
83
  page
57
84
  .search(tag)
58
85
  .each do |h|
59
86
  key = find_key(h)
60
- key && sections[key] ||= parse_section(h)
87
+ key && sections[key] ||= SectionWrapper.new(h)
61
88
  end
62
89
  end
63
- sections
64
90
  end
65
91
 
66
92
  def find_key(h)
67
93
  title = normalize(h.content)
68
- SECTION_TYPES.each do |grp|
94
+ SECTION_DEFS.each do |grp|
69
95
  grp[:patterns].each do |pat|
70
- m = title.match(/#{pat}/i)
71
- next unless m
96
+ next unless (m = title.match(/#{pat}/i))
72
97
 
73
98
  no = m.names.include?('no') && m['no'] || '1'
74
99
  return format(grp[:key], no: no)
@@ -77,37 +102,6 @@ module AtCoderFriends
77
102
  nil
78
103
  end
79
104
 
80
- def parse_section(h)
81
- text = ''
82
- pre = nil
83
- nx = h.next
84
- while nx && nx.name != h.name
85
- text += nx.content.gsub("\r\n", "\n")
86
- %w[pre blockquote].each do |tag|
87
- pre ||= (nx.name == tag ? nx : nx.search(tag)[0])
88
- end
89
- nx = nx.next
90
- end
91
- code = (pre&.text || '').lstrip.gsub("\r\n", "\n")
92
- [text, code]
93
- end
94
-
95
- def apply_sections(pbm, sections)
96
- sections.each do |key, (text, code)|
97
- case key
98
- when 'constraints'
99
- pbm.desc += text
100
- when 'input format'
101
- pbm.desc += text
102
- pbm.fmt = code
103
- when /^sample input (?<no>\d+)$/
104
- pbm.add_smp($LAST_MATCH_INFO[:no], :in, code)
105
- when /^sample output (?<no>\d+)$/
106
- pbm.add_smp($LAST_MATCH_INFO[:no], :exp, code)
107
- end
108
- end
109
- end
110
-
111
105
  def normalize(s)
112
106
  s
113
107
  .tr(' 0-9A-Za-z', ' 0-9A-Za-z')