retroforth/vm/nga-python/retro.py
crc 68d2baa2bd nga-python: add populate_dictionary(), find_entry() now checks cached dictionary first (working on #20)
FossilOrigin-Name: 1229de35d262ac6cc1d7b6bde9477165548c5d4ebc35056c24ff8c864f1ac06a
2020-12-16 14:59:08 +00:00

511 lines
15 KiB
Python

#!/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 FileSystemDevice import FileSystem
from FloatStack import FloatStack
from IntegerStack import IntegerStack
from Memory import Memory
ip = 0
stack = IntegerStack()
address = []
memory = Memory("ngaImage", 1000000)
clock = Clock()
rng = RNG()
floats = FloatStack()
afloats = FloatStack()
files = FileSystem()
class Retro:
def __init__(self):
self.ip = 0
self.stack = IntegerStack()
self.address = IntegerStack()
self.memory = Memory("ngaImage", 1000000)
self.clock = Clock()
self.rng = RNG()
self.files = FileSystem()
self.floats = FloatStack()
self.afloats = FloatStack()
self.Dictionary = dict()
self.populate_dictionary()
self.Cached = dict()
self.Cached['interpreter'] = self.memory.fetch(self.find_entry("interpret") + 1)
self.Cached['not_found'] = self.memory.fetch(self.find_entry("err:notfound") + 1)
self.Cached['s:eq?'] = self.memory.fetch(self.find_entry("s:eq?") + 1)
self.instructions = [
self.i_nop,
self.i_lit,
self.i_dup,
self.i_drop,
self.i_swap,
self.i_push,
self.i_pop,
self.i_jump,
self.i_call,
self.i_ccall,
self.i_return,
self.i_eq,
self.i_neq,
self.i_lt,
self.i_gt,
self.i_fetch,
self.i_store,
self.i_add,
self.i_subtract,
self.i_multiply,
self.i_divmod,
self.i_and,
self.i_or,
self.i_xor,
self.i_shift,
self.i_zreturn,
self.i_halt,
self.i_ienumerate,
self.i_iquery,
self.i_iinvoke,
]
def div_mod(self, 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 populate_dictionary(self):
header = self.memory.fetch(2)
while header != 0:
named = self.extract_string(header + 3)
self.Dictionary[named] = header
header = self.memory.fetch(header)
def find_entry(self, named):
if named in self.Dictionary:
return self.Dictionary[named]
header = self.memory.fetch(2)
Done = False
while header != 0 and not Done:
if named == self.extract_string(header + 3):
self.Dictionary[named] = header
Done = True
else:
header = self.memory.fetch(header)
return header
def get_input(self):
return ord(sys.stdin.read(1))
def display_character(self):
if self.stack.tos() > 0 and self.stack.tos() < 128:
if self.stack.tos() == 8:
sys.stdout.write(chr(self.stack.pop()))
sys.stdout.write(chr(32))
sys.stdout.write(chr(8))
else:
sys.stdout.write(chr(self.stack.pop()))
else:
sys.stdout.write("\033[2J\033[1;1H")
self.stack.pop()
sys.stdout.flush()
def i_nop(self):
pass
def i_lit(self):
self.ip += 1
self.stack.push(self.memory.fetch(self.ip))
def i_dup(self):
self.stack.dup()
def i_drop(self):
self.stack.drop()
def i_swap(self):
self.stack.swap()
def i_push(self):
self.address.push(self.stack.pop())
def i_pop(self):
self.stack.push(self.address.pop())
def i_jump(self):
self.ip = self.stack.pop() - 1
def i_call(self):
self.address.push(self.ip)
self.ip = self.stack.pop() - 1
def i_ccall(self):
target = self.stack.pop()
if self.stack.pop() != 0:
self.address.push(self.ip)
self.ip = target - 1
def i_return(self):
self.ip = self.address.pop()
def i_eq(self):
a = self.stack.pop()
b = self.stack.pop()
if b == a:
self.stack.push(-1)
else:
self.stack.push(0)
def i_neq(self):
a = self.stack.pop()
b = self.stack.pop()
if b != a:
self.stack.push(-1)
else:
self.stack.push(0)
def i_lt(self):
a = self.stack.pop()
b = self.stack.pop()
if b < a:
self.stack.push(-1)
else:
self.stack.push(0)
def i_gt(self):
a = self.stack.pop()
b = self.stack.pop()
if b > a:
self.stack.push(-1)
else:
self.stack.push(0)
def i_fetch(self):
target = self.stack.pop()
if target == -1:
self.stack.push(self.stack.depth())
elif target == -2:
self.stack.push(self.address.depth())
elif target == -3:
self.stack.push(self.memory.size())
elif target == -4:
self.stack.push(2147483648)
elif target == -5:
self.stack.push(2147483647)
else:
self.stack.push(self.memory.fetch(target))
def i_store(self):
mi = self.stack.pop()
self.memory.store(self.stack.pop(), mi)
def i_add(self):
t = self.stack.pop()
v = self.stack.pop()
self.stack.push(unpack("=l", pack("=L", (t + v) & 0xFFFFFFFF))[0])
def i_subtract(self):
t = self.stack.pop()
v = self.stack.pop()
self.stack.push(unpack("=l", pack("=L", (v - t) & 0xFFFFFFFF))[0])
def i_multiply(self):
t = self.stack.pop()
v = self.stack.pop()
self.stack.push(unpack("=l", pack("=L", (v * t) & 0xFFFFFFFF))[0])
def i_divmod(self):
t = self.stack.pop()
v = self.stack.pop()
b, a = self.div_mod(v, t)
self.stack.push(unpack("=l", pack("=L", a & 0xFFFFFFFF))[0])
self.stack.push(unpack("=l", pack("=L", b & 0xFFFFFFFF))[0])
def i_and(self):
t = self.stack.pop()
m = self.stack.pop()
self.stack.push(m & t)
def i_or(self):
t = self.stack.pop()
m = self.stack.pop()
self.stack.push(m | t)
def i_xor(self):
t = self.stack.pop()
m = self.stack.pop()
self.stack.push(m ^ t)
def i_shift(self):
t = self.stack.pop()
v = self.stack.pop()
if t < 0:
v <<= t * -1
else:
v >>= t
self.stack.push(v)
def i_zreturn(self):
if self.stack.tos() == 0:
self.stack.pop()
self.ip = self.address.pop()
def i_halt(self):
self.ip = 9000000
def i_ienumerate(self):
self.stack.push(6)
def i_iquery(self):
device = self.stack.pop()
if device == 0: # generic output
self.stack.push(0)
self.stack.push(0)
if device == 1: # floating point
self.stack.push(1)
self.stack.push(2)
if device == 2: # files
self.stack.push(0)
self.stack.push(4)
if device == 3: # rng
self.stack.push(0)
self.stack.push(10)
if device == 4: # time
self.stack.push(0)
self.stack.push(5)
if device == 5: # scripting
self.stack.push(0)
self.stack.push(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.push(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.push(floats.eq()), # eq
12: lambda: stack.push(floats.neq()), # -eq
13: lambda: stack.push(floats.lt()), # lt
14: lambda: stack.push(floats.gt()), # gt
15: lambda: stack.push(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.push(afloats.depth()), # alt. depth
}
files_instr = {
0: lambda: stack.push(file_open()),
1: lambda: file_close(),
2: lambda: stack.push(file_read()),
3: lambda: file_write(),
4: lambda: stack.push(file_pos()),
5: lambda: file_seek(),
6: lambda: stack.push(file_size()),
7: lambda: file_delete(),
8: lambda: 1 + 1,
}
rng_instr = {0: lambda: stack.push(rng())}
clock_instr = {
0: lambda: stack.push(int(time.time())),
1: lambda: stack.push(clock["day"]),
2: lambda: stack.push(clock["month"]),
3: lambda: stack.push(clock["year"]),
4: lambda: stack.push(clock["hour"]),
5: lambda: stack.push(clock["minute"]),
6: lambda: stack.push(clock["second"]),
7: lambda: stack.push(clock["day_utc"]),
8: lambda: stack.push(clock["month_utc"]),
9: lambda: stack.push(clock["year_utc"]),
10: lambda: stack.push(clock["hour_utc"]),
11: lambda: stack.push(clock["minute_utc"]),
12: lambda: stack.push(clock["second_utc"]),
}
def i_iinvoke(self):
device = self.stack.pop()
if device == 0: # generic output
self.display_character()
if device == 1: # floating point
action = self.stack.pop()
float_instr[int(action)]()
if device == 2: # files
action = self.stack.pop()
files_instr[int(action)]()
if device == 3: # rng
rng_instr[0]()
if device == 4: # clock
action = self.stack.pop()
clock_instr[int(action)]()
if device == 5: # scripting
action = self.stack.pop()
if action == 0:
self.stack.push(len(sys.argv) - 2)
if action == 1:
a = self.stack.pop()
b = self.stack.pop()
self.stack.push(self.inject_string(sys.argv[a + 2], b))
if action == 2:
run_file(self.extract_string(self.stack.pop()))
if action == 3:
b = self.stack.pop()
self.stack.push(self.inject_string(sys.argv[0], b))
def validate_opcode(self, I0, I1, I2, I3):
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(self, at):
s = ""
while self.memory.fetch(at) != 0:
s = s + chr(self.memory.fetch(at))
at = at + 1
return s
def inject_string(self, s, to):
for c in s:
self.memory.store(ord(c), to)
to = to + 1
self.memory.store(0, to)
def execute(self, word, notfound):
self.ip = word
if self.address.depth() == 0:
self.address.push(0)
while self.ip < 100000:
if self.ip == notfound:
print("ERROR: word not found!")
opcode = self.memory.fetch(self.ip)
I0 = opcode & 0xFF
I1 = (opcode >> 8) & 0xFF
I2 = (opcode >> 16) & 0xFF
I3 = (opcode >> 24) & 0xFF
if self.validate_opcode(I0, I1, I2, I3):
# print("Bytecode: ", I0, I1, I2, I3, "at", self.ip)
if I0 != 0:
self.instructions[I0]()
if I1 != 0:
self.instructions[I1]()
if I2 != 0:
self.instructions[I2]()
if I3 != 0:
self.instructions[I3]()
else:
print("Invalid Bytecode: ", opcode, "at", self.ip)
self.ip = 2000000
if self.address.depth() == 0:
self.ip = 2000000
self.ip = self.ip + 1
return
def run(self):
done = False
while not done:
line = input("\nOk> ")
if line == "bye":
done = True
else:
for token in line.split():
self.inject_string(token, 1024)
self.stack.push(1024)
self.execute(self.Cached['interpreter'], self.Cached['not_found'])
def run_file(self, 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():
self.inject_string(token, 1024)
self.stack.push(1024)
self.execute(self.Cached['interpreter'], self.Cached['not_found'])
def update_image(self):
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)
if __name__ == "__main__":
retro = Retro()
if len(sys.argv) == 1:
retro.run()
if len(sys.argv) == 2:
retro.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] != "-":
retro.run_file(sys.argv[1])
else:
for source in sources:
retro.run_file(source)