From 6925755432696e68f80a3c1793bde39be6a454f3 Mon Sep 17 00:00:00 2001 From: Russ Olsen Date: Fri, 17 Apr 2020 08:56:06 -0400 Subject: [PATCH] Added better support for calling native code, added readline history, else to ifs. --- sallyforth/compiler.py | 10 ++- sallyforth/kernel.py | 32 +++++--- sallyforth/sallyforth.py | 49 +++++++++++- sallyforth/startup.sf | 31 +++++++- sallyforth/words.py | 166 ++++++++++++++++++++++++++++++++++++--- 5 files changed, 258 insertions(+), 30 deletions(-) diff --git a/sallyforth/compiler.py b/sallyforth/compiler.py index 24f4572..bfdf0c3 100644 --- a/sallyforth/compiler.py +++ b/sallyforth/compiler.py @@ -13,14 +13,18 @@ class Compiler: def offset(self): return len(self.instructions) - def push_offset(self): - self.offsets.push(self.offset()) + def push_offset(self, value=None): + if not value: + self.offsets.push(self.offset()) + else: + self.offsets.push(value) + #print("compiler stack", self.offsets.stack) def pop_offset(self): return self.offsets.pop() def _str__(self): - result = f'Compiler {name} {immediate} ' + result = f'Compiler {name}' for i in self.instructions: result += str(i) result += ' ' diff --git a/sallyforth/kernel.py b/sallyforth/kernel.py index 37bf936..b2d413b 100644 --- a/sallyforth/kernel.py +++ b/sallyforth/kernel.py @@ -1,6 +1,7 @@ import sys from os import path from words import * +import words from lex import forth_prompt, read_tokens, is_string, tokenize from stack import Stack @@ -27,13 +28,16 @@ class Forth: 'true': const_f(True), 'false': const_f(False), 'nil': const_f(None), - 'def': w_def, - 'import': w_import, '0': const_f(0), '1': const_f(1), '2': const_f(2), ';': w_semi, ':': w_colon, + '->list': w_list, + '[': w_startlist, + ']': w_endlist, + '{': w_startmap, + '}': w_endmap, '+': w_add, '+': w_add, '-': w_sub, @@ -43,22 +47,22 @@ class Forth: '<=': w_le, '>=': w_ge, '=': w_eq, - 'dup': w_dup, - 'swap': w_swap, - '.': w_dot, - 'nl': w_nl, - 'dump': w_dump, - 'idump': w_idump, - 'stack': w_stack, - 'begin': w_begin, - 'until': w_until, - 'if': w_if, - 'then': w_then} + '.': w_dot} + + self.import_from_module(words, 'w_') self.compiler = None if startup: execute_startup(startup) + def import_from_module(self, m, prefix): + names = dir(m) + prefix_len = len(prefix) + for name in names: + if name.startswith(prefix): + word_name = name[prefix_len::] + self.dictionary[word_name] = m.__getattribute__(name) + def defvar(self, name, value): self.dictionary[name] = const_f(value) @@ -81,6 +85,7 @@ class Forth: self.compile_token(token) def execute_file(self, fpath): + old_source = self.dictionary.get('*source*', None) self.defvar('*source*', fpath) with open(fpath) as f: line = f.readline() @@ -89,6 +94,7 @@ class Forth: self.execute_tokens(tokens) line = f.readline() self.defvar('*source*', '') + self.dictionary['*source*'] = old_source def compile_token(self, token): if self.compiler.name == None: diff --git a/sallyforth/sallyforth.py b/sallyforth/sallyforth.py index 6831574..f856f1e 100644 --- a/sallyforth/sallyforth.py +++ b/sallyforth/sallyforth.py @@ -1,19 +1,62 @@ import os +import sys from kernel import Forth from lex import tokenize +import readline +HistoryFile=".sallyforth" + +histfile = os.path.join(os.path.expanduser("~"), HistoryFile) + +try: + readline.read_history_file(histfile) +except FileNotFoundError: + pass source_dir = os.path.dirname(os.path.abspath(__file__)) startup_file = f'{source_dir}/startup.sf' -print(startup_file) f = Forth() +class Completer: + def __init__(self, f): + self.f = f + def complete(self, prefix, index): + self.matching_words = [w for w in self.f.dictionary.keys() if w.startswith(prefix) ] + try: + return self.matching_words[index] + except IndexError: + return None + +completer = Completer(f) + +readline.parse_and_bind("tab: complete") +readline.set_completer(completer.complete) + +f.defvar("argv", sys.argv[1::]) + if os.path.exists(startup_file): f.execute_file(startup_file) while True: p = f.evaluate_token('*prompt*') - line = input(p) + try: + line = input(p) + except KeyboardInterrupt: + print("<>") + line = '' + except EOFError: + break + tokens = tokenize(line) - f.execute_tokens(tokens) + try: + f.execute_tokens(tokens) + except: + exc_type, exc_value, exc_traceback = sys.exc_info() + print("Error:", exc_type) + print("Error:", exc_value) + print("Error:", exc_traceback) + +readline.write_history_file(histfile) + +print("Bye!") diff --git a/sallyforth/startup.sf b/sallyforth/startup.sf index e33feaa..07308fd 100644 --- a/sallyforth/startup.sf +++ b/sallyforth/startup.sf @@ -1,10 +1,37 @@ "Executing " . *source* . nl -: prompt "Yo> " ; +"time" require +"math" require +"sys" require +"os" require +"os.path" require +"io" require + +: *prompt* "SF> " ; + +: [] ( -- ) [ ] ; + +: px0 (mod fname -- result) [] px ; +: px1 (mod fname arg -- result) 1 ->list px ; +: px2 (mod fname arg arg -- result) 2 ->list px ; +: px3 (mod fname arg arg arg -- result) 3 ->list px ; + +: dir (mod -- keys) "dir" px0 ; +: ctime [] time.ctime ; +: sleep 1 ->list stack time.sleep drop ; : hello (simple greeting) "Hello" . nl ; : >0 0 > ; : <0 0 < ; -: p dup . nl ; +: p . nl ; +: top dup p ; + +: source-if-exists + dup + 1 ->list os.path.exists + if source else drop then +; + +"init.sf" source-if-exists diff --git a/sallyforth/words.py b/sallyforth/words.py index 02a8ce3..8b830ca 100644 --- a/sallyforth/words.py +++ b/sallyforth/words.py @@ -1,5 +1,45 @@ from compiler import Compiler import importlib +from inspect import isfunction, isbuiltin + +def const_f(value): + def x(f): + f.stack.push(value) + return 1 + return x + +def native_function_handler(func): + def handle(forth): + args = forth.stack.pop() + result = func(*args) + forth.stack.push(result) + return 1 + return handle + +def import_native_module(forth, m, alias=None, excludes=[]): + if not alias: + alias = m.__name__ + raw_names = dir(m) + names = [x for x in raw_names if x not in excludes] + for name in names: + localname = f'{alias}.{name}' + print(localname) + val = m.__getattribute__(name) + if isfunction(val) or isbuiltin(val): + forth.dictionary[localname] = native_function_handler(val) + else: + forth.dictionary[localname] = const_f(val) + +def w_require(f): + name = f.stack.pop() + m = importlib.import_module(name) + import_native_module(f, m, name) + return 1 + +def w_source(f): + path = f.stack.pop() + f.execute_file(path) + return 1 def execute_f(name, instructions): # print("execute_f:", len(instructions)) @@ -27,22 +67,98 @@ def ifnot_jump_f(n): return 1 return ifnot_jump -def const_f(value): - def x(f): - f.stack.push(value) - return 1 - return x +def jump_f(n): + def do_jump(forth): + return n + return do_jump def w_import(f): name = f.stack.pop() m = importlib.import_module(name) - f.stack.push(m) + f.dictionary[name] = const_f(m) + return 1 + +def w_px(f): + args = f.stack.pop() + print("args", args) + name = f.stack.pop() + print("name", name) + m = f.stack.pop() + print("mod:", m) + func = m.__dict__[name] + print("f:", f); + result = func(*args) + print("result", result) + f.stack.push(result) + return 1 + +def w_list(f): + n = f.stack.pop() + l = [] + for i in range(n): + l.append(f.stack.pop()) + print(l) + f.stack.push(l) + return 1 + +ListMarker = object() + +def w_startlist(f): # [ + f.stack.push(ListMarker) + return 1 + +def w_endlist(f): # ] + l = [] + x = f.stack.pop() + while x != ListMarker: + l.append(x) + x = f.stack.pop() + l.reverse() + f.stack.push(l) + return 1 + +MapMarker = object() + +def w_startmap(f): # { + f.stack.push(MapMarker) + return 1 + +def w_endmap(f): # } + l = [] + x = f.stack.pop() + while x != MapMarker: + l.append(x) + x = f.stack.pop() + if (len(l) % 2) != 0: + print("Maps need even number of entries.") + return 1 + l.reverse() + result = {} + for i in range(0, len(l), 2): + result[l[i]] = l[i+1] + f.stack.push(result) + return 1 + +def w_get(f): + name = f.stack.pop() + m = f.stack.pop() + result = m[name] + f.stack.push(result) + return 1 + +def w_getattribute(f): + name = f.stack.pop() + x = f.stack.pop() + result = x.__getattribute__(name) + f.stack.push(result) + return 1 def w_def(f): value = f.stack.pop() name = f.stack.pop() f.defvar(name, value) print('name', name, 'value', value) + return 1 def w_gt(f): a = f.stack.pop() @@ -108,6 +224,19 @@ def w_dup(f): f.stack.push(x) return 1 +def w_rot(f): + c = f.stack.pop() + b = f.stack.pop() + a = f.stack.pop() + f.stack.push(b) + f.stack.push(c) + f.stack.push(a) + return 1 + +def w_drop(f): + f.stack.pop() + return 1 + def w_swap(f): a = f.stack.pop() b = f.stack.pop() @@ -143,6 +272,7 @@ def w_if(forth): print("w_if") compiler = forth.compiler compiler.push_offset() + compiler.push_offset() compiler.add_instruction(w_should_not_happen) return 1 @@ -150,14 +280,31 @@ w_if.__dict__['immediate'] = True def w_then(forth): compiler = forth.compiler + else_offset = compiler.pop_offset() if_offset = compiler.pop_offset() - end_offset = compiler.offset() - delta = end_offset - if_offset - compiler.instructions[if_offset] = ifnot_jump_f(delta) + then_offset = compiler.offset() + if else_offset == if_offset: + delta = then_offset - if_offset + compiler.instructions[if_offset] = ifnot_jump_f(delta) + else: + if_delta = else_offset - if_offset + 1 + compiler.instructions[if_offset] = ifnot_jump_f(if_delta) + else_delta = then_offset - else_offset + compiler.instructions[else_offset] = jump_f(else_delta) return 1 w_then.__dict__['immediate'] = True + +def w_else(forth): + compiler = forth.compiler + compiler.pop_offset() + compiler.push_offset() + compiler.add_instruction(w_should_not_happen) + return 1 + +w_else.__dict__['immediate'] = True + def w_do(forth): print("w_do") compiler = forth.compiler @@ -196,6 +343,7 @@ def w_until(forth): w_until.__dict__['immediate'] = True + def w_dump(f): f.dump() return 1