#!/usr/bin/env python3 # Nga: a Virtual Machine # Copyright (c) 2010 - 2019, 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 ip = 0 stack = [] * 128 address = [] memory = [] class clock: def __getitem__(self, id): now = datetime.datetime.now() ids = { "time": time.time, "year": now.year, "month": now.month, "day": now.day, "hour": now.hour, "minute": now.minute, "second": now.second, # No time_utc? "year_utc": now.utcnow().year, "month_utc": now.utcnow().month, "day_utc": now.utcnow().day, "hour_utc": now.utcnow().hour, "minute_utc": now.utcnow().minute, "second_utc": now.utcnow().second, } return ids[id] clock = clock() class rng: def __call__(self, seed=None, new_seed=True): return random.randint(-2147483647, 2147483646) rng = rng() class float_stack(object): def __init__(self, *d): self.data = list(d) def __getitem__(self, id): return self.data[id] def add(self): self.data.append(self.data.pop() + self.data.pop()) def sub(self): self.data.append(0 - (self.data.pop() - self.data.pop())) def mul(self): self.data.append(self.data.pop() * self.data.pop()) def div(self): a, b = self.data.pop(), self.data.pop() self.data.append(b / a) def ceil(self): self.data.append(math.ceil(self.data.pop())) def floor(self): self.data.append(math.floor(self.data.pop())) def eq(self): return 0 - (self.data.pop() == self.data.pop()) def neq(self): return 0 - (self.data.pop() != self.data.pop()) def gt(self): a, b = self.data.pop(), self.data.pop() return 0 - (b > a) def lt(self): a, b = self.data.pop(), self.data.pop() return 0 - (b < a) def depth(self): return len(self.data) def drop(self): self.data.pop() def pop(self): return self.data.pop() def swap(self): a, b = self.data.pop(), self.data.pop() self.data += [a, b] def push(self, n): self.data.append(n) def log(self): a, b = self.data.pop(), self.data.pop() self.data.append(math.log(b, a)) def power(self): a, b = self.data.pop(), self.data.pop() self.data.append(math.pow(a, b)) def sin(self): self.data.append(math.sin(self.data.pop())) def cos(self): self.data.append(math.cos(self.data.pop())) def tan(self): self.data.append(math.tan(self.data.pop())) def asin(self): self.data.append(math.asin(self.data.pop())) def acos(self): self.data.append(math.acos(self.data.pop())) def atan(self): self.data.append(math.atan(self.data.pop())) floats = float_stack() afloats = float_stack() 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[-1] > 0 and stack[-1] < 128: if stack[-1] == 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(): global ip, memory, stack, address stack.append(stack[-1]) def i_dr(): global ip, memory, stack, address stack.pop() def i_sw(): global ip, memory, stack, address a = stack[-2] stack[-2] = stack[-1] stack[-1] = a def i_pu(): global ip, memory, stack, address address.append(stack.pop()) def i_po(): global ip, memory, stack, address stack.append(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(): global ip, memory, stack, address if stack[-1] == -1: stack[-1] = len(stack) - 1 elif stack[-1] == -2: stack[-1] = len(address) elif stack[-1] == -3: stack[-1] = len(memory) elif stack[-1] == -4: stack[-1] = -2147483648 elif stack[-1] == -5: stack[-1] = 2147483647 else: stack[-1] = memory[stack[-1]] def i_st(): global ip, memory, stack, address mi = stack.pop() memory[mi] = stack.pop() def i_ad(): global ip, memory, stack, address t = stack.pop() stack[-1] += t stack[-1] = unpack("=l", pack("=L", stack[-1] & 0xFFFFFFFF))[0] def i_su(): global ip, memory, stack, address t = stack.pop() stack[-1] -= t stack[-1] = unpack("=l", pack("=L", stack[-1] & 0xFFFFFFFF))[0] def i_mu(): global ip, memory, stack, address t = stack.pop() stack[-1] *= t stack[-1] = unpack("=l", pack("=L", stack[-1] & 0xFFFFFFFF))[0] def i_di(): global ip, memory, stack, address a = stack[-1] b = stack[-2] stack[-1], stack[-2] = div_mod(b, a) stack[-1] = unpack("=l", pack("=L", stack[-1] & 0xFFFFFFFF))[0] stack[-2] = unpack("=l", pack("=L", stack[-2] & 0xFFFFFFFF))[0] def i_an(): global ip, memory, stack, address t = stack.pop() stack[-1] &= t def i_or(): global ip, memory, stack, address t = stack.pop() stack[-1] |= t def i_xo(): global ip, memory, stack, address t = stack.pop() stack[-1] ^= t def i_sh(): global ip, memory, stack, address t = stack.pop() if t < 0: stack[-1] <<= t * -1 else: stack[-1] >>= t def i_zr(): global ip, memory, stack, address if stack[-1] == 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)