diff --git a/sallyforth/basic_words.py b/sallyforth/basic_words.py index 926a813..50f670c 100644 --- a/sallyforth/basic_words.py +++ b/sallyforth/basic_words.py @@ -3,6 +3,7 @@ import importlib import os from compiler import Compiler from arglist import Arglist +from operator_words import w_not def const_f(value): def x(f, i): @@ -25,11 +26,15 @@ def native_function_handler(func): 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}' + localname = f'{alias}/{name}' val = getattr(m, name) + print("setting", localname) forth.namespace.set(localname, const_f(val)) def w_eval(f, i): @@ -62,10 +67,18 @@ def w_ns(f, i): 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[new_name] = f.namespace[old_name] + f.namespace.alias(new_name, old_name) return i + 1 def w_require(f, i): @@ -145,11 +158,11 @@ def w_import(f, i): def w_call(f, i): func = f.stack.pop() args = f.stack.pop() - # print('f', f, 'args', args) + #print('f', f, 'args', args) try: result = func(*args) except: - print(f'Error executing {func}{list(args)}') + print(f'Error executing {func}({args})') raise # print('result', result) f.stack.push(result) @@ -189,6 +202,9 @@ def i_semi(forth, i): 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 @@ -223,6 +239,14 @@ def i_if(forth, i): compiler.add_instruction(w_should_not_happen) return i+1 +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 + def i_then(forth, i): compiler = forth.compiler else_offset = compiler.pop_offset() @@ -252,26 +276,40 @@ def i_do(forth, i): compiler.add_instruction(w_should_not_happen) return i+1 -def i_while(forth, i): - compiler = forth.compiler - do_offset = compiler.pop_offset() - while_offset = compiler.offset() - delta = do_offset - while_offset - compiler.instructions[if_offset] = ifnot_jump_f(delta) - return i+1 - 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 -def i_until(forth, i): +def i_repeat(forth, i): compiler = forth.compiler + while_offset = compiler.pop_offset() begin_offset = compiler.pop_offset() - until_offset = compiler.offset() - delta = begin_offset - until_offset - #print('Delta:', delta) - compiler.instructions.append(ifnot_jump_f(delta)) + 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): + print("marker1") + return i+1 + +def w_marker2(f, i): + print("marker3") + return i+1 + +def w_marker3(f, i): + print("marker3") return i+1 def w_dump(f, i): diff --git a/sallyforth/data_words.py b/sallyforth/data_words.py index 26b2a55..d56c2d4 100644 --- a/sallyforth/data_words.py +++ b/sallyforth/data_words.py @@ -76,8 +76,10 @@ def w_list(f, ip): # ->list def w_thread(f, i): # @@ contents = f.stack.pop() + print("Contents:", contents) result = contents[0] for field in contents[1::]: + print("Result:", result) if isinstance(field, str) and hasattr(result, field): result = getattr(result, field) # result.field elif isinstance(field, Arglist): diff --git a/sallyforth/init.sf b/sallyforth/init.sf index f1c0b04..d375af4 100644 --- a/sallyforth/init.sf +++ b/sallyforth/init.sf @@ -28,8 +28,8 @@ 'os import -: expandvars #os.path.expandvars !!1 ; -: expanduser #os.path.expanduser !!1 ; +: expandvars #os/path/expandvars !!1 ; +: expanduser #os/path/expanduser !!1 ; : expand expanduser expandvars ; : tokenize <. $? 'split .> " " swap !!1 ; @@ -48,4 +48,5 @@ then ; + : % "Toggle" p ; diff --git a/sallyforth/io.sf b/sallyforth/io.sf index 3d0c4d1..b46a3c2 100644 --- a/sallyforth/io.sf +++ b/sallyforth/io.sf @@ -1,12 +1,12 @@ -: open builtins.open !!1 ; +: 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-line (prompt -- input-line) - builtins.input !!1 + builtins/input !!1 ; \ : read-next nexttoken second ; diff --git a/sallyforth/kernel.py b/sallyforth/kernel.py index eba98a2..8ccd1f3 100644 --- a/sallyforth/kernel.py +++ b/sallyforth/kernel.py @@ -3,18 +3,10 @@ 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 from stack import Stack from namespace import Namespace -def to_number(token): - try: - return int(token) - except ValueError: - try: - return float(token) - except ValueError: - return None - class Forth: def __init__(self, startup=None): self.streams = Stack() @@ -39,6 +31,7 @@ class Forth: 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") self.compiler = None @@ -58,77 +51,71 @@ class Forth: def compiling(self): return self.compiler - def _compile_token(self, kind, token): + 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 + self.compiler.name = token.value return - if kind in ['dqstring', 'sqstring']: - self.compiler.add_instruction(const_f(token)) + if token.isnumber() or token.isstring(): + self.compiler.add_instruction(const_f(token.value)) return - if token in self.namespace: - entry = self.namespace[token] - #print(token, entry) - 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) + if token.iskeyword(): + self.compiler.add_instruction(Keyword(token.value)) return - n = to_number(token) - if n == None: + 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: - self.compiler.add_instruction(const_f(n)) + value = entry.get_cvalue() + self.compiler.add_instruction(value) - def _eval_token(self, kind, token): - #print(f'eval token {token} kind {kind}') - if kind in ['dqstring', 'sqstring']: - self.stack.push(token) - return - - if token in self.namespace: - #print("executing ", token) - f = self.namespace[token].get_ivalue() - #print(f) - f(self, 0) - return - - n = to_number(token) - if n == None: + 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: - self.stack.push(n) + entry = self.namespace[token.value] + f = entry.get_ivalue() + f(self, 0) - def execute_token(self, kind, token): + def execute_token(self, token): #print(f'execute_token: {token}') - kts = self.macro_expand_token(kind, token) - #print(kts) - for kt in kts: - this_kind, this_token = kt + expanded_tokens = self.macro_expand_token(token) + #print(expanded_tokens) + for expanded in expanded_tokens: if not self.compiling(): - #print("interactive", this_token) - self._eval_token(this_kind, this_token) + self._eval_token(expanded) else: - #print("compiling...", this_token) - self._compile_token(this_kind, this_token) - #print("Done") + self._compile_token(expanded) def execute_current_stream(self): s = self.streams.peek() #print("exec current s:", s) - kind, token = s.get_token() - while kind != 'eof': - self.execute_token(kind, token) - kind, token = s.get_token() + 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): @@ -140,6 +127,22 @@ class Forth: 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() @@ -154,24 +157,29 @@ class Forth: rargs.reverse() if rargs: for a in rargs: - # print("pushing", a); + #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, kind, token): - if len(token) <= 0 or token[0] != '#': - return [[kind, token]] + 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] - tag = token[1:] + print("Expanding token:", token) + tag = token.value[1:] parts = tag.split('.') - result = [ '<.', parts[0] ] - result = [['word', '<.'], ['word', parts[0]]] + print("Parts", parts) + result = [ ts.Token('word', '<.'), ts.Token('word', parts[0]) ] for part in parts[1::]: - result.append(['sqstring', part]) - result.append(['word', '.>']) - #print(result) + result.append(ts.Token('string', part)) + result.append(ts.Token('word', '.>')) + print(result) return result def set_ns(self, ns_name): diff --git a/sallyforth/kword.py b/sallyforth/kword.py new file mode 100644 index 0000000..3081f40 --- /dev/null +++ b/sallyforth/kword.py @@ -0,0 +1,12 @@ +from collections import UserString + +class Keyword(UserString): + def __init__(self, value): + value = value[1::] + UserString.__init__(self, value) + + def __call__(self, d): + return d[self] + + def __repr__(self): + return ':' + str(self) diff --git a/sallyforth/list.sf b/sallyforth/list.sf index 4d44919..df1690e 100644 --- a/sallyforth/list.sf +++ b/sallyforth/list.sf @@ -11,7 +11,7 @@ : slice (start stop -- slice-obj) swap 2 ->list - builtins.slice + builtins/slice !! ; @@ -25,11 +25,11 @@ [x] ; -: repeat (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? ; @@ -39,3 +39,9 @@ : ffirst (list -- first-of-first) first first ; : fffirst (list -- fff-irst) first first first ; + +: append (x list -- list-with-x-appended) + dup tbm + <. $? 'append .> !!1 + drop +; diff --git a/sallyforth/namespace.py b/sallyforth/namespace.py index 37f2ffd..6ae43e3 100644 --- a/sallyforth/namespace.py +++ b/sallyforth/namespace.py @@ -24,6 +24,9 @@ class Namespace: 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. diff --git a/sallyforth/startup.sf b/sallyforth/startup.sf index af95258..8203098 100644 --- a/sallyforth/startup.sf +++ b/sallyforth/startup.sf @@ -38,7 +38,7 @@ 'current_ns '*ns* alias : *prompt* "Sally> " ; -: *execute-command* stack execute ; +: *execute-command* execute ; \ Make a list. 'list-marker unique def @@ -73,7 +73,7 @@ : }} 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 + builtins/set !! \ Call set with 1 argument inline ; @@ -83,17 +83,17 @@ : :] ] ->arglist inline ; -: str builtins.str !!1 ; +: str builtins/str !!1 ; -: type builtins.type !!1 ; +: type builtins/type !!1 ; : type? (x class -- bool) swap type = ; -: ctime time.ctime !!0 ; +: ctime time/ctime !!0 ; -: sleep time.sleep !!1 drop ; +: sleep time/sleep !!1 drop ; -: callable? builtins.callable !!1 ; +: callable? builtins/callable !!1 ; : hello "Hello" . nl ; @@ -114,16 +114,18 @@ : neg? 0 < inline ; : zero? 0 = inline ; -: exists? os.path.exists !!1 ; +: 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 !! ; +: getattr ( obj attr -- attr-value ) swap 2 ->list builtins/getattr !! ; : .!! (obj args method-name -- result) tbm getattr !! ; : .!!0 (obj method-name -- result ) [] swap .!! ; @@ -132,11 +134,11 @@ : .!!3 (obj a1 a2 a3 method-name -- result ) swap 3 ->list swap .!! ; : assert ( bool msg -- ) - swap + p if - "OK " . p + "OK " p else - #builtins.AssertionError !!1 + #builtins/AssertionError !!1 raise then ; diff --git a/sallyforth/string.sf b/sallyforth/string.sf index dd4271a..c18df47 100644 --- a/sallyforth/string.sf +++ b/sallyforth/string.sf @@ -1,4 +1,4 @@ -: split (delimit str -- tokens) 2 ->list <. builtins.str 'split .> !! ; +: split (delimit str -- tokens) 2 ->list <. builtins/str 'split .> !! ; : dot-split (str -- tokens) "." swap split ; diff --git a/sallyforth/test.sf b/sallyforth/test.sf index 03a238e..5959f6c 100644 --- a/sallyforth/test.sf +++ b/sallyforth/test.sf @@ -91,3 +91,13 @@ reset false 1-or-2 2 = "False part of ifelse fires." 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 + +\ Loop + +: test-while ( n -- ) -999 swap begin dup zero? while -- repeat -888 ; + +5 test-while 3 ->list [ -999 0 -888 ] "While loop works" assert + +: zero-trip-while begin false while "Should not get here." repeat ; + +888 zero-trip-while 888 = "While should handle zero trip case." assert