mirror of
https://github.com/russolsen/sallyforth
synced 2024-12-25 21:58:18 +01:00
Added basic namespace support.
This commit is contained in:
parent
8d4375dbf1
commit
f10edf9fd7
7 changed files with 236 additions and 57 deletions
2
sallyforth/arglist.py
Normal file
2
sallyforth/arglist.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
class Arglist(list):
|
||||
pass
|
|
@ -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
86
sallyforth/namespace.py
Normal 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'])
|
|
@ -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__":
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue