Skip to content

Commit c85899b

Browse files
kmykkyuridenamida
authored andcommitted
Add a code generator for Python 3 (kyuridenamida#97)
* Add a code generator for Python 3 * Add tests for the code generator for Python 3 * Update README.md * Remove some typehints from default_template.py * Fix the option of autopep8 in .travis.yml
1 parent bb03ef6 commit c85899b

File tree

16 files changed

+571
-5
lines changed

16 files changed

+571
-5
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ install:
1818

1919
script:
2020
- flake8 --ignore=E501, W605
21-
- autopep8 -r . --diff | tee check_autopep8
21+
- autopep8 -r . --exclude 'default_template.py,test_codegen' --diff | tee check_autopep8
2222
- test ! -s check_autopep8
2323
- atcoder-tools gen arc050 --without-login
2424
- nosetests tests --exe -v --with-coverage --cover-package=atcodertools

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Python 3.5 以降で動作する [AtCoder](http://atcoder.jp/) からサンプ
2020
|C++|[@kyuridenamida](https://github.com/kyuridenamida/) (generator, template)|[@asi1024](https://github.com/asi1024/) (template)|
2121
|Java|[@kyuridenamida](https://github.com/kyuridenamida/) (generator, template)||
2222
|Rust|[@fukatani](https://github.com/fukatani/) (generator, template)|[@koba-e964](https://github.com/koba-e964/) (template, CR)|
23+
|Python3|[@kmyk](https://github.com/kmyk/) (generator, template)||
2324

2425
## How to install
2526
`pip3 install atcoder-tools`
@@ -83,6 +84,7 @@ optional arguments:
8384
[Default (C++)] /atcodertools/tools/templates/default_template.cpp
8485
[Default (Java)] /atcodertools/tools/templates/default_template.java
8586
[Default (Rust)] /atcodertools/tools/templates/default_template.rs
87+
[Default (Python3)] /atcodertools/tools/templates/default_template.py
8688
8789
--parallel Prepare problem directories asynchronously using multi processors.
8890
--save-no-session-cache
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
from typing import Dict, Any, Optional, List
2+
3+
from atcodertools.codegen.code_style_config import CodeStyleConfig
4+
from atcodertools.codegen.models.code_gen_args import CodeGenArgs
5+
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 {loop_var} in range({length}):".format(
21+
loop_var=loop_var,
22+
length=index.get_length()
23+
)
24+
25+
26+
class Python3CodeGenerator:
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 "float"
52+
elif type_ == Type.int:
53+
return "int"
54+
elif type_ == Type.str:
55+
return "str"
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 = "List[{}]".format(ctype)
63+
if var.dim_num():
64+
ctype = '"{}"'.format(ctype)
65+
return ctype
66+
67+
def _actual_arguments(self) -> str:
68+
"""
69+
:return the string form of actual arguments e.g. "N, K, a"
70+
"""
71+
return ", ".join([v.name for v in self._format.all_vars()])
72+
73+
def _formal_arguments(self):
74+
"""
75+
:return the string form of formal arguments e.g. "N: int, K: int, a: List[int]"
76+
"""
77+
return ", ".join([
78+
"{name}: {decl_type}".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] -> array = [int()] * (n-1+1) # type: List[int]
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+
ctype = self._convert_type(var.type)
99+
if len(dims) == 0:
100+
ctor = "{}()".format(ctype)
101+
elif len(dims) == 1:
102+
ctor = "[{ctype}()] * ({dim})".format(ctype=ctype, dim=dims[0])
103+
else:
104+
ctor = "[{ctype}()] * ({dim})".format(ctype=ctype, dim=dims[0])
105+
for dim in dims[-2::-1]:
106+
ctor = "[{ctor} for _ in range({dim})]".format(
107+
ctor=ctor, dim=dim)
108+
109+
line = "{name} = {constructor} # type: {decl_type} ".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_token(self, type_: Type) -> str:
117+
if type_ == Type.float:
118+
return "float(next(tokens))"
119+
elif type_ == Type.int:
120+
return "int(next(tokens))"
121+
elif type_ == Type.str:
122+
return "next(tokens)"
123+
else:
124+
raise NotImplementedError
125+
126+
def _input_code_for_single_pattern(self, pattern: Pattern) -> str:
127+
assert len(pattern.all_vars()) == 1
128+
var = pattern.all_vars()[0]
129+
130+
if isinstance(pattern, SingularPattern):
131+
input_ = self._input_code_for_token(var.type)
132+
133+
elif isinstance(pattern, ParallelPattern):
134+
input_ = "[ {input_} for _ in range({length}) ]".format(
135+
input_=self._input_code_for_token(var.type),
136+
length=var.first_index.get_length())
137+
138+
elif isinstance(pattern, TwoDimensionalPattern):
139+
input_ = "[ [ {input_} for _ in range({second_length}) ] for _ in range({first_length}) ]".format(
140+
input_=self._input_code_for_token(var.type),
141+
first_length=var.first_index.get_length(),
142+
second_length=var.second_index.get_length())
143+
144+
else:
145+
raise NotImplementedError
146+
147+
return "{name} = {input_} # type: {type_}".format(
148+
name=var.name,
149+
input_=input_,
150+
type_=self._get_declaration_type(var))
151+
152+
@staticmethod
153+
def _get_var_name(var: Variable):
154+
name = var.name
155+
if var.dim_num() >= 1:
156+
name += "[i]"
157+
if var.dim_num() >= 2:
158+
name += "[j]"
159+
return name
160+
161+
def _input_code_for_non_single_pattern(self, pattern: Pattern) -> List[str]:
162+
lines = []
163+
for var in pattern.all_vars():
164+
lines.append(self._generate_declaration(var))
165+
representative_var = pattern.all_vars()[0]
166+
167+
if isinstance(pattern, SingularPattern):
168+
assert False
169+
170+
elif isinstance(pattern, ParallelPattern):
171+
lines.append(_loop_header(representative_var, False))
172+
for var in pattern.all_vars():
173+
lines.append("{indent}{name} = {input_}".format(indent=self._indent(1),
174+
name=self._get_var_name(
175+
var),
176+
input_=self._input_code_for_token(var.type)))
177+
178+
elif isinstance(pattern, TwoDimensionalPattern):
179+
lines.append(_loop_header(representative_var, False))
180+
lines.append(
181+
"{indent}{line}".format(indent=self._indent(1), line=_loop_header(representative_var, True)))
182+
for var in pattern.all_vars():
183+
lines.append("{indent}{name} = {input_}".format(indent=self._indent(2),
184+
name=self._get_var_name(
185+
var),
186+
input_=self._input_code_for_token(var.type)))
187+
188+
else:
189+
raise NotImplementedError
190+
return lines
191+
192+
def _render_pattern(self, pattern: Pattern):
193+
if len(pattern.all_vars()) == 1:
194+
return [self._input_code_for_single_pattern(pattern)]
195+
else:
196+
return self._input_code_for_non_single_pattern(pattern)
197+
198+
def _indent(self, depth):
199+
return self._config.indent(depth)
200+
201+
202+
def main(args: CodeGenArgs) -> str:
203+
code_parameters = Python3CodeGenerator(
204+
args.format, args.config).generate_parameters()
205+
return render(
206+
args.template,
207+
mod=args.constants.mod,
208+
yes_str=args.constants.yes_str,
209+
no_str=args.constants.no_str,
210+
**code_parameters
211+
)

atcodertools/common/language.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import re
22
from typing import Pattern, Callable
33

4-
from atcodertools.codegen.code_generators import cpp, java, rust
4+
from atcodertools.codegen.code_generators import cpp, java, rust, python
55
from atcodertools.codegen.models.code_gen_args import CodeGenArgs
66
from atcodertools.tools.templates import get_default_template_path
77

@@ -67,5 +67,14 @@ def from_name(cls, name: str):
6767
default_template_path=get_default_template_path('rs'),
6868
)
6969

70-
ALL_LANGUAGES = [CPP, JAVA, RUST]
70+
PYTHON = Language(
71+
name="python",
72+
display_name="Python3",
73+
extension="py",
74+
submission_lang_pattern=re.compile(".*Python3.*"),
75+
default_code_generator=python.main,
76+
default_template_path=get_default_template_path('py'),
77+
)
78+
79+
ALL_LANGUAGES = [CPP, JAVA, RUST, PYTHON]
7180
ALL_LANGUAGE_NAMES = [lang.display_name for lang in ALL_LANGUAGES]
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/env python3
2+
{% if prediction_success %}
3+
import sys
4+
{% endif %}
5+
6+
{% if mod %}
7+
MOD = {{ mod }} # type: int
8+
{% endif %}
9+
{% if yes_str %}
10+
YES = "{{ yes_str }}" # type: str
11+
{% endif %}
12+
{% if no_str %}
13+
NO = "{{ no_str }}" # type: str
14+
{% endif %}
15+
16+
{% if prediction_success %}
17+
def solve({{ formal_arguments }}):
18+
return
19+
20+
{% endif %}
21+
22+
def main():
23+
{% if prediction_success %}
24+
def iterate_tokens():
25+
for line in sys.stdin:
26+
for word in line.split():
27+
yield word
28+
tokens = iterate_tokens()
29+
{{ input_part }}
30+
solve({{ actual_arguments }})
31+
{% else %}
32+
# Failed to predict input format
33+
pass
34+
{% endif %}
35+
36+
if __name__ == '__main__':
37+
main()
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env python3
2+
3+
def solve(${formal_arguments}):
4+
return
5+
6+
7+
def main():
8+
def iterate_tokens():
9+
for line in sys.stdin:
10+
for word in line.split():
11+
yield word
12+
tokens = iterate_tokens()
13+
${input_part}
14+
solve(${actual_arguments})
15+
16+
if __name__ == '__main__':
17+
main()
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/env python3
2+
{% if prediction_success %}
3+
import sys
4+
{% endif %}
5+
6+
{% if mod %}
7+
MOD = {{ mod }} # type: int
8+
{% endif %}
9+
{% if yes_str %}
10+
YES = "{{ yes_str }}" # type: str
11+
{% endif %}
12+
{% if no_str %}
13+
NO = "{{ no_str }}" # type: str
14+
{% endif %}
15+
16+
{% if prediction_success %}
17+
def solve({{ formal_arguments }}):
18+
return
19+
20+
{% endif %}
21+
22+
def main():
23+
{% if prediction_success %}
24+
def iterate_tokens():
25+
for line in sys.stdin:
26+
for word in line.split():
27+
yield word
28+
tokens = iterate_tokens()
29+
{{ input_part }}
30+
solve({{ actual_arguments }})
31+
{% else %}
32+
# Failed to predict input format
33+
pass
34+
{% endif %}
35+
36+
if __name__ == '__main__':
37+
main()

0 commit comments

Comments
 (0)