From a956438aec4bdfe237bb89f531c94cea8d2c82a7 Mon Sep 17 00:00:00 2001 From: Russ Olsen Date: Fri, 1 May 2020 08:06:08 -0400 Subject: [PATCH] Clean up forth eval/execute, make repl more forth driven. --- sallyforth/basic_words.py | 38 ++++++++++++---- sallyforth/compiler.py | 4 ++ sallyforth/init.sf | 6 ++- sallyforth/kernel.py | 15 ++++--- sallyforth/namespace.py | 12 +++-- sallyforth/sallyforth.py | 23 ++++++---- sallyforth/stack.py | 3 ++ sallyforth/stack_words.py | 10 ++--- sallyforth/startup.sf | 67 +++++++++++++++++----------- sallyforth/test.sf | 93 +++++++++++++++++++++++++++++++++++++++ sallyforth/tokenstream.py | 2 + 11 files changed, 214 insertions(+), 59 deletions(-) create mode 100644 sallyforth/test.sf diff --git a/sallyforth/basic_words.py b/sallyforth/basic_words.py index 322664a..926a813 100644 --- a/sallyforth/basic_words.py +++ b/sallyforth/basic_words.py @@ -32,14 +32,14 @@ def import_native_module(forth, m, alias=None, excludes=[]): val = getattr(m, name) forth.namespace.set(localname, const_f(val)) -def xx_w_nexttoken(f, i): - token = f.read_next_token() - f.stack.push(token) - return i+1 - def w_eval(f, i): token = f.stack.pop() - f.execute_token(token) + 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): @@ -155,6 +155,20 @@ def w_call(f, i): 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 + def w_nl(f, i): print() return i+1 @@ -165,11 +179,16 @@ def w_return(f, i): 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) - forth.defword(name, word_f) + entry = forth.defword(name, word_f) + entry.inline = forth.compiler.inline + entry.definition = forth.compiler.instructions forth.compiler = None return i+1 @@ -277,4 +296,7 @@ def w_enlist(f, i): f.stack.push([x]) return i+1 - +def w_raise(f, i): + ex = f.stack.pop() + raise ex + return i+1 diff --git a/sallyforth/compiler.py b/sallyforth/compiler.py index 10e5953..78cc6e7 100644 --- a/sallyforth/compiler.py +++ b/sallyforth/compiler.py @@ -5,10 +5,14 @@ class Compiler: self.name = name self.instructions = [] self.offsets = Stack() + self.inline = False def add_instruction(self, ins): self.instructions.append(ins) + def add_instructions(self, instructions): + self.instructions.extend(instructions) + def offset(self): return len(self.instructions) diff --git a/sallyforth/init.sf b/sallyforth/init.sf index 77004ad..f1c0b04 100644 --- a/sallyforth/init.sf +++ b/sallyforth/init.sf @@ -36,9 +36,9 @@ : prompt-and-run ( -- prog-status) ">> " read-line - dup "x" = + dup "q" = if - "Exit!" p + "Quit!" p drop else tokenize @@ -47,3 +47,5 @@ recur then ; + +: % "Toggle" p ; diff --git a/sallyforth/kernel.py b/sallyforth/kernel.py index c7c051d..eba98a2 100644 --- a/sallyforth/kernel.py +++ b/sallyforth/kernel.py @@ -50,10 +50,10 @@ class Forth: self.namespace = user_ns def defword(self, name, value): - self.namespace.set(name, value) + return self.namespace.set(name, value) def defvar(self, name, value): - self.defword(name, const_f(value)) + return self.defword(name, const_f(value)) def compiling(self): return self.compiler @@ -61,7 +61,7 @@ class Forth: def _compile_token(self, kind, token): #print(f"compile: {self.compiler.name}: {token}") if self.compiler.name == None: - print(f'Compiling {token}') + #print(f'Compiling {token}') self.compiler.name = token return @@ -75,6 +75,8 @@ class Forth: 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) @@ -82,12 +84,13 @@ class Forth: n = to_number(token) if n == None: - print(f'{token}? Compile of {self.compiler.name} terminated.') + print(f'[{token}]?? Compile of [{self.compiler.name}] terminated.') self.compiler = None else: self.compiler.add_instruction(const_f(n)) def _eval_token(self, kind, token): + #print(f'eval token {token} kind {kind}') if kind in ['dqstring', 'sqstring']: self.stack.push(token) return @@ -153,7 +156,7 @@ class Forth: for a in rargs: # print("pushing", a); self.stack.push(a) - print(f'Before eval stack is {str(self.stack)}') + #print(f'Before eval stack is {str(self.stack)}') return self.evaluate_string(s) @@ -168,7 +171,7 @@ class Forth: for part in parts[1::]: result.append(['sqstring', part]) result.append(['word', '.>']) - print(result) + #print(result) return result def set_ns(self, ns_name): diff --git a/sallyforth/namespace.py b/sallyforth/namespace.py index 73b33bf..37f2ffd 100644 --- a/sallyforth/namespace.py +++ b/sallyforth/namespace.py @@ -1,8 +1,10 @@ class Entry: - def __init__(self, name, value, immediate): + def __init__(self, name, value, immed): self.name = name self.value = value - self.immediate = immediate + self.immediate = immed + self.inline = False + self.definition = None def get_ivalue(self): return self.value @@ -11,7 +13,10 @@ class Entry: return self.value def __str__(self): - return f'Entry {self.name} {self.immediate}' + result = f'Entry {self.name} {self.immediate} {self.inline}\n' + for x in self.definition: + result += f'{x}\n' + return result class Namespace: def __init__(self, name, initial_contents={}, refers=[]): @@ -51,6 +56,7 @@ class Namespace: entry.cvalue = cvalue entry.immediate = immediate self.contents[name] = entry + return entry def keys(self): return self.contents.keys() diff --git a/sallyforth/sallyforth.py b/sallyforth/sallyforth.py index ce774d2..c258e6d 100644 --- a/sallyforth/sallyforth.py +++ b/sallyforth/sallyforth.py @@ -44,16 +44,21 @@ def setup_forth(): def repl(f): while True: - prompt = f.evaluate_string('*prompt*') try: - line = input(prompt) - except EOFError: - return - try: - f.execute_string(line) - except: - traceback.print_exc() - + prompt = f.evaluate_string('*prompt*') + try: + line = input(prompt) + line += "\n" + except EOFError: + return + try: + f.stack.push(line) + f.execute_string("*execute-command*") + #f.execute_string(line) + except: + traceback.print_exc() + except KeyboardInterrupt: + print() if __name__ == "__main__": f = setup_forth() diff --git a/sallyforth/stack.py b/sallyforth/stack.py index a89fa26..ae1e8a8 100644 --- a/sallyforth/stack.py +++ b/sallyforth/stack.py @@ -23,6 +23,9 @@ class Stack: for i in range(0, self.top+1): yield self.stack[i] + def depth(self): + return self.top + 1 + def empty(self): return self.top == -1 diff --git a/sallyforth/stack_words.py b/sallyforth/stack_words.py index f4e20f4..bb47159 100644 --- a/sallyforth/stack_words.py +++ b/sallyforth/stack_words.py @@ -16,6 +16,10 @@ def w_dot(f, i): print(a, end='') return i+1 +def w_stackdepth(f, i): + d = f.stack.depth() + f.stack.push(d) + def w_splat(f, i): l = f.stack.pop() l.reverse() @@ -29,12 +33,6 @@ def w_dup(f, i): return i+1 def w_tmb(f, i): # A noop - # t = f.stack.pop() - # m = f.stack.pop() - # b = f.stack.pop() - # f.stack.push(b) - # f.stack.push(m) - # f.stack.push(t) return i+1 def w_tbm(f, i): diff --git a/sallyforth/startup.sf b/sallyforth/startup.sf index f662ee6..af95258 100644 --- a/sallyforth/startup.sf +++ b/sallyforth/startup.sf @@ -11,6 +11,8 @@ "io" require "time" require +'builtins import + \ Basic aliases 'None 'nil alias @@ -36,13 +38,14 @@ 'current_ns '*ns* alias : *prompt* "Sally> " ; +: *execute-command* stack execute ; \ Make a list. 'list-marker unique def -: [ list-marker ; -: ] list-marker [list] ; +: [ list-marker inline ; +: ] list-marker [list] inline ; -: [] ( -- ) [ ] ; +: [] ( -- ) [ ] inline ; \ Look up attributes on a value. @@ -51,16 +54,16 @@ : $? swap ; \ Call native functions with various # arguments. -: !!0 [] swap !! ; -: !!1 swap 1 ->list swap !! ; -: !!2 swap 2 ->list swap !! ; +: !!0 [] swap !! inline ; +: !!1 swap 1 ->list swap !! inline ; +: !!2 swap 2 ->list swap !! inline ; \ Make a map. 'map-marker unique def -: { map-marker ; -: } map-marker [list] list->map ; +: { map-marker inline ; +: } map-marker [list] list->map inline ; -: {} ( -- ) { } ; +: {} ( -- ) { } inline ; \ Make a set. @@ -71,16 +74,21 @@ 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 ; -: [: [ ; -: :] ] ->arglist ; +: [: [ 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 ; @@ -89,22 +97,22 @@ : hello "Hello" . nl ; -: >0 0 > ; -: =0 0 = ; -: <1 1 < ; -: <0 0 < ; -: >1 1 > ; -: <1 1 < ; +: >0 0 > inline ; +: =0 0 = inline ; +: <1 1 < inline ; +: <0 0 < inline ; +: >1 1 > inline ; +: <1 1 < inline ; : p . nl ; : top dup p ; -: -- -1 + ; -: ++ 1 + ; -: *2 2 * ; -: pos? 0 > ; -: neg? 0 < ; -: zero? 0 = ; +: -- -1 + inline ; +: ++ 1 + inline ; +: *2 2 * inline ; +: pos? 0 > inline ; +: neg? 0 < inline ; +: zero? 0 = inline ; : exists? os.path.exists !!1 ; @@ -123,7 +131,16 @@ : .!!2 (obj a1 a2 method-name -- result ) swap 2 ->list swap .!! ; : .!!3 (obj a1 a2 a3 method-name -- result ) swap 3 ->list swap .!! ; -\ todo : tokenize #forth.tokenizer.tokenize !!1 ; +: assert ( bool msg -- ) + swap + if + "OK " . p + else + #builtins.AssertionError !!1 + raise + then +; + "string.sf" source "list.sf" source diff --git a/sallyforth/test.sf b/sallyforth/test.sf new file mode 100644 index 0000000..03a238e --- /dev/null +++ b/sallyforth/test.sf @@ -0,0 +1,93 @@ + +\ Test stack and arithmetic. + +reset stackdepth 0 = "Stack starts empty." assert +reset 111 stackdepth 1 = "One item on stack" assert +reset 111 222 333 stackdepth 3 = "Three items on stack" assert + +reset 1 2 3 reset stackdepth 0 = "Reset empties the stack." assert + +10 10 = "Stack and equality." assert +1 2 < "1 less than 2." assert +2 1 > "2 bigger than 1." assert +99 99 = "99 is equal to itself." assert + +1 1 + 2 = "1 + 1 = 2" assert +10 99 + 109 = "10 + 99 = 109" assert +10 1 - 9 = "10 - 1 = 9" 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 + +1 pos? "One is positive." assert +-1 pos? not "One is not positive." assert + +1 ++ 2 = "1++ is two." assert +0 -- -1 = "0-- is -1." assert + +\ Booleans + +true "True is true." assert +true not false = "Not true is false" assert +false not "Not false is true." assert + +true true or "T or T is T." assert +true false or "T or F is T." assert +false true or "F or T is T." assert +false false or not "F or F is F." assert + +true true and "T and T is T." assert +true false and not "T and F is F." assert +false true and not "F and T is F." assert +false false and not "F and F is F." assert + +\ Secondary words + +: push8 8 ; push8 8 = "A word can push a number." assert + +: push8-again push8 ; +push8-again 8 = "A word can call another word." assert + +: push64 push8 push8 * ; +push64 64 = "A word can use primitive and sec words." assert + +\ Logic + +: 1-if-true if 1 then ; + +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 ; + +reset true 1-or-2 1 = "True part of ifelse fires." assert +reset false 1-or-2 2 = "False part of ifelse fires." assert + +\ built in functions + +123 str "123" = "Str turns numbers into strings." assert +"abcd" len 4 = "Len gets length of strings." 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 + +\ Lists + +0 ->list len 0 = "->list with a lenght of 0 gives you empty list." assert +44 1 ->list len 1 = "->list with a lenght of 1 gives you 1 list." assert +5 7 2 ->list len 2 = "->list with a lenght of 2 gives you 2 list." assert + +[ ] 0 ->list = "Brackets are the same as ->list." assert +[ 88 ] 88 1 ->list = "Brackets are the same as ->list." assert +[ 88 99 ] 99 88 2 ->list = "Brackets are the same as ->list." assert + +[ "hello" ] first "hello" = "First works" assert + +[ ] empty? "Empty? knows an empty list." assert +[ 1 ] empty? not "Empty? knows a non-empty list." assert +[ 1 2 ] empty? not "Empty? knows a non-empty list." assert diff --git a/sallyforth/tokenstream.py b/sallyforth/tokenstream.py index f2a8af6..618c495 100644 --- a/sallyforth/tokenstream.py +++ b/sallyforth/tokenstream.py @@ -36,6 +36,8 @@ class TokenStream: return ['eof', ''] elif state == 'start' and ch == '\\': state = 'lcomment' + elif state == 'start' and ch == '%': + return ['word', ch] elif state == 'lcomment' and ch == '\n': state = 'start' elif state == 'start' and ch == '(':