Over on Code Golf, we've decided, for once, to write some readable code! However, we're obviously not very good at that, so we need your help to make it even better.
I've proposed this challenge on PPCG, which requires an interpreter for a new golfing language, written in Python 3.4. I've finished the base file, but, as this will be collaborated on by many users, I need to make sure it's readable, understandable and, well, not golfed.
The basic interpreter included a collection of different memory models, as to not restrict modifications, such as a tape (à la brainfuck), or a stack, similar to ><>, and also includes the basis of commands needed to perform the tasks in the linked challenge.
Unfortunately, it is sparse when it comes to comments, and in addition to feedback on how the code is written, I'd appreciate feedback or suggestions on the level of commenting needed, ranging from I can barely understand what this is doing, fill it all with comments to Pepper a few throughout, near the more complicated parts. The code is as follows:
import argparse
import collections
import sys
# === Constants === #
inf = float('inf')
nan = float('nan')
digits = str.maketrans('⁰¹²³⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉', '01234567890123456789')
code_page = '''ÀÁÂÄÆÃÅĀĄ\t\nĆČÇĎÐÈÉÊËĒĖĚĘÌÍÎÏĪĮĹĽ'''
code_page += ''' !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~¶'''
code_page += '''ŁŃŇÑŊÒÓÔÖŒÕØŌŔŘŚŠŤŦÙÚÛÜŮŪŴÝŶŸŹŽŻàáâäæãåāąćčçďðèéêëēėěęìíîïīįĺľłńňñŋòóôöœøōõŕřßśšťŧùúûüůūŵýŷÿźžż◊'''
code_page += '''ΑΆΒΓΔΕΈΖΗΉΘΙΊΚΛΜΝΞΟΌΠΡΣΤΥΎΦΧΨΩΏ'''
code_page += '''αάβγδεέζηήθιίΐκλμνξοόπσςτυύΰφχψωώ'''
code_page += '''ǴḰḾṔẂǵḱḿṕẃḂḞĠḢṀȮṖṠṪẊḃḟġḣṁȯṗṡṫẋ§ĂĞĬŎŬĴăğĭŏŭĵªº‹›'''
code_page += '''ƁƇƊƑƓƘⱮƝƤƬƲȤɓƈɗƒɠɦƙɱɲƥʠɼʂƭʋȥ©®ıȷ'''
code_page += '''ЉЊЕРТЗУИОПШАСДФГХЈКЛЧЋЅЏЦВБНМЂЖљњертзуиопшасдфгхјклчћѕџцвбнмђжÞþ'''
code_page += '''†∂∆≈≠√∈∉∌∋∩∪¬∧∨⊕¤₽¥£¢€₩‰¿¡⁇⁈‼⁉‽⸘…°•”“„’‘≤«·»≥ᴇ∞¦×⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾÷₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎'''
# === Memory models === #
class Model:
def is_tape(self):
return self.__class__.__name__ == 'Tape'
def is_stack(self):
return self.__class__.__name__ == 'Stack'
def is_grid(self):
return self.__class__.__name__ == 'Grid'
def is_register(self):
return self.__class__.__name__ == 'Register'
def is_deque(self):
return self.__class__.__name__ == 'Deque'
class Stack(Model):
def __init__(self, *values):
self.stack = list(values)
def push(self, *values):
for value in values:
self.stack.append(value)
def pop(self, index = -1):
return self.stack.pop(index)
def peek(self, index = -1):
return self.stack[index % len(self.stack)]
def __repr__(self):
return 'Stack({})'.format(self.stack)
def __str__(self):
return str(self.stack)
def __iter__(self):
return iter(self.stack)
def __getitem__(self, index):
return self.stack[index]
def __setitem__(self, index, value):
self.stack[index] = value
class Tape(Model):
def __init__(self, size = inf, wrap = True):
self.size = size
self.wrap = wrap
self.tape = [0]
self.index = 0
if size != inf:
self.tape = [0] * size
def __repr__(self):
if self.size == inf:
return 'Tape([ … ])'
final = 'Tape(['
for i, val in enumerate(self.tape):
val = str(val)
if i == self.index:
final += '{' + val + '}'
else:
final += val
final += ' '
final += '])'
return final
def __str__(self):
if self.size == inf:
if len(self.tape):
template = '[ … {} … ]'
final = ''
for i, val in enumerate(self.tape):
val = str(val)
if i == self.index:
final += '{' + val + '}'
else:
final += val
final += ' '
return template.format(final[:-1])
else:
return '[ … ]'
else:
final = '['
for i, val in enumerate(self.tape):
val = str(val)
if i == self.index:
final += '{' + val + '}'
else:
final += val
final += ' '
final = final.strip() + ']'
return final
def __iter__(self):
return iter(self.tape)
@property
def cell(self):
return self.tape[self.index]
def write_cell(self, value):
self.tape[self.index] = value
def move_right(self):
self.index += 1
if self.size != inf:
if self.wrap:
self.index %= self.size
else:
while self.index >= len(self.tape):
self.tape.append(0)
def move_left(self):
self.index -= 1
if self.size != inf:
if self.wrap and self.index < 0:
self.index = self.size - 1
elif not self.wrap and self.index < 0:
raise IndexError
else:
if self.index < 0:
self.tape.insert(0, 0)
self.index = 0
class Grid(Model):
def __init__(self, xlen = inf, ylen = inf, xwrap = True, ywrap = True):
self.xlen = xlen
self.ylen = ylen
self.xwrap = xwrap
self.ywrap = ywrap
self.xindex = 0
self.yindex = 0
self.grid = []
self.pointer_value = 0
if xlen != inf:
if ylen != inf:
for _ in range(ylen):
self.grid.append([0] * xlen)
else:
self.grid.append([0] * xlen)
else:
if ylen != inf:
for _ in range(ylen):
self.grid.append([0])
else:
for _ in range(10):
self.grid.append([0] * 10)
def __repr__(self):
final = ''
if self.xlen == inf:
if self.ylen == inf:
final = '[ … …\n ⋮ ⋮ ]'
else:
final = ''
for line in self.grid:
final += ' ' + str(line) \
.replace(',', '') \
.replace('[', '') \
.replace(']', '') + ' …\n'
final = '[' + final[1:-1] + ']'
else:
if self.ylen == inf:
final = '[' + ('⋮ ' * int(len(self.grid[0]) * 4/5)) + '\n'
for line in self.grid:
final += ' ' + str(line) \
.replace(',', '') \
.replace('[', '') \
.replace(']', '') + '\n'
final += ' ' + ('⋮ ' * int(len(self.grid[0]) * 4/5)) + ']'
else:
final = ''
for line in self.grid:
final += ' ' + str(line) \
.replace(',', '') \
.replace('[', '') \
.replace(']', '') + '\n'
final = '[' + final[1:-1] + ']'
return final
def __str__(self):
array = list(map(lambda a: list(map(str, a)), self.grid))
max_row_length = max(map(len, array))
for index, row in enumerate(array):
while len(row) < max_row_length:
row.append('0')
array[index] = row
pad = max(max(map(len, a)) for a in array) + 1
final = ''
for row in array:
for element in row:
element = element.rjust(pad)
final += element
final += '\n'
return final + '<> {} <> {}'.format(self.xindex, self.yindex)
def __iter__(self):
return iter(strip(flatten(self.grid), 0))
@property
def cell(self):
return self.grid[self.yindex][self.xindex]
def write_cell(self, value):
self.grid[self.yindex][self.xindex] = value
def write_pointer(self):
self.pointer_value = self.cell
def move_right(self):
self.xindex += 1
if self.xlen == inf:
self.grid[self.yindex].append(0)
else:
if self.xwrap:
self.xindex %= self.xlen
def move_left(self):
self.xindex -= 1
if self.xlen == inf and self.xindex < 0:
self.grid[self.yindex].insert(0, 0)
self.xindex = 0
else:
if self.xindex < 0 and self.xwrap:
self.xindex = self.xlen - abs(self.xindex)
elif self.xindex < 0 and not self.xwrap:
self.xindex = self.xlen + 1
def move_down(self):
self.yindex += 1
if self.ylen == inf:
if self.xlen != inf:
self.grid.append([0] * self.xlen)
else:
self.grid.append([0])
else:
if self.ywrap:
self.yindex %= self.ylen
def move_up(self):
self.yindex -= 1
if self.ylen == inf and self.yindex < 0:
self.grid[self.xindex].insert(0, 0)
self.yindex = 0
else:
if self.yindex < 0 and self.ywrap:
self.yindex = self.ylen - abs(self.yindex)
elif self.yindex < 0 and not self.ywrap:
self.yindex = self.ylen + 1
class Register(Model, int):
def __init__(self, value = None):
self.value = value
def __repr__(self):
return 'Register({})'.format(self.value)
def __str__(self):
return str(self.value)
class Deque(Model, collections.deque):
def __repr__(self):
rep = super().__repr__()[5:]
return 'Deque' + rep
def __str__(self):
return super().__repr__()[6:-1]
push = collections.deque.append
def current_value(model):
if model.is_tape() or model.is_grid():
return model.cell
if model.is_stack() or model.is_deque():
return model.pop()
if model.is_register():
return model.value
return False
def identity(value):
return value
def overload(stack_cmd = identity, tape_cmd = identity, grid_cmd = identity,
register_cmd = identity, deque_cmd = identity, all_cmd = None):
def inner(model):
if all_cmd is not None:
return all_cmd(model)
if model.is_stack():
return stack_cmd(model)
if model.is_tape():
return tape_cmd(model)
if model.is_grid():
return grid_cmd(model)
if model.is_register():
return register_cmd(model)
return deque_cmd(model)
return inner
def make_nilad(value, model):
value = eval(value)
if model.is_tape() or model.is_grid():
model.write_cell(value)
if model.is_stack():
model.push(value)
if model.is_deque():
model.append(value)
if model.is_register():
model.value = value
# === Functions === #
# = Stack Commands = #
def add(stack):
stack.push(stack.pop() + stack.pop())
def subtract(stack):
stack.push(stack.pop() - stack.pop())
def multiply(stack):
stack.push(stack.pop() * stack.pop())
def divide(stack):
stack.push(stack.pop() / stack.pop())
def modulo(stack):
stack.push(stack.pop() % stack.pop())
def swap(array):
array[-2], array[-1] = array[-1], array[-2]
def reverse(stack):
if hasattr(stack.peek(), '__iter__'):
stack.push(stack.pop()[::-1])
else:
stack.stack = stack.stack[::-1]
def product(array):
total = 1
for element in array:
total *= element
return total
def flatten(array):
flat = []
if type(array) == list:
for item in array:
flat += flatten(item)
else:
flat.append(array)
return flat
def strip(array, trim):
final = []
for value in array:
if value != trim or final:
final.append(value)
array = final[::-1]
final = []
for value in array:
if value != trim or final:
final.append(value)
return final[::-1]
def rotate(stack):
a = stack.pop()
b = stack.pop()
c = stack.pop()
stack.push(a, c, b)
# = Tape / Grid Commands = #
def increment_cell(tape_grid):
tape_grid.write_cell(tape_grid.cell + 1)
def decrement_cell(tape_grid):
tape_grid.write_cell(tape_grid.cell - 1)
def write_grid_pointer(grid):
grid.pointer_value
def getchar():
ret = sys.stdin.read(1)
if ret:
return ret
return chr(0)
def getinput():
try:
inputted = input()
except:
return ''
try:
return eval(inputted)
except:
return inputted
# === Operators === #
def while_loop(code):
def while_inner(model, used):
while current_value(model):
model = interpret(code, model, used)
return while_inner
def if_statement(code):
def if_inner(model, used):
else_, if_ = list(map(lambda a: a[::-1], code[::-1].split('}', 1)))
if current_value(model):
model = interpret(if_, model, used)
else:
model = interpret(else_, model, used)
return if_inner
# === Command lookups === #
memories = {
'#': (0, Tape),
'$': (0, Stack),
'G': (0, Grid),
'D': (0, Deque),
'S': (1, Register),
'À': (2, lambda size, wrap: Tape(size, wrap)),
'Á': (1, lambda size: Tape(size, wrap = True)),
'Â': (1, lambda size: Tape(size, wrap = False)),
'Ä': (1, lambda wrap: Tape(wrap = wrap)),
'Ǵ': (4, lambda xlen, ylen, xwrap, ywrap: Grid(xlen, ylen, xwrap, ywrap)),
}
functions = {
'+': overload(
stack_cmd = add,
deque_cmd = add,
tape_cmd = increment_cell,
grid_cmd = increment_cell,
),
'-': overload(
stack_cmd = subtract,
deque_cmd = subtract,
tape_cmd = decrement_cell,
grid_cmd = decrement_cell,
),
'%': overload(
stack_cmd = modulo,
deque_cmd = modulo,
),
':': overload(
stack_cmd = lambda stack: stack.push(stack.peek()),
deque_cmd = lambda deque: deque.append(deque[-1]),
),
';': overload(
stack_cmd = lambda stack: stack.pop(),
deque_cmd = lambda deque: deque.pop(),
tape_cmd = lambda tape: tape.write_cell(0),
grid_cmd = lambda grid: grid.write_cell(0),
),
'<': overload(
tape_cmd = Tape.move_left,
grid_cmd = Grid.move_left,
),
'=': overload(
stack_cmd = lambda stack: stack.push(stack.pop() == stack.pop()),
grid_cmd = Grid.move_down,
),
'>': overload(
tape_cmd = Tape.move_right,
grid_cmd = Grid.move_right,
),
'?': overload(
stack_cmd = lambda stack: stack.push(getinput()),
deque_cmd = lambda deque: deque.append(getinput()),
tape_cmd = lambda tape: tape.write_cell(ord(getchar())),
grid_cmd = lambda grid: grid.write_cell(ord(getchar())),
),
'O': overload(
all_cmd = lambda model: print(end = str(current_value(model))),
),
'Z': overload(
stack_cmd = lambda stack: stack.push(stack.pop() > 0),
),
'R': overload(
stack_cmd = reverse,
),
'^': overload(
grid_cmd = Grid.move_up,
),
'h': overload(
all_cmd = print,
),
'o': overload(
all_cmd = lambda model: print(end = chr(current_value(model))),
),
'r': overload(
stack_cmd = rotate,
deque_cmd = lambda deque: deque.rotate(deque.pop()),
),
's': overload(
stack_cmd = swap,
deque_cmd = swap,
),
'Π': overload(
stack_cmd = lambda stack: stack.push(product(stack)),
tape_cmd = lambda tape: tape.write_cell(product(tape)),
grid_cmd = lambda grid: grid.write_cell(product(flatten(list(grid)))),
),
'Σ': overload(
stack_cmd = lambda stack: stack.push(sum(stack)),
tape_cmd = lambda tape: tape.write_cell(sum(tape)),
grid_cmd = lambda grid: grid.write_cell(sum(flatten(list(grid)))),
),
'×': overload(
stack_cmd = multiply,
grid_cmd = lambda grid: grid.write_cell(grid.pointer_value),
),
'÷': overload(
stack_cmd = divide,
grid_cmd = Grid.write_pointer,
),
}
operators = {
'[': while_loop,
'{': if_statement,
}
def decode(bytestring):
decoded = ''
continue_byte = False
for index, byte in enumerate(bytestring):
if continue_byte:
continue_byte = False
continue
if byte == 0xff:
continue_byte = True
byte += bytestring[index + 1] + 1
try:
decoded += code_page[byte]
except:
raise UnicodeDecodeError('Unknown byte value: {}'.format(byte))
return decoded
def next_index(string, start, char):
index = start
depth = 1
while depth and index < len(string) - 1:
index += 1
if string[index] in operators.keys():
depth += 1
if string[index] == char:
depth -= 1
return index
def parse(code):
tokens = []
index = 0
comment = False
while index < len(code):
char = code[index]
if char == '◊':
comment = not comment
if comment:
index += 1
continue
if char.isdigit() or char == '_':
tokens.append(char)
index += 1
if index < len(code):
char = code[index]
while char.isdigit() and index < len(code):
tokens[-1] += char
index += 1
char = code[index]
tokens[-1] = tokens[-1].replace('_', '-')
if char in operators.keys():
ret = next_index(code, index, ']')
tokens.append(operators[char](code[index + 1 : ret]))
index = ret
else:
tokens.append(char)
index += 1
return tokens
def interpret(code, memory = None, used = None):
tokens = parse(code)
operable = (memory is not None)
if used is None:
used = []
for tkn in tokens:
if callable(tkn) and operable:
tkn(memory, used)
elif tkn in '₀₁₂₃₄₅₆₇₈₉' and operable:
tkn = int(tkn.translate(digits))
memory = used[tkn]
elif tkn.isdigit() or (tkn[0] in '-' and tkn[1:].isdigit()):
make_nilad(tkn, memory)
elif tkn in memories.keys():
arity, mem_type = memories[tkn]
memory = mem_type(*[current_value(memory) for _ in range(arity)])
used.append(memory)
operable = True
elif tkn == '@' and operable:
used.append(memory)
memory = used.pop(-2)
elif tkn in functions.keys() and operable:
functions[tkn](memory)
else:
continue
return memory
if __name__ == '__main__':
argparser = argparse.ArgumentParser()
a = 'store_true'
getcode = argparser.add_mutually_exclusive_group()
getcode.add_argument('-f', '--file', help = 'Specifies that code be read from a file', action = a)
getcode.add_argument('-c', '--cmd', '--cmdline', help = 'Specifies that code be read from the command line', action = a)
argparser.add_argument('-u', '--unicode', help = 'Use utf-8 encoding for files', action = a)
argparser.add_argument('program')
argparser.add_argument('argv', nargs = '*')
settings = argparser.parse_args()
if settings.file:
with open(settings.program, mode = 'rb') as file:
contents = file.read()
if settings.unicode:
contents = ''.join([char for char in contents.decode('utf-8') if char in code_page])
else:
contents = decode(contents)
if settings.cmd:
contents = bytes(settings.program, 'utf-8')
if settings.unicode:
contents = ''.join([char for char in contents.decode('utf-8') if char in code_page])
else:
contents = decode(contents)
interpret(contents)
For those interested, the GitHub repository contains 18 examples, all responses to the challenges listed in the question linked in the second paragraph
--file/-f
flag followed by the filename, and the same with the--cmdline/-c
flag for CLI \$\endgroup\$