Fairly complete rewrite of VM. Now supports composite words with { w1 w2...}. Rewrote :, if, ifelse and while to use composite words. Namespaces now have Clojure like vars for indirection and also support inline words. Words defined in Python now use Python function decorators to add Sallyforth related metadata.

This commit is contained in:
Russ Olsen 2020-05-09 10:47:47 -04:00
parent ed5ccfd261
commit 9e9573b3dd
21 changed files with 728 additions and 1126 deletions

88
sallyforth/0.sf Normal file
View file

@ -0,0 +1,88 @@
"Hello from 0.sf" p
\ Pull in libs.
"builtins" load
"time" load
"math" load
"sys" load
"os" load
"os.path" load
"io" load
"time" load
'builtins import
'time import
\ Basic aliases
: -- { 1 - }
: ++ { 1 + }
: =0 { 0 = }
: pos? { 0 > }
: neg? { 0 < }
: zero? { 0 = }
: ->inline { *last-word* inline }
\ List making.
unique 'list-marker =!
: [ list-marker
: ] { list-marker [list] }
: [] { [ ] }
unique 'map-marker =!
: {{ map-marker
: }} { map-marker [list] list->map }
: {{}} { {{ }} }
\ Spelunk thru objects and properties.
: <. [
: .> { ] @@ }
: $? swap
\ Set the interactive prompt.
: *prompt* "sallySh> "
\ Function calling.
: !!0 { [] swap !! }
: !!1 { swap 1 ->list swap !! }
: !!2 { mbt 2 ->list swap !! }
: getattr ( obj attr -- attr-value ) {
swap 2 ->list builtins/getattr !!
}
: setattr ( obj attr value -- ) {
bmt 3 ->list builtins/setattr
}
: .!! (obj args method-name -- result) {
tbm getattr !!
}
\ Handy utilities
: str { builtins/str !!1 }
: type { builtins/type !!1 }
: callable? { builtins/callable !!1 }
: sleep { time/sleep !!1 drop }
: ctime { time/ctime !!0 }
: assert ( bool msg -- ) {
dup
p
swap
ifelse
{ drop "OK " p }
{ builtins/AssertionError !!1 raise }
}
\ Other startup files.
*sallyforth-dir* "/" "io.sf" + + source
*sallyforth-dir* "/" "list.sf" + + source
*sallyforth-dir* "/" "string.sf" + + source

View file

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

View file

@ -1,161 +1,24 @@
from inspect import isfunction, isbuiltin import tokenstream as ts
from wrappers import noop
from util import word
from unique import Unique
import importlib import importlib
import os from pprint import pprint
from compiler import Compiler
from arglist import Arglist
from operator_words import w_not
def const_f(value): @word('raise')
def x(f, i): def w_raise(f):
#print("const f, pushing", value) ex = f.stack.pop()
f.stack.push(value) raise ex
return i + 1
return x
def native_function_handler(func): @word(immediate=True)
def handle(forth, i): def readtoken(f):
args = forth.stack.pop() t = f.stream.get_token()
#print(f"Native fun, calling {func}({args})") def push_token(xforth):
result = func(*args) xforth.stack.push(t)
#print(f'Result: {result}') return push_token
forth.stack.push(result)
#print("pushed result")
return i + 1
return handle
def import_native_module(forth, m, alias=None, excludes=[]): @word("!!")
if not alias: def w_call(f):
alias = m.__name__
alias = alias.replace(".", "/")
print(m, alias)
raw_names = dir(m)
names = [x for x in raw_names if x not in excludes]
for name in names:
localname = f'{alias}/{name}'
val = getattr(m, name)
print("setting", localname)
forth.namespace.set(localname, const_f(val))
def w_eval(f, i):
token = f.stack.pop()
f.evaluate_string(token)
return i+1
def w_execute(f, i):
token = f.stack.pop()
f.execute_string(token)
return i+1
def w_no_op(f, i):
return i+1
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_resolve(f, i):
token = f.stack.pop()
print("token", token)
resolved = f.resolve_token(token)
print("resovled:", resolved)
f.stack.push(resolved)
return i + 1
def w_alias(f, i):
new_name = f.stack.pop()
old_name = f.stack.pop()
f.namespace.alias(new_name, old_name)
return i + 1
def w_require(f, i):
name = f.stack.pop()
m = importlib.import_module(name)
import_native_module(f, m, name)
return i + 1
def source(f, path):
old_source_f = f.namespace.get('*source*', None)
old_namespace = f.namespace
try:
f.execute_file(path)
finally:
f.namespace['*source*'] = old_source_f
f.namespace = old_namespace
def w_load(f, i):
path = f.stack.pop()
source(f, path)
return i + 1
def w_source(f, i):
path = f.stack.pop()
if os.path.isabs(path):
source(f, path)
return i+1
relative_dir = os.path.dirname(f.evaluate_string('*source*'))
relative_path = f'{relative_dir}/{path}'
if os.path.exists(relative_path):
source(f, relative_path)
return i+1
source(f, path)
return i+1
def execute_f(name, instructions):
#print('execute_f:', name, len(instructions))
def inner(forth, i, debug=False):
#print('inner f:', name)
#print('inner f:', len(instructions))
j = 0
while j >= 0:
#print(j, '=>', instructions[j])
new_j = instructions[j](forth, j)
if new_j == None:
print(f'Instruction {instructions[j]} None')
raise RuntimeError
j = new_j
return i + 1
return inner
def ifnot_jump_f(n):
def ifnot_jump(forth, i):
x = forth.stack.pop()
if not x:
return i+n
return i+1
return ifnot_jump
def jump_f(n):
def do_jump(forth, i):
return n+i
return do_jump
def w_recur(f, i):
return 0
def w_import(f, i):
name = f.stack.pop()
m = importlib.import_module(name)
f.namespace.set(name, const_f(m))
return i+1
def w_call(f, i):
func = f.stack.pop() func = f.stack.pop()
args = f.stack.pop() args = f.stack.pop()
#print('f', f, 'args', args) #print('f', f, 'args', args)
@ -166,175 +29,169 @@ def w_call(f, i):
raise raise
#print('result', result) #print('result', result)
f.stack.push(result) f.stack.push(result)
return i+1
def w_kwcall(f, i): @word()
func = f.stack.pop() def unique(f):
kws = f.stack.pop() f.stack.push(Unique())
args = f.stack.pop()
print('f', f, 'args', args, 'kws', kws)
try:
result = func(*args, **kws)
except:
print(f'Error executing {func}{list(args)}{kws}')
raise
print('result', result)
f.stack.push(result)
return i+1
def w_nl(f, i): @word()
print() def load(f):
return i+1
def w_return(f, i):
return -9999;
def w_colon(f, i):
f.compiler = Compiler()
def i_inline(f, i):
f.compiler.inline = True
def i_semi(forth, i):
forth.compiler.add_instruction(w_return)
name = forth.compiler.name
word_f = execute_f(name, forth.compiler.instructions)
entry = forth.defword(name, word_f)
entry.inline = forth.compiler.inline
entry.definition = forth.compiler.instructions
#print(name)
#for ins in entry.definition:
# print(ins)
forth.compiler = None
return i+1
def w_compiling(f, i):
return f.stack.push(f.compiling())
def i_readtoken(f, i):
kind, token = f.read_next_token()
if f.compiling():
compiler = f.compiler
compiler.add_instruction(const_f(token))
else:
f.stack.push(token)
return i+1
def w_immediate(f, i):
flag = f.stack.pop()
name = f.stack.pop() name = f.stack.pop()
print(f'name: {name} flag {flag}') m = importlib.import_module(name)
f.namespace[name].immediate = flag f.set_constant(name, m)
return i+1
def w_should_not_happen(forth, i): @word('import')
print('Should not execute this word!') def w_import(f):
raise ValueError name = f.stack.pop()
m = importlib.import_module(name)
f.ns.import_native_module(m)
def i_if(forth, i): @word()
#print('w_if') def lexicon(f):
compiler = forth.compiler name = f.stack.pop()
compiler.push_offset() m = importlib.import_module(name)
compiler.push_offset() f.ns.import_from_module(m)
compiler.add_instruction(w_should_not_happen)
return i+1
def i_ifnot(forth, i): @word('source')
compiler = forth.compiler def w_source(f):
compiler.add_instruction(w_not) path = f.stack.pop()
compiler.push_offset() f.eval_file(path)
compiler.push_offset()
compiler.add_instruction(w_should_not_happen)
return i+2
def i_then(forth, i): @word('alias')
compiler = forth.compiler def w_alias(f):
else_offset = compiler.pop_offset() new_name = f.stack.pop()
if_offset = compiler.pop_offset() old_name = f.stack.pop()
then_offset = compiler.offset() f.alias(new_name, old_name)
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 i+1
def i_else(forth, i): @word()
compiler = forth.compiler def rawdef(f):
compiler.pop_offset() name = f.stack.pop()
compiler.push_offset() value = f.stack.pop()
compiler.add_instruction(w_should_not_happen) f.set(name, value)
return i+1
def i_do(forth, i): @word("=!")
#print('w_do') def equal_bang(f):
compiler = forth.compiler name = f.stack.pop()
compiler.push_offset() value = f.stack.pop()
compiler.add_instruction(w_should_not_happen) f.set_constant(name, value)
return i+1
def i_begin(forth, i): @word("*prompt*")
compiler = forth.compiler def promptword(f):
compiler.push_offset() f.stack.push(">> ")
return i
def i_while(forth, i): @word()
compiler = forth.compiler def lookup(f):
compiler.push_offset() name = f.stack.pop()
compiler.add_instruction(w_should_not_happen) f.stack.push(f.ns[name])
return i+1
def i_repeat(forth, i): @word()
compiler = forth.compiler def forget(f):
while_offset = compiler.pop_offset() name = f.stack.pop()
begin_offset = compiler.pop_offset() del f.ns[name]
repeat_offset = compiler.offset()
begin_delta = begin_offset - repeat_offset
while_delta = repeat_offset - while_offset + 1
print("Begin delta", begin_delta)
print("while delta", while_delta)
compiler.instructions[while_offset] = ifnot_jump_f(while_delta)
compiler.add_instruction(jump_f(begin_delta))
return i+1
def w_marker1(f, i): @word()
print("marker1") def p(f):
return i+1 print(f.stack.pop())
def w_marker2(f, i): @word()
print("marker3") def nl(f):
return i+1
def w_marker3(f, i):
print("marker3")
return i+1
def w_dump(f, i):
f.dump()
return i+1
def i_idump(f, i):
f.dump()
return i+1
def w_stack(f, i):
print("Stack:", end=' ')
for x in f.stack:
print(f'{repr(x)}', end=' ')
print() print()
return i+1
def w_enlist(f, i): @word('.')
# print("Enlist!") def dot(f):
x = f.stack.pop() print(f.stack.pop(), end='')
# print("Popped", x)
f.stack.push([x])
return i+1
def w_raise(f, i): @word()
ex = f.stack.pop() def splat(f):
raise ex l = f.stack.pop()
return i+1 l.reverse()
for x in l:
f.stack.push(x)
@word()
def stack(f):
print(f.stack)
@word()
def ns(f):
print(f.ns.name)
print(f.ns.contents)
@word(':', True)
def colon(forth):
name = forth.stream.get_token().value
body = forth.compile_next()
forth.set(name, body)
forth.set_constant('*last-word*', name)
return noop
@word()
def inline(forth):
name = forth.stack.pop()
print("Word name:", name)
var = forth.ns[name]
print('var', var)
print('value', var.value)
print('value dict', var.value.__dict__)
var.value.forth_inline = True
print('moded value dict', var.value.__dict__)
@word()
def current_stream(forth):
forth.stack.push(forth.stream)
@word()
def debug(forth):
word = forth.stack.pop()
print("Word:", word)
var = forth.ns[word]
pprint(var)
pprint(var.value.__dict__)
@word()
def fresult(forth, f):
f(forth)
result = forth.stack.pop()
return result
@word('while', True)
def w_while(forth):
cond = forth.compile_next()
body = forth.compile_next()
def dowhile(xforth):
b = fresult(xforth, cond)
while b:
body(xforth)
b = fresult(xforth, cond)
dowhile.forth_inline = False
dowhile.forth_primitive = True
dowhile.forth_immediate = False
return dowhile
@word('if', True)
def w_if(forth):
compiled = forth.compile_next()
print("compiled", compiled)
def doif(forth):
value = forth.stack.pop()
if value:
compiled(forth)
doif.forth_inline = False
doif.forth_primitive = True
doif.forth_immediate = False
return doif
@word('ifelse', True)
def ifelse(forth):
compiled_true = forth.compile_next()
compiled_false = forth.compile_next()
def doif(forth):
value = forth.stack.pop()
if value:
compiled_true(forth)
else:
compiled_false(forth)
doif.forth_inline = False
doif.forth_primitive = True
doif.forth_immediate = False
return doif

View file

@ -1,35 +1,63 @@
from stack import Stack from tokenstream import Token
from wrappers import value_f, inner_f, ref_f
class Compiler: LBrace = Token('word', '{')
def __init__(self, name=None): RBrace = Token('word', '}')
self.name = name
self.instructions = []
self.offsets = Stack()
self.inline = False
def add_instruction(self, ins): def compile_word(forth, w):
self.instructions.append(ins) name = w.value
var = forth.ns[name]
value = var.value
def add_instructions(self, instructions): if value.forth_immediate:
self.instructions.extend(instructions) return value(forth)
elif var.dynamic:
def offset(self): return ref_f(var)
return len(self.instructions)
def push_offset(self, value=None):
if not value:
self.offsets.push(self.offset())
else: else:
self.offsets.push(value) return value
#print("compiler stack", self.offsets.stack)
def pop_offset(self): def compile_token(forth, t):
return self.offsets.pop() if t.kind in ['number', 'string', 'keyword']:
f = value_f(t.value)
elif t.kind == 'word':
f = compile_word(forth, t)
else:
print(f'{n}??')
raise ValueError()
return f
def _str__(self): def compile_value(contents, v):
result = f'Compiler {name}' #print("compiling", v, v.__dict__)
for i in self.instructions: if v.forth_inline and v.forth_contents:
result += str(i) contents.extend(v.forth_contents)
result += ' ' else:
return result contents.append(v)
return contents
def compile_next(forth, stream, current_token=None):
if current_token:
t = current_token
else:
t = stream.get_token()
if t == None:
return None
if t != LBrace:
return compile_token(forth, t)
contents = []
t = stream.get_token()
while t != RBrace:
compile_value(contents, compile_next(forth, stream, t))
t = stream.get_token()
f = inner_f(contents)
return f
def eval_stream(forth, stream):
t = stream.get_token()
while t:
compiled = compile_next(forth, stream, t)
#print(f"*** compiled {t} => {compiled}")
compiled(forth)
t = stream.get_token()

View file

@ -1,48 +1,8 @@
from util import word, get_attribute
from unique import Unique from unique import Unique
from arglist import Arglist
def w_unique(f, ip): # pushes a uique object. @word('[list]')
f.stack.push(Unique()) def w_bounded_list(f):
return ip+1
def w_map(f, ip):
word = f.stack.pop()
l = f.stack.pop()
word_f = f.namespace.get(word, None).get_ivalue()
result = []
for item in l:
f.stack.push(item)
word_f(f, 0)
result.append(f.stack.pop())
f.stack.push(result)
return ip+1
def w_reduce(f, ip):
l = f.stack.pop()
word = f.stack.pop()
word_f = f.namespace.get(word, None).get_ivalue()
if len(l) <= 0:
f.stack.push(None)
elif len(l) == 1:
f.stack.push(l[0])
else:
result = l[0]
l = l[1::-1]
for item in l:
f.stack.push(result)
f.stack.push(item)
word_f(f, 0)
result = f.stack.pop()
f.stack.push(result)
return ip+1
def w_bounded_list(f, ip):
"""Create a list from delimted values on the stack. """Create a list from delimted values on the stack.
[list] [list]
(marker a b c marker -- [a b c] (marker a b c marker -- [a b c]
@ -59,22 +19,54 @@ def w_bounded_list(f, ip):
x = f.stack.pop() x = f.stack.pop()
l.reverse() l.reverse()
f.stack.push(l) f.stack.push(l)
return ip+1
def w_to_arglist(f, ip): @word('->list')
l = f.stack.pop() def w_list(f):
f.stack.push(Arglist(l))
return ip+1
def w_list(f, ip): # ->list
n = f.stack.pop() n = f.stack.pop()
l = [] l = []
for i in range(n): for i in range(n):
l.append(f.stack.pop()) l.append(f.stack.pop())
f.stack.push(l) f.stack.push(l)
return ip+1
def w_thread(f, i): # @@ @word()
def w_map(f):
word = f.stack.pop()
l = f.stack.pop()
word_f = f.lookup(word)
print(word_f)
result = []
for item in l:
f.stack.push(item)
word_f(f)
result.append(f.stack.pop())
f.stack.push(result)
@word()
def w_reduce(f):
l = f.stack.pop()
word = f.stack.pop()
print(f'L: {l} word {word}')
word_f = f.lookup(word)
if len(l) <= 0:
f.stack.push(None)
elif len(l) == 1:
f.stack.push(l[0])
else:
result = l[0]
l = l[1::]
for item in l:
f.stack.push(result)
f.stack.push(item)
word_f(f)
result = f.stack.pop()
f.stack.push(result)
@word('@@')
def w_thread(f):
contents = f.stack.pop() contents = f.stack.pop()
print("Contents:", contents) print("Contents:", contents)
result = contents[0] result = contents[0]
@ -82,79 +74,28 @@ def w_thread(f, i): # @@
print("Result:", result) print("Result:", result)
if isinstance(field, str) and hasattr(result, field): if isinstance(field, str) and hasattr(result, field):
result = getattr(result, field) # result.field result = getattr(result, field) # result.field
elif isinstance(field, Arglist):
result = result(*field) # result(*field)
else: else:
result = result[field] # result[field] result = result[field] # result[field]
f.stack.push(result) f.stack.push(result)
return i+1
@word('list->map')
ListMarker = object() def w_list_to_map(f): # list->map
def w_startlist(f, i): # [
f.stack.push(ListMarker)
return i+1
def w_endlist(f, i): # ]
l = []
x = f.stack.pop()
while x != ListMarker:
l.append(x)
x = f.stack.pop()
l.reverse()
f.stack.push(l)
return i+1
MapMarker = object()
def w_startmap(f, i): # {
f.stack.push(MapMarker)
return i+1
def w_endmap(f, ip): # }
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 i+1
l.reverse()
result = {}
for i in range(0, len(l), 2):
result[l[i]] = l[i+1]
f.stack.push(result)
return ip+1
def w_list_to_map(f, ip): # list->map
l = f.stack.pop() l = f.stack.pop()
result = {} result = {}
for i in range(0, len(l), 2): for i in range(0, len(l), 2):
result[l[i]] = l[i+1] result[l[i]] = l[i+1]
f.stack.push(result) f.stack.push(result)
return ip+1
def w_get(f, i): @word()
def w_get(f):
name = f.stack.pop() name = f.stack.pop()
m = f.stack.pop() m = f.stack.pop()
result = m[name] result = m[name]
f.stack.push(result) f.stack.push(result)
return i+1
def w_getattribute(f, i): @word()
def w_getattribute(f):
name = f.stack.pop() name = f.stack.pop()
x = f.stack.pop() x = f.stack.pop()
result = x.__getattribute__(name) result = x.__getattribute__(name)
f.stack.push(result) f.stack.push(result)
return i+1
def w_def(f, i):
value = f.stack.pop()
name = f.stack.pop()
f.defvar(name, value)
# print('name', name, 'value', value)
return i+1

View file

@ -1,52 +0,0 @@
'*prompt* "sallySh> " def
: child-run-prog (argv pid -- <<exit>>)
drop \ Get rid of 0 pid.
execvp \ Run the program
;
: parent-wait (cmd pid -- exit-status)
"parent" p
waitpid
"Child status:" p p
drop
;
: run-prog (argv -- status)
'child-run-prog 'parent-wait fork
;
'cmd-marker unique def
: a cmd-marker ;
: z
cmd-marker bounded_list
stack
run-prog
;
'os import
: expandvars #os/path/expandvars !!1 ;
: expanduser #os/path/expanduser !!1 ;
: expand expanduser expandvars ;
: tokenize <. $? 'split .> " " swap !!1 ;
: prompt-and-run ( -- prog-status)
">> " read-line
dup "q" =
if
"Quit!" p
drop
else
tokenize
'expand map
run-prog
recur
then
;
: % "Toggle" p ;

View file

@ -1,12 +1,8 @@
: open builtins/open !!1 ; : open { builtins/open !!1 }
: close <. $? 'close .> !!0 drop ; : close { <. $? 'close .> !!0 drop }
: read-file (path -- contents) open dup <. $? 'read .> !!0 swap close ; : read-file (path -- contents) { open dup <. $? 'read .> !!0 swap close }
: read-lines (path -- contents) open dup <. $? 'readlines .> !!0 swap close ; : read-lines (path -- contents) { open dup <. $? 'readlines .> !!0 swap close }
: read-line (prompt -- input-line) : read-line (prompt -- input-line) { builtins/input !!1 }
builtins/input !!1
;
\ : read-next nexttoken second ;

View file

@ -1,212 +1,82 @@
import sys import sys
from os import path import os
import basic_words, data_words, operator_words, stack_words, os_words
from basic_words import const_f, w_enlist
import tokenstream as ts
from kword import Keyword
from stack import Stack from stack import Stack
from namespace import Namespace from namespace import Namespace
import basic_words
import stack_words
import operator_words
import data_words
import tokenstream as ts
import compiler
from wrappers import value_f
class Forth: class Forth:
def __init__(self, startup=None): def __init__(self):
self.streams = Stack()
self.stack = Stack() self.stack = Stack()
self.namespaces = {} self.stream = None
self.ns = Namespace('core')
self.set_constant('forth', self)
self.set_constant('nil', None)
self.set_constant('true', True)
self.set_constant('false', False)
self.set_constant('*source*', '<<input>>')
self.set_constant('*sallyforth-dir*',
os.path.dirname(os.path.abspath(__file__)))
self.ns.import_from_module(basic_words)
self.ns.import_from_module(stack_words)
self.ns.import_from_module(operator_words)
self.ns.import_from_module(data_words)
self.forth_ns = self.make_namespace('forth') def set_constant(self, name, value):
self.namespace = self.forth_ns return self.ns.set(name, value_f(value))
user_ns = self.make_namespace('user', {}, [self.forth_ns])
self.defword('*prompt*', const_f('SallyForth>> ')) def set(self, name, fvalue):
self.defword('*source*', const_f(__file__)) return self.ns.set(name, fvalue)
self.defword('true', const_f(True))
self.defword('false', const_f(False))
self.defword('None', const_f(None))
self.defword('0', const_f(0))
self.defword('1', const_f(1))
self.defword('2', const_f(2))
self.forth_ns.import_from_module(basic_words) def get(self, name, def_value=None):
self.forth_ns.import_from_module(data_words) if name in self.ns:
self.forth_ns.import_from_module(operator_words) return self.ns[name]
self.forth_ns.import_from_module(stack_words) return def_value
self.forth_ns.import_from_module(os_words)
self.namespace.alias("*execute-command*", "execute")
self.compiler = None def alias(self, new_name, old_name):
self.ns.alias(new_name, old_name)
self.defvar("argv", sys.argv[1::]) def compile_next(self, current_token=None):
return compiler.compile_next(self, self.stream, current_token)
if startup: def eval_stream(self, stream):
self.execute_file(startup) old_stream = self.stream
self.stream = stream
compiler.eval_stream(self, stream)
self.stream = old_stream
self.namespace = user_ns def eval_file(self, path):
old_source = self.ns['*source*']
with open(path) as f:
fns = ts.file_token_stream(f)
return self.eval_stream(fns)
self.ns['*source*'] = old_source
def defword(self, name, value): def eval_string(self, s):
return self.namespace.set(name, value) self.eval_stream(ts.string_token_stream(s))
def defvar(self, name, value): def eval_string_r(self, s):
return self.defword(name, const_f(value)) self.eval_string(s)
def compiling(self):
return self.compiler
def _compile_token(self, token):
#print(f"compile: {self.compiler.name}: {token}")
if self.compiler.name == None:
#print(f'Compiling {token}')
self.compiler.name = token.value
return
if token.isnumber() or token.isstring():
self.compiler.add_instruction(const_f(token.value))
return
if token.iskeyword():
self.compiler.add_instruction(Keyword(token.value))
return
if token.value not in self.namespace:
print(f'[{token}]?? Compile of [{self.compiler.name}] terminated.')
self.compiler = None
return
entry = self.namespace[token.value]
if entry.immediate:
value = entry.get_ivalue()
value(self, 0)
elif entry.inline:
self.compiler.add_instructions(entry.definition[slice(0,-1)])
else:
value = entry.get_cvalue()
self.compiler.add_instruction(value)
def _eval_token(self, token):
#print(f'***Eval token {token}')
if token == None:
print(f'{token}?')
elif token.isnumber() or token.isstring():
self.stack.push(token.value)
elif token.iskeyword():
self.stack.push(Keyword(token.value))
return
elif token.value not in self.namespace:
print(f"{token.value}??")
else:
entry = self.namespace[token.value]
f = entry.get_ivalue()
f(self, 0)
def execute_token(self, token):
#print(f'execute_token: {token}')
expanded_tokens = self.macro_expand_token(token)
#print(expanded_tokens)
for expanded in expanded_tokens:
if not self.compiling():
self._eval_token(expanded)
else:
self._compile_token(expanded)
def execute_current_stream(self):
s = self.streams.peek()
#print("exec current s:", s)
token = s.get_token()
while token:
#print("Exec:", token)
self.execute_token(token)
token = s.get_token()
self.streams.pop()
def execute_token_stream(self, s):
#print("exec token stream:", s)
self.streams.push(s)
self.execute_current_stream()
def execute_string(self, s):
token_stream = ts.string_token_stream(s)
return self.execute_token_stream(token_stream)
def resolve_token(self, s):
token_stream = ts.string_token_stream(s)
token = token_stream.get_token()
print("token", token)
if token.isstring():
return token.value
elif token.isnumber():
return token.value
elif token.isword():
entry = self.namespace[token.value]
return entry.get_ivalue()
else:
return None
def evaluate_string(self, s):
self.execute_string(s)
return self.stack.pop() return self.stack.pop()
def read_next_token(self): def lookup(self, name):
s = self.streams.peek() return self.ns[name]
return s.get_token()
def py_evaluate(self, s, *args): if __name__ == "__main__":
#print(f'Evaluate: token [{token}] args <<{args}>>') x = 0
rargs = list(args)
rargs.reverse()
if rargs:
for a in rargs:
#print("pushing", a);
self.stack.push(a)
#print(f'Before eval stack is {str(self.stack)}')
return self.evaluate_string(s)
def pmt():
global x
x += 1
return f'Yes{x}>> '
def macro_expand_token(self, token): pis = ts.PromptInputStream(pmt)
if not token.isword(): tstream = ts.TokenStream(pis.getc)
return [token]
elif len(token.value) <= 1:
return [token]
elif token.value[0] != '#':
return [token]
print("Expanding token:", token) forth = Forth()
tag = token.value[1:] forth.eval_stream(tstream)
parts = tag.split('.')
print("Parts", parts)
result = [ ts.Token('word', '<.'), ts.Token('word', parts[0]) ]
for part in parts[1::]:
result.append(ts.Token('string', part))
result.append(ts.Token('word', '.>'))
print(result)
return result
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.namespace.get('*source*', None)
old_namespace = self.namespace
self.defvar('*source*', fpath)
with open(fpath) as f:
fts = ts.file_token_stream(f)
self.execute_token_stream(fts)
self.namespace['*source*'] = old_source
self.namespace = old_namespace
def dump(self):
print('Forth:', self)
print('Stack:', self.stack)
print('Dictionary:', self.namespace)
print('Compiler:', self.compiler)

View file

@ -1,47 +1,48 @@
\ Index into the x'th item. \ Index into the x'th item.
: [x] (col key -- value) 1 ->list '__getitem__ .!! ;
: first (list -- first-item) 0 [x] ; : [x] (col key -- value) { 1 ->list '__getitem__ .!! }
: second (list -- second-item) 1 [x] ;
: third (list -- third-item) 2 [x] ;
: fourth (list -- fourth-item) 3 [x] ;
: last (list -- last-item) -1 [x] ; : first (list -- first-item) { 0 [x] }
: second (list -- second-item) { 1 [x] }
: third (list -- third-item) { 2 [x] }
: fourth (list -- fourth-item) { 3 [x] }
: slice (start stop -- slice-obj) : last (list -- last-item) { -1 [x] }
: slice (start stop -- slice-obj) {
swap swap
2 ->list 2 ->list
builtins/slice builtins/slice
!! !!
; }
: take (n list -- first-n-items) : take (n list -- first-n-items) {
swap 0 swap slice \ Make the 0..n slice. swap 0 swap slice \ Make the 0..n slice.
[x] \ Do a[0..n]. [x] \ Do a[0..n].
; }
: skip (n list -- all-but-first-n-items) : skip (n list -- all-but-first-n-items) {
swap nil slice \ Make the n..None slice. swap nil slice \ Make the n..None slice.
[x] [x]
; }
: n-of (n x -- list-of-x-repeated-n-times) : n-of (n x -- list-of-x-repeated-n-times) {
1 ->list * 1 ->list *
; }
: len builtins/len !!1 ; : len { builtins/len !!1 }
: empty? len zero? ; : empty? { len zero? }
: rest (list -- all-but-first) 1 swap skip ; : rest (list -- all-but-first) { 1 swap skip }
: rrest (list -- rest-of-rest) rest rest ; : rrest (list -- rest-of-rest) { rest rest }
: rrrest (list -- all-but-first) rest rest rest ; : rrrest (list -- all-but-first) { rest rest rest }
: ffirst (list -- first-of-first) first first ; : ffirst (list -- first-of-first) { first first }
: fffirst (list -- fff-irst) first first first ; : fffirst (list -- fff-irst) { first first first }
: append (x list -- list-with-x-appended) : append (x list -- list-with-x-appended) {
dup tbm dup tbm
<. $? 'append .> !!1 <. $? 'append .> !!1
drop drop
; }

View file

@ -1,38 +1,26 @@
class Entry: from util import get_attribute
def __init__(self, name, value, immed): from wrappers import value_f
class Var:
def __init__(self, name, value, dynamic=True):
self.name = name self.name = name
self.value = value self.value = value
self.immediate = immed self.dynamic = dynamic
self.inline = False
self.definition = None
def get_ivalue(self):
return self.value
def get_cvalue(self):
return self.value
def __str__(self): def __str__(self):
result = f'Entry {self.name} {self.immediate} {self.inline}\n' return f'[[[[Var({self.name}/{self.dynamic}::{self.value})]]]'
for x in self.definition:
result += f'{x}\n' def __repr__(self):
return result return str(self)
class Namespace: class Namespace:
def __init__(self, name, initial_contents={}, refers=[]): def __init__(self, name):
self.contents = {}
self.name = name self.name = name
self.contents = initial_contents.copy()
self.refers = refers.copy()
def alias(self, new_name, existing_name): def alias(self, new_name, existing_name):
self.contents[new_name] = self.contents[existing_name] self.contents[new_name] = self.contents[existing_name]
def refer(self, ns):
"""
Add the supplied namespace to the refers list.
"""
self.refers.append(ns)
def import_from_module(self, m): def import_from_module(self, m):
""" """
Import all of the word defining functions in Import all of the word defining functions in
@ -41,82 +29,55 @@ class Namespace:
""" """
names = dir(m) names = dir(m)
for name in names: for name in names:
if name.startswith("w_"): value = getattr(m, name)
word_name = name[2::] if get_attribute(value, 'forth_word'):
#print(f'Setting {word_name} to false') forth_name = value.forth_name or name
self.set(word_name, getattr(m, name)) var = self.set(forth_name, value, False)
elif name.startswith("i_"): var.immediate = value.forth_immediate
word_name = name[2::] #print(var)
#print(f'Setting {word_name} to true') if var.immediate:
self.set(word_name, getattr(m, name), immediate=True) print(name, 'immediate')
def set(self, name, value, cvalue=None, immediate=False): def import_native_module(self, m, alias=None):
if name not in self.contents: if not alias:
entry = Entry(name, value, immediate) alias = m.__name__
alias = alias.replace(".", "/")
print(m, alias)
names = dir(m)
for name in names:
localname = f'{alias}/{name}'
val = getattr(m, name)
#print("setting", localname)
var = self.set(localname, value_f(val), False)
def set(self, key, value, dynamic=True):
if key not in self.contents:
var = Var(key, value, dynamic)
self.contents[key] = var
else: else:
entry = self[name] var = self.contents[key]
entry.value = value var.value = value
entry.cvalue = cvalue var.dynamic = dynamic
entry.immediate = immediate return var
self.contents[name] = entry
return entry
def keys(self): def keys(self):
return self.contents.keys() 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): def __contains__(self, key):
#print(f'Namespace contains {key}') return self.contents.__contains(key)
if self.contents.__contains__(key):
#print(self.contents[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): def __delattr__(self, key):
return self.contents.__delattr__(key) return self.contents.__delattr__(key)
def __setitem__(self, key, x): def __setitem__(self, key, x):
self.contents[key] = x return self.set(key, x)
def __iter__(self): def __iter__(self):
return self.contents.__iter__() return self.contents.__iter__()
def __getitem__(self, key): def __getitem__(self, key):
#print("get item", key, self.name) return self.contents.__getitem__(key)
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): def __str__(self):
return f'Namespace({self.name})' 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

@ -1,67 +1,69 @@
def w_gt(f, i): from util import word
a = f.stack.pop()
b = f.stack.pop()
f.stack.push(b > a)
return i+1
def w_lt(f, i): @word('>')
a = f.stack.pop() def gt(forth):
b = f.stack.pop() a = forth.stack.pop()
f.stack.push(b < a) b = forth.stack.pop()
return i+1 forth.stack.push(b > a)
def w_eq(f, i): @word('<')
a = f.stack.pop() def lt(forth):
b = f.stack.pop() a = forth.stack.pop()
f.stack.push(a==b) b = forth.stack.pop()
return i+1 forth.stack.push(b < a)
def w_le(f, i): @word('=')
a = f.stack.pop() def eq(forth):
b = f.stack.pop() a = forth.stack.pop()
f.stack.push(b<=a) b = forth.stack.pop()
return i+1 forth.stack.push(a==b)
def w_ge(f, i): @word('<=')
a = f.stack.pop() def le(forth):
b = f.stack.pop() a = forth.stack.pop()
f.stack.push(b>=a) b = forth.stack.pop()
return i+1 forth.stack.push(b<=a)
def w_add(f, i): @word('>=')
a = f.stack.pop() def ge(forth):
b = f.stack.pop() a = forth.stack.pop()
f.stack.push(b+a) b = forth.stack.pop()
return i+1 forth.stack.push(b>=a)
def w_mul(f, i): @word('+')
a = f.stack.pop() def add(forth):
b = f.stack.pop() a = forth.stack.pop()
f.stack.push(b*a) b = forth.stack.pop()
return i+1 forth.stack.push(b+a)
def w_sub(f, i): @word('*')
a = f.stack.pop() def mul(forth):
b = f.stack.pop() a = forth.stack.pop()
f.stack.push(b-a) b = forth.stack.pop()
return i+1 forth.stack.push(b*a)
def w_div(f, i): @word('-')
a = f.stack.pop() def sub(forth):
b = f.stack.pop() a = forth.stack.pop()
f.stack.push(b/a) b = forth.stack.pop()
return i+1 forth.stack.push(b-a)
def w_and(f, i): @word('/')
f.stack.push(f.stack.pop() and f.stack.pop()) def div(forth):
return i+1 a = forth.stack.pop()
b = forth.stack.pop()
forth.stack.push(b/a)
def w_or(f, i): @word('and')
f.stack.push(f.stack.pop() or f.stack.pop()) def w_and(forth):
return i+1 forth.stack.push(forth.stack.pop() and forth.stack.pop())
def w_not(f, i): @word('or')
f.stack.push(not f.stack.pop()) def w_or(forth):
return i+1 forth.stack.push(forth.stack.pop() or forth.stack.pop())
@word('not')
def w_not(forth):
forth.stack.push(not forth.stack.pop())

View file

@ -1,7 +1,9 @@
import os import os
import sys import sys
from util import word
def w_fork(f, i): @word('fork')
def w_fork(f):
parent_word = f.stack.pop() parent_word = f.stack.pop()
child_word = f.stack.pop() child_word = f.stack.pop()
parent_f = f.namespace.get(parent_word, None).get_ivalue() parent_f = f.namespace.get(parent_word, None).get_ivalue()
@ -14,28 +16,26 @@ def w_fork(f, i):
else: else:
print("parent:", pid) print("parent:", pid)
parent_f(f, 0) parent_f(f, 0)
return i+1
def w_execvp(f, i): @word('execvp')
def w_execvp(f):
args = f.stack.pop() args = f.stack.pop()
path = args[0] path = args[0]
print(f"path {path} args: {args}") print(f"path {path} args: {args}")
os.execvp(path, args) os.execvp(path, args)
return i+1
def w_waitpid(f, i): @word('waitpid')
def w_waitpid(f):
pid = f.stack.pop() pid = f.stack.pop()
result = os.waitpid(pid, 0) result = os.waitpid(pid, 0)
f.stack.push(result) f.stack.push(result)
return i+1
def w_exit(f, i): @word('exit')
def w_exit(f):
n = f.stack.pop() n = f.stack.pop()
sys.exit(n) sys.exit(n)
return i+1
def w_exit_bang(f, i): @word('exit!')
def w_exit_bang(f):
n = f.stack.pop() n = f.stack.pop()
os._exit(n) os._exit(n)
return i+1

View file

@ -14,7 +14,8 @@ class Completer:
def __init__(self, f): def __init__(self, f):
self.f = f self.f = f
def complete(self, prefix, index): def complete(self, prefix, index):
self.matching_words = [w for w in self.f.namespace.all_keys() if w.startswith(prefix) ] self.matching_words = \
[w for w in self.f.ns.keys() if w.startswith(prefix)]
try: try:
return self.matching_words[index] return self.matching_words[index]
except IndexError: except IndexError:
@ -27,6 +28,7 @@ def setup_readline(history_path, f):
except FileNotFoundError: except FileNotFoundError:
pass pass
readline.parse_and_bind("tab: complete") readline.parse_and_bind("tab: complete")
readline.set_completer_delims(' \t\n()[{]}\\|;:\'",')
readline.set_completer(completer.complete) readline.set_completer(completer.complete)
def save_history(): def save_history():
readline.write_history_file(history_path) readline.write_history_file(history_path)
@ -34,27 +36,24 @@ def setup_readline(history_path, f):
def setup_forth(): def setup_forth():
source_dir = os.path.dirname(os.path.abspath(__file__)) source_dir = os.path.dirname(os.path.abspath(__file__))
startup_file = f'{source_dir}/startup.sf' startup_file = f'{source_dir}/0.sf'
if os.path.exists(startup_file):
f = Forth(startup_file)
else:
f = Forth() f = Forth()
if os.path.exists(startup_file):
f.eval_file(startup_file)
return f return f
def repl(f): def repl(f):
while True: while True:
try: try:
prompt = f.evaluate_string('*prompt*') prompt = f.eval_string_r('*prompt*')
try: try:
line = input(prompt) line = input(prompt)
line += "\n" line += "\n"
except EOFError: except EOFError:
return return
try: try:
f.stack.push(line) f.eval_string(line)
f.execute_string("*execute-command*")
#f.execute_string(line)
except: except:
traceback.print_exc() traceback.print_exc()
except KeyboardInterrupt: except KeyboardInterrupt:

View file

@ -1,43 +1,33 @@
class Stack: class Stack:
def __init__(self): def __init__(self):
self.top = -1 self.stack = []
self.stack = 100 * [None]
def push(self, x): def push(self, x):
#print("stack push", x) self.stack.append(x)
self.top += 1
self.stack[self.top] = x
return x return x
def pop(self): def pop(self):
result = self.stack[self.top] return self.stack.pop()
#print("stack pop", result)
self.top -= 1
if self.top < -1:
print("stack overpop")
self.top = -1;
raise ValueError("Stack underflow")
return result
def __iter__(self): def __iter__(self):
for i in range(0, self.top+1): for x in self.stack:
yield self.stack[i] yield x
def depth(self): def depth(self):
return self.top + 1 return len(self.stack)
def empty(self): def empty(self):
return self.top == -1 return len(self.stack) == 0
def peek(self): def peek(self):
return self.stack[self.top] return self.stack[-1]
def reset(self): def reset(self):
self.top = -1 self.stack = []
def __str__(self): def __str__(self):
result = '' result = ''
for i in range(self.top + 1): for x in self.stack:
result += str(self.stack[i]) result += str(x)
result += ' ' result += ' '
return result return result

View file

@ -1,101 +1,83 @@
def w_px(f, i): from util import word
args = f.stack.pop()
name = f.stack.pop()
m = f.stack.pop()
func = m.__dict__[name]
result = func(*args)
f.stack.push(result)
return i+1
def w_reset(f, i): @word()
a = f.stack.reset() def stackdepth(forth):
return i+1 forth.stack.push(forth.stack.depth())
def w_dot(f, i): @word()
def reset(forth):
forth.stack.reset()
@word()
def drop(forth):
forth.stack.pop()
@word()
def dup(forth):
a = forth.stack.peek()
forth.stack.push(a)
@word()
def swap(f):
a = f.stack.pop() a = f.stack.pop()
print(a, end='') b = f.stack.pop()
return i+1 f.stack.push(a)
f.stack.push(b)
def w_stackdepth(f, i): @word()
d = f.stack.depth() def tmb(f): # A noop
f.stack.push(d) pass
def w_splat(f, i): @word()
l = f.stack.pop() def tbm(f):
l.reverse()
for x in l:
f.stack.push(x)
return i+1
def w_dup(f, i):
x = f.stack.peek()
f.stack.push(x)
return i+1
def w_tmb(f, i): # A noop
return i+1
def w_tbm(f, i):
t = f.stack.pop() t = f.stack.pop()
m = f.stack.pop() m = f.stack.pop()
b = f.stack.pop() b = f.stack.pop()
f.stack.push(m) f.stack.push(m)
f.stack.push(b) f.stack.push(b)
f.stack.push(t) f.stack.push(t)
return i+1
def w_bmt(f, i): @word()
def bmt(f):
t = f.stack.pop() t = f.stack.pop()
m = f.stack.pop() m = f.stack.pop()
b = f.stack.pop() b = f.stack.pop()
f.stack.push(t) f.stack.push(t)
f.stack.push(m) f.stack.push(m)
f.stack.push(b) f.stack.push(b)
return i+1
def w_btm(f, i): @word()
def btm(f):
t = f.stack.pop() t = f.stack.pop()
m = f.stack.pop() m = f.stack.pop()
b = f.stack.pop() b = f.stack.pop()
f.stack.push(m) f.stack.push(m)
f.stack.push(t) f.stack.push(t)
f.stack.push(b) f.stack.push(b)
return i+1
def w_mtb(f, i): @word()
def mtb(f):
t = f.stack.pop() t = f.stack.pop()
m = f.stack.pop() m = f.stack.pop()
b = f.stack.pop() b = f.stack.pop()
f.stack.push(b) f.stack.push(b)
f.stack.push(t) f.stack.push(t)
f.stack.push(m) f.stack.push(m)
return i+1
def w_mbt(f, i): @word()
def mbt(f):
t = f.stack.pop() t = f.stack.pop()
m = f.stack.pop() m = f.stack.pop()
b = f.stack.pop() b = f.stack.pop()
f.stack.push(t) f.stack.push(t)
f.stack.push(b) f.stack.push(b)
f.stack.push(m) f.stack.push(m)
return i+1
def w_rot(f, i): @word()
def rot(f):
c = f.stack.pop() c = f.stack.pop()
b = f.stack.pop() b = f.stack.pop()
a = f.stack.pop() a = f.stack.pop()
f.stack.push(b) f.stack.push(b)
f.stack.push(c) f.stack.push(c)
f.stack.push(a) f.stack.push(a)
return i+1
def w_drop(f, i):
f.stack.pop()
return i+1
def w_swap(f, i):
a = f.stack.pop()
b = f.stack.pop()
f.stack.push(a)
f.stack.push(b)
return i+1

View file

@ -1,151 +0,0 @@
\ "Executing " dot *source* dot nl
\ Pull in libs.
"builtins" require
"time" require
"math" require
"sys" require
"os" require
"os.path" require
"io" require
"time" require
'builtins import
\ Basic aliases
'None 'nil alias
'dot '. alias
'colon ': alias
'semi '; alias
'thread '@@ alias
'exit_bang 'exit! alias
'bounded_list '[list] alias
'list '->list alias
'to_arglist '->arglist alias
'list_to_map 'list->map alias
'call '!! alias
'add '+ alias
'sub '- alias
'mul '* alias
'div '/ alias
'gt '> alias
'lt '< alias
'le '<= alias
'ge '>= alias
'eq '= alias
'current_ns '*ns* alias
: *prompt* "Sally> " ;
: *execute-command* execute ;
\ Make a list.
'list-marker unique def
: [ list-marker inline ;
: ] list-marker [list] inline ;
: [] ( -- <empty list>) [ ] inline ;
\ Look up attributes on a value.
: <. [ ;
: .> ] thread ;
: $? swap ;
\ Call native functions with various # arguments.
: !!0 [] swap !! inline ;
: !!1 swap 1 ->list swap !! inline ;
: !!2 swap 2 ->list swap !! inline ;
\ Make a map.
'map-marker unique def
: { map-marker inline ;
: } map-marker [list] list->map inline ;
: {} ( -- <empty map>) { } inline ;
\ Make a set.
'set-marker unique def
: {{ set-marker ;
: }}
set-marker [list] \ Turn elements into list
set-marker swap set-marker [list] \ Nest list in argument list
builtins/set !! \ Call set with 1 argument
inline
;
: {{}} ( -- <empty set>) {{ }} inline ;
: [: [ inline ;
: :] ] ->arglist inline ;
: str builtins/str !!1 ;
: type builtins/type !!1 ;
: type? (x class -- bool) swap type = ;
: ctime time/ctime !!0 ;
: sleep time/sleep !!1 drop ;
: callable? builtins/callable !!1 ;
: hello "Hello" . nl ;
: >0 0 > inline ;
: =0 0 = inline ;
: <1 1 < inline ;
: <0 0 < inline ;
: >1 1 > inline ;
: <1 1 < inline ;
: p . nl ;
: top dup p ;
: -- -1 + inline ;
: ++ 1 + inline ;
: *2 2 * inline ;
: pos? 0 > inline ;
: neg? 0 < inline ;
: zero? 0 = inline ;
: exists? os/path/exists !!1 ;
: source-if-exists
(path -- result-of-sourcing)
"SOURCE IF EXISTS" p
stack
dup
exists?
if source else drop then
;
: getattr ( obj attr -- attr-value ) swap 2 ->list builtins/getattr !! ;
: .!! (obj args method-name -- result) tbm getattr !! ;
: .!!0 (obj method-name -- result ) [] swap .!! ;
: .!!1 (obj arg method-name -- result ) swap 1 ->list swap .!! ;
: .!!2 (obj a1 a2 method-name -- result ) swap 2 ->list swap .!! ;
: .!!3 (obj a1 a2 a3 method-name -- result ) swap 3 ->list swap .!! ;
: assert ( bool msg -- )
p
if
"OK " p
else
#builtins/AssertionError !!1
raise
then
;
"string.sf" source
"list.sf" source
"io.sf" source
"init.sf" source-if-exists

View file

@ -1,5 +1,9 @@
: split (delimit str -- tokens) 2 ->list <. builtins/str 'split .> !! ; : split (delimit str -- tokens) {
2 ->list
<. builtins/str 'split .>
!!
}
: dot-split (str -- tokens) "." swap split ; : dot-split (str -- tokens) { "." swap split }

View file

@ -18,7 +18,6 @@ reset 1 2 3 reset stackdepth 0 = "Reset empties the stack." assert
2 3 * 6 = "2*3 = 6" assert 2 3 * 6 = "2*3 = 6" assert
100 5 / 20 = "100 divided by 5 is 20." assert 100 5 / 20 = "100 divided by 5 is 20." assert
1 >0 "One is > 0." assert
0 =0 "Zero is equal to itself." assert 0 =0 "Zero is equal to itself." assert
1 =0 not "One is not =0." assert 1 =0 not "One is not =0." assert
@ -46,22 +45,23 @@ false false and not "F and F is F." assert
\ Secondary words \ Secondary words
: push8 8 ; push8 8 = "A word can push a number." assert : push8 8
push8 8 = "A word can push a number." assert
: push8-again push8 ; : push8-again push8
push8-again 8 = "A word can call another word." assert push8-again 8 = "A word can call another word." assert
: push64 push8 push8 * ; : push64 { push8 push8 * }
push64 64 = "A word can use primitive and sec words." assert push64 64 = "A word can use primitive and sec words." assert
\ Logic \ Logic
: 1-if-true if 1 then ; : 1-if-true { if { 1 } }
reset true 1-if-true 1 = "True part of if fires." assert reset true 1-if-true 1 = "True part of if fires." assert
reset false 1-if-true stackdepth 0 = "if does not fire on false." assert reset false 1-if-true stackdepth 0 = "if does not fire on false." assert
: 1-or-2 if 1 else 2 then ; : 1-or-2 { ifelse 1 2 }
reset true 1-or-2 1 = "True part of ifelse fires." assert reset true 1-or-2 1 = "True part of ifelse fires." assert
reset false 1-or-2 2 = "False part of ifelse fires." assert reset false 1-or-2 2 = "False part of ifelse fires." assert
@ -74,7 +74,7 @@ reset false 1-or-2 2 = "False part of ifelse fires." assert
\ Name lookup and calls \ Name lookup and calls
"12" <. builtins 'len .> !!1 2 = "Can use bracket dot notation." assert "12" <. builtins 'len .> !!1 2 = "Can use bracket dot notation." assert
"12" #builtins.len !!1 2 = "Can use sharp lookup notation." assert "12" builtins/len !!1 2 = "Can use sharp lookup notation." assert
\ Lists \ Lists
@ -94,10 +94,10 @@ reset false 1-or-2 2 = "False part of ifelse fires." assert
\ Loop \ Loop
: test-while ( n -- ) -999 swap begin dup zero? while -- repeat -888 ; : test-while ( n -- ) { -999 swap while { dup zero? } { -- } -888 }
5 test-while 3 ->list [ -999 0 -888 ] "While loop works" assert 5 test-while 3 ->list [ -999 0 -888 ] "While loop works" assert
: zero-trip-while begin false while "Should not get here." repeat ; : zero-trip-while { while { false } { "Should not get here." } }
888 zero-trip-while 888 = "While should handle zero trip case." assert 888 zero-trip-while 888 = "While should handle zero trip case." assert

View file

@ -14,6 +14,17 @@ class Token:
self.kind = kind self.kind = kind
self.value = value self.value = value
def __eq__(self, other):
if not isinstance(other, Token):
return False
return self.kind == other.kind and self.value == other.value
def __hash__(self):
return self.kind.__hash__() + self.value.__hash__()
def isblock(self):
return self.kind == 'block'
def isstring(self): def isstring(self):
return self.kind == 'string' return self.kind == 'string'
@ -30,7 +41,7 @@ class Token:
return str(self) return str(self)
def __str__(self): def __str__(self):
return f'Token[{self.kind} => {self.value}]' return f'Token {self.kind} => {self.value}'
class PromptInputStream: class PromptInputStream:
@ -52,6 +63,7 @@ class PromptInputStream:
class TokenStream: class TokenStream:
def __init__(self, read_f): def __init__(self, read_f):
#print("Tokenstream", read_f)
self.read_f = read_f self.read_f = read_f
def whitespace(self, ch): def whitespace(self, ch):
@ -73,6 +85,9 @@ class TokenStream:
return Token('string', token) return Token('string', token)
if state in ['word']: if state in ['word']:
return Token('word', token) return Token('word', token)
if state == 'number':
return Token('number', token)
#print("x get returning NONE")
return None return None
elif state == 'start' and ch == ':': elif state == 'start' and ch == ':':
token = ch token = ch
@ -101,7 +116,8 @@ class TokenStream:
token += ch token += ch
elif state == 'number' and self.whitespace(ch): elif state == 'number' and self.whitespace(ch):
n = to_number(token) n = to_number(token)
if n: if n != None:
#print("returning number", n)
return Token('number', n) return Token('number', n)
else: else:
return Token('word', token) return Token('word', token)
@ -111,13 +127,14 @@ class TokenStream:
return Token('string', token) return Token('string', token)
elif state == 'keyword' and self.whitespace(ch): elif state == 'keyword' and self.whitespace(ch):
state = 'start' state = 'start'
if len(token) == 1: if token in [':']:
return Token('word', token) return Token('word', token)
return Token('keyword', token) return Token('keyword', token)
elif state in ['word', 'dqstring', 'sqstring', 'number', 'keyword']: elif state in ['word', 'dqstring', 'sqstring', 'number', 'keyword']:
token += ch token += ch
def file_token_stream(f): def file_token_stream(f):
#print("file token stream:", f)
return TokenStream(lambda : f.read(1)) return TokenStream(lambda : f.read(1))
def string_token_stream(s): def string_token_stream(s):

19
sallyforth/util.py Normal file
View file

@ -0,0 +1,19 @@
def get_attribute(x, name):
return getattr(x, name, None)
class word:
def __init__(self, name=None, immediate=False):
self.name = name
self.immediate = immediate
def __call__(self, f):
f.forth_word = True
if self.name:
f.forth_name = self.name
else:
f.forth_name = f.__name__
f.forth_type = 'primitive'
f.forth_inline = False
f.forth_immediate = self.immediate
return f

52
sallyforth/wrappers.py Normal file
View file

@ -0,0 +1,52 @@
class Reference:
def __init__(self, var):
self.var = var
def __call__(self, forth):
#print("indirect call on", self.var.name)
return self.var.value(forth)
@property
def forth_immediate(self):
return self.var.value.forth_immediate
@property
def forth_contents(self):
return self.var.value.forth_contents
@property
def forth_primitive(self):
return self.var.value.forth_primitive
@property
def forth_name(self):
return self.var.value.forth_name
@property
def forth_inline(self):
return self.var.value.forth_inline
def ref_f(var):
return Reference(var)
def value_f(value):
def push_constant(f):
f.stack.push(value)
push_constant.forth_inline = False
push_constant.forth_primitive = True
push_constant.forth_name = 'pushv'
push_constant.forth_immediate = False
return push_constant
def inner_f(contents):
def inner(forth):
for fn in contents:
fn(forth)
inner.forth_primitive = False
inner.forth_immediate = False
inner.forth_contents = contents
inner.forth_inline = False
return inner
def noop(value):
pass