Added basic namespace support.

This commit is contained in:
Russ Olsen 2020-04-21 09:22:25 -04:00
parent 8d4375dbf1
commit f10edf9fd7
7 changed files with 236 additions and 57 deletions

2
sallyforth/arglist.py Normal file
View file

@ -0,0 +1,2 @@
class Arglist(list):
pass

View file

@ -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)

86
sallyforth/namespace.py Normal file
View file

@ -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'])

View file

@ -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__":

View file

@ -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]

View file

@ -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 @@
: {{}} ( -- <empty set>) {{ }} ;
: 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

View file

@ -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: <B[{f.stack}]T>')
print("::top::")
for x in f.stack:
print(f'{x}')
print("::bottom::")
return i+1