From f10edf9fd7699645a82079a4807fb16d2e05eede Mon Sep 17 00:00:00 2001 From: Russ Olsen Date: Tue, 21 Apr 2020 09:22:25 -0400 Subject: [PATCH] Added basic namespace support. --- sallyforth/arglist.py | 2 + sallyforth/kernel.py | 64 +++++++++++++++++++----------- sallyforth/namespace.py | 86 ++++++++++++++++++++++++++++++++++++++++ sallyforth/sallyforth.py | 12 +++--- sallyforth/stack.py | 4 ++ sallyforth/startup.sf | 39 +++++++++++------- sallyforth/words.py | 86 +++++++++++++++++++++++++++++++++------- 7 files changed, 236 insertions(+), 57 deletions(-) create mode 100644 sallyforth/arglist.py create mode 100644 sallyforth/namespace.py diff --git a/sallyforth/arglist.py b/sallyforth/arglist.py new file mode 100644 index 0000000..d7b9b31 --- /dev/null +++ b/sallyforth/arglist.py @@ -0,0 +1,2 @@ +class Arglist(list): + pass diff --git a/sallyforth/kernel.py b/sallyforth/kernel.py index 73019c2..e4655cb 100644 --- a/sallyforth/kernel.py +++ b/sallyforth/kernel.py @@ -4,6 +4,7 @@ from words import * import words from lex import forth_prompt, read_tokens, is_string, tokenize from stack import Stack +from namespace import Namespace def to_number(token): try: @@ -17,31 +18,33 @@ def to_number(token): class Forth: def __init__(self, startup=None): self.stack = Stack() - self.dictionary = { + self.namespaces = {} + initial_defs = { '*prompt*': const_f('SallyForth>> '), 'true': const_f(True), 'false': const_f(False), 'nil': const_f(None), '0': const_f(0), '1': const_f(1), - '2': const_f(2) } + '2': const_f(2)} - self.import_from_module(words, 'w_') + self.forth_ns = self.make_namespace('forth', initial_defs) + user_ns = self.make_namespace('user', {}, [self.forth_ns]) + + self.forth_ns.import_from_module(words, 'w_') + self.namespace = self.forth_ns 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) + self.defvar("argv", sys.argv[1::]) + + if startup: + self.execute_file(startup) + + self.namespace = user_ns def defvar(self, name, value): - self.dictionary[name] = const_f(value) + self.namespace[name] = const_f(value) def evaluate_token(self, token): self.execute_token(token) @@ -62,8 +65,22 @@ class Forth: else: self.compile_token(token) + def set_ns(self, ns_name): + if ns_name in self.namespaces: + self.namespace = self.namespaces[ns_name] + else: + raise ValueError(f'No such namespace: {ns_name}') + + def make_namespace(self, ns_name, initial_defs={}, refers=[]): + print(f'New namespace {ns_name} {refers}') + result = Namespace(ns_name, initial_defs, refers) + self.namespaces[ns_name] = result + print(f'Returning {result}') + return result + def execute_file(self, fpath): - old_source = self.dictionary.get('*source*', None) + old_source = self.namespace.get('*source*', None) + old_namespace = self.namespace self.defvar('*source*', fpath) with open(fpath) as f: line = f.readline() @@ -71,8 +88,8 @@ class Forth: tokens = tokenize(line) self.execute_tokens(tokens) line = f.readline() - self.defvar('*source*', '') - self.dictionary['*source*'] = old_source + self.namespace['*source*'] = old_source + self.namespace = old_namespace def compile_token(self, token): if self.compiler.name == None: @@ -83,28 +100,29 @@ class Forth: self.compiler.add_instruction(const_f(token[1::])) return - if token in self.dictionary: - word = self.dictionary[token] + if token in self.namespace: + word = self.namespace[token] if 'immediate' in word.__dict__: word(self, 0) else: - self.compiler.add_instruction(self.dictionary[token]) + self.compiler.add_instruction(self.namespace[token]) return n = to_number(token) if n == None: + print(f'{token}? Compile of {self.compiler.name} terminated.') self.compiler = None - print(f'{token}? Compile terminated.') else: self.compiler.add_instruction(const_f(n)) def execute_token(self, token): + # print("x token:", token) if is_string(token): self.stack.push(token[1::]) return - if token in self.dictionary: - self.dictionary[token](self, 0) + if token in self.namespace: + self.namespace[token](self, 0) return n = to_number(token) @@ -116,5 +134,5 @@ class Forth: def dump(self): print('Forth:', self) print('Stack:', self.stack) - print('Dictionary:', self.dictionary) + print('Dictionary:', self.namespace) print('Compiler:', self.compiler) diff --git a/sallyforth/namespace.py b/sallyforth/namespace.py new file mode 100644 index 0000000..5d11ab1 --- /dev/null +++ b/sallyforth/namespace.py @@ -0,0 +1,86 @@ +class Namespace: + def __init__(self, name, initial_contents={}, refers=[]): + print('name', name) + print('initial contents', initial_contents) + print('refers', refers) + print('===') + self.name = name + self.contents = initial_contents.copy() + self.refers = refers.copy() + + def refer(self, ns): + """ + Add the supplied namespace to the refers list. + """ + self.refers.append(ns) + + def import_from_module(self, m, prefix): + """ + Import all of the word defining functions in + module m whose function names start with prefix + into this namespace. Removes the prefix. + """ + names = dir(m) + prefix_len = len(prefix) + for name in names: + if name.startswith(prefix): + word_name = name[prefix_len::] + self[word_name] = m.__getattribute__(name) + + def keys(self): + return self.contents.keys() + + def all_keys(self): + result = set(self.contents.keys()) + for r in self.refers: + result = result.union(set(r.contents.keys())) + return result + + def get(self, key, default): + if not self.__contains__(key): + return default + return self[key] + + def __contains__(self, key): + if self.contents.__contains__(key): + return True + for r in self.refers: + if r.__contains__(key): + return True + return False + + def local_contains(self, key): + return self.contents.__contains__(key) + + def __delattr__(self, key): + return self.contents.__delattr__(key) + + def __setitem__(self, key, x): + self.contents[key] = x + + def __iter__(self): + return self.contents.__iter__() + + def __getitem__(self, key): + # print("get item", key, self.contents) + if key in self.contents: + return self.contents[key] + # print("not in local ns") + for imp in self.refers: + # print("trying ", imp) + if key in imp: + return imp[key] + # print("not found") + raise KeyError(key) + + def __str__(self): + return f'Namespace({self.name})' + +if __name__ == '__main__': + print("main program") + x = Namespace('x', {'a': 1, 'b': 2}) + print(x['a']) + y = Namespace('y', {'c': 3, 'd': 4}) + print(y['c']) + y.refer(x) + print(y['a']) diff --git a/sallyforth/sallyforth.py b/sallyforth/sallyforth.py index 95cb31f..45da16b 100644 --- a/sallyforth/sallyforth.py +++ b/sallyforth/sallyforth.py @@ -4,6 +4,7 @@ import atexit from kernel import Forth from lex import tokenize import readline +import traceback HistoryFile=".sallyforth" @@ -13,7 +14,7 @@ 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) ] + self.matching_words = [w for w in self.f.namespace.all_keys() if w.startswith(prefix) ] try: return self.matching_words[index] except IndexError: @@ -32,14 +33,14 @@ def setup_readline(history_path, f): atexit.register(save_history) def setup_forth(): - f = Forth() - f.defvar("argv", sys.argv[1::]) - source_dir = os.path.dirname(os.path.abspath(__file__)) startup_file = f'{source_dir}/startup.sf' if os.path.exists(startup_file): - f.execute_file(startup_file) + f = Forth(startup_file) + else: + f = Forth() + return f @@ -62,6 +63,7 @@ def repl(f): print("Error:", exc_type) print("Error:", exc_value) print("Error:", exc_traceback) + traceback.print_tb(exc_traceback) if __name__ == "__main__": diff --git a/sallyforth/stack.py b/sallyforth/stack.py index 5b19828..c7e654a 100644 --- a/sallyforth/stack.py +++ b/sallyforth/stack.py @@ -18,6 +18,10 @@ class Stack: self.top = -1; return result + def __iter__(self): + for i in range(self.top, -1, -1): + yield self.stack[i] + def peek(self): return self.stack[self.top] diff --git a/sallyforth/startup.sf b/sallyforth/startup.sf index a07536a..88bab3c 100644 --- a/sallyforth/startup.sf +++ b/sallyforth/startup.sf @@ -17,6 +17,7 @@ 'semi '; alias 'bounded_list '[list] alias 'list '->list alias +'to_arglist '->arglist alias 'list_to_map 'list->map alias 'lookup '@@ alias 'call '!! alias @@ -29,6 +30,7 @@ 'le '<= alias 'ge '>= alias 'eq '= alias +'current_ns '*ns* alias : *prompt* "Sally> " ; @@ -54,18 +56,25 @@ : {{}} ( -- ) {{ }} ; -: type (x -- type-of-x) 1 ->list builtins.type !! ; +: [: [ ; +: :] ] ->arglist ; -: 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 ; +: => [ ; +: >! ] arrow ; + +: <. [ ; +: .> ] arrow ; + +: !!0 [] swap call ; + +: type 1 ->list builtins.type !! ; -: dir (mod -- keys) "dir" px0 ; : ctime [] time.ctime ; : sleep 1 ->list stack time.sleep drop ; -: hello (simple greeting) "Hello" . nl ; +: callable? 1 ->list builtins.callable ; + +: hello "Hello" . nl ; : >0 0 > ; : <0 0 < ; @@ -75,20 +84,22 @@ : p . nl ; : top dup p ; -: -- ( n -- n-1 ) -1 + ; -: ++ ( n -- n+1 ) 1 + ; -: pos? (n -- bool) 0 > ; -: neg? (n -- bool) 0 < ; -: zero? (n -- bool) 0 = ; +: -- -1 + ; +: ++ 1 + ; +: pos? 0 > ; +: neg? 0 < ; +: zero? 0 = ; -: source-if-exists (path --) +: source-if-exists + (path --) dup 1 ->list os.path.exists if source else drop then ; : << [ ; -: >> ] @@ ; +: >>@ ] @@ ; +: >>! ] @@ [] swap !! ; "init.sf" source-if-exists diff --git a/sallyforth/words.py b/sallyforth/words.py index bcddbe4..c4435b2 100644 --- a/sallyforth/words.py +++ b/sallyforth/words.py @@ -1,6 +1,7 @@ -from compiler import Compiler -import importlib from inspect import isfunction, isbuiltin +import importlib +from compiler import Compiler +from arglist import Arglist class Unique: def __str__(self): @@ -30,18 +31,31 @@ def import_native_module(forth, m, alias=None, excludes=[]): #print(localname) val = m.__getattribute__(name) if isfunction(val) or isbuiltin(val): - forth.dictionary[localname] = native_function_handler(val) + forth.namespace[localname] = native_function_handler(val) else: - forth.dictionary[localname] = const_f(val) + forth.namespace[localname] = const_f(val) def w_forth(f, i): f.stack.push(f) return i+1 +def w_current_ns(f, i): + f.stack.push(f.namespace) + return i + 1 + +def w_ns(f, i): + name = f.stack.pop() + if name in f.namespaces: + f.namespace = f.namespaces[name] + else: + new_ns = f.make_namespace(name, {}, [f.forth_ns]) + f.namespace = new_ns + return i + 1 + def w_alias(f, i): new_name = f.stack.pop() old_name = f.stack.pop() - f.dictionary[new_name] = f.dictionary[old_name] + f.namespace[new_name] = f.namespace[old_name] return i + 1 def w_require(f, i): @@ -91,7 +105,7 @@ def w_recur(f, i): def w_import(f, i): name = f.stack.pop() m = importlib.import_module(name) - f.dictionary[name] = const_f(m) + f.namespace[name] = const_f(m) return i+1 def w_call(f, i): @@ -105,15 +119,10 @@ def w_call(f, i): def w_px(f, i): 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 i+1 @@ -136,16 +145,20 @@ def w_bounded_list(f, ip): f.stack.push(l) return ip+1 +def w_to_arglist(f, ip): + l = f.stack.pop() + f.stack.push(Arglist(l)) + return ip+1 + def w_list(f, ip): # ->list n = f.stack.pop() l = [] for i in range(n): l.append(f.stack.pop()) - #print(l) f.stack.push(l) return ip+1 -def w_lookup(f, i): # @@ +def qqw_lookup(f, i): # @@ l = f.stack.pop() value = l[0] for field in l[1::]: @@ -153,6 +166,29 @@ def w_lookup(f, i): # @@ f.stack.push(value) return i+1 +def w_lookup(f, i): # -> + value = f.stack.pop() + fields = f.stack.pop() + print(f'value {value} fields {fields}') + + if not isinstance(fields, list): + fields = [fields] + + for field in fields: + print(f'value {value} field {field}') + if isinstance(field, str) and hasattr(value, field): + print("->getattr") + value = getattr(value, field) + elif isinstance(field, Arglist): + print("->arglist") + value = value(*field) + else: + print("index") + value = value[field] + f.stack.push(value) + return i+1 + + ListMarker = object() def w_startlist(f, i): # [ @@ -213,6 +249,23 @@ def w_getattribute(f, i): f.stack.push(result) return i+1 +def w_arrow(f, i): # -> + contents = f.stack.pop() + result = contents[0] + for field in contents[1::]: + print(f'result {result} field {field}') + if isinstance(field, str) and hasattr(result, field): + print("->getattr") + result = getattr(result, field) + elif isinstance(field, Arglist): + print("->arglist") + result = result(*field) + else: + print("index") + result = result[field] + f.stack.push(result) + return i+1 + def w_def(f, i): value = f.stack.pop() name = f.stack.pop() @@ -322,7 +375,7 @@ def w_semi(forth, i): forth.compiler.add_instruction(w_return) name = forth.compiler.name word_f = execute_f(name, forth.compiler.instructions) - forth.dictionary[name] = word_f + forth.namespace[name] = word_f forth.compiler = None return i+1 @@ -419,5 +472,8 @@ def w_idump(f, i): w_idump.__dict__['immediate'] = True def w_stack(f, i): - print(f'Stack: ') + print("::top::") + for x in f.stack: + print(f'{x}') + print("::bottom::") return i+1