diff --git a/sallyforth/0.sf b/sallyforth/0.sf new file mode 100644 index 0000000..22b7b5d --- /dev/null +++ b/sallyforth/0.sf @@ -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 diff --git a/sallyforth/arglist.py b/sallyforth/arglist.py deleted file mode 100644 index d7b9b31..0000000 --- a/sallyforth/arglist.py +++ /dev/null @@ -1,2 +0,0 @@ -class Arglist(list): - pass diff --git a/sallyforth/basic_words.py b/sallyforth/basic_words.py index 50f670c..c8cf527 100644 --- a/sallyforth/basic_words.py +++ b/sallyforth/basic_words.py @@ -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 os -from compiler import Compiler -from arglist import Arglist -from operator_words import w_not +from pprint import pprint -def const_f(value): - def x(f, i): - #print("const f, pushing", value) - f.stack.push(value) - return i + 1 - return x +@word('raise') +def w_raise(f): + ex = f.stack.pop() + raise ex -def native_function_handler(func): - def handle(forth, i): - args = forth.stack.pop() - #print(f"Native fun, calling {func}({args})") - result = func(*args) - #print(f'Result: {result}') - forth.stack.push(result) - #print("pushed result") - return i + 1 - return handle +@word(immediate=True) +def readtoken(f): + t = f.stream.get_token() + def push_token(xforth): + xforth.stack.push(t) + return push_token -def import_native_module(forth, m, alias=None, excludes=[]): - if not alias: - 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): +@word("!!") +def w_call(f): func = f.stack.pop() args = f.stack.pop() #print('f', f, 'args', args) @@ -164,177 +27,171 @@ def w_call(f, i): except: print(f'Error executing {func}({args})') raise - # print('result', result) + #print('result', result) f.stack.push(result) - return i+1 -def w_kwcall(f, i): - func = f.stack.pop() - kws = f.stack.pop() - 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 +@word() +def unique(f): + f.stack.push(Unique()) -def w_nl(f, i): - print() - 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() +@word() +def load(f): name = f.stack.pop() - print(f'name: {name} flag {flag}') - f.namespace[name].immediate = flag - return i+1 + m = importlib.import_module(name) + f.set_constant(name, m) -def w_should_not_happen(forth, i): - print('Should not execute this word!') - raise ValueError +@word('import') +def w_import(f): + name = f.stack.pop() + m = importlib.import_module(name) + f.ns.import_native_module(m) -def i_if(forth, i): - #print('w_if') - compiler = forth.compiler - compiler.push_offset() - compiler.push_offset() - compiler.add_instruction(w_should_not_happen) - return i+1 +@word() +def lexicon(f): + name = f.stack.pop() + m = importlib.import_module(name) + f.ns.import_from_module(m) -def i_ifnot(forth, i): - compiler = forth.compiler - compiler.add_instruction(w_not) - compiler.push_offset() - compiler.push_offset() - compiler.add_instruction(w_should_not_happen) - return i+2 +@word('source') +def w_source(f): + path = f.stack.pop() + f.eval_file(path) -def i_then(forth, i): - compiler = forth.compiler - else_offset = compiler.pop_offset() - if_offset = compiler.pop_offset() - 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 i+1 +@word('alias') +def w_alias(f): + new_name = f.stack.pop() + old_name = f.stack.pop() + f.alias(new_name, old_name) -def i_else(forth, i): - compiler = forth.compiler - compiler.pop_offset() - compiler.push_offset() - compiler.add_instruction(w_should_not_happen) - return i+1 +@word() +def rawdef(f): + name = f.stack.pop() + value = f.stack.pop() + f.set(name, value) -def i_do(forth, i): - #print('w_do') - compiler = forth.compiler - compiler.push_offset() - compiler.add_instruction(w_should_not_happen) - return i+1 +@word("=!") +def equal_bang(f): + name = f.stack.pop() + value = f.stack.pop() + f.set_constant(name, value) -def i_begin(forth, i): - compiler = forth.compiler - compiler.push_offset() - return i - -def i_while(forth, i): - compiler = forth.compiler - compiler.push_offset() - compiler.add_instruction(w_should_not_happen) - return i+1 +@word("*prompt*") +def promptword(f): + f.stack.push(">> ") -def i_repeat(forth, i): - compiler = forth.compiler - while_offset = compiler.pop_offset() - begin_offset = compiler.pop_offset() - 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 +@word() +def lookup(f): + name = f.stack.pop() + f.stack.push(f.ns[name]) -def w_marker1(f, i): - print("marker1") - return i+1 +@word() +def forget(f): + name = f.stack.pop() + del f.ns[name] -def w_marker2(f, i): - print("marker3") - return i+1 +@word() +def p(f): + print(f.stack.pop()) -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=' ') +@word() +def nl(f): print() - return i+1 - -def w_enlist(f, i): - # print("Enlist!") - x = f.stack.pop() - # print("Popped", x) - f.stack.push([x]) - return i+1 -def w_raise(f, i): - ex = f.stack.pop() - raise ex - return i+1 +@word('.') +def dot(f): + print(f.stack.pop(), end='') + +@word() +def splat(f): + l = f.stack.pop() + 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 diff --git a/sallyforth/compiler.py b/sallyforth/compiler.py index 78cc6e7..7d27fbb 100644 --- a/sallyforth/compiler.py +++ b/sallyforth/compiler.py @@ -1,35 +1,63 @@ -from stack import Stack +from tokenstream import Token +from wrappers import value_f, inner_f, ref_f -class Compiler: - def __init__(self, name=None): - self.name = name - self.instructions = [] - self.offsets = Stack() - self.inline = False +LBrace = Token('word', '{') +RBrace = Token('word', '}') - def add_instruction(self, ins): - self.instructions.append(ins) +def compile_word(forth, w): + name = w.value + var = forth.ns[name] + value = var.value - def add_instructions(self, instructions): - self.instructions.extend(instructions) + if value.forth_immediate: + return value(forth) + elif var.dynamic: + return ref_f(var) + else: + return value - def offset(self): - return len(self.instructions) +def compile_token(forth, t): + 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 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 compile_value(contents, v): + #print("compiling", v, v.__dict__) + if v.forth_inline and v.forth_contents: + contents.extend(v.forth_contents) + else: + contents.append(v) + return contents - def pop_offset(self): - return self.offsets.pop() +def compile_next(forth, stream, current_token=None): + if current_token: + t = current_token + else: + t = stream.get_token() - def _str__(self): - result = f'Compiler {name}' - for i in self.instructions: - result += str(i) - result += ' ' - return result + 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() diff --git a/sallyforth/data_words.py b/sallyforth/data_words.py index d56c2d4..f57ff26 100644 --- a/sallyforth/data_words.py +++ b/sallyforth/data_words.py @@ -1,48 +1,8 @@ +from util import word, get_attribute from unique import Unique -from arglist import Arglist -def w_unique(f, ip): # pushes a uique object. - f.stack.push(Unique()) - 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): +@word('[list]') +def w_bounded_list(f): """Create a list from delimted values on the stack. [list] (marker a b c marker -- [a b c] @@ -59,22 +19,54 @@ def w_bounded_list(f, ip): x = f.stack.pop() l.reverse() 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 +@word('->list') +def w_list(f): n = f.stack.pop() l = [] for i in range(n): l.append(f.stack.pop()) 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() print("Contents:", contents) result = contents[0] @@ -82,79 +74,28 @@ def w_thread(f, i): # @@ print("Result:", result) if isinstance(field, str) and hasattr(result, field): result = getattr(result, field) # result.field - elif isinstance(field, Arglist): - result = result(*field) # result(*field) else: result = result[field] # result[field] f.stack.push(result) - return i+1 - -ListMarker = object() - -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 +@word('list->map') +def w_list_to_map(f): # list->map l = f.stack.pop() result = {} for i in range(0, len(l), 2): result[l[i]] = l[i+1] f.stack.push(result) - return ip+1 -def w_get(f, i): +@word() +def w_get(f): name = f.stack.pop() m = f.stack.pop() result = m[name] f.stack.push(result) - return i+1 -def w_getattribute(f, i): +@word() +def w_getattribute(f): name = f.stack.pop() x = f.stack.pop() result = x.__getattribute__(name) 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 - - diff --git a/sallyforth/init.sf b/sallyforth/init.sf deleted file mode 100644 index d375af4..0000000 --- a/sallyforth/init.sf +++ /dev/null @@ -1,52 +0,0 @@ -'*prompt* "sallySh> " def - -: child-run-prog (argv pid -- <>) - 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 ; diff --git a/sallyforth/io.sf b/sallyforth/io.sf index b46a3c2..0d72618 100644 --- a/sallyforth/io.sf +++ b/sallyforth/io.sf @@ -1,12 +1,8 @@ -: open builtins/open !!1 ; -: close <. $? 'close .> !!0 drop ; +: open { builtins/open !!1 } +: close { <. $? 'close .> !!0 drop } -: read-file (path -- contents) open dup <. $? 'read .> !!0 swap close ; -: read-lines (path -- contents) open dup <. $? 'readlines .> !!0 swap close ; +: read-file (path -- contents) { open dup <. $? 'read .> !!0 swap close } +: read-lines (path -- contents) { open dup <. $? 'readlines .> !!0 swap close } -: read-line (prompt -- input-line) - builtins/input !!1 -; - -\ : read-next nexttoken second ; +: read-line (prompt -- input-line) { builtins/input !!1 } diff --git a/sallyforth/kernel.py b/sallyforth/kernel.py index 8ccd1f3..371f9bc 100644 --- a/sallyforth/kernel.py +++ b/sallyforth/kernel.py @@ -1,212 +1,82 @@ import sys -from os import path -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 +import os from stack import Stack 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: - def __init__(self, startup=None): - self.streams = Stack() + def __init__(self): 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*', '<>') + 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') - self.namespace = self.forth_ns - user_ns = self.make_namespace('user', {}, [self.forth_ns]) + def set_constant(self, name, value): + return self.ns.set(name, value_f(value)) - self.defword('*prompt*', const_f('SallyForth>> ')) - self.defword('*source*', const_f(__file__)) - 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)) + def set(self, name, fvalue): + return self.ns.set(name, fvalue) - self.forth_ns.import_from_module(basic_words) - self.forth_ns.import_from_module(data_words) - self.forth_ns.import_from_module(operator_words) - self.forth_ns.import_from_module(stack_words) - self.forth_ns.import_from_module(os_words) - self.namespace.alias("*execute-command*", "execute") + def get(self, name, def_value=None): + if name in self.ns: + return self.ns[name] + return def_value - 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: - self.execute_file(startup) + def eval_stream(self, stream): + 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): - return self.namespace.set(name, value) + def eval_string(self, s): + self.eval_stream(ts.string_token_stream(s)) - def defvar(self, name, value): - return self.defword(name, const_f(value)) - - 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) + def eval_string_r(self, s): + self.eval_string(s) return self.stack.pop() - def read_next_token(self): - s = self.streams.peek() - return s.get_token() + def lookup(self, name): + return self.ns[name] - def py_evaluate(self, s, *args): - #print(f'Evaluate: token [{token}] args <<{args}>>') - 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 macro_expand_token(self, token): - if not token.isword(): - return [token] - elif len(token.value) <= 1: - return [token] - elif token.value[0] != '#': - return [token] - - print("Expanding token:", token) - tag = token.value[1:] - 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) +if __name__ == "__main__": + x = 0 + + def pmt(): + global x + x += 1 + return f'Yes{x}>> ' + + pis = ts.PromptInputStream(pmt) + tstream = ts.TokenStream(pis.getc) + + forth = Forth() + forth.eval_stream(tstream) diff --git a/sallyforth/list.sf b/sallyforth/list.sf index df1690e..278c533 100644 --- a/sallyforth/list.sf +++ b/sallyforth/list.sf @@ -1,47 +1,48 @@ \ Index into the x'th item. -: [x] (col key -- value) 1 ->list '__getitem__ .!! ; -: first (list -- first-item) 0 [x] ; -: second (list -- second-item) 1 [x] ; -: third (list -- third-item) 2 [x] ; -: fourth (list -- fourth-item) 3 [x] ; +: [x] (col key -- value) { 1 ->list '__getitem__ .!! } -: 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 2 ->list builtins/slice !! -; +} -: take (n list -- first-n-items) +: take (n list -- first-n-items) { swap 0 swap slice \ Make the 0..n slice. [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. [x] -; +} -: n-of (n x -- list-of-x-repeated-n-times) +: n-of (n x -- list-of-x-repeated-n-times) { 1 ->list * -; +} -: len builtins/len !!1 ; +: len { builtins/len !!1 } -: empty? len zero? ; +: empty? { len zero? } -: rest (list -- all-but-first) 1 swap skip ; -: rrest (list -- rest-of-rest) rest rest ; -: rrrest (list -- all-but-first) rest rest rest ; +: rest (list -- all-but-first) { 1 swap skip } +: rrest (list -- rest-of-rest) { rest rest } +: rrrest (list -- all-but-first) { rest rest rest } -: ffirst (list -- first-of-first) first first ; -: fffirst (list -- fff-irst) first first first ; +: ffirst (list -- first-of-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 <. $? 'append .> !!1 drop -; +} diff --git a/sallyforth/namespace.py b/sallyforth/namespace.py index 6ae43e3..2f92191 100644 --- a/sallyforth/namespace.py +++ b/sallyforth/namespace.py @@ -1,38 +1,26 @@ -class Entry: - def __init__(self, name, value, immed): +from util import get_attribute +from wrappers import value_f + +class Var: + def __init__(self, name, value, dynamic=True): self.name = name self.value = value - self.immediate = immed - self.inline = False - self.definition = None - - def get_ivalue(self): - return self.value - - def get_cvalue(self): - return self.value + self.dynamic = dynamic def __str__(self): - result = f'Entry {self.name} {self.immediate} {self.inline}\n' - for x in self.definition: - result += f'{x}\n' - return result + return f'[[[[Var({self.name}/{self.dynamic}::{self.value})]]]' + + def __repr__(self): + return str(self) class Namespace: - def __init__(self, name, initial_contents={}, refers=[]): + def __init__(self, name): + self.contents = {} self.name = name - self.contents = initial_contents.copy() - self.refers = refers.copy() def alias(self, new_name, 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): """ Import all of the word defining functions in @@ -41,82 +29,55 @@ class Namespace: """ names = dir(m) for name in names: - if name.startswith("w_"): - word_name = name[2::] - #print(f'Setting {word_name} to false') - self.set(word_name, getattr(m, name)) - elif name.startswith("i_"): - word_name = name[2::] - #print(f'Setting {word_name} to true') - self.set(word_name, getattr(m, name), immediate=True) + value = getattr(m, name) + if get_attribute(value, 'forth_word'): + forth_name = value.forth_name or name + var = self.set(forth_name, value, False) + var.immediate = value.forth_immediate + #print(var) + if var.immediate: + print(name, 'immediate') - def set(self, name, value, cvalue=None, immediate=False): - if name not in self.contents: - entry = Entry(name, value, immediate) + def import_native_module(self, m, alias=None): + if not alias: + 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: - entry = self[name] - entry.value = value - entry.cvalue = cvalue - entry.immediate = immediate - self.contents[name] = entry - return entry + var = self.contents[key] + var.value = value + var.dynamic = dynamic + return var 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): - #print(f'Namespace 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) + return self.contents.__contains(key) def __delattr__(self, key): return self.contents.__delattr__(key) def __setitem__(self, key, x): - self.contents[key] = x + return self.set(key, x) def __iter__(self): return self.contents.__iter__() def __getitem__(self, key): - #print("get item", key, self.name) - 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) + return self.contents.__getitem__(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/operator_words.py b/sallyforth/operator_words.py index dbd1800..1d69c93 100644 --- a/sallyforth/operator_words.py +++ b/sallyforth/operator_words.py @@ -1,67 +1,69 @@ -def w_gt(f, i): - a = f.stack.pop() - b = f.stack.pop() - f.stack.push(b > a) - return i+1 +from util import word -def w_lt(f, i): - a = f.stack.pop() - b = f.stack.pop() - f.stack.push(b < a) - return i+1 +@word('>') +def gt(forth): + a = forth.stack.pop() + b = forth.stack.pop() + forth.stack.push(b > a) -def w_eq(f, i): - a = f.stack.pop() - b = f.stack.pop() - f.stack.push(a==b) - return i+1 +@word('<') +def lt(forth): + a = forth.stack.pop() + b = forth.stack.pop() + forth.stack.push(b < a) -def w_le(f, i): - a = f.stack.pop() - b = f.stack.pop() - f.stack.push(b<=a) - return i+1 +@word('=') +def eq(forth): + a = forth.stack.pop() + b = forth.stack.pop() + forth.stack.push(a==b) -def w_ge(f, i): - a = f.stack.pop() - b = f.stack.pop() - f.stack.push(b>=a) - return i+1 +@word('<=') +def le(forth): + a = forth.stack.pop() + b = forth.stack.pop() + forth.stack.push(b<=a) -def w_add(f, i): - a = f.stack.pop() - b = f.stack.pop() - f.stack.push(b+a) - return i+1 +@word('>=') +def ge(forth): + a = forth.stack.pop() + b = forth.stack.pop() + forth.stack.push(b>=a) -def w_mul(f, i): - a = f.stack.pop() - b = f.stack.pop() - f.stack.push(b*a) - return i+1 +@word('+') +def add(forth): + a = forth.stack.pop() + b = forth.stack.pop() + forth.stack.push(b+a) -def w_sub(f, i): - a = f.stack.pop() - b = f.stack.pop() - f.stack.push(b-a) - return i+1 +@word('*') +def mul(forth): + a = forth.stack.pop() + b = forth.stack.pop() + forth.stack.push(b*a) -def w_div(f, i): - a = f.stack.pop() - b = f.stack.pop() - f.stack.push(b/a) - return i+1 +@word('-') +def sub(forth): + a = forth.stack.pop() + b = forth.stack.pop() + forth.stack.push(b-a) -def w_and(f, i): - f.stack.push(f.stack.pop() and f.stack.pop()) - return i+1 +@word('/') +def div(forth): + a = forth.stack.pop() + b = forth.stack.pop() + forth.stack.push(b/a) -def w_or(f, i): - f.stack.push(f.stack.pop() or f.stack.pop()) - return i+1 +@word('and') +def w_and(forth): + forth.stack.push(forth.stack.pop() and forth.stack.pop()) -def w_not(f, i): - f.stack.push(not f.stack.pop()) - return i+1 +@word('or') +def w_or(forth): + forth.stack.push(forth.stack.pop() or forth.stack.pop()) + +@word('not') +def w_not(forth): + forth.stack.push(not forth.stack.pop()) diff --git a/sallyforth/os_words.py b/sallyforth/os_words.py index 9f3e9cc..9e2196d 100644 --- a/sallyforth/os_words.py +++ b/sallyforth/os_words.py @@ -1,7 +1,9 @@ import os import sys +from util import word -def w_fork(f, i): +@word('fork') +def w_fork(f): parent_word = f.stack.pop() child_word = f.stack.pop() parent_f = f.namespace.get(parent_word, None).get_ivalue() @@ -14,28 +16,26 @@ def w_fork(f, i): else: print("parent:", pid) parent_f(f, 0) - return i+1 -def w_execvp(f, i): +@word('execvp') +def w_execvp(f): args = f.stack.pop() path = args[0] print(f"path {path} args: {args}") os.execvp(path, args) - return i+1 -def w_waitpid(f, i): +@word('waitpid') +def w_waitpid(f): pid = f.stack.pop() result = os.waitpid(pid, 0) f.stack.push(result) - return i+1 -def w_exit(f, i): +@word('exit') +def w_exit(f): n = f.stack.pop() sys.exit(n) - return i+1 -def w_exit_bang(f, i): +@word('exit!') +def w_exit_bang(f): n = f.stack.pop() os._exit(n) - return i+1 - diff --git a/sallyforth/sallyforth.py b/sallyforth/sallyforth.py index c258e6d..1b4158e 100644 --- a/sallyforth/sallyforth.py +++ b/sallyforth/sallyforth.py @@ -14,7 +14,8 @@ class Completer: def __init__(self, f): self.f = f 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: return self.matching_words[index] except IndexError: @@ -27,6 +28,7 @@ def setup_readline(history_path, f): except FileNotFoundError: pass readline.parse_and_bind("tab: complete") + readline.set_completer_delims(' \t\n()[{]}\\|;:\'",') readline.set_completer(completer.complete) def save_history(): readline.write_history_file(history_path) @@ -34,27 +36,24 @@ def setup_readline(history_path, f): def setup_forth(): source_dir = os.path.dirname(os.path.abspath(__file__)) - startup_file = f'{source_dir}/startup.sf' + startup_file = f'{source_dir}/0.sf' + f = Forth() if os.path.exists(startup_file): - f = Forth(startup_file) - else: - f = Forth() + f.eval_file(startup_file) return f def repl(f): while True: try: - prompt = f.evaluate_string('*prompt*') + prompt = f.eval_string_r('*prompt*') try: line = input(prompt) line += "\n" except EOFError: return try: - f.stack.push(line) - f.execute_string("*execute-command*") - #f.execute_string(line) + f.eval_string(line) except: traceback.print_exc() except KeyboardInterrupt: diff --git a/sallyforth/stack.py b/sallyforth/stack.py index ae1e8a8..260499a 100644 --- a/sallyforth/stack.py +++ b/sallyforth/stack.py @@ -1,43 +1,33 @@ class Stack: def __init__(self): - self.top = -1 - self.stack = 100 * [None] + self.stack = [] def push(self, x): - #print("stack push", x) - self.top += 1 - self.stack[self.top] = x + self.stack.append(x) return x def pop(self): - result = self.stack[self.top] - #print("stack pop", result) - self.top -= 1 - if self.top < -1: - print("stack overpop") - self.top = -1; - raise ValueError("Stack underflow") - return result + return self.stack.pop() def __iter__(self): - for i in range(0, self.top+1): - yield self.stack[i] + for x in self.stack: + yield x def depth(self): - return self.top + 1 + return len(self.stack) def empty(self): - return self.top == -1 + return len(self.stack) == 0 def peek(self): - return self.stack[self.top] + return self.stack[-1] def reset(self): - self.top = -1 + self.stack = [] def __str__(self): result = '' - for i in range(self.top + 1): - result += str(self.stack[i]) + for x in self.stack: + result += str(x) result += ' ' return result diff --git a/sallyforth/stack_words.py b/sallyforth/stack_words.py index bb47159..53b5620 100644 --- a/sallyforth/stack_words.py +++ b/sallyforth/stack_words.py @@ -1,101 +1,83 @@ -def w_px(f, i): - 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 +from util import word -def w_reset(f, i): - a = f.stack.reset() - return i+1 +@word() +def stackdepth(forth): + 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() - print(a, end='') - return i+1 + b = f.stack.pop() + f.stack.push(a) + f.stack.push(b) + +@word() +def tmb(f): # A noop + pass -def w_stackdepth(f, i): - d = f.stack.depth() - f.stack.push(d) - -def w_splat(f, i): - l = f.stack.pop() - 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): +@word() +def tbm(f): t = f.stack.pop() m = f.stack.pop() b = f.stack.pop() f.stack.push(m) f.stack.push(b) f.stack.push(t) - return i+1 -def w_bmt(f, i): +@word() +def bmt(f): t = f.stack.pop() m = f.stack.pop() b = f.stack.pop() f.stack.push(t) f.stack.push(m) f.stack.push(b) - return i+1 -def w_btm(f, i): +@word() +def btm(f): t = f.stack.pop() m = f.stack.pop() b = f.stack.pop() f.stack.push(m) f.stack.push(t) f.stack.push(b) - return i+1 -def w_mtb(f, i): +@word() +def mtb(f): t = f.stack.pop() m = f.stack.pop() b = f.stack.pop() f.stack.push(b) f.stack.push(t) f.stack.push(m) - return i+1 -def w_mbt(f, i): +@word() +def mbt(f): t = f.stack.pop() m = f.stack.pop() b = f.stack.pop() f.stack.push(t) f.stack.push(b) f.stack.push(m) - return i+1 -def w_rot(f, i): +@word() +def 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 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 diff --git a/sallyforth/startup.sf b/sallyforth/startup.sf deleted file mode 100644 index 8203098..0000000 --- a/sallyforth/startup.sf +++ /dev/null @@ -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 ; - -: [] ( -- ) [ ] 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 ; - -: {} ( -- ) { } 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 -; - -: {{}} ( -- ) {{ }} 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 diff --git a/sallyforth/string.sf b/sallyforth/string.sf index c18df47..10f719c 100644 --- a/sallyforth/string.sf +++ b/sallyforth/string.sf @@ -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 } diff --git a/sallyforth/test.sf b/sallyforth/test.sf index 5959f6c..0c5bbee 100644 --- a/sallyforth/test.sf +++ b/sallyforth/test.sf @@ -18,7 +18,6 @@ reset 1 2 3 reset stackdepth 0 = "Reset empties the stack." assert 2 3 * 6 = "2*3 = 6" 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 1 =0 not "One is not =0." assert @@ -46,22 +45,23 @@ false false and not "F and F is F." assert \ 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 -: push64 push8 push8 * ; +: push64 { push8 push8 * } push64 64 = "A word can use primitive and sec words." assert \ 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 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 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 "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 @@ -94,10 +94,10 @@ reset false 1-or-2 2 = "False part of ifelse fires." assert \ 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 -: 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 diff --git a/sallyforth/tokenstream.py b/sallyforth/tokenstream.py index f2769d1..89ee6c7 100644 --- a/sallyforth/tokenstream.py +++ b/sallyforth/tokenstream.py @@ -14,6 +14,17 @@ class Token: self.kind = kind 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): return self.kind == 'string' @@ -30,7 +41,7 @@ class Token: return str(self) def __str__(self): - return f'Token[{self.kind} => {self.value}]' + return f'Token {self.kind} => {self.value}' class PromptInputStream: @@ -52,6 +63,7 @@ class PromptInputStream: class TokenStream: def __init__(self, read_f): + #print("Tokenstream", read_f) self.read_f = read_f def whitespace(self, ch): @@ -73,6 +85,9 @@ class TokenStream: return Token('string', token) if state in ['word']: return Token('word', token) + if state == 'number': + return Token('number', token) + #print("x get returning NONE") return None elif state == 'start' and ch == ':': token = ch @@ -101,7 +116,8 @@ class TokenStream: token += ch elif state == 'number' and self.whitespace(ch): n = to_number(token) - if n: + if n != None: + #print("returning number", n) return Token('number', n) else: return Token('word', token) @@ -111,13 +127,14 @@ class TokenStream: return Token('string', token) elif state == 'keyword' and self.whitespace(ch): state = 'start' - if len(token) == 1: + if token in [':']: return Token('word', token) return Token('keyword', token) elif state in ['word', 'dqstring', 'sqstring', 'number', 'keyword']: token += ch def file_token_stream(f): + #print("file token stream:", f) return TokenStream(lambda : f.read(1)) def string_token_stream(s): diff --git a/sallyforth/util.py b/sallyforth/util.py new file mode 100644 index 0000000..f7c78cb --- /dev/null +++ b/sallyforth/util.py @@ -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 + diff --git a/sallyforth/wrappers.py b/sallyforth/wrappers.py new file mode 100644 index 0000000..dfa35e0 --- /dev/null +++ b/sallyforth/wrappers.py @@ -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