at_coder_friends 0.6.8 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +1 -0
  3. data/.github/workflows/codeql-analysis.yml +70 -0
  4. data/.github/workflows/ruby.yml +38 -0
  5. data/.gitignore +3 -0
  6. data/.rubocop.yml +8 -1
  7. data/CHANGELOG.md +23 -4
  8. data/Gemfile.lock +43 -43
  9. data/README.md +15 -92
  10. data/at_coder_friends.gemspec +4 -3
  11. data/lib/at_coder_friends/cli.rb +13 -28
  12. data/lib/at_coder_friends/config_loader.rb +2 -4
  13. data/lib/at_coder_friends/generator/any_builtin.rb +22 -0
  14. data/lib/at_coder_friends/generator/base.rb +55 -6
  15. data/lib/at_coder_friends/generator/c_builtin.rb +22 -0
  16. data/lib/at_coder_friends/generator/cxx_builtin.rb +9 -241
  17. data/lib/at_coder_friends/generator/fragment.rb +106 -0
  18. data/lib/at_coder_friends/generator/main.rb +20 -14
  19. data/lib/at_coder_friends/generator/ruby_builtin.rb +13 -166
  20. data/lib/at_coder_friends/parser/binary.rb +12 -5
  21. data/lib/at_coder_friends/parser/constraints.rb +8 -8
  22. data/lib/at_coder_friends/parser/input_format.rb +19 -15
  23. data/lib/at_coder_friends/parser/modulo.rb +5 -7
  24. data/lib/at_coder_friends/parser/section_wrapper.rb +3 -5
  25. data/lib/at_coder_friends/parser/sections.rb +2 -2
  26. data/lib/at_coder_friends/path_util.rb +1 -1
  27. data/lib/at_coder_friends/problem.rb +5 -5
  28. data/lib/at_coder_friends/scraping/agent.rb +1 -1
  29. data/lib/at_coder_friends/scraping/authentication.rb +3 -3
  30. data/lib/at_coder_friends/scraping/session.rb +1 -1
  31. data/lib/at_coder_friends/scraping/tasks.rb +6 -8
  32. data/lib/at_coder_friends/test_runner/base.rb +1 -2
  33. data/lib/at_coder_friends/test_runner/judge.rb +3 -3
  34. data/lib/at_coder_friends/version.rb +1 -1
  35. data/lib/at_coder_friends.rb +3 -0
  36. data/templates/any_builtin.md.erb +30 -0
  37. data/templates/any_builtin_fragments.yml +5 -0
  38. data/templates/c_builtin.c.erb +36 -0
  39. data/templates/c_builtin_fragments.yml +127 -0
  40. data/templates/csharp_sample.cs.erb +29 -0
  41. data/templates/csharp_sample_fragments.yml +159 -0
  42. data/templates/java_sample.java.erb +25 -0
  43. data/templates/java_sample_fragments.yml +98 -0
  44. data/templates/ruby_builtin_fragments.yml +93 -0
  45. metadata +26 -6
  46. data/.travis.yml +0 -16
  47. data/docs/CONFIGURATION.md +0 -421
@@ -1,13 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'yaml'
3
4
  require 'erb'
4
5
 
5
6
  module AtCoderFriends
6
7
  module Generator
7
- Attributes = Struct.new(:file_ext, :default_template)
8
+ Attributes = Struct.new(:file_ext, :template, :fragments)
8
9
 
9
10
  # common behavior of generators
10
11
  class Base
12
+ ACF_HOME = File.realpath(File.join(__dir__, '..', '..', '..'))
13
+ TMPL_DIR = File.join(ACF_HOME, 'templates')
14
+
11
15
  attr_reader :cfg, :pbm
12
16
 
13
17
  def initialize(cfg = nil)
@@ -15,21 +19,36 @@ module AtCoderFriends
15
19
  end
16
20
 
17
21
  def process(pbm)
18
- pbm.add_src(attrs.file_ext, generate(pbm))
22
+ pbm.add_src(config_file_ext, generate(pbm))
19
23
  end
20
24
 
21
25
  def generate(pbm)
22
26
  @pbm = pbm
23
- src = File.read(select_template)
24
- src = ERB.new(src, safe_level = nil, trim_mode = '-').result(binding)
27
+ template = File.read(config_template)
28
+ src = ERB.new(template, trim_mode: '-').result(binding)
25
29
  src = render(src) if respond_to?(:render)
26
30
  src
27
31
  end
28
32
 
29
- def select_template
30
- cfg['default_template'] || attrs.default_template
33
+ def fragments
34
+ @fragments ||= YAML.load_file(config_fragments)
35
+ end
36
+
37
+ def config_file_ext
38
+ cfg['file_ext']&.to_sym || attrs.file_ext
31
39
  end
32
40
 
41
+ def config_template
42
+ template = cfg['template'] || cfg['default_template'] || attrs.template
43
+ template.sub(/\A@/, TMPL_DIR)
44
+ end
45
+
46
+ def config_fragments
47
+ fragments = cfg['fragments'] || attrs.fragments
48
+ fragments.sub(/\A@/, TMPL_DIR)
49
+ end
50
+
51
+ # deprecated, use ERB syntax
33
52
  def embed_lines(src, pat, lines)
34
53
  re = Regexp.escape(pat)
35
54
  src.gsub(
@@ -38,5 +57,35 @@ module AtCoderFriends
38
57
  )
39
58
  end
40
59
  end
60
+
61
+ module ConstFragmentMixin
62
+ def gen_consts
63
+ pbm.constants.map { |c| gen_const(c) }
64
+ end
65
+
66
+ def gen_const(c)
67
+ ConstFragment.new(c, fragments['constant']).generate
68
+ end
69
+ end
70
+
71
+ module DeclFragmentMixin
72
+ def gen_decls
73
+ pbm.formats.map { |inpdef| gen_decl(inpdef).split("\n") }.flatten
74
+ end
75
+
76
+ def gen_decl(inpdef)
77
+ InputFormatFragment.new(inpdef, fragments['declaration']).generate
78
+ end
79
+ end
80
+
81
+ module InputFragmentMixin
82
+ def gen_inputs
83
+ pbm.formats.map { |inpdef| gen_input(inpdef).split("\n") }.flatten
84
+ end
85
+
86
+ def gen_input(inpdef)
87
+ InputFormatFragment.new(inpdef, fragments['input']).generate
88
+ end
89
+ end
41
90
  end
42
91
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtCoderFriends
4
+ module Generator
5
+ # generates C source from problem description
6
+ class CBuiltin < Base
7
+ include ConstFragmentMixin
8
+ include DeclFragmentMixin
9
+ include InputFragmentMixin
10
+
11
+ ACF_HOME = File.realpath(File.join(__dir__, '..', '..', '..'))
12
+ TMPL_DIR = File.join(ACF_HOME, 'templates')
13
+ TEMPLATE = File.join(TMPL_DIR, 'c_builtin.c.erb')
14
+ FRAGMENTS = File.join(TMPL_DIR, 'c_builtin_fragments.yml')
15
+ ATTRS = Attributes.new(:c, TEMPLATE, FRAGMENTS)
16
+
17
+ def attrs
18
+ ATTRS
19
+ end
20
+ end
21
+ end
22
+ end
@@ -2,256 +2,24 @@
2
2
 
3
3
  module AtCoderFriends
4
4
  module Generator
5
- # generates C++ constants
6
- module CxxBuiltinConstGen
7
- def gen_const(c)
8
- v = cnv_const_value(c.value)
9
- if c.type == :max
10
- "const int #{c.name.upcase}_MAX = #{v};"
11
- else
12
- "const int MOD = #{v};"
13
- end
14
- end
15
-
16
- def cnv_const_value(v)
17
- v
18
- .sub(/\b10\^/, '1e')
19
- .sub(/\b2\^/, '1<<')
20
- .gsub(',', "'")
21
- end
22
- end
23
-
24
- # generates C++ variable declarations
25
- module CxxBuiltinDeclGen
26
- TYPE_TBL = {
27
- number: 'int',
28
- decimal: 'double',
29
- string: 'char',
30
- char: 'char'
31
- }.tap { |h| h.default = 'int' }
32
- def gen_decl(inpdef)
33
- if inpdef.components
34
- inpdef.components.map { |cmp| gen_decl(cmp) }
35
- else
36
- case inpdef.container
37
- when :single
38
- gen_single_decl(inpdef)
39
- when :harray
40
- gen_harray_decl(inpdef)
41
- when :varray
42
- gen_varray_decl(inpdef)
43
- when :matrix, :vmatrix, :hmatrix
44
- gen_matrix_decl(inpdef)
45
- end
46
- end
47
- end
48
-
49
- def gen_single_decl(inpdef)
50
- names, cols = inpdef.vars.transpose
51
- if cols.uniq.size == 1 && cols[0] != :string
52
- type = TYPE_TBL[cols[0]]
53
- dcl = names.join(', ')
54
- "#{type} #{dcl};"
55
- else
56
- inpdef.vars.map do |v, item|
57
- type = TYPE_TBL[item]
58
- dcl = v
59
- dcl += "[#{v.upcase}_MAX + 1]" if item == :string
60
- "#{type} #{dcl};"
61
- end
62
- end
63
- end
64
-
65
- def gen_harray_decl(inpdef)
66
- type = TYPE_TBL[inpdef.item]
67
- v = inpdef.names[0]
68
- sz = gen_arr_size(inpdef.size)[0]
69
- case inpdef.item
70
- when :number, :decimal
71
- "#{type} #{v}[#{sz}];"
72
- when :string
73
- "#{type} #{v}[#{sz}][#{v.upcase}_MAX + 1];"
74
- when :char
75
- "#{type} #{v}[#{sz} + 1];"
76
- end
77
- end
78
-
79
- def gen_varray_decl(inpdef)
80
- sz = gen_arr_size(inpdef.size)[0]
81
- inpdef.vars.map do |v, item|
82
- type = TYPE_TBL[item]
83
- dcl = "#{v}[#{sz}]"
84
- dcl += "[#{v.upcase}_MAX + 1]" if item == :string
85
- "#{type} #{dcl};"
86
- end
87
- end
88
-
89
- def gen_matrix_decl(inpdef)
90
- sz1, sz2 = gen_arr_size(inpdef.size)
91
- inpdef.vars.map do |v, item|
92
- type = TYPE_TBL[item]
93
- dcl = "#{v}[#{sz1}]"
94
- dcl += item == :char ? "[#{sz2} + 1]" : "[#{sz2}]"
95
- dcl += "[#{v.upcase}_MAX + 1]" if item == :string
96
- "#{type} #{dcl};"
97
- end
98
- end
99
-
100
- def gen_arr_size(szs)
101
- szs.map { |sz| sz.gsub(/([a-z][a-z0-9_]*)/i, '\1_MAX').upcase }
102
- end
103
- end
104
-
105
- # generates C++ input source
106
- module CxxBuiltinInputGen
107
- SCANF_FMTS = [
108
- 'scanf("%<fmt>s", %<addr>s);',
109
- 'REP(i, %<sz1>s) scanf("%<fmt>s", %<addr>s);',
110
- 'REP(i, %<sz1>s) REP(j, %<sz2>s) scanf("%<fmt>s", %<addr>s);'
111
- ].freeze
112
- SCANF_FMTS_CMB = {
113
- varray_matrix:
114
- [
115
- <<~TEXT,
116
- REP(i, %<sz1>s) {
117
- scanf("%<fmt1>s", %<addr1>s);
118
- scanf("%<fmt2>s", %<addr2>s);
119
- }
120
- TEXT
121
- <<~TEXT
122
- REP(i, %<sz1>s) {
123
- scanf("%<fmt1>s", %<addr1>s);
124
- REP(j, %<sz2>s[i]) scanf("%<fmt2>s", %<addr2>s);
125
- }
126
- TEXT
127
- ],
128
- matrix_varray:
129
- [
130
- <<~TEXT,
131
- REP(i, %<sz1>s) {
132
- scanf("%<fmt1>s", %<addr1>s);
133
- scanf("%<fmt2>s", %<addr2>s);
134
- }
135
- TEXT
136
- <<~TEXT
137
- REP(i, %<sz1>s) {
138
- REP(j, %<sz2>s) scanf("%<fmt1>s", %<addr1>s);
139
- scanf("%<fmt2>s", %<addr2>s);
140
- }
141
- TEXT
142
- ]
143
- }.tap { |h| h.default = h[:varray_matrix] }
144
- FMT_FMTS = {
145
- number: '%d',
146
- decimal: '%lf',
147
- string: '%s',
148
- char: '%s'
149
- }.tap { |h| h.default = h[:number] }
150
- SINGLE_ADDR_FMTS = {
151
- number: '&%<v>s',
152
- decimal: '&%<v>s',
153
- string: '%<v>s'
154
- }.tap { |h| h.default = h[:number] }
155
- ARRAY_ADDR_FMTS = {
156
- number: '%<v>s + i',
157
- decimal: '%<v>s + i',
158
- string: '%<v>s[i]',
159
- char: '%<v>s'
160
- }.tap { |h| h.default = h[:number] }
161
- MATRIX_ADDR_FMTS = {
162
- number: '&%<v>s[i][j]',
163
- decimal: '&%<v>s[i][j]',
164
- string: '%<v>s[i][j]',
165
- char: '%<v>s[i]'
166
- }.tap { |h| h.default = h[:number] }
167
- ADDR_FMTS = {
168
- single: SINGLE_ADDR_FMTS,
169
- harray: ARRAY_ADDR_FMTS,
170
- varray: ARRAY_ADDR_FMTS,
171
- matrix: MATRIX_ADDR_FMTS,
172
- vmatrix: MATRIX_ADDR_FMTS,
173
- hmatrix: MATRIX_ADDR_FMTS
174
- }.tap { |h| h.default = h[:single] }
175
-
176
- def gen_input(inpdef)
177
- if inpdef.components
178
- gen_cmb_input(inpdef)
179
- else
180
- gen_plain_input(inpdef)
181
- end
182
- end
183
-
184
- def gen_plain_input(inpdef)
185
- scanf = SCANF_FMTS[inpdef.size.size - (inpdef.item == :char ? 1 : 0)]
186
- sz1, sz2 = inpdef.size
187
- fmt, addr = scanf_params(inpdef)
188
- format(scanf, sz1: sz1, sz2: sz2, fmt: fmt, addr: addr)
189
- end
190
-
191
- def gen_cmb_input(inpdef)
192
- scanf = SCANF_FMTS_CMB.dig(
193
- inpdef.container, inpdef.item == :char ? 0 : 1
194
- )
195
- sz1 = inpdef.size[0]
196
- sz2 = inpdef.size[1].split('_')[0]
197
- fmt1, addr1, fmt2, addr2 =
198
- inpdef.components.map { |cmp| scanf_params(cmp) }.flatten
199
- format(
200
- scanf,
201
- sz1: sz1, sz2: sz2,
202
- fmt1: fmt1, addr1: addr1,
203
- fmt2: fmt2, addr2: addr2
204
- ).split("\n")
205
- end
206
-
207
- def scanf_params(inpdef)
208
- [scanf_fmt(inpdef), scanf_addr(inpdef)]
209
- end
210
-
211
- def scanf_fmt(inpdef)
212
- inpdef.vars.map { |(_v, item)| FMT_FMTS[item] }.join
213
- end
214
-
215
- def scanf_addr(inpdef)
216
- inpdef.vars.map do |(v, item)|
217
- addr_fmt = ADDR_FMTS.dig(inpdef.container, item)
218
- format(addr_fmt, v: v)
219
- end.join(', ')
220
- end
221
- end
222
-
223
5
  # generates C++ source from problem description
224
- class CxxBuiltin < Base
225
- include CxxBuiltinConstGen
226
- include CxxBuiltinDeclGen
227
- include CxxBuiltinInputGen
228
-
229
- ACF_HOME = File.realpath(File.join(__dir__, '..', '..', '..'))
230
- TMPL_DIR = File.join(ACF_HOME, 'templates')
231
- DEFAULT_TMPL = File.join(TMPL_DIR, 'cxx_builtin.cxx.erb')
232
- ATTRS = Attributes.new(:cxx, DEFAULT_TMPL)
6
+ class CxxBuiltin < CBuiltin
7
+ TEMPLATE = File.join(TMPL_DIR, 'cxx_builtin.cxx.erb')
8
+ ATTRS = Attributes.new(:cxx, TEMPLATE, FRAGMENTS)
233
9
 
234
10
  def attrs
235
11
  ATTRS
236
12
  end
237
13
 
14
+ def gen_const(c)
15
+ ConstFragment.new(c, fragments['cxx_constant']).generate
16
+ end
17
+
18
+ # deprecated, use ERB syntax
238
19
  def render(src)
239
20
  src = embed_lines(src, '/*** CONSTS ***/', gen_consts)
240
21
  src = embed_lines(src, '/*** DCLS ***/', gen_decls)
241
- src = embed_lines(src, '/*** INPUTS ***/', gen_inputs)
242
- src
243
- end
244
-
245
- def gen_consts(constants = pbm.constants)
246
- constants.map { |c| gen_const(c) }
247
- end
248
-
249
- def gen_decls(inpdefs = pbm.formats)
250
- inpdefs.map { |inpdef| gen_decl(inpdef) }.flatten
251
- end
252
-
253
- def gen_inputs(inpdefs = pbm.formats)
254
- inpdefs.map { |inpdef| gen_input(inpdef) }.flatten
22
+ embed_lines(src, '/*** INPUTS ***/', gen_inputs)
255
23
  end
256
24
  end
257
25
  end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erb'
4
+
5
+ module AtCoderFriends
6
+ module Generator
7
+ # base class for code fragment generator
8
+ class FragmentBase
9
+ attr_reader :templates
10
+
11
+ def initialize(obj, templates)
12
+ @obj = obj
13
+ @templates = templates
14
+ end
15
+
16
+ def render(*keys)
17
+ keys = keys.map(&:to_s)
18
+ template = templates.dig(*keys) || (raise AppError, "fragment key #{keys} not found")
19
+
20
+ return ERB.new(template, trim_mode: '-').result(binding) if template.is_a?(String)
21
+
22
+ if template.is_a?(Hash)
23
+ sub_key_props = template['__key'] || (raise AppError, "'__key' not found in fragment hash #{keys}")
24
+ sub_keys = sub_key_props.map { |k| send(k) }
25
+ return render(*keys, *sub_keys)
26
+ end
27
+
28
+ raise AppError, "can't render fragment #{keys}"
29
+ end
30
+
31
+ # delegate method calls to obj
32
+ def method_missing(name, *args, &block)
33
+ if @obj.respond_to?(name)
34
+ @obj.send(name, *args, &block)
35
+ elsif templates.key?(name.to_s)
36
+ render(name)
37
+ else
38
+ super
39
+ end
40
+ end
41
+
42
+ def respond_to_missing?(name, include_private = false)
43
+ @obj.respond_to?(name, include_private) ||
44
+ templates.key?(name.to_s) ||
45
+ super
46
+ end
47
+
48
+ def to_s
49
+ @obj.to_s
50
+ end
51
+ end
52
+
53
+ # base class for constant declaration generator
54
+ class ConstFragment < FragmentBase
55
+ def generate
56
+ render(type)
57
+ end
58
+ end
59
+
60
+ # base class for variable declaration generator
61
+ class InputFormatFragment < FragmentBase
62
+ def generate
63
+ render(:main)
64
+ end
65
+
66
+ def vs
67
+ names
68
+ end
69
+
70
+ def v
71
+ vs[0]
72
+ end
73
+
74
+ def sz1
75
+ size[0]
76
+ end
77
+ alias sz sz1
78
+
79
+ def sz2
80
+ size[1]
81
+ end
82
+
83
+ def delims
84
+ delim.chars
85
+ end
86
+
87
+ def vars
88
+ @vars ||= super.map do |v, item|
89
+ var = Problem::InputFormat.new(
90
+ container: container,
91
+ names: [v],
92
+ item: item,
93
+ size: size
94
+ )
95
+ self.class.new(var, templates)
96
+ end
97
+ end
98
+
99
+ def components
100
+ @components ||= super&.map do |cmp|
101
+ self.class.new(cmp, templates)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -12,34 +12,40 @@ module AtCoderFriends
12
12
  end
13
13
 
14
14
  def process(pbm)
15
- generators = ctx.config.dig('generators') || []
15
+ generators = ctx.config['generators'] || []
16
16
  generators.each do |gen_name|
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
17
+ gen_obj = load_obj(gen_name)
18
+ gen_obj.process(pbm)
19
+ rescue StandardError => e
20
+ puts "an error occurred in generator:#{gen_name}."
21
+ puts e
22
+ puts e.backtrace
25
23
  end
26
24
  end
27
25
 
28
26
  def load_obj(gen_name)
29
27
  @cache[gen_name] ||= begin
30
- gen_class = load_class(gen_name)
28
+ cls_name = gen_name.split('_')[0]
29
+ gen_class = load_class(cls_name)
31
30
  gen_cnf = config_for(gen_name)
32
31
  gen_class.new(gen_cnf)
33
32
  end
34
33
  end
35
34
 
36
35
  def load_class(gen_name)
37
- unless AtCoderFriends::Generator.const_defined?(gen_name)
38
- require "at_coder_friends/generator/#{to_snake(gen_name)}"
39
- end
36
+ snake_gen_name = to_snake(gen_name)
37
+ require "at_coder_friends/generator/#{snake_gen_name}" unless AtCoderFriends::Generator.const_defined?(gen_name)
40
38
  AtCoderFriends::Generator.const_get(gen_name)
41
39
  rescue LoadError
42
- raise AppError, "plugin load error : generator #{gen_name} not found."
40
+ raise AppError, <<~MSG
41
+ Error: Failed to load plugin.
42
+ The '#{gen_name}' plugin could not be found. To use this plugin, please install the required gem by following these steps:
43
+
44
+ 1. Open a terminal or command prompt.
45
+ 2. Run the following command:
46
+ gem install at_coder_friends-generator-#{snake_gen_name}
47
+ 3. Once the above command completes, please run the program again.
48
+ MSG
43
49
  end
44
50
 
45
51
  def config_for(gen_name)