Starting point for a collaboratively developed golfing language interpreted in Python
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
8
down vote
favorite
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 = '''ÃÂÃÂÃÂÃÂÃÂÃÂÃÂ
ÃÂÃÂtnÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂêîùý'''
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 = Â
up vote
8
down vote
favorite
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 = '''ÃÂÃÂÃÂÃÂÃÂÃÂÃÂ
ÃÂÃÂtnÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂêîùý'''
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)
Try it online!
For those interested, the GitHub repository contains 18 examples, all responses to the challenges listed in the question linked in the second paragraph
python python-3.x interpreter
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 = '''ÃÂÃÂÃÂÃÂÃÂÃÂÃÂ
ÃÂÃÂtnÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂêîùý'''
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)
Try it online!
For those interested, the GitHub repository contains 18 examples, all responses to the challenges listed in the question linked in the second paragraph
python python-3.x interpreter
edited Apr 5 at 16:39
200_success
123k14142399
123k14142399
asked Apr 5 at 8:08
caird coinheringaahing
24529
24529
add a comment |Â
If I understand correctly, at least one of file or cmd CLI argument is required. Am I right ? I am experiencing issue trying to run a simple example from your Github project.
â Josay
Apr 5 at 9:09
@Josay Correct. In order to read and run a file, you must provide the--file/-f
flag followed by the filename, and the same with the--cmdline/-c
flag for CLI
â caird coinheringaahing
Apr 5 at 9:15
If I understand correctly, at least one of file or cmd CLI argument is required. Am I right ? I am experiencing issue trying to run a simple example from your Github project.
â Josay
Apr 5 at 9:09
If I understand correctly, at least one of file or cmd CLI argument is required. Am I right ? I am experiencing issue trying to run a simple example from your Github project.
â Josay
Apr 5 at 9:09
@Josay Correct. In order to read and run a file, you must provide the
--file/-f
flag followed by the filename, and the same with the --cmdline/-c
flag for CLIâ caird coinheringaahing
Apr 5 at 9:15
@Josay Correct. In order to read and run a file, you must provide the
--file/-f
flag followed by the filename, and the same with the --cmdline/-c
flag for CLIâ caird coinheringaahing
Apr 5 at 9:15
add a comment |Â
2 Answers
2
active
oldest
votes
up vote
6
down vote
Object programming
Y'ouve split your code into various classes. Yet, it seems like the way you use them could be improved.
Indeed, the various "is_SOMETHING" method are very suspicious, both in the fact that it exists and the way it is implemented.
The implementation relies on the name of the class. This is fragile but also wrong if you actually plan to use inheritance. You could have an instance of a subclass of "Deque": its class would not be called "Deque" but it would actually be a Deque.
Thus, isinstance
is what you should be using.
For instance:
def is_tape(self):
return isinstance(self, Tape)
def is_stack(self):
return isinstance(self, Stack)
def is_grid(self):
return isinstance(self, Grid)
def is_register(self):
return isinstance(self, Register)
def is_deque(self):
return isinstance(self, Deque)
This is better but as I said previously, even having these methods in a first place seems to be against the basic principle of object programming.
You should not need to care about the type of an object, the behavior of the object itself should be defined properly. For instance, instead of having a current_value
function checking the model class, you could have a abstract method, overriden in each subclass to have the behavior you need. This applies to make_nilad
.
This needs quite a lot of changes, at this stage, the code looks like http://termbin.com/q8jc .
Then, the same principle applies to overload
but it is trickier - work in progress in http://termbin.com/7x7cd .
Index out of bound error
I had a feeling that the boundary check in ithe while char.isdigit()...
loop was weird and indeed, giving an input ending with 2 (or more) digits, en end up going too far in the input and throwing an exception.
I guess a better (but far from perfect) solution would be:
def parse(code):
tokens =
index = 0
comment = False
while index < len(code):
char = code[index]
if char == 'âÂÂ':
comment = not comment
elif not comment:
if char in operators.keys():
ret = next_index(code, index, ']')
token = operators[char](code[index + 1 : ret])
index = ret
elif char.isdigit() or char == '_':
#######################################
token = char.replace('_', '-')
index += 1
while index < len(code):
char = code[index]
if not char.isdigit():
break
token += char
index += 1
#######################################
else:
token = char
tokens.append(token)
index += 1
return tokens
add a comment |Â
up vote
5
down vote
There's a lot of code here, so I'm going to go through it very sketchily. If you need a point explaining in more detail, ask.
There's no documentation. What is the specification of the language being interpreted? The idea of the challenge to extend and modify the language and its interpreter. The documentation will need to be extended and modified at the same time. How is this going to be done?
There are no docstrings. How are people supposed to modify this code if there's no specification for what the classes mean and the methods do?
PEP8 recommends
UPPER_CASE
for module-level constants.Instead of
digits
, useunicodedata.normalize
.Python concatenates adjacent string constants, so you can create
code_page
without the need for+=
.code_page[32:128]
looks as if it is supposed to be the same as the corresponding code points in ASCII so why not make use of that fact and avoid the risk of typos?There ought to be an
encode
function to correspond todecode
otherwise how are people going to write their programs? Better still, make it a codec so that it can be passed toopen
.What is the purpose of the
Model
class? It doesn't provide any common behaviour.The design doesn't make use of object orientation. Consider a function like:
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 FalseIn an object-oriented program this function would be a method on the
Model
class and its subclasses. In theModel
class this would be an abstract implementation:@abstractmethod
@property
def current_value(self):
"""Docstring here."""and then the various subclasses would override this method with concrete implementations.
It seems wrong for
current_value
to modify the model in some cases (stack and deque) but not in others (tape, grid and register).The
Stack
model seems to be a fairly thin wrapper around a list, so it could be implemented more simply by inheriting fromlist
(as theDeque
model inherits fromcollections.deque
).The
Register
model inherits fromint
but does not make use of this fact. Instead it uses a superfluous attributevalue
.What is a "nilad"? Google says that it is like a monad, but taking no arguments. This does not seem to correspond to anything in the
make_nilad
function. Could you choose a better name?Commands that operate on a
Stack
object (add
,subtract
etc.) ought to be methods on theStack
class. Similarly for tape and grid commands.The arguments to the
overload
function all end with_cmd
; this shared suffix is unnecessary.I think it would be clearer if the
Model
subclasses did their own command dispatch, instead of doing this via thefunctions
mapping and theoverload
function.Input is delivered to the model via
getinput
andgetchar
, which read from standard input. This makes it difficult to write unit tests because you have to make a file or a pipe in order to deliver the input for the test case. It would be better to take input in some other way, for example from an buffer object passed tointerpret
.There are lots of opportunities for ÷-conversion. For example
lambda size, wrap: Tape(size, wrap)
could be justTape
andlambda xlen, ylen, xwrap, ywrap: Grid(xlen, ylen, xwrap, ywrap)
could be justGrid
.It seems strange that an if expression starts with
but ends with
]
. I would have expected it to end with.
next_index
goes wrong if]
appears in a comment. (Maybe]
is not allowed to appear in a comment but if so I think this would need to be checked inparse
.)while_loop
re-parses the body of the loop each time round the loop. Is this deliberate? This seems like a waste of time compared to the normal approach of parsing just once.I recommend splitting up the tasks of tokenization, parsing and interpretation, instead of mixing them as here. (See these answers.)
There are no unit tests.
I got the termnilad
from this golfing language I use. However, when I googled "nilad", all the results were relating to a plant. What did you search?
â caird coinheringaahing
Apr 6 at 17:57
Also, what do you mean by÷-conversion
? I'm not familiar with the term
â caird coinheringaahing
Apr 6 at 18:12
÷-conversion is a term from the û-calculus â it refers to the operation that transforms $ûx.yx$ into $y$.
â Gareth Rees
Apr 6 at 18:33
add a comment |Â
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
6
down vote
Object programming
Y'ouve split your code into various classes. Yet, it seems like the way you use them could be improved.
Indeed, the various "is_SOMETHING" method are very suspicious, both in the fact that it exists and the way it is implemented.
The implementation relies on the name of the class. This is fragile but also wrong if you actually plan to use inheritance. You could have an instance of a subclass of "Deque": its class would not be called "Deque" but it would actually be a Deque.
Thus, isinstance
is what you should be using.
For instance:
def is_tape(self):
return isinstance(self, Tape)
def is_stack(self):
return isinstance(self, Stack)
def is_grid(self):
return isinstance(self, Grid)
def is_register(self):
return isinstance(self, Register)
def is_deque(self):
return isinstance(self, Deque)
This is better but as I said previously, even having these methods in a first place seems to be against the basic principle of object programming.
You should not need to care about the type of an object, the behavior of the object itself should be defined properly. For instance, instead of having a current_value
function checking the model class, you could have a abstract method, overriden in each subclass to have the behavior you need. This applies to make_nilad
.
This needs quite a lot of changes, at this stage, the code looks like http://termbin.com/q8jc .
Then, the same principle applies to overload
but it is trickier - work in progress in http://termbin.com/7x7cd .
Index out of bound error
I had a feeling that the boundary check in ithe while char.isdigit()...
loop was weird and indeed, giving an input ending with 2 (or more) digits, en end up going too far in the input and throwing an exception.
I guess a better (but far from perfect) solution would be:
def parse(code):
tokens =
index = 0
comment = False
while index < len(code):
char = code[index]
if char == 'âÂÂ':
comment = not comment
elif not comment:
if char in operators.keys():
ret = next_index(code, index, ']')
token = operators[char](code[index + 1 : ret])
index = ret
elif char.isdigit() or char == '_':
#######################################
token = char.replace('_', '-')
index += 1
while index < len(code):
char = code[index]
if not char.isdigit():
break
token += char
index += 1
#######################################
else:
token = char
tokens.append(token)
index += 1
return tokens
add a comment |Â
up vote
6
down vote
Object programming
Y'ouve split your code into various classes. Yet, it seems like the way you use them could be improved.
Indeed, the various "is_SOMETHING" method are very suspicious, both in the fact that it exists and the way it is implemented.
The implementation relies on the name of the class. This is fragile but also wrong if you actually plan to use inheritance. You could have an instance of a subclass of "Deque": its class would not be called "Deque" but it would actually be a Deque.
Thus, isinstance
is what you should be using.
For instance:
def is_tape(self):
return isinstance(self, Tape)
def is_stack(self):
return isinstance(self, Stack)
def is_grid(self):
return isinstance(self, Grid)
def is_register(self):
return isinstance(self, Register)
def is_deque(self):
return isinstance(self, Deque)
This is better but as I said previously, even having these methods in a first place seems to be against the basic principle of object programming.
You should not need to care about the type of an object, the behavior of the object itself should be defined properly. For instance, instead of having a current_value
function checking the model class, you could have a abstract method, overriden in each subclass to have the behavior you need. This applies to make_nilad
.
This needs quite a lot of changes, at this stage, the code looks like http://termbin.com/q8jc .
Then, the same principle applies to overload
but it is trickier - work in progress in http://termbin.com/7x7cd .
Index out of bound error
I had a feeling that the boundary check in ithe while char.isdigit()...
loop was weird and indeed, giving an input ending with 2 (or more) digits, en end up going too far in the input and throwing an exception.
I guess a better (but far from perfect) solution would be:
def parse(code):
tokens =
index = 0
comment = False
while index < len(code):
char = code[index]
if char == 'âÂÂ':
comment = not comment
elif not comment:
if char in operators.keys():
ret = next_index(code, index, ']')
token = operators[char](code[index + 1 : ret])
index = ret
elif char.isdigit() or char == '_':
#######################################
token = char.replace('_', '-')
index += 1
while index < len(code):
char = code[index]
if not char.isdigit():
break
token += char
index += 1
#######################################
else:
token = char
tokens.append(token)
index += 1
return tokens
add a comment |Â
up vote
6
down vote
up vote
6
down vote
Object programming
Y'ouve split your code into various classes. Yet, it seems like the way you use them could be improved.
Indeed, the various "is_SOMETHING" method are very suspicious, both in the fact that it exists and the way it is implemented.
The implementation relies on the name of the class. This is fragile but also wrong if you actually plan to use inheritance. You could have an instance of a subclass of "Deque": its class would not be called "Deque" but it would actually be a Deque.
Thus, isinstance
is what you should be using.
For instance:
def is_tape(self):
return isinstance(self, Tape)
def is_stack(self):
return isinstance(self, Stack)
def is_grid(self):
return isinstance(self, Grid)
def is_register(self):
return isinstance(self, Register)
def is_deque(self):
return isinstance(self, Deque)
This is better but as I said previously, even having these methods in a first place seems to be against the basic principle of object programming.
You should not need to care about the type of an object, the behavior of the object itself should be defined properly. For instance, instead of having a current_value
function checking the model class, you could have a abstract method, overriden in each subclass to have the behavior you need. This applies to make_nilad
.
This needs quite a lot of changes, at this stage, the code looks like http://termbin.com/q8jc .
Then, the same principle applies to overload
but it is trickier - work in progress in http://termbin.com/7x7cd .
Index out of bound error
I had a feeling that the boundary check in ithe while char.isdigit()...
loop was weird and indeed, giving an input ending with 2 (or more) digits, en end up going too far in the input and throwing an exception.
I guess a better (but far from perfect) solution would be:
def parse(code):
tokens =
index = 0
comment = False
while index < len(code):
char = code[index]
if char == 'âÂÂ':
comment = not comment
elif not comment:
if char in operators.keys():
ret = next_index(code, index, ']')
token = operators[char](code[index + 1 : ret])
index = ret
elif char.isdigit() or char == '_':
#######################################
token = char.replace('_', '-')
index += 1
while index < len(code):
char = code[index]
if not char.isdigit():
break
token += char
index += 1
#######################################
else:
token = char
tokens.append(token)
index += 1
return tokens
Object programming
Y'ouve split your code into various classes. Yet, it seems like the way you use them could be improved.
Indeed, the various "is_SOMETHING" method are very suspicious, both in the fact that it exists and the way it is implemented.
The implementation relies on the name of the class. This is fragile but also wrong if you actually plan to use inheritance. You could have an instance of a subclass of "Deque": its class would not be called "Deque" but it would actually be a Deque.
Thus, isinstance
is what you should be using.
For instance:
def is_tape(self):
return isinstance(self, Tape)
def is_stack(self):
return isinstance(self, Stack)
def is_grid(self):
return isinstance(self, Grid)
def is_register(self):
return isinstance(self, Register)
def is_deque(self):
return isinstance(self, Deque)
This is better but as I said previously, even having these methods in a first place seems to be against the basic principle of object programming.
You should not need to care about the type of an object, the behavior of the object itself should be defined properly. For instance, instead of having a current_value
function checking the model class, you could have a abstract method, overriden in each subclass to have the behavior you need. This applies to make_nilad
.
This needs quite a lot of changes, at this stage, the code looks like http://termbin.com/q8jc .
Then, the same principle applies to overload
but it is trickier - work in progress in http://termbin.com/7x7cd .
Index out of bound error
I had a feeling that the boundary check in ithe while char.isdigit()...
loop was weird and indeed, giving an input ending with 2 (or more) digits, en end up going too far in the input and throwing an exception.
I guess a better (but far from perfect) solution would be:
def parse(code):
tokens =
index = 0
comment = False
while index < len(code):
char = code[index]
if char == 'âÂÂ':
comment = not comment
elif not comment:
if char in operators.keys():
ret = next_index(code, index, ']')
token = operators[char](code[index + 1 : ret])
index = ret
elif char.isdigit() or char == '_':
#######################################
token = char.replace('_', '-')
index += 1
while index < len(code):
char = code[index]
if not char.isdigit():
break
token += char
index += 1
#######################################
else:
token = char
tokens.append(token)
index += 1
return tokens
edited Apr 6 at 14:24
answered Apr 5 at 11:47
Josay
23.8k13580
23.8k13580
add a comment |Â
add a comment |Â
up vote
5
down vote
There's a lot of code here, so I'm going to go through it very sketchily. If you need a point explaining in more detail, ask.
There's no documentation. What is the specification of the language being interpreted? The idea of the challenge to extend and modify the language and its interpreter. The documentation will need to be extended and modified at the same time. How is this going to be done?
There are no docstrings. How are people supposed to modify this code if there's no specification for what the classes mean and the methods do?
PEP8 recommends
UPPER_CASE
for module-level constants.Instead of
digits
, useunicodedata.normalize
.Python concatenates adjacent string constants, so you can create
code_page
without the need for+=
.code_page[32:128]
looks as if it is supposed to be the same as the corresponding code points in ASCII so why not make use of that fact and avoid the risk of typos?There ought to be an
encode
function to correspond todecode
otherwise how are people going to write their programs? Better still, make it a codec so that it can be passed toopen
.What is the purpose of the
Model
class? It doesn't provide any common behaviour.The design doesn't make use of object orientation. Consider a function like:
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 FalseIn an object-oriented program this function would be a method on the
Model
class and its subclasses. In theModel
class this would be an abstract implementation:@abstractmethod
@property
def current_value(self):
"""Docstring here."""and then the various subclasses would override this method with concrete implementations.
It seems wrong for
current_value
to modify the model in some cases (stack and deque) but not in others (tape, grid and register).The
Stack
model seems to be a fairly thin wrapper around a list, so it could be implemented more simply by inheriting fromlist
(as theDeque
model inherits fromcollections.deque
).The
Register
model inherits fromint
but does not make use of this fact. Instead it uses a superfluous attributevalue
.What is a "nilad"? Google says that it is like a monad, but taking no arguments. This does not seem to correspond to anything in the
make_nilad
function. Could you choose a better name?Commands that operate on a
Stack
object (add
,subtract
etc.) ought to be methods on theStack
class. Similarly for tape and grid commands.The arguments to the
overload
function all end with_cmd
; this shared suffix is unnecessary.I think it would be clearer if the
Model
subclasses did their own command dispatch, instead of doing this via thefunctions
mapping and theoverload
function.Input is delivered to the model via
getinput
andgetchar
, which read from standard input. This makes it difficult to write unit tests because you have to make a file or a pipe in order to deliver the input for the test case. It would be better to take input in some other way, for example from an buffer object passed tointerpret
.There are lots of opportunities for ÷-conversion. For example
lambda size, wrap: Tape(size, wrap)
could be justTape
andlambda xlen, ylen, xwrap, ywrap: Grid(xlen, ylen, xwrap, ywrap)
could be justGrid
.It seems strange that an if expression starts with
but ends with
]
. I would have expected it to end with.
next_index
goes wrong if]
appears in a comment. (Maybe]
is not allowed to appear in a comment but if so I think this would need to be checked inparse
.)while_loop
re-parses the body of the loop each time round the loop. Is this deliberate? This seems like a waste of time compared to the normal approach of parsing just once.I recommend splitting up the tasks of tokenization, parsing and interpretation, instead of mixing them as here. (See these answers.)
There are no unit tests.
I got the termnilad
from this golfing language I use. However, when I googled "nilad", all the results were relating to a plant. What did you search?
â caird coinheringaahing
Apr 6 at 17:57
Also, what do you mean by÷-conversion
? I'm not familiar with the term
â caird coinheringaahing
Apr 6 at 18:12
÷-conversion is a term from the û-calculus â it refers to the operation that transforms $ûx.yx$ into $y$.
â Gareth Rees
Apr 6 at 18:33
add a comment |Â
up vote
5
down vote
There's a lot of code here, so I'm going to go through it very sketchily. If you need a point explaining in more detail, ask.
There's no documentation. What is the specification of the language being interpreted? The idea of the challenge to extend and modify the language and its interpreter. The documentation will need to be extended and modified at the same time. How is this going to be done?
There are no docstrings. How are people supposed to modify this code if there's no specification for what the classes mean and the methods do?
PEP8 recommends
UPPER_CASE
for module-level constants.Instead of
digits
, useunicodedata.normalize
.Python concatenates adjacent string constants, so you can create
code_page
without the need for+=
.code_page[32:128]
looks as if it is supposed to be the same as the corresponding code points in ASCII so why not make use of that fact and avoid the risk of typos?There ought to be an
encode
function to correspond todecode
otherwise how are people going to write their programs? Better still, make it a codec so that it can be passed toopen
.What is the purpose of the
Model
class? It doesn't provide any common behaviour.The design doesn't make use of object orientation. Consider a function like:
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 FalseIn an object-oriented program this function would be a method on the
Model
class and its subclasses. In theModel
class this would be an abstract implementation:@abstractmethod
@property
def current_value(self):
"""Docstring here."""and then the various subclasses would override this method with concrete implementations.
It seems wrong for
current_value
to modify the model in some cases (stack and deque) but not in others (tape, grid and register).The
Stack
model seems to be a fairly thin wrapper around a list, so it could be implemented more simply by inheriting fromlist
(as theDeque
model inherits fromcollections.deque
).The
Register
model inherits fromint
but does not make use of this fact. Instead it uses a superfluous attributevalue
.What is a "nilad"? Google says that it is like a monad, but taking no arguments. This does not seem to correspond to anything in the
make_nilad
function. Could you choose a better name?Commands that operate on a
Stack
object (add
,subtract
etc.) ought to be methods on theStack
class. Similarly for tape and grid commands.The arguments to the
overload
function all end with_cmd
; this shared suffix is unnecessary.I think it would be clearer if the
Model
subclasses did their own command dispatch, instead of doing this via thefunctions
mapping and theoverload
function.Input is delivered to the model via
getinput
andgetchar
, which read from standard input. This makes it difficult to write unit tests because you have to make a file or a pipe in order to deliver the input for the test case. It would be better to take input in some other way, for example from an buffer object passed tointerpret
.There are lots of opportunities for ÷-conversion. For example
lambda size, wrap: Tape(size, wrap)
could be justTape
andlambda xlen, ylen, xwrap, ywrap: Grid(xlen, ylen, xwrap, ywrap)
could be justGrid
.It seems strange that an if expression starts with
but ends with
]
. I would have expected it to end with.
next_index
goes wrong if]
appears in a comment. (Maybe]
is not allowed to appear in a comment but if so I think this would need to be checked inparse
.)while_loop
re-parses the body of the loop each time round the loop. Is this deliberate? This seems like a waste of time compared to the normal approach of parsing just once.I recommend splitting up the tasks of tokenization, parsing and interpretation, instead of mixing them as here. (See these answers.)
There are no unit tests.
I got the termnilad
from this golfing language I use. However, when I googled "nilad", all the results were relating to a plant. What did you search?
â caird coinheringaahing
Apr 6 at 17:57
Also, what do you mean by÷-conversion
? I'm not familiar with the term
â caird coinheringaahing
Apr 6 at 18:12
÷-conversion is a term from the û-calculus â it refers to the operation that transforms $ûx.yx$ into $y$.
â Gareth Rees
Apr 6 at 18:33
add a comment |Â
up vote
5
down vote
up vote
5
down vote
There's a lot of code here, so I'm going to go through it very sketchily. If you need a point explaining in more detail, ask.
There's no documentation. What is the specification of the language being interpreted? The idea of the challenge to extend and modify the language and its interpreter. The documentation will need to be extended and modified at the same time. How is this going to be done?
There are no docstrings. How are people supposed to modify this code if there's no specification for what the classes mean and the methods do?
PEP8 recommends
UPPER_CASE
for module-level constants.Instead of
digits
, useunicodedata.normalize
.Python concatenates adjacent string constants, so you can create
code_page
without the need for+=
.code_page[32:128]
looks as if it is supposed to be the same as the corresponding code points in ASCII so why not make use of that fact and avoid the risk of typos?There ought to be an
encode
function to correspond todecode
otherwise how are people going to write their programs? Better still, make it a codec so that it can be passed toopen
.What is the purpose of the
Model
class? It doesn't provide any common behaviour.The design doesn't make use of object orientation. Consider a function like:
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 FalseIn an object-oriented program this function would be a method on the
Model
class and its subclasses. In theModel
class this would be an abstract implementation:@abstractmethod
@property
def current_value(self):
"""Docstring here."""and then the various subclasses would override this method with concrete implementations.
It seems wrong for
current_value
to modify the model in some cases (stack and deque) but not in others (tape, grid and register).The
Stack
model seems to be a fairly thin wrapper around a list, so it could be implemented more simply by inheriting fromlist
(as theDeque
model inherits fromcollections.deque
).The
Register
model inherits fromint
but does not make use of this fact. Instead it uses a superfluous attributevalue
.What is a "nilad"? Google says that it is like a monad, but taking no arguments. This does not seem to correspond to anything in the
make_nilad
function. Could you choose a better name?Commands that operate on a
Stack
object (add
,subtract
etc.) ought to be methods on theStack
class. Similarly for tape and grid commands.The arguments to the
overload
function all end with_cmd
; this shared suffix is unnecessary.I think it would be clearer if the
Model
subclasses did their own command dispatch, instead of doing this via thefunctions
mapping and theoverload
function.Input is delivered to the model via
getinput
andgetchar
, which read from standard input. This makes it difficult to write unit tests because you have to make a file or a pipe in order to deliver the input for the test case. It would be better to take input in some other way, for example from an buffer object passed tointerpret
.There are lots of opportunities for ÷-conversion. For example
lambda size, wrap: Tape(size, wrap)
could be justTape
andlambda xlen, ylen, xwrap, ywrap: Grid(xlen, ylen, xwrap, ywrap)
could be justGrid
.It seems strange that an if expression starts with
but ends with
]
. I would have expected it to end with.
next_index
goes wrong if]
appears in a comment. (Maybe]
is not allowed to appear in a comment but if so I think this would need to be checked inparse
.)while_loop
re-parses the body of the loop each time round the loop. Is this deliberate? This seems like a waste of time compared to the normal approach of parsing just once.I recommend splitting up the tasks of tokenization, parsing and interpretation, instead of mixing them as here. (See these answers.)
There are no unit tests.
There's a lot of code here, so I'm going to go through it very sketchily. If you need a point explaining in more detail, ask.
There's no documentation. What is the specification of the language being interpreted? The idea of the challenge to extend and modify the language and its interpreter. The documentation will need to be extended and modified at the same time. How is this going to be done?
There are no docstrings. How are people supposed to modify this code if there's no specification for what the classes mean and the methods do?
PEP8 recommends
UPPER_CASE
for module-level constants.Instead of
digits
, useunicodedata.normalize
.Python concatenates adjacent string constants, so you can create
code_page
without the need for+=
.code_page[32:128]
looks as if it is supposed to be the same as the corresponding code points in ASCII so why not make use of that fact and avoid the risk of typos?There ought to be an
encode
function to correspond todecode
otherwise how are people going to write their programs? Better still, make it a codec so that it can be passed toopen
.What is the purpose of the
Model
class? It doesn't provide any common behaviour.The design doesn't make use of object orientation. Consider a function like:
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 FalseIn an object-oriented program this function would be a method on the
Model
class and its subclasses. In theModel
class this would be an abstract implementation:@abstractmethod
@property
def current_value(self):
"""Docstring here."""and then the various subclasses would override this method with concrete implementations.
It seems wrong for
current_value
to modify the model in some cases (stack and deque) but not in others (tape, grid and register).The
Stack
model seems to be a fairly thin wrapper around a list, so it could be implemented more simply by inheriting fromlist
(as theDeque
model inherits fromcollections.deque
).The
Register
model inherits fromint
but does not make use of this fact. Instead it uses a superfluous attributevalue
.What is a "nilad"? Google says that it is like a monad, but taking no arguments. This does not seem to correspond to anything in the
make_nilad
function. Could you choose a better name?Commands that operate on a
Stack
object (add
,subtract
etc.) ought to be methods on theStack
class. Similarly for tape and grid commands.The arguments to the
overload
function all end with_cmd
; this shared suffix is unnecessary.I think it would be clearer if the
Model
subclasses did their own command dispatch, instead of doing this via thefunctions
mapping and theoverload
function.Input is delivered to the model via
getinput
andgetchar
, which read from standard input. This makes it difficult to write unit tests because you have to make a file or a pipe in order to deliver the input for the test case. It would be better to take input in some other way, for example from an buffer object passed tointerpret
.There are lots of opportunities for ÷-conversion. For example
lambda size, wrap: Tape(size, wrap)
could be justTape
andlambda xlen, ylen, xwrap, ywrap: Grid(xlen, ylen, xwrap, ywrap)
could be justGrid
.It seems strange that an if expression starts with
but ends with
]
. I would have expected it to end with.
next_index
goes wrong if]
appears in a comment. (Maybe]
is not allowed to appear in a comment but if so I think this would need to be checked inparse
.)while_loop
re-parses the body of the loop each time round the loop. Is this deliberate? This seems like a waste of time compared to the normal approach of parsing just once.I recommend splitting up the tasks of tokenization, parsing and interpretation, instead of mixing them as here. (See these answers.)
There are no unit tests.
edited Apr 6 at 14:06
answered Apr 6 at 14:00
Gareth Rees
41.1k394166
41.1k394166
I got the termnilad
from this golfing language I use. However, when I googled "nilad", all the results were relating to a plant. What did you search?
â caird coinheringaahing
Apr 6 at 17:57
Also, what do you mean by÷-conversion
? I'm not familiar with the term
â caird coinheringaahing
Apr 6 at 18:12
÷-conversion is a term from the û-calculus â it refers to the operation that transforms $ûx.yx$ into $y$.
â Gareth Rees
Apr 6 at 18:33
add a comment |Â
I got the termnilad
from this golfing language I use. However, when I googled "nilad", all the results were relating to a plant. What did you search?
â caird coinheringaahing
Apr 6 at 17:57
Also, what do you mean by÷-conversion
? I'm not familiar with the term
â caird coinheringaahing
Apr 6 at 18:12
÷-conversion is a term from the û-calculus â it refers to the operation that transforms $ûx.yx$ into $y$.
â Gareth Rees
Apr 6 at 18:33
I got the term
nilad
from this golfing language I use. However, when I googled "nilad", all the results were relating to a plant. What did you search?â caird coinheringaahing
Apr 6 at 17:57
I got the term
nilad
from this golfing language I use. However, when I googled "nilad", all the results were relating to a plant. What did you search?â caird coinheringaahing
Apr 6 at 17:57
Also, what do you mean by
÷-conversion
? I'm not familiar with the termâ caird coinheringaahing
Apr 6 at 18:12
Also, what do you mean by
÷-conversion
? I'm not familiar with the termâ caird coinheringaahing
Apr 6 at 18:12
÷-conversion is a term from the û-calculus â it refers to the operation that transforms $ûx.yx$ into $y$.
â Gareth Rees
Apr 6 at 18:33
÷-conversion is a term from the û-calculus â it refers to the operation that transforms $ûx.yx$ into $y$.
â Gareth Rees
Apr 6 at 18:33
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f191311%2fstarting-point-for-a-collaboratively-developed-golfing-language-interpreted-in-p%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
If I understand correctly, at least one of file or cmd CLI argument is required. Am I right ? I am experiencing issue trying to run a simple example from your Github project.
â Josay
Apr 5 at 9:09
@Josay Correct. In order to read and run a file, you must provide the
--file/-f
flag followed by the filename, and the same with the--cmdline/-c
flag for CLIâ caird coinheringaahing
Apr 5 at 9:15