retroforth/vm/nga-python/retro.py

663 lines
14 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
# Nga: a Virtual Machine
# Copyright (c) 2010 - 2020, Charles Childers
# Floating Point I/O by Arland Childers, (c) 2020
# Optimizations and process() rewrite by Greg Copeland
# -----------------------------------------------------
import os, sys, math, time, struct, random, datetime
from struct import pack, unpack
from ClockDevice import Clock
from RNGDevice import RNG
from FloatStack import FloatStack
from IntegerStack import IntegerStack
from Memory import Memory
ip = 0
stack = IntegerStack()
address = []
memory = []
clock = Clock()
rng = RNG()
floats = FloatStack()
afloats = FloatStack()
files = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
def file_open():
global files
slot = 0
i = 1
while i < 8:
if files[i] == 0:
slot = i
i += 1
mode = stack.pop()
name = extract_string(stack.pop())
if slot > 0:
if mode == 0:
if os.path.exists(name):
files[slot] = open(name, "r")
else:
slot = 0
elif mode == 1:
files[slot] = open(name, "w")
elif mode == 2:
files[slot] = open(name, "a")
elif mode == 3:
if os.path.exists(name):
files[slot] = open(name, "r+")
else:
slot = 0
return slot
def file_read():
global stack
slot = stack.pop()
return ord(files[slot].read(1))
def file_write():
global stack
slot = stack.pop()
files[slot].write(chr(stack.pop()))
return 1
def file_close():
global files, stack
slot = stack.pop()
files[slot].close()
files[slot] = 0
return 0
def file_pos():
global stack
slot = stack.pop()
return files[slot].tell()
def file_seek():
global stack
slot = stack.pop()
pos = stack.pop()
return files[slot].seek(pos, 0)
def file_size():
global stack
slot = stack.pop()
at = files[slot].tell()
files[slot].seek(0, 2) # SEEK_END
end = files[slot].tell()
files[slot].seek(at, 0) # SEEK_SET
return end
def file_delete():
global stack
name = extract_string(stack.pop())
i = 0
if os.path.exists(name):
os.remove(name)
i = 1
return i
def div_mod(a, b):
x = abs(a)
y = abs(b)
q, r = divmod(x, y)
if a < 0 and b < 0:
r *= -1
elif a > 0 and b < 0:
q *= -1
elif a < 0 and b > 0:
r *= -1
q *= -1
return q, r
def find_entry(named):
header = memory[2]
Done = False
while header != 0 and not Done:
if named == extract_string(header + 3):
Done = True
else:
header = memory[header]
return header
def get_input():
return ord(sys.stdin.read(1))
def display_character():
global stack
if stack.tos() > 0 and stack.tos() < 128:
if stack.tos() == 8:
sys.stdout.write(chr(stack.pop()))
sys.stdout.write(chr(32))
sys.stdout.write(chr(8))
else:
sys.stdout.write(chr(stack.pop()))
else:
sys.stdout.write("\033[2J\033[1;1H")
stack.pop()
sys.stdout.flush()
def i_no():
pass
def i_li():
global ip, memory, stack, address
ip += 1
stack.append(memory[ip])
def i_du():
stack.dup()
def i_dr():
stack.drop()
def i_sw():
stack.swap()
def i_pu():
global ip, memory, stack, address
address.append(stack.pop())
def i_po():
global ip, memory, stack, address
stack.push(address.pop())
def i_ju():
global ip, memory, stack, address
ip = stack.pop() - 1
def i_ca():
global ip, memory, stack, address
address.append(ip)
ip = stack.pop() - 1
def i_cc():
global ip, memory, stack, address
target = stack.pop()
if stack.pop() != 0:
address.append(ip)
ip = target - 1
def i_re():
global ip, memory, stack, address
ip = address.pop()
def i_eq():
global ip, memory, stack, address
a = stack.pop()
b = stack.pop()
if b == a:
stack.append(-1)
else:
stack.append(0)
def i_ne():
global ip, memory, stack, address
a = stack.pop()
b = stack.pop()
if b != a:
stack.append(-1)
else:
stack.append(0)
def i_lt():
global ip, memory, stack, address
a = stack.pop()
b = stack.pop()
if b < a:
stack.append(-1)
else:
stack.append(0)
def i_gt():
global ip, memory, stack, address
a = stack.pop()
b = stack.pop()
if b > a:
stack.append(-1)
else:
stack.append(0)
def i_fe():
target = stack.pop()
if target == -1:
stack.push(stack.depth())
elif target == -2:
stack.push(len(address))
elif target == -3:
stack.push(len(memory))
elif target == -4:
stack.push(2147483648)
elif target == -5:
stack.push(2147483647)
else:
stack.push(memory[target])
def i_st():
global ip, memory, stack, address
mi = stack.pop()
memory[mi] = stack.pop()
def i_ad():
t = stack.pop()
v = stack.pop()
stack.push(unpack("=l", pack("=L", (t + v) & 0xFFFFFFFF))[0])
def i_su():
t = stack.pop()
v = stack.pop()
stack.push(unpack("=l", pack("=L", (v - t) & 0xFFFFFFFF))[0])
def i_mu():
t = stack.pop()
v = stack.pop()
stack.push(unpack("=l", pack("=L", (v * t) & 0xFFFFFFFF))[0])
def i_di():
t = stack.pop()
v = stack.pop()
b, a = div_mod(v, t)
stack.push(unpack("=l", pack("=L", a & 0xFFFFFFFF))[0])
stack.push(unpack("=l", pack("=L", b & 0xFFFFFFFF))[0])
def i_an():
global ip, memory, stack, address
t = stack.pop()
m = stack.pop()
stack.push(m &= t)
def i_or():
global ip, memory, stack, address
t = stack.pop()
m = stack.pop()
stack.push(m |= t)
def i_xo():
global ip, memory, stack, address
t = stack.pop()
m = stack.pop()
stack.push(m ^= t)
def i_sh():
global ip, memory, stack, address
t = stack.pop()
v = stack.pop()
if t < 0:
v <<= t * -1
else:
v >>= t
stack.push(v)
def i_zr():
global ip, memory, stack, address
if stack.tos() == 0:
stack.pop()
ip = address.pop()
def i_ha():
global ip, memory, stack, address
ip = 9000000
def i_ie():
stack.append(6)
def i_iq():
device = stack.pop()
if device == 0: # generic output
stack.append(0)
stack.append(0)
if device == 1: # floating point
stack.append(1)
stack.append(2)
if device == 2: # files
stack.append(0)
stack.append(4)
if device == 3: # rng
stack.append(0)
stack.append(10)
if device == 4: # time
stack.append(0)
stack.append(5)
if device == 5: # scripting
stack.append(0)
stack.append(9)
float_instr = {
0: lambda: floats.push(float(stack.pop())), # number to float
1: lambda: floats.push(float(extract_string(stack.pop()))), # string to float
2: lambda: stack.append(int(floats.pop())), # float to number
3: lambda: inject_string(str(floats.pop()), stack.pop()), # float to string
4: lambda: floats.add(), # add
5: lambda: floats.sub(), # sub
6: lambda: floats.mul(), # mul
7: lambda: floats.div(), # div
8: lambda: floats.floor(), # floor
9: lambda: floats.ceil(), # ceil
10: lambda: floats.sqrt(), # sqrt
11: lambda: stack.append(floats.eq()), # eq
12: lambda: stack.append(floats.neq()), # -eq
13: lambda: stack.append(floats.lt()), # lt
14: lambda: stack.append(floats.gt()), # gt
15: lambda: stack.append(floats.depth()), # depth
16: lambda: floats.dup(), # dup
17: lambda: floats.drop(), # drop
18: lambda: floats.swap(), # swap
19: lambda: floats.log(), # log
20: lambda: floats.pow(), # pow
21: lambda: floats.sin(), # sin
22: lambda: floats.cos(), # cos
23: lambda: floats.tan(), # tan
24: lambda: floats.asin(), # asin
25: lambda: floats.atan(), # atan
26: lambda: floats.acos(), # acos
27: lambda: afloats.push(floats.pop()), # to alt
28: lambda: floats.push(afloats.pop()), # from alt
29: lambda: stack.append(afloats.depth()), # alt. depth
}
files_instr = {
0: lambda: stack.append(file_open()),
1: lambda: file_close(),
2: lambda: stack.append(file_read()),
3: lambda: file_write(),
4: lambda: stack.append(file_pos()),
5: lambda: file_seek(),
6: lambda: stack.append(file_size()),
7: lambda: file_delete(),
8: lambda: 1 + 1,
}
rng_instr = {0: lambda: stack.append(rng())}
clock_instr = {
0: lambda: stack.append(int(time.time())),
1: lambda: stack.append(clock["day"]),
2: lambda: stack.append(clock["month"]),
3: lambda: stack.append(clock["year"]),
4: lambda: stack.append(clock["hour"]),
5: lambda: stack.append(clock["minute"]),
6: lambda: stack.append(clock["second"]),
7: lambda: stack.append(clock["day_utc"]),
8: lambda: stack.append(clock["month_utc"]),
9: lambda: stack.append(clock["year_utc"]),
10: lambda: stack.append(clock["hour_utc"]),
11: lambda: stack.append(clock["minute_utc"]),
12: lambda: stack.append(clock["second_utc"]),
}
def i_ii():
global stack, memory, floats, files
device = stack.pop()
if device == 0: # generic output
display_character()
if device == 1: # floating point
action = stack.pop()
float_instr[int(action)]()
if device == 2: # files
action = stack.pop()
files_instr[int(action)]()
if device == 3: # rng
rng_instr[0]()
if device == 4: # clock
action = stack.pop()
clock_instr[int(action)]()
if device == 5: # scripting
action = stack.pop()
if action == 0:
stack.append(len(sys.argv) - 2)
if action == 1:
a = stack.pop()
b = stack.pop()
stack.append(inject_string(sys.argv[a + 2], b))
if action == 2:
run_file(extract_string(stack.pop()))
if action == 3:
b = stack.pop()
stack.append(inject_string(sys.argv[0], b))
instructions = [
i_no,
i_li,
i_du,
i_dr,
i_sw,
i_pu,
i_po,
i_ju,
i_ca,
i_cc,
i_re,
i_eq,
i_ne,
i_lt,
i_gt,
i_fe,
i_st,
i_ad,
i_su,
i_mu,
i_di,
i_an,
i_or,
i_xo,
i_sh,
i_zr,
i_ha,
i_ie,
i_iq,
i_ii,
]
def validate_opcode(opcode):
I0 = opcode & 0xFF
I1 = (opcode >> 8) & 0xFF
I2 = (opcode >> 16) & 0xFF
I3 = (opcode >> 24) & 0xFF
if (
(I0 >= 0 and I0 <= 29)
and (I1 >= 0 and I1 <= 29)
and (I2 >= 0 and I2 <= 29)
and (I3 >= 0 and I3 <= 29)
):
return True
else:
return False
def extract_string(at):
s = ""
while memory[at] != 0:
s = s + chr(memory[at])
at = at + 1
return s
def inject_string(s, to):
global memory
i = to
for c in s:
memory[i] = ord(c)
i = i + 1
memory[i] = 0
return to
def execute(word, notfound, output="console"):
global ip, memory, stack, address
ip = word
if len(address) == 0:
address.append(0)
while ip < 100000:
if ip == notfound:
print("ERROR: word not found!")
opcode = memory[ip]
if validate_opcode(opcode):
I0 = opcode & 0xFF
I1 = (opcode >> 8) & 0xFF
I2 = (opcode >> 16) & 0xFF
I3 = (opcode >> 24) & 0xFF
if I0 != 0:
instructions[I0]()
if I1 != 0:
instructions[I1]()
if I2 != 0:
instructions[I2]()
if I3 != 0:
instructions[I3]()
else:
print("Invalid Bytecode: ", opcode, "at", ip)
ip = 2000000
if len(address) == 0:
ip = 2000000
ip = ip + 1
return
def load_image():
global memory
cells = int(os.path.getsize("ngaImage") / 4)
f = open("ngaImage", "rb")
memory = list(struct.unpack(cells * "i", f.read()))
f.close()
remaining = 1000000 - cells
memory.extend([0] * remaining)
def run():
done = False
while not done:
line = input("\nOk> ")
if line == "bye":
done = True
else:
for token in line.split(" "):
inject_string(token, 1025)
stack.append(1025)
execute(interpreter, not_found)
def run_file(file):
if not os.path.exists(file):
print("File '{0}' not found".format(file))
return
in_block = False
with open(file, "r") as source:
for line in source.readlines():
if line.rstrip() == "~~~":
in_block = not in_block
elif in_block:
for token in line.strip().split(" "):
if token != "":
inject_string(token, 1025)
stack.append(1025)
execute(interpreter, not_found)
def update_image():
import requests
import shutil
data = requests.get("http://forth.works/ngaImage", stream=True)
with open("ngaImage", "wb") as f:
data.raw.decode_content = True
shutil.copyfileobj(data.raw, f)
def interactive_startup():
cmd = input("Would you like to download the latest image?\n (y/n)\t")
if cmd.lower().strip() == "y":
update_image()
load_image()
cmd = input("Would you like to run a file?\n (y/n)\t")
if cmd.lower().strip() == "n":
run()
else:
cmd = input("Enter the name of the file you wish to run\t")
if os.path.exists(cmd):
run_file(cmd)
else:
print(" Error: File not found. ")
if __name__ == "__main__":
load_image()
interpreter = memory[find_entry("interpret") + 1]
not_found = memory[find_entry("err:notfound") + 1]
if len(sys.argv) == 1:
run()
if len(sys.argv) == 2:
run_file(sys.argv[1])
sources = []
if len(sys.argv) > 2:
i = 1
e = len(sys.argv)
while i < e:
param = sys.argv[i]
if param == "-f":
i += 1
sources.append(sys.argv[i])
i += 1
if len(sys.argv) > 2 and sys.argv[1][0] != "-":
run_file(sys.argv[1])
else:
for source in sources:
run_file(source)