at_coder_friends 0.5.2 → 0.6.0

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 (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')