The way you define your various functions is inconsistent:
- you define
add
, sub
, putch
, yet use data[index] = getch()[0]
directly in the generated code;
- you group together multiple
+
, -
, <
, and >
but neither .
nor ,
(not that this last one would be of great use but still, for the sake of consistency);
- you access global data into those functions;
putch
is defined using a lambda
but add
and sub
uses def
…
Since [
and ]
are special anyway as they do more than producing a string, I'd rather choose to either:
- inline everything in the generated code;
- or only use functions that take
data
, index
and count
as parameters.
I would also generate code that respect some standard practices: defining a main
function and wrapping its call into an if __name__ == '__main__':
guard. This would mean starting level
at 1 as you would generate code that is inside a def main():
.
You should also take a look at textwrap.dedent
to simplify your header (and thus footer too) generation.
And since you are using formated strings, you could make directly use of the variable width alignment such as f'{"": >{level*4}}rest of the code'
.
Storing generated instructions into a list that is returned at the end feels like your function would benefit from being turned into a generator. Especially if you plan on writing the generated string to a file, this would help interleaving computation and waiting for I/O.
Lastly your assert
ion feels off as they can be turned off at runtime. Instead I would raise a real exception such as ValueError
with a proper error message.
Proposed improvements with code inlined:
import itertools
from textwrap import dedent
from contextlib import suppress
BYTECODES = {
'+': '{0: >{1}}data[index] = (data[index] + {2}) % 256',
'-': '{0: >{1}}data[index] = (data[index] - {2}) % 256',
'<': '{0: >{1}}index -= {2}',
'>': '{0: >{1}}index += {2}',
'.': '{0: >{1}}for _ in range({2}):\n{0: >{1}} print(chr(data[index]), end="")',
',': '{0: >{1}}for _ in range({2}):\n{0: >{1}} data[index] = getch()[0]',
}
def pairwise(iterable):
a, b = itertools.tee(iterable)
next(b, None)
yield from itertools.zip_longest(a, b)
def compile(sourcecode):
yield dedent("""\
from collections import defaultdict
from msvcrt import getch
def main():
index = 0
data = defaultdict(int)""")
level = count = 1
for operation, next_operation in pairwise(sourcecode):
if operation == '[':
yield f'{"": >{level*4}}while data[index]:'
level += 1
elif operation == ']':
level -= 1
if level < 1:
raise ValueError('Unbalanced brackets, found ] before opening [')
elif operation == next_operation:
count += 1
else:
with suppress(KeyError):
yield BYTECODES[operation].format('', level * 4, count)
count = 1
yield dedent("""\
if __name__ == '__main__':
main()""")
def translate(sourcecode):
return '\n'.join(compile(sourcecode))
if __name__ == '__main__':
code = '++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'
for line in compile(code):
print(line)
print('*------*')
print(translate(code))
Proposed improvements with functions defined:
import itertools
from textwrap import dedent
from contextlib import suppress
BYTECODES = {
'+': '{0: >{1}}add(data, index, {2})',
'-': '{0: >{1}}sub(data, index, {2})',
'<': '{0: >{1}}index -= {2}',
'>': '{0: >{1}}index += {2}',
'.': '{0: >{1}}putchar(data, index, {2})',
',': '{0: >{1}}getchar(data, index, {2})',
}
def pairwise(iterable):
a, b = itertools.tee(iterable)
next(b, None)
yield from itertools.zip_longest(a, b)
def compile(sourcecode):
yield dedent("""\
from collections import defaultdict
from msvcrt import getch
def add(data, index, x):
data[index] = (data[index] + x) % 256
def sub(data, index, x):
data[index] = (data[index] - x) % 256
def putchar(data, index, x):
for _ in range(x):
print(chr(data[index]), end='')
def getchar(data, index, x):
for _ in range(x):
data[index] = getch()[0]
def main():
index = 0
data = defaultdict(int)""")
level = count = 1
for operation, next_operation in pairwise(sourcecode):
if operation == '[':
yield f'{"": >{level*4}}while data[index]:'
level += 1
elif operation == ']':
level -= 1
if level < 1:
raise ValueError('Unbalanced brackets, found ] before opening [')
elif operation == next_operation:
count += 1
else:
with suppress(KeyError):
yield BYTECODES[operation].format('', level * 4, count)
count = 1
yield dedent("""\
if __name__ == '__main__':
main()""")
def translate(sourcecode):
return '\n'.join(compile(sourcecode))
if __name__ == '__main__':
code = '++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'
for line in compile(code):
print(line)
print('*------*')
print(translate(code))
As you can see, differences are minimal but generated code feels neater for the second version. I also kept the translate
function for ease of use but the generator is simple enough to use on its own.