Skip to content

Commit 85c6bda

Browse files
authored
Universal Code Generator to support new languages easily (kyuridenamida#228)
* universal_code_generatorを追加 * scanfのカンマの後に空白を入れるように変更 * TOML表記でハマった点をREADMEに追加 * universal_code_generatorをパスで渡すように変更, Enumを使う方式に変更してコードをシンプルに * ユニバーサルジェネレータのキーワードを可能な限り一本化, bits/stdc++.hを使わないように変更 * if分岐を元に戻した * 再度if分岐をenum使うように変更
1 parent 8af0cbd commit 85c6bda

File tree

86 files changed

+1992
-1509
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+1992
-1509
lines changed

README.md

Lines changed: 199 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,205 @@ out_example_format="out_{}.txt"
250250
### カスタムコードジェネレーター
251251
[標準のC++コードジェネレーター](https://github.com/kyuridenamida/atcoder-tools/blob/master/atcodertools/codegen/code_generators/cpp.py)に倣って、
252252
`(CogeGenArgs) -> str(ソースコード)`が型であるような`main`関数を定義した.pyファイルを`code_generator_file`で指定すると、コード生成時にカスタムコードジェネレーターを利用できます。
253-
253+
254+
255+
### ユニバーサルコードジェネレーター
256+
ユニバーサルコードジェネレーターはループ・配列アクセス方法等のいくつかの言語仕様を記述するだけでカスタムコードジェネレーターよりも簡単にコード生成することを意図して作成したジェネレーターです。設定ファイルの書き方は以下です。
257+
258+
- *base_indent* 入力部分のインデント数
259+
- *insert_space_around_operators* 入力部分の変数や演算子の間にスペースを入れるかどうかをtrue/falseで指定
260+
- *newline_after_input* 入力部分で入力ごとに空行を入れるかどうかをtrue/falseで指定
261+
- *global_prefix* グローバル変数の宣言時に入れる接頭辞(Javaなどでstaticを指定したりできます)
262+
263+
以下のようにテーブルを定義します。各項目はダブルコーテーションあるいはシングルコーテーションを用いた文字列で指定します。Pythonのformatメソッドに渡されるため、波括弧等の文字を直に書きたい場合はエスケープする必要があります。
264+
テーブルのキーは整数(int), 浮動小数(float), 文字列(str), およびこれら3つを使った1次元配列(seq), 2次元配列(2d_seq)となっています。
265+
266+
- *[index]* ループインデックスの名称を指定します。1重目を`i`, 2重目を`j`で指定してください。省略可能で省略した場合はi, jが指定されます。Perl, PHPなどの言語で$i, $jなどとi, j以外の名前を指定しなければならないとき用のつもりです。
267+
- *[loop]* ループに関することを記述します
268+
- **header** ループの最初に記述する内容。ループを回すための変数は`{loop_var}`, 回す回数は`{length}`を用いてください。
269+
- **footer** ループの最後に記述する内容。C++, Javaでは閉じカッコになります。波括弧の場合は`}}`とエスケープする必要があることに注意してください。
270+
- *[type]* タイプ(int, float, str)のタイプについて記述します。例を参照してください。
271+
- *[default]* デフォルトの値について記述します。例を参照してください。注意: TOMLの表記に癖があるようで、ダブルコーテーション2つ(空の文字列)を表記する際にはstr='""'とするとよいようです。"\"\""だとエラーになるようです。
272+
- *[input_func]* int, float, strについて入力時に呼び出す関数を記述します。
273+
- *[arg]* solve関数の引数の記述方法について指定します。`int`, `float`, `str`, `seq`, `2d_seq`について記述してください。`{name}`が変数名, `{type}``seq`, `2d_seq`についてベースとなる型です。
274+
- *[actual_arg]* `seq`, `2d_seq`についてsolve関数を呼び出す際の引数の渡し方について記述します。C++などでmoveをつかってメモリを節約したいときなどに指定できます。省略可能で、省略した場合はそのまま渡されます。
275+
276+
- *[access]* 配列のアクセス方法について記述します。`seq`, `2d_seq`について指定してください。`{name}`で変数名, `{index_i}`, `{index_j}`でインデックス名を指定します。
277+
278+
279+
以下は宣言・確保・入力を行うためのコードを記述します。いくつかを同時に行う方法も指定できます。いずれも一行または複数行に渡る指定が可能でセミコロン等の終端子も(必要な言語では)記述してください。
280+
キーワードとして`{name}`, `{type}`はそれぞれ対象となる変数名、タイプ名で、上記で指定した`{default}`が使えます。また、指定していれば`{input_func}`も使えます。`seq`, `2d_seq`の場合は`{type}`はベースとなる型名になります(`vector<int>`における`int`)のでご注意ください。また、`seq`の長さは`{length}`, `2d_seq`の長さは`{length_i}`, `{length_j}`となっています。
281+
282+
- *[declare]* int, float, str, seq, 2d_seqの宣言方法について記述します。
283+
- *[allocate]* `seq`, `2d_seq`の確保の方法を記述します。
284+
- *[declare_and_allocate]* `seq`, `2d_seq`について宣言と確保を同時に行う方法について記述します。
285+
- *[input]* int, float, strの入力方法について記述します。
286+
287+
以下は入力コードの冗長性を下げる目的で指定するテーブルで省略可能なものです。指定方法についてはPythonの設定を参照してください。
288+
289+
- *[allocate_and_input]* `seq`, `2d_seq`について確保と入力をまとめて行うことができる場合に記述します。省略した場合、上記で指定した確保と入力の方式を複合したものが挿入されます
290+
- *[declare_and_allocate_and_input]* `seq`, `2d_seq`について宣言・確保・入力をまとめて行うことができる場合に記述します。省略した場合、上記で指定した宣言と確保と入力の方式を複合したものが挿入されます
291+
292+
例えばC++での設定方法は以下です。
293+
```toml
294+
base_indent = 1
295+
insert_space_around_operators = false
296+
297+
# global変数宣言時の接頭辞
298+
global_prefix = ""
299+
300+
# ループ
301+
[loop]
302+
header = "for(int {loop_var} = 0 ; {loop_var} < {length} ; {loop_var}++){{"
303+
footer = "}}"
304+
305+
# タイプ
306+
[type]
307+
int = "long long"
308+
float = "long double"
309+
str = "std::string"
310+
311+
# デフォルト値
312+
[default]
313+
int = "0"
314+
float = "0.0"
315+
str = '""'
316+
317+
# 引数
318+
[arg]
319+
int = "long long {name}"
320+
float = "double {name}"
321+
str = "std::string {name}"
322+
seq = "std::vector<{type}> {name}"
323+
2d_seq = "std::vector<std::vector<{type}>> {name}"
324+
325+
# 引数への渡し方
326+
[actual_arg]
327+
seq = "std::move({name})"
328+
2d_seq = "std::move({name})"
329+
330+
# 配列アクセス
331+
[access]
332+
seq = "{name}[{index}]"
333+
2d_seq = "{name}[{index_i}][{index_j}]"
334+
335+
# 宣言
336+
[declare]
337+
int = "long long {name};"
338+
float = "long double {name};"
339+
str = "std::string {name};"
340+
seq = "std::std::vector<{type}> {name};"
341+
2d_seq = "std::vector<std::vector<{type}>> {name};"
342+
343+
# 確保
344+
[allocate]
345+
seq = "{name}.assign({length}, {default});"
346+
2d_seq = "{name}.assign({length_i}, std::vector<{type}>({length_j}));"
347+
348+
# 宣言と確保
349+
[declare_and_allocate]
350+
seq = "std::vector<{type}> {name}({length});"
351+
2d_seq = "std::vector<std::vector<{type}>> {name}({length_i}, std::vector<{type}>({length_j}));"
352+
353+
# 入力
354+
[input]
355+
#int = "std::cin >> {name};"
356+
int = "scanf(\"%lld\", &{name});"
357+
#float = "std::cin >> {name};"
358+
float = "scanf(\"%Lf\", &{name});"
359+
str = "std::cin >> {name};"
360+
```
361+
362+
例えばPythonでの設定方法は以下です。
363+
```toml
364+
base_indent = 1
365+
insert_space_around_operators = true
366+
367+
# global変数宣言時の接頭辞
368+
global_prefix = ""
369+
370+
# インデックス
371+
[index]
372+
i = "i"
373+
j = "j"
374+
375+
# ループ
376+
[loop]
377+
header = "for {loop_var} in range({length}):"
378+
footer = ""
379+
380+
# タイプ
381+
[type]
382+
int = "int"
383+
float = "float"
384+
str = "str"
385+
386+
# デフォルト値
387+
[default]
388+
int = "int()"
389+
float = "float()"
390+
str = "str()"
391+
392+
# 宣言
393+
[declare]
394+
int = ""
395+
float = ""
396+
str = ""
397+
seq = ""
398+
2d_seq = ""
399+
400+
# 確保
401+
[allocate]
402+
seq = "{name} = [{default}] * ({length})"
403+
2d_seq = "{name} = [[{default}] * ({length_j}) for _ in {length_i}]"
404+
405+
# 宣言と確保
406+
[declare_and_allocate]
407+
seq = "{name} = [{default}] * ({length}) # type: \"List[{type}]\""
408+
self.declare_and_allocate_2d_seq = "{name} = [[{default}] * ({length_j}) for _ in {length_i}] # type: \"List[List[{type}]]\""
409+
410+
# 入力関数
411+
[input_func]
412+
int = "int(next(tokens))"
413+
float = "float(next(tokens))"
414+
str = "next(tokens)"
415+
416+
# 入力
417+
[input]
418+
int = "{name} = {input_func}"
419+
float = "{name} = {input_func}"
420+
str = "{name} = {input_func}"
421+
422+
# 宣言と入力
423+
[declare_and_input]
424+
int = "{name} = {input_func} # type: int"
425+
float = "{name} = {input_func} # type: float"
426+
str = "{name} = {input_func} # type: str"
427+
428+
# 確保と入力
429+
[allocate_and_input]
430+
seq = "{name} = [{input_func} for _ in range({length})]"
431+
2d_seq = "{name} = [[{input_func} for _ in range({length_j})] for _ in range({length_i})]"
432+
433+
# 宣言と確保と入力
434+
[declare_and_allocate_and_input]
435+
seq = "{name} = [{input_func} for _ in range({length})] # type: \"List[{type}]\""
436+
2d_seq = "{name} = [[{input_func} for _ in range({length_j})] for _ in range({length_i})] # type: \"List[List[{type}]]\""
437+
438+
# 引数
439+
[arg]
440+
int = "{name}: int"
441+
float = "{name}: float"
442+
str = "{name}: str"
443+
seq = "{name}: \"List[{type}]\""
444+
2d_seq = "{name}: \"List[List[{type}]]\""
445+
446+
# 配列アクセス
447+
[access]
448+
seq = "{name}[{index}]"
449+
2d_seq = "{name}[{index_i}][{index_j}]"
450+
```
451+
254452
## テンプレートの例
255453
`atcoder-tools gen`コマンドに対し`--template`でテンプレートソースコードを指定できます。
256454
テンプレートエンジンの仕様については[jinja2](http://jinja.pocoo.org/docs/2.10/) の公式ドキュメントを参照してください。

atcodertools/codegen/code_generators/cpp.py

Lines changed: 3 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -1,177 +1,12 @@
1-
from typing import Dict, Any, Optional
2-
3-
from atcodertools.codegen.code_style_config import CodeStyleConfig
41
from atcodertools.codegen.models.code_gen_args import CodeGenArgs
52
from atcodertools.codegen.template_engine import render
6-
from atcodertools.fmtprediction.models.format import Pattern, SingularPattern, ParallelPattern, TwoDimensionalPattern, \
7-
Format
8-
from atcodertools.fmtprediction.models.type import Type
9-
from atcodertools.fmtprediction.models.variable import Variable
10-
11-
12-
def _loop_header(var: Variable, for_second_index: bool):
13-
if for_second_index:
14-
index = var.second_index
15-
loop_var = "j"
16-
else:
17-
index = var.first_index
18-
loop_var = "i"
19-
20-
return "for(int {loop_var} = 0 ; {loop_var} < {length} ; {loop_var}++){{".format(
21-
loop_var=loop_var,
22-
length=index.get_length()
23-
)
24-
25-
26-
class CppCodeGenerator:
27-
28-
def __init__(self,
29-
format_: Optional[Format[Variable]],
30-
config: CodeStyleConfig):
31-
self._format = format_
32-
self._config = config
33-
34-
def generate_parameters(self) -> Dict[str, Any]:
35-
if self._format is None:
36-
return dict(prediction_success=False)
37-
38-
return dict(formal_arguments=self._formal_arguments(),
39-
actual_arguments=self._actual_arguments(),
40-
input_part=self._input_part(),
41-
prediction_success=True)
42-
43-
def _input_part(self):
44-
lines = []
45-
for pattern in self._format.sequence:
46-
lines += self._render_pattern(pattern)
47-
return "\n{indent}".format(indent=self._indent(1)).join(lines)
48-
49-
def _convert_type(self, type_: Type) -> str:
50-
if type_ == Type.float:
51-
return "long double"
52-
elif type_ == Type.int:
53-
return "long long"
54-
elif type_ == Type.str:
55-
return "std::string"
56-
else:
57-
raise NotImplementedError
58-
59-
def _get_declaration_type(self, var: Variable):
60-
ctype = self._convert_type(var.type)
61-
for _ in range(var.dim_num()):
62-
ctype = 'std::vector<{}>'.format(ctype)
63-
return ctype
64-
65-
def _actual_arguments(self) -> str:
66-
"""
67-
:return the string form of actual arguments e.g. "N, K, a"
68-
"""
69-
return ", ".join([
70-
v.name if v.dim_num() == 0 else 'std::move({})'.format(v.name)
71-
for v in self._format.all_vars()])
72-
73-
def _formal_arguments(self):
74-
"""
75-
:return the string form of formal arguments e.g. "int N, int K, std::vector<int> a"
76-
"""
77-
return ", ".join([
78-
"{decl_type} {name}".format(
79-
decl_type=self._get_declaration_type(v),
80-
name=v.name)
81-
for v in self._format.all_vars()
82-
])
83-
84-
def _generate_declaration(self, var: Variable):
85-
"""
86-
:return: Create declaration part E.g. array[1..n] -> std::vector<int> array = std::vector<int>(n-1+1);
87-
"""
88-
if var.dim_num() == 0:
89-
dims = []
90-
elif var.dim_num() == 1:
91-
dims = [var.first_index.get_length()]
92-
elif var.dim_num() == 2:
93-
dims = [var.first_index.get_length(),
94-
var.second_index.get_length()]
95-
else:
96-
raise NotImplementedError
97-
98-
if len(dims) == 0:
99-
ctor = ''
100-
elif len(dims) == 1:
101-
ctor = '({})'.format(dims[0])
102-
else:
103-
ctor = '({})'.format(dims[-1])
104-
ctype = self._convert_type(var.type)
105-
for dim in dims[-2::-1]:
106-
ctype = 'std::vector<{}>'.format(ctype)
107-
ctor = '({}, {}{})'.format(dim, ctype, ctor)
108-
109-
line = "{decl_type} {name}{constructor};".format(
110-
name=var.name,
111-
decl_type=self._get_declaration_type(var),
112-
constructor=ctor
113-
)
114-
return line
115-
116-
def _input_code_for_var(self, var: Variable) -> str:
117-
name = self._get_var_name(var)
118-
if var.type == Type.float:
119-
return 'scanf("%Lf",&{name});'.format(name=name)
120-
elif var.type == Type.int:
121-
return 'scanf("%lld",&{name});'.format(name=name)
122-
elif var.type == Type.str:
123-
return 'std::cin >> {name};'.format(name=name)
124-
else:
125-
raise NotImplementedError
126-
127-
@staticmethod
128-
def _get_var_name(var: Variable):
129-
name = var.name
130-
if var.dim_num() >= 1:
131-
name += "[i]"
132-
if var.dim_num() >= 2:
133-
name += "[j]"
134-
return name
135-
136-
def _render_pattern(self, pattern: Pattern):
137-
lines = []
138-
for var in pattern.all_vars():
139-
lines.append(self._generate_declaration(var))
140-
141-
representative_var = pattern.all_vars()[0]
142-
if isinstance(pattern, SingularPattern):
143-
lines.append(self._input_code_for_var(representative_var))
144-
elif isinstance(pattern, ParallelPattern):
145-
lines.append(_loop_header(representative_var, False))
146-
for var in pattern.all_vars():
147-
lines.append("{indent}{line}".format(indent=self._indent(1),
148-
line=self._input_code_for_var(var)))
149-
lines.append("}")
150-
elif isinstance(pattern, TwoDimensionalPattern):
151-
lines.append(_loop_header(representative_var, False))
152-
lines.append(
153-
"{indent}{line}".format(indent=self._indent(1), line=_loop_header(representative_var, True)))
154-
for var in pattern.all_vars():
155-
lines.append("{indent}{line}".format(indent=self._indent(2),
156-
line=self._input_code_for_var(var)))
157-
lines.append("{indent}}}".format(indent=self._indent(1)))
158-
lines.append("}")
159-
else:
160-
raise NotImplementedError
161-
162-
return lines
163-
164-
def _indent(self, depth):
165-
return self._config.indent(depth)
166-
1673

168-
class NoPredictionResultGiven(Exception):
169-
pass
4+
from atcodertools.codegen.code_generators.universal_code_generator import UniversalCodeGenerator, get_builtin_code_generator_info_toml_path
1705

1716

1727
def main(args: CodeGenArgs) -> str:
173-
code_parameters = CppCodeGenerator(
174-
args.format, args.config).generate_parameters()
8+
code_parameters = UniversalCodeGenerator(
9+
args.format, args.config, get_builtin_code_generator_info_toml_path("cpp")).generate_parameters()
17510
return render(
17611
args.template,
17712
mod=args.constants.mod,

0 commit comments

Comments
 (0)