- Befunge 98 with concurrency and file io!
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
# Befunge
|
# Befunge
|
||||||
[Befunge](https://en.wikipedia.org/wiki/Befunge) interpreter and debugger for Befunge 93,
|
[Befunge](https://en.wikipedia.org/wiki/Befunge) interpreter and debugger for Befunge 93/98: [Funges](https://github.com/catseye/Funge-98/blob/master/doc/funge98.markdown#Whatis).
|
||||||
the first of the [Funges](https://web.archive.org/web/20041225010717/http://quadium.net/funge/spec98.html).
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
`pip install befunge@git+https://github.com/wimpomp/befunge.git`
|
`pip install befunge@git+https://github.com/wimpomp/befunge.git`
|
||||||
@@ -9,4 +8,4 @@ the first of the [Funges](https://web.archive.org/web/20041225010717/http://quad
|
|||||||
`befunge --help`
|
`befunge --help`
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
`befunge examples/factorial0.bf -i 20 -d 0.05`
|
`befunge examples/factorial0.bf 20 -d 0.05`
|
||||||
@@ -1,35 +1,19 @@
|
|||||||
from enum import Enum
|
import os
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import curses
|
||||||
from random import randint
|
from random import randint
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from itertools import chain
|
from itertools import chain, takewhile, dropwhile
|
||||||
from curses import wrapper
|
from datetime import datetime
|
||||||
|
from ._version import __version__
|
||||||
|
|
||||||
|
|
||||||
class OperatorException(Exception):
|
class StopExecution(Exception):
|
||||||
def __init__(self, op):
|
pass
|
||||||
super().__init__(f'Could not parse operator {op}')
|
|
||||||
|
|
||||||
|
|
||||||
class Direction(Enum):
|
|
||||||
RIGHT = 0
|
|
||||||
UP = 1
|
|
||||||
LEFT = 2
|
|
||||||
DOWN = 3
|
|
||||||
|
|
||||||
def __add__(self, other):
|
|
||||||
return Direction((self.value + other) % len(Direction))
|
|
||||||
|
|
||||||
def __radd__(self, other):
|
|
||||||
return self + other
|
|
||||||
|
|
||||||
def __sub__(self, other):
|
|
||||||
return Direction((self.value - other) % len(Direction))
|
|
||||||
|
|
||||||
def __rsub__(self, other):
|
|
||||||
return self - other
|
|
||||||
|
|
||||||
|
|
||||||
class Input(list):
|
class Input(list):
|
||||||
@@ -37,49 +21,441 @@ class Input(list):
|
|||||||
return self.pop(0)
|
return self.pop(0)
|
||||||
|
|
||||||
|
|
||||||
class Grid(dict):
|
class Stack(list):
|
||||||
def __init__(self, code=None, version=None):
|
def copy(self):
|
||||||
self.version = version or 'b93'
|
return __class__(super().copy())
|
||||||
self.cursor = True
|
|
||||||
if self.version == 'b93':
|
def pop(self, *args, **kwargs):
|
||||||
self.shape = 80, 25
|
try:
|
||||||
|
return super().pop()
|
||||||
|
except IndexError:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def push(self, value):
|
||||||
|
self.append(value)
|
||||||
|
|
||||||
|
|
||||||
|
class StackStack(list):
|
||||||
|
def __repr__(self):
|
||||||
|
return '\n'.join(str(stack) for stack in self[::-1])
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return __class__(stack.copy() for stack in self)
|
||||||
|
|
||||||
|
def push(self, value):
|
||||||
|
self.append(value)
|
||||||
|
|
||||||
|
|
||||||
|
class IP:
|
||||||
|
def __init__(self, funge, stackstack=None, position=(0, 0), delta=(1, 0), offset=(0, 0), version=982):
|
||||||
|
self.funge = funge
|
||||||
|
self.id = len([ip.id for ip in self.funge.ips]) if hasattr(self.funge, 'ip') else 0
|
||||||
|
self.stackstack = stackstack or StackStack([Stack()])
|
||||||
|
self.position = position
|
||||||
|
self.delta = delta
|
||||||
|
self.offset = offset
|
||||||
|
self.version = version
|
||||||
|
self.string = False
|
||||||
|
self.fingerprint_ops = {}
|
||||||
|
if self.op in (ord(' '), ord(';')):
|
||||||
|
self.advance()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stack(self):
|
||||||
|
if not self.stackstack:
|
||||||
|
self.stackstack.append(Stack())
|
||||||
|
return self.stackstack[-1]
|
||||||
|
|
||||||
|
@stack.setter
|
||||||
|
def stack(self, stack):
|
||||||
|
if self.stackstack:
|
||||||
|
self.stackstack[-1] = stack
|
||||||
else:
|
else:
|
||||||
self.shape = None
|
self.stackstack.append(stack)
|
||||||
self._ip = [0, 0]
|
|
||||||
self.direction = Direction.RIGHT
|
@property
|
||||||
|
def op(self):
|
||||||
|
return self.funge[self.position]
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return __class__(self.funge, self.stackstack.copy(), self.position, self.delta)
|
||||||
|
|
||||||
|
def reverse(self):
|
||||||
|
self.delta = -self.delta[0], -self.delta[1]
|
||||||
|
|
||||||
|
def turn_right(self):
|
||||||
|
self.delta = -self.delta[1], self.delta[0]
|
||||||
|
|
||||||
|
def turn_left(self):
|
||||||
|
self.delta = self.delta[1], -self.delta[0]
|
||||||
|
|
||||||
|
def read_string(self):
|
||||||
|
string = ''
|
||||||
|
while True:
|
||||||
|
f = self.stack.pop()
|
||||||
|
if f == 0:
|
||||||
|
return string
|
||||||
|
else:
|
||||||
|
string += chr(f)
|
||||||
|
|
||||||
|
def read_fingerprint(self):
|
||||||
|
n = self.stack.pop()
|
||||||
|
t = 0
|
||||||
|
for _ in range(n):
|
||||||
|
t *= 256
|
||||||
|
t += self.stack.pop()
|
||||||
|
return t
|
||||||
|
|
||||||
|
def not_implemented(self):
|
||||||
|
print(f'operator {self.op} at {self.position} not implemented', file=self.funge.output)
|
||||||
|
self.reverse()
|
||||||
|
|
||||||
|
def get_info(self, n):
|
||||||
|
time = datetime.now()
|
||||||
|
match n:
|
||||||
|
case 1:
|
||||||
|
yield 15
|
||||||
|
case 2:
|
||||||
|
yield 2**1024 # as much as the memory can hold
|
||||||
|
case 3:
|
||||||
|
yield sum([256 ** i * ord(char) for i, char in enumerate('wpfunge')])
|
||||||
|
case 4:
|
||||||
|
yield __version__.replace('.', '')
|
||||||
|
case 5:
|
||||||
|
yield 1
|
||||||
|
case 6:
|
||||||
|
yield ord(os.path.sep)
|
||||||
|
case 7:
|
||||||
|
yield 2
|
||||||
|
case 8:
|
||||||
|
yield self.id
|
||||||
|
case 9:
|
||||||
|
yield 0
|
||||||
|
case 10:
|
||||||
|
yield from self.position
|
||||||
|
case 11:
|
||||||
|
yield from self.delta
|
||||||
|
case 12:
|
||||||
|
yield from self.offset
|
||||||
|
case 13:
|
||||||
|
yield from self.funge.extent[::2]
|
||||||
|
case 14:
|
||||||
|
yield from self.funge.extent[1::2]
|
||||||
|
case 15:
|
||||||
|
yield (time.year - 1900) * 256 * 256 + time.month * 256 + time.day
|
||||||
|
case 16:
|
||||||
|
yield time.hour * 256 * 256 + time.minute * 256 + time.second
|
||||||
|
case 17:
|
||||||
|
yield len(self.stackstack)
|
||||||
|
case 18:
|
||||||
|
yield from (len(stack) for stack in self.stackstack[::-1])
|
||||||
|
case 19:
|
||||||
|
yield 0
|
||||||
|
yield from [ord(char) for arg in sys.argv[2:] for char in f'{arg}\x00'][::-1]
|
||||||
|
yield 0
|
||||||
|
yield from [ord(char) for char in Path(sys.argv[1]).name][::-1]
|
||||||
|
case 20:
|
||||||
|
yield 0
|
||||||
|
yield from [ord(char) for key, value in os.environ.items() for char in f'{key}={value}\x00'][::-1]
|
||||||
|
case i:
|
||||||
|
i -= 20
|
||||||
|
yield self.stack[-i] if len(self.stack) >= i else 0
|
||||||
|
|
||||||
|
def step(self, k=False):
|
||||||
|
if self.string:
|
||||||
|
match chr(self.op):
|
||||||
|
case '"':
|
||||||
|
self.string = False
|
||||||
|
case s:
|
||||||
|
self.stack.push(ord(s))
|
||||||
|
elif self.op in self.fingerprint_ops:
|
||||||
|
try:
|
||||||
|
self.fingerprint_ops[self.op]()
|
||||||
|
except Exception:
|
||||||
|
self.reverse()
|
||||||
|
elif 0 <= self.op < 255:
|
||||||
|
match chr(self.op):
|
||||||
|
case '+':
|
||||||
|
self.stack.push(self.stack.pop() + self.stack.pop())
|
||||||
|
case '-':
|
||||||
|
b, a = self.stack.pop(), self.stack.pop()
|
||||||
|
self.stack.push(a - b)
|
||||||
|
case '*':
|
||||||
|
self.stack.push(self.stack.pop() * self.stack.pop())
|
||||||
|
case '/':
|
||||||
|
b, a = self.stack.pop(), self.stack.pop()
|
||||||
|
self.stack.push(a // b)
|
||||||
|
case '%':
|
||||||
|
b, a = self.stack.pop(), self.stack.pop()
|
||||||
|
self.stack.push(a % b)
|
||||||
|
case '!':
|
||||||
|
self.stack.push(int(not self.stack.pop()))
|
||||||
|
case '`':
|
||||||
|
self.stack.push(int(self.stack.pop() < self.stack.pop()))
|
||||||
|
case '>':
|
||||||
|
self.delta = 1, 0
|
||||||
|
case '<':
|
||||||
|
self.delta = -1, 0
|
||||||
|
case '^':
|
||||||
|
self.delta = 0, -1
|
||||||
|
case 'v':
|
||||||
|
self.delta = 0, 1
|
||||||
|
case '?':
|
||||||
|
self.delta = ((-1, 0), (1, 0), (0, -1), (0, 1))[randint(0, 3)]
|
||||||
|
case '_':
|
||||||
|
self.delta = ((1, 0), (-1, 0))[bool(self.stack.pop())]
|
||||||
|
case '|':
|
||||||
|
self.delta = ((0, 1), (0, -1))[bool(self.stack.pop())]
|
||||||
|
case '"':
|
||||||
|
self.string = True
|
||||||
|
case ':':
|
||||||
|
a = self.stack.pop()
|
||||||
|
self.stack.push(a)
|
||||||
|
self.stack.push(a)
|
||||||
|
case '\\':
|
||||||
|
b, a = self.stack.pop(), self.stack.pop()
|
||||||
|
self.stack.push(b)
|
||||||
|
self.stack.push(a)
|
||||||
|
case '$':
|
||||||
|
self.stack.pop()
|
||||||
|
case '.':
|
||||||
|
print(str(self.stack.pop()) + ' ', end='', file=self.funge.output)
|
||||||
|
case ',':
|
||||||
|
print(chr(self.stack.pop()), end='', file=self.funge.output)
|
||||||
|
case '#':
|
||||||
|
self.move()
|
||||||
|
case 'p':
|
||||||
|
y, x, a = self.stack.pop(), self.stack.pop(), self.stack.pop()
|
||||||
|
self.funge[x + self.offset[0], y + self.offset[1]] = a
|
||||||
|
case 'g':
|
||||||
|
y, x = self.stack.pop(), self.stack.pop()
|
||||||
|
self.stack.push(self.funge[x + self.offset[0], y + self.offset[1]])
|
||||||
|
case '&':
|
||||||
|
try:
|
||||||
|
self.stack.push(int(''.join(
|
||||||
|
takewhile(lambda i: i.isdigit(), dropwhile(lambda i: not i.isdigit(), self.funge.input())))))
|
||||||
|
except Exception:
|
||||||
|
self.delta = -self.delta[0], -self.delta[1]
|
||||||
|
case '~':
|
||||||
|
try:
|
||||||
|
self.stack.push(ord(self.funge.input()))
|
||||||
|
except Exception:
|
||||||
|
self.reverse()
|
||||||
|
case '@':
|
||||||
|
return
|
||||||
|
case ' ':
|
||||||
|
self.advance()
|
||||||
|
yield self.step()
|
||||||
|
return
|
||||||
|
# 98 from here
|
||||||
|
case '[':
|
||||||
|
self.turn_left()
|
||||||
|
case ']':
|
||||||
|
self.turn_right()
|
||||||
|
case '\'':
|
||||||
|
self.move()
|
||||||
|
self.stack.push(self.op)
|
||||||
|
case '{':
|
||||||
|
n = self.stack.pop()
|
||||||
|
cells = -n * [0] if n < 0 else self.stack[-n:][::-1]
|
||||||
|
for coordinate in self.offset:
|
||||||
|
self.stack.push(coordinate)
|
||||||
|
self.stackstack.push(Stack())
|
||||||
|
for cell in cells:
|
||||||
|
self.stack.push(cell)
|
||||||
|
self.offset = self.next_pos
|
||||||
|
case '}':
|
||||||
|
n = self.stack.pop()
|
||||||
|
cells = -n * [0] if n < 0 else [self.stack.pop() for _ in range(n)][::-1]
|
||||||
|
self.stackstack.pop()
|
||||||
|
y, x = self.stack.pop(), self.stack.pop()
|
||||||
|
self.offset = x, y
|
||||||
|
for cell in cells:
|
||||||
|
self.stack.push(cell)
|
||||||
|
case '=':
|
||||||
|
self.stack.push(os.system(self.read_string()))
|
||||||
|
case '(':
|
||||||
|
# no fingerprints are implemented
|
||||||
|
self.read_fingerprint()
|
||||||
|
# self.fingerprint_ops[] = lambda i: i
|
||||||
|
self.reverse()
|
||||||
|
case ')':
|
||||||
|
self.read_fingerprint()
|
||||||
|
# self.fingerprint_ops.pop()
|
||||||
|
case 'i':
|
||||||
|
file = Path(self.read_string())
|
||||||
|
flags, y0, x0 = self.stack.pop(), self.stack.pop(), self.stack.pop()
|
||||||
|
try:
|
||||||
|
text = file.read_text()
|
||||||
|
if flags % 2:
|
||||||
|
width, height = len(text), 1
|
||||||
|
for x, char in enumerate(text, x0):
|
||||||
|
self.funge[x, y0] = char
|
||||||
|
else:
|
||||||
|
text = text.splitlines()
|
||||||
|
height = len(text)
|
||||||
|
width = max([len(line) for line in text])
|
||||||
|
self.funge.insert_code([line + ' ' * (width - len(line)) for line in text], x0, y0)
|
||||||
|
except Exception:
|
||||||
|
width, height = 0, 0
|
||||||
|
self.stack.push(x0)
|
||||||
|
self.stack.push(y0)
|
||||||
|
self.stack.push(width)
|
||||||
|
self.stack.push(height)
|
||||||
|
case 'j':
|
||||||
|
for _ in range(self.stack.pop()):
|
||||||
|
self.move()
|
||||||
|
case 'k':
|
||||||
|
self.advance()
|
||||||
|
ips = [self]
|
||||||
|
for n in range(self.stack.pop()):
|
||||||
|
ips = [i for ip in ips for i in ip.step(True)]
|
||||||
|
yield from ips
|
||||||
|
return
|
||||||
|
case 'n':
|
||||||
|
self.stack = Stack()
|
||||||
|
case 'o':
|
||||||
|
file = Path(self.read_string())
|
||||||
|
flags, x0, y0, width, height = (self.stack.pop() for _ in range(5))
|
||||||
|
try:
|
||||||
|
if flags % 2:
|
||||||
|
text = '\n'.join([''.join([chr(self.funge[x, y]) for x in range(x0, x0 + width)]).rstrip(' ')
|
||||||
|
for y in range(y0, y0 + height)]).rstrip('\n')
|
||||||
|
else:
|
||||||
|
text = '\n'.join([''.join([chr(self.funge[x, y]) for x in range(x0, x0 + width)])
|
||||||
|
for y in range(y0, y0 + height)])
|
||||||
|
file.write_text(text)
|
||||||
|
except Exception:
|
||||||
|
self.reverse()
|
||||||
|
case 'q':
|
||||||
|
raise StopExecution()
|
||||||
|
case 'r':
|
||||||
|
self.reverse()
|
||||||
|
case 's':
|
||||||
|
self.move()
|
||||||
|
self.funge[self.position] = self.stack.pop()
|
||||||
|
case 't':
|
||||||
|
new = self.copy()
|
||||||
|
new.reverse()
|
||||||
|
yield new.advance()
|
||||||
|
case 'u':
|
||||||
|
if len(self.stackstack) > 1:
|
||||||
|
n = self.stack.pop()
|
||||||
|
for _ in range(abs(n)):
|
||||||
|
toss = self.stackstack.pop()
|
||||||
|
toss.push(self.stack.pop())
|
||||||
|
self.stackstack.push(toss)
|
||||||
|
else:
|
||||||
|
self.reverse()
|
||||||
|
case 'w':
|
||||||
|
b, a = self.stack.pop(), self.stack.pop()
|
||||||
|
if a < b:
|
||||||
|
self.turn_left()
|
||||||
|
elif a > b:
|
||||||
|
self.turn_right()
|
||||||
|
case 'x':
|
||||||
|
dy, dx = self.stack.pop(), self.stack.pop()
|
||||||
|
self.delta = dx, dy
|
||||||
|
case 'y':
|
||||||
|
n = self.stack.pop()
|
||||||
|
if n <= 0:
|
||||||
|
for j in range(1, 21):
|
||||||
|
for i in self.get_info(j):
|
||||||
|
self.stack.push(i)
|
||||||
|
else:
|
||||||
|
for i in self.get_info(n):
|
||||||
|
self.stack.push(i)
|
||||||
|
case 'z':
|
||||||
|
pass
|
||||||
|
case d:
|
||||||
|
if d in '1234567890':
|
||||||
|
self.stack.push(int(d))
|
||||||
|
elif d in 'abcdef':
|
||||||
|
self.stack.push(ord(d) - 87)
|
||||||
|
else:
|
||||||
|
self.not_implemented()
|
||||||
|
else:
|
||||||
|
self.not_implemented()
|
||||||
|
if not k:
|
||||||
|
self.advance()
|
||||||
|
yield self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def next_pos(self):
|
||||||
|
pos = tuple(p + d for p, d in zip(self.position, self.delta))
|
||||||
|
# TODO: analytic solution
|
||||||
|
if not all(a <= p < b for p, a, b in zip(pos, self.funge.extent[::2], self.funge.extent[1::2])):
|
||||||
|
while True:
|
||||||
|
pos = tuple(p - d for p, d in zip(pos, self.delta))
|
||||||
|
if not all(a <= p < b for p, a, b in zip(pos, self.funge.extent[::2], self.funge.extent[1::2])):
|
||||||
|
break
|
||||||
|
pos = tuple(p + d for p, d in zip(pos, self.delta))
|
||||||
|
return pos
|
||||||
|
|
||||||
|
def move(self):
|
||||||
|
self.position = self.next_pos
|
||||||
|
|
||||||
|
def advance(self):
|
||||||
|
""" move the ip to the next valid instruction """
|
||||||
|
if self.string:
|
||||||
|
if self.op == ord(' ') and self.version // 10 > 93:
|
||||||
|
while self.op == ord(' '):
|
||||||
|
self.move()
|
||||||
|
else:
|
||||||
|
self.move()
|
||||||
|
else:
|
||||||
|
while True:
|
||||||
|
if self.op != ord(';'):
|
||||||
|
self.move()
|
||||||
|
if self.op == ord(';'):
|
||||||
|
self.move()
|
||||||
|
while self.op != ord(';'):
|
||||||
|
self.move()
|
||||||
|
self.move()
|
||||||
|
while self.op == ord(' '):
|
||||||
|
self.move()
|
||||||
|
if self.op != ord(';'):
|
||||||
|
break
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class Befunge(dict):
|
||||||
|
def __init__(self, code=None, inputs=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
for y, line in enumerate(code.splitlines()):
|
self.extent = [0, 0, 0, 0] # xl, xr, yt, yb
|
||||||
for x, char in enumerate(line):
|
if code.startswith(r'#!/usr/bin/env befunge') or code.startswith(r'#!/usr/bin/env -S befunge'):
|
||||||
self[(x, y)] = char
|
code = '\n'.join(code.splitlines()[1:])
|
||||||
|
self.insert_code(code)
|
||||||
def wrap(self, value, dim):
|
self.output = None
|
||||||
if self.shape is None:
|
if inputs is None:
|
||||||
return value
|
self.input = input
|
||||||
else:
|
else:
|
||||||
return value % self.shape[dim]
|
self.input = Input(inputs)
|
||||||
|
self.string = False
|
||||||
|
self.steps = 0
|
||||||
|
self.terminated = False
|
||||||
|
self.ips = [IP(self)]
|
||||||
|
|
||||||
@property
|
def insert_code(self, code, x0=0, y0=0):
|
||||||
def x(self):
|
if isinstance(code, str):
|
||||||
return self._ip[0]
|
code = code.splitlines()
|
||||||
|
for y, line in enumerate(code, y0):
|
||||||
@x.setter
|
for x, char in enumerate(line, x0):
|
||||||
def x(self, value):
|
self[x, y] = char
|
||||||
self._ip[0] = self.wrap(value, 0)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def y(self):
|
|
||||||
return self._ip[1]
|
|
||||||
|
|
||||||
@y.setter
|
|
||||||
def y(self, value):
|
|
||||||
self._ip[1] = self.wrap(value, 1)
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
return self.get(key, ord(' '))
|
return self.get(key, ord(' '))
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
super().__setitem__(tuple(self.wrap(k, i) for i, k in enumerate(key)),
|
if key[0] < self.extent[0]:
|
||||||
ord(value) if isinstance(value, str) else value)
|
self.extent[0] = key[0]
|
||||||
|
if key[0] >= self.extent[1]:
|
||||||
|
self.extent[1] = key[0] + 1
|
||||||
|
if key[1] < self.extent[2]:
|
||||||
|
self.extent[2] = key[2]
|
||||||
|
if key[1] >= self.extent[3]:
|
||||||
|
self.extent[3] = key[1] + 1
|
||||||
|
super().__setitem__(key, ord(value) if isinstance(value, str) else value)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
lines = []
|
lines = []
|
||||||
@@ -89,70 +465,16 @@ class Grid(dict):
|
|||||||
while len(lines[y]) <= x:
|
while len(lines[y]) <= x:
|
||||||
lines[y].append(' ')
|
lines[y].append(' ')
|
||||||
lines[y][x] = chr(value) if 32 <= value <= 126 or 161 <= value <= 255 else chr(164)
|
lines[y][x] = chr(value) if 32 <= value <= 126 or 161 <= value <= 255 else chr(164)
|
||||||
lines = [''.join(line) for line in lines]
|
|
||||||
if self.cursor:
|
|
||||||
return '\n'.join(lines[:self.y] +
|
|
||||||
[lines[self.y][:self.x] +
|
|
||||||
'\x1b[37m\x1b[40m' + lines[self.y][self.x] + '\033[0m' +
|
|
||||||
lines[self.y][self.x + 1:]] +
|
|
||||||
lines[self.y + 1:])
|
|
||||||
else:
|
|
||||||
return '\n'.join(lines)
|
|
||||||
|
|
||||||
@property
|
for ip in self.ips:
|
||||||
def op(self):
|
lines[ip.position[1]][ip.position[0]] = f'\x1b[37m\x1b[40m{lines[ip.position[1]][ip.position[0]]}\033[0m'
|
||||||
return self[self.x, self.y]
|
return 'grid:\n' + '\n'.join([''.join(line) for line in lines]) + '\n\n' + \
|
||||||
|
'stacks:\n' + '\n-\n'.join(str(ip.stackstack) for ip in self.ips)
|
||||||
def advance(self):
|
|
||||||
match self.direction:
|
|
||||||
case Direction.RIGHT:
|
|
||||||
self.x += 1
|
|
||||||
case Direction.UP:
|
|
||||||
self.y -= 1
|
|
||||||
case Direction.LEFT:
|
|
||||||
self.x -= 1
|
|
||||||
case Direction.DOWN:
|
|
||||||
self.y += 1
|
|
||||||
|
|
||||||
|
|
||||||
class Stack(list):
|
|
||||||
def pop(self, index=-1):
|
|
||||||
try:
|
|
||||||
return super().pop(index)
|
|
||||||
except IndexError:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def push(self, value):
|
|
||||||
self.append(value)
|
|
||||||
|
|
||||||
|
|
||||||
class Befunge(Grid):
|
|
||||||
def __init__(self, code=None, version=None, inputs=None):
|
|
||||||
super().__init__(code, version)
|
|
||||||
self.output = None
|
|
||||||
if inputs is None:
|
|
||||||
self.input = input
|
|
||||||
else:
|
|
||||||
self.input = Input(inputs)
|
|
||||||
self.stack = Stack()
|
|
||||||
self.string = False
|
|
||||||
self.steps = 0
|
|
||||||
self.terminated = False
|
|
||||||
self.operations = {'b93': '+-*/%!`><^v?_|":\\$.,#pg&~@ 1234567890'}[self.version]
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_file(file, version=None, inputs=None):
|
def from_file(file, inputs=None):
|
||||||
file = Path(file)
|
file = Path(file)
|
||||||
if version is None:
|
return Befunge(file.read_text(), inputs)
|
||||||
match file.suffix:
|
|
||||||
case '.bf':
|
|
||||||
version = 'b93'
|
|
||||||
case suffix:
|
|
||||||
version = suffix.strip('.')
|
|
||||||
return Befunge(file.read_text(), version, inputs)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'grid:\n{super().__repr__()}\n\nstack:\n{self.stack}'
|
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return self
|
return self
|
||||||
@@ -166,146 +488,84 @@ class Befunge(Grid):
|
|||||||
for _ in self:
|
for _ in self:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def debug(self, time_step):
|
def debug(self, time_step=None):
|
||||||
def fun(stdscr):
|
def fun(stdscr):
|
||||||
def scr_input():
|
def scr_input():
|
||||||
height, width = stdscr.getmaxyx()
|
height, width = stdscr.getmaxyx()
|
||||||
stdscr.move(height - 1, 0)
|
stdscr.move(height - 1, 0)
|
||||||
stdscr.clrtoeol()
|
stdscr.clrtoeol()
|
||||||
stdscr.addstr(height - 1, 0, 'input?')
|
stdscr.addstr(height - 1, 0, 'input?'[:width])
|
||||||
stdscr.move(self.y + 1, self.x)
|
|
||||||
stdscr.refresh()
|
stdscr.refresh()
|
||||||
return stdscr.getstr()
|
return stdscr.getstr()
|
||||||
|
|
||||||
|
curses.curs_set(False)
|
||||||
|
curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE)
|
||||||
|
pattern = re.compile(r'\x1b\[[\d;]+m')
|
||||||
self.output = StringIO()
|
self.output = StringIO()
|
||||||
self.cursor = False
|
|
||||||
if not isinstance(self.input, Input):
|
if not isinstance(self.input, Input):
|
||||||
self.input = scr_input
|
self.input = scr_input
|
||||||
stdscr.clear()
|
stdscr.clear()
|
||||||
stdscr.refresh()
|
stdscr.refresh()
|
||||||
|
|
||||||
for b in chain((self,), self):
|
for b in chain((self,), self, (self,)):
|
||||||
height, width = stdscr.getmaxyx()
|
height, width = stdscr.getmaxyx()
|
||||||
stdscr.clear()
|
stdscr.clear()
|
||||||
stdscr.addstr(f'{b}\n\noutput:\n{b.output.getvalue()}\n\nstep:\n{b.steps}')
|
b_str = re.sub(pattern, '', str(b))
|
||||||
if time_step > 0:
|
for y, line in enumerate(f'{b_str}\n\noutput:\n{b.output.getvalue()}\n\nstep:\n{b.steps}'.splitlines()):
|
||||||
stdscr.move(b.y + 1, b.x)
|
if y >= height:
|
||||||
|
break
|
||||||
|
stdscr.addstr(y, 0, line[:width])
|
||||||
|
|
||||||
|
for ip in b.ips:
|
||||||
|
x, y = ip.position
|
||||||
|
if x < width and y < height:
|
||||||
|
stdscr.addstr(y + 1, x, b_str.splitlines()[y + 1][x], curses.color_pair(1))
|
||||||
|
if b.terminated:
|
||||||
|
stdscr.addstr(height - 1, 0, 'Press any key to quit.'[:width])
|
||||||
|
stdscr.refresh()
|
||||||
|
stdscr.getch()
|
||||||
|
elif time_step is None:
|
||||||
|
stdscr.addstr(height - 1, 0, 'Press any key to continue.'[:width])
|
||||||
|
stdscr.refresh()
|
||||||
|
stdscr.getch()
|
||||||
|
else:
|
||||||
stdscr.refresh()
|
stdscr.refresh()
|
||||||
sleep(time_step)
|
sleep(time_step)
|
||||||
else:
|
|
||||||
stdscr.addstr(height - 1, 0, 'Press any key to continue.')
|
|
||||||
stdscr.move(b.y + 1, b.x)
|
|
||||||
stdscr.refresh()
|
|
||||||
stdscr.getch()
|
|
||||||
|
|
||||||
height, width = stdscr.getmaxyx()
|
|
||||||
stdscr.move(height - 1, 0)
|
|
||||||
stdscr.clrtoeol()
|
|
||||||
stdscr.addstr(height - 1, 0, 'Press any key to quit.')
|
|
||||||
stdscr.move(self.y + 1, self.x)
|
|
||||||
stdscr.getch()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wrapper(fun)
|
curses.wrapper(fun)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def step(self, n=1):
|
def step(self, n=1):
|
||||||
m = 0
|
for i in range(n):
|
||||||
while m < n:
|
|
||||||
if self.string:
|
|
||||||
if self.op == ord('"'):
|
|
||||||
self.string = False
|
|
||||||
else:
|
|
||||||
self.stack.push(self.op)
|
|
||||||
elif chr(self.op) in self.operations:
|
|
||||||
match chr(self.op):
|
|
||||||
case '+':
|
|
||||||
self.stack.push(self.stack.pop() + self.stack.pop())
|
|
||||||
case '-':
|
|
||||||
self.stack.push(self.stack.pop(-2) - self.stack.pop())
|
|
||||||
case '*':
|
|
||||||
self.stack.push(self.stack.pop() * self.stack.pop())
|
|
||||||
case '/':
|
|
||||||
self.stack.push(self.stack.pop(-2) // self.stack.pop())
|
|
||||||
case '%':
|
|
||||||
self.stack.push(self.stack.pop(-2) % self.stack.pop())
|
|
||||||
case '!':
|
|
||||||
self.stack.push(int(not self.stack.pop()))
|
|
||||||
case '`':
|
|
||||||
self.stack.push(int(self.stack.pop() < self.stack.pop()))
|
|
||||||
case '>':
|
|
||||||
self.direction = Direction.RIGHT
|
|
||||||
case '<':
|
|
||||||
self.direction = Direction.LEFT
|
|
||||||
case '^':
|
|
||||||
self.direction = Direction.UP
|
|
||||||
case 'v':
|
|
||||||
self.direction = Direction.DOWN
|
|
||||||
case '?':
|
|
||||||
self.direction = Direction(randint(0, 3))
|
|
||||||
case '_':
|
|
||||||
self.direction = Direction(2 * bool(self.stack.pop()))
|
|
||||||
case '|':
|
|
||||||
self.direction = Direction(3 - 2 * bool(self.stack.pop()))
|
|
||||||
case '[':
|
|
||||||
self.direction += 1
|
|
||||||
case ']':
|
|
||||||
self.direction -= 1
|
|
||||||
case '"':
|
|
||||||
self.string = True
|
|
||||||
case ':':
|
|
||||||
if len(self.stack):
|
|
||||||
self.stack.push(self.stack[-1])
|
|
||||||
case '\\':
|
|
||||||
if len(self.stack) > 1:
|
|
||||||
self.stack.push(self.stack.pop(-2))
|
|
||||||
case '$':
|
|
||||||
self.stack.pop()
|
|
||||||
case '.':
|
|
||||||
print(str(self.stack.pop()) + ' ', end='', file=self.output)
|
|
||||||
case ',':
|
|
||||||
print(chr(self.stack.pop()), end='', file=self.output)
|
|
||||||
case '#':
|
|
||||||
self.advance()
|
|
||||||
case 'p':
|
|
||||||
self[self.stack.pop(-2), self.stack.pop()] = self.stack.pop(-3)
|
|
||||||
case 'g':
|
|
||||||
self.stack.push(self[self.stack.pop(-2), self.stack.pop()])
|
|
||||||
case '&':
|
|
||||||
self.stack.push(int(self.input()))
|
|
||||||
case '~':
|
|
||||||
self.stack.push(ord(self.input()))
|
|
||||||
case '@':
|
|
||||||
self.terminated = True
|
|
||||||
case ' ':
|
|
||||||
pass
|
|
||||||
case op:
|
|
||||||
self.stack.append(int(op))
|
|
||||||
else:
|
|
||||||
raise OperatorException(self.op)
|
|
||||||
self.advance()
|
|
||||||
if not (not self.string and self.op == ord(' ')):
|
|
||||||
m += 1
|
|
||||||
self.steps += 1
|
self.steps += 1
|
||||||
|
try:
|
||||||
|
self.ips = [i for ip in self.ips for i in ip.step()]
|
||||||
|
except StopExecution:
|
||||||
|
self.ips = []
|
||||||
|
if not self.ips:
|
||||||
|
self.terminated = True
|
||||||
|
return self
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = ArgumentParser(description='Display info and save as tif')
|
parser = ArgumentParser(description='Funge interpreter and debugger')
|
||||||
group = parser.add_mutually_exclusive_group()
|
parser.add_argument('input', help='funge code file or string')
|
||||||
group.add_argument('file', help='funge code file', nargs='?')
|
parser.add_argument('args', help='arguments to the funge (& or ~)', nargs='*')
|
||||||
group.add_argument('-s', '--string', help='funge code string', default=None)
|
parser.add_argument('-v', '--version', help='show interpreter\'s version number and exit',
|
||||||
parser.add_argument('-v', '--version', help='funge version: b93, b98', type=str, default=None)
|
action='version', version=__version__)
|
||||||
parser.add_argument('-d', '--debug', help='debug, steps / second, 0: continue on key press', type=float, default=-1)
|
parser.add_argument('-d', '--debug', help='debug, step on key press or steps / second',
|
||||||
parser.add_argument('-i', '--inputs', help='inputs for when befunge asks for it', nargs='*')
|
type=float, default=False, nargs='?')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if args.file:
|
|
||||||
befunge = Befunge.from_file(args.file, args.version, args.inputs)
|
|
||||||
else:
|
|
||||||
befunge = Befunge(args.string, args.version, args.inputs)
|
|
||||||
|
|
||||||
if args.debug < 0:
|
if Path(args.input).exists():
|
||||||
|
befunge = Befunge.from_file(args.input, args.args or None)
|
||||||
|
else:
|
||||||
|
befunge = Befunge(args.input, args.args or None)
|
||||||
|
|
||||||
|
if args.debug is False:
|
||||||
befunge.run()
|
befunge.run()
|
||||||
else:
|
else:
|
||||||
befunge.debug(args.debug)
|
befunge.debug(args.debug)
|
||||||
|
|||||||
12
examples/99.bf
Executable file
12
examples/99.bf
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
|
992+*: v: <
|
||||||
|
v" ".:<_091+".reeb fo selttob erom on ,llaw eht no reeb fo selttob erom oN">:v
|
||||||
|
,v"Go to the store and buy some more, 99 bottles of beer on the wall."+910<^,_v
|
||||||
|
>:#,_@>:101-*+v >$0" ,llaw eht no reeb fo ">:#,_$:.1v ^ $<
|
||||||
|
>0\ >> ^ v_0"elttob">:#,_$\:!| >091+".reeb fo ">:#,_$ v
|
||||||
|
>0"selttob"^ >1-!| >" ",\v
|
||||||
|
>$2\ ^\2<," ".< >091+:".llaw eht no reeb fo "v
|
||||||
|
^_v > ^ |:< v!:<"Take one down and pass it around, "0< v!:<
|
||||||
|
!,v ^ ^:-1$_, ^ v$_>,^
|
||||||
|
>^ ^ <
|
||||||
|
^:<"no more "0< > ^
|
||||||
4
examples/cat_v.bf
Executable file
4
examples/cat_v.bf
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
|
w4110'vin10
|
||||||
|
"<v"
|
||||||
|
>>#,:#<_@
|
||||||
3
examples/concurrent.bf
Executable file
3
examples/concurrent.bf
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
|
v
|
||||||
|
@000_1t111@
|
||||||
9
examples/dna.bf
Executable file
9
examples/dna.bf
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
|
7^DN>vA
|
||||||
|
v_#v? v
|
||||||
|
7^<""""
|
||||||
|
3 ACGT
|
||||||
|
90!""""
|
||||||
|
4*:>>>v
|
||||||
|
+8^-1,<
|
||||||
|
> ,+,@)
|
||||||
0
examples/ex1.bf
Normal file → Executable file
0
examples/ex1.bf
Normal file → Executable file
0
examples/factorial.bf
Normal file → Executable file
0
examples/factorial.bf
Normal file → Executable file
2
examples/factorial0.bf
Normal file → Executable file
2
examples/factorial0.bf
Normal file → Executable file
@@ -1,5 +1,5 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
v
|
v
|
||||||
0
|
|
||||||
&
|
&
|
||||||
>>:1v
|
>>:1v
|
||||||
|:-<
|
|:-<
|
||||||
|
|||||||
3
examples/factorial_eso.bf
Executable file
3
examples/factorial_eso.bf
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
|
&>:1-:v v *_$.@
|
||||||
|
^ _$>\:^
|
||||||
1
examples/factorial_heap.bf
Normal file → Executable file
1
examples/factorial_heap.bf
Normal file → Executable file
@@ -1,2 +1,3 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
&:>00p1-::v
|
&:>00p1-::v
|
||||||
^ *g00 _g.25*,@
|
^ *g00 _g.25*,@
|
||||||
|
|||||||
2
examples/fibonacci.bf
Executable file
2
examples/fibonacci.bf
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
|
j1\:b0p+:.' 1
|
||||||
25
examples/guess.bf
Executable file
25
examples/guess.bf
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
|
vv < <
|
||||||
|
2
|
||||||
|
^ v<
|
||||||
|
v1<?>3v4
|
||||||
|
^ ^
|
||||||
|
> >?> ?>5^
|
||||||
|
v v
|
||||||
|
v9<?>7v6
|
||||||
|
v v<
|
||||||
|
8
|
||||||
|
> > ^
|
||||||
|
vv < <
|
||||||
|
2
|
||||||
|
^ v<
|
||||||
|
v1<?>3v4
|
||||||
|
^ ^
|
||||||
|
> >?> ?>5^
|
||||||
|
v v v ,*25 <<
|
||||||
|
v9<?>7v6 ,,
|
||||||
|
v v< ""
|
||||||
|
8 ><
|
||||||
|
> > ^ ""v
|
||||||
|
>*: >0"!rebmun tupnI">:#,_$25*,:&:99p`|^< _0"!niw uoY">:#,_$25*,@
|
||||||
|
^ < >:99g01-*+^
|
||||||
16
examples/guess2.bf
Executable file
16
examples/guess2.bf
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
|
v>>> > v>>> > v
|
||||||
|
012 3 012 3
|
||||||
|
^?^ ^?^
|
||||||
|
>>?#v?4>>?#v?4v
|
||||||
|
v?v v?v
|
||||||
|
98765 98765
|
||||||
|
>>>>> ^>>>>> v
|
||||||
|
v 0 + * + : 5 <
|
||||||
|
>"!sseuG">:#,_v
|
||||||
|
0v_v#:-&:,+:5$<
|
||||||
|
, v>0"!niw uoY"
|
||||||
|
+0>:#,_$5:+,@
|
||||||
|
:>`0\"!"\v
|
||||||
|
v"small"_"gib"
|
||||||
|
^>" ooT">:#,_$5
|
||||||
2
examples/hello_b98.bf
Executable file
2
examples/hello_b98.bf
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
|
<>:#,_# @#"Hello, World!"
|
||||||
0
examples/hello_world.bf
Normal file → Executable file
0
examples/hello_world.bf
Normal file → Executable file
0
examples/hello_world2.bf
Normal file → Executable file
0
examples/hello_world2.bf
Normal file → Executable file
9
examples/mill.bf
Executable file
9
examples/mill.bf
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
|
] v
|
||||||
|
>v
|
||||||
|
v?t1?
|
||||||
|
1 t
|
||||||
|
t 1
|
||||||
|
?1t?v
|
||||||
|
v< <
|
||||||
|
>29*y.@
|
||||||
0
examples/multiplier.bf
Normal file → Executable file
0
examples/multiplier.bf
Normal file → Executable file
4
examples/pi.bf
Executable file
4
examples/pi.bf
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
|
"^a&EPm=kY}t/qYC+i9wHye$m N@~x+"v
|
||||||
|
"|DsY<"-"z6n<[Yo2x|UP5VD:">:#v_@>
|
||||||
|
-:19+/"0"+,19+%"0"+, ^ >39*
|
||||||
2
examples/quine1.bf
Executable file
2
examples/quine1.bf
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
|
01->1# +# :# 0# g# ,# :# 5# 8# *# 4# +# -# _@
|
||||||
0
examples/random.bf
Normal file → Executable file
0
examples/random.bf
Normal file → Executable file
6
examples/random_n.bf
Executable file
6
examples/random_n.bf
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
|
& :v>00g2/.@
|
||||||
|
v00_^#!`/2g00:<
|
||||||
|
>0p:1>>:10p` !|
|
||||||
|
>+00p^?<*2g01:<
|
||||||
|
^ g00:<
|
||||||
5
examples/sieve.bf
Executable file
5
examples/sieve.bf
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
|
2>:3g" "-!v\ g30 <
|
||||||
|
|!`"O":+1_:.:03p>03g+:"O"`|
|
||||||
|
@ ^ p3\" ":<
|
||||||
|
2 234567890123456789012345678901234567890123456789012345678901234567890123456789
|
||||||
23
examples/soup.bf
Executable file
23
examples/soup.bf
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
|
060p070 p'O80v
|
||||||
|
pb2*90p4$4> $4$>v>
|
||||||
|
v4$>4$>4$>4$># ARGH>!
|
||||||
|
<{[BEFUNGE_97]}> FUNGE!
|
||||||
|
##:-:## #####* 4$*>4$ >060p> 60g80g -!#v_ 60g1+ 60p60v
|
||||||
|
#vOOGAH **>4$>^!!eg nufeB^ $4$4$4 $4<v#<<v-*2a:: v7-1g<
|
||||||
|
#>70g>90g-! #@_^Befunge!! 123456 123456 VvVv!#!>Weird! >0ggv*
|
||||||
|
^$4$4p07+1g07 ,a<$4< <$4$4< <$4$4< <$4$4< <<#<*-=-=-=-=-* -=-=v*
|
||||||
|
::48*-#v_>,4$> 4$4$4 $4$4$ 4$4$4$ 4$4$4$ 4$^*!* XXXXXX XXX>
|
||||||
|
BOINK>$60g1-7 0g+d2* %'A+,1 $1$1$1 $1$1$1 $>^<$ HAR!!! 8888
|
||||||
|
Befunge_is such_a pretty langua ge,_is n't_i t?_It_ 8888
|
||||||
|
looks_so much_l ike_li ne_noi se_and it's_ STILL_ ‘88’
|
||||||
|
Turing- Complet e!_Cam ouflag e_your code!! Confu se_the
|
||||||
|
hell_out of_every one_re ading_ your_co de._Oh, AND_y ou.:-) ,o88o.
|
||||||
|
Once_this_thing_i s_code d,_rea ding_it_back_ver ges_on the_imp 888888
|
||||||
|
ossible._Obfusc ate_the_obfus cated!_Befunge_ debuggers_are__ 888888
|
||||||
|
your_friends! By:_Alexios Chouchou las... X-X-X-X-X-X-X! 888888
|
||||||
|
-=*##*=- \*****/ 9797* -=97=- !@-*= ***** ‘"88P’
|
||||||
|
*!@-*
|
||||||
|
=*!@-
|
||||||
|
-=*!@
|
||||||
|
@-=*!
|
||||||
2
examples/test.bf
Executable file
2
examples/test.bf
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env befunge
|
||||||
|
45*1-y>>#,:#<_$:w@
|
||||||
1
examples/v
Executable file
1
examples/v
Executable file
@@ -0,0 +1 @@
|
|||||||
|
Hello world!
|
||||||
Reference in New Issue
Block a user