Add namespaces, fix parser bugs, add compile to python back in.

This commit is contained in:
Russ Olsen 2020-06-04 07:59:18 -04:00
parent 66a4e271d8
commit 960e4b0033
23 changed files with 746 additions and 330 deletions

View file

@ -1,6 +1,4 @@
"Hello from 0.sf" p / Pull in libs.
\ Pull in libs.
"builtins" load "builtins" load
"time" load "time" load
@ -13,64 +11,66 @@
'builtins import 'builtins import
'time import 'time import
\ Basic aliases / Basic aliases
: ->inline { *last-word* inline } : ->compile { *last-word* compile }
: ->static { false *last-word* dynamic } : ->inline { *last-word* inline }
: ->dynamic { true *last-word* dynamic } : ->optimize { ->inline ->compile }
: ->static { false *last-word* dynamic }
: ->dynamic { true *last-word* dynamic }
: -- { 1 - } ->inline : -- { 1 - } ->compile
: ++ { 1 + } ->inline : ++ { 1 + } ->compile
: =0 { 0 = } ->inline : =0 { 0 = } ->compile
: pos? { 0 > } ->inline : pos? { 0 > } ->compile
: neg? { 0 < } ->inline : neg? { 0 < } ->compile
: zero? { 0 = } ->inline : zero? { 0 = } ->compile
: drop1 { drop } ->inline : drop1 { drop } ->compile
: drop2 { drop drop } ->inline : drop2 { drop drop } ->compile
: drop3 { drop2 drop } ->inline : drop3 { drop2 drop } ->compile
\ List making. / List making.
unique 'list-marker =! unique 'list-marker =!
: [ list-marker : [ list-marker ->compile
: ] { list-marker [list] } : ] { list-marker [list] } ->compile
: [] { [ ] } : [] { [ ] } ->compile
unique 'map-marker =! unique 'map-marker =!
: {{ map-marker : <<= map-marker ->compile
: }} { map-marker [list] list->map } : =>> { map-marker [list] list->map } ->compile
: {{}} { {{ }} } : <<=>> { <<= =>> } ->compile
\ Spelunk thru objects and properties. / Spelunk thru objects and properties.
: <. [ : <. [
: .> { ] @@ } : .> { ] @@ }
: $? swap : $? swap
\ Set the interactive prompt.
: *prompt* "sallySh> " / Function calling.
\ Function calling.
: !!0 { [] swap !! } : !!0 { [] swap !! }
: !!1 { swap 1 ->list swap !! } : !!1 { swap 1 ->list swap !! }
: !!2 { mbt 2 ->list swap !! } : !!2 { mbt 2 ->list swap !! }
: getattr ( obj attr -- attr-value ) { / obj attr -- attr-value
: getattr {
swap 2 ->list builtins/getattr !! swap 2 ->list builtins/getattr !!
} }
: setattr ( obj attr value -- ) { / obj attr value
: setattr {
bmt 3 ->list builtins/setattr bmt 3 ->list builtins/setattr
} }
: .!! (obj args method-name -- result) { / obj args method-name -- result
: .!! {
tbm getattr !! tbm getattr !!
} }
\ Handy utilities / Handy utilities
: str { builtins/str !!1 } : str { builtins/str !!1 }
: type { builtins/type !!1 } : type { builtins/type !!1 }
@ -78,7 +78,12 @@ unique 'map-marker =!
: sleep { time/sleep !!1 drop } : sleep { time/sleep !!1 drop }
: ctime { time/ctime !!0 } : ctime { time/ctime !!0 }
: assert ( bool msg -- ) { / Set the interactive prompt.
: *prompt* { <. *ns* 'name .> str " sf>> " + }
/ bool msg --
: assert {
dup dup
p p
swap swap
@ -87,7 +92,24 @@ unique 'map-marker =!
{ builtins/AssertionError !!1 raise } { builtins/AssertionError !!1 raise }
} }
\ Other startup files. / Namespaces
: namespaces { <. forth 'namespaces .> }
: namespace {
dup ns? ifelse
{ stack set-ns }
{ dup new-ns set-ns }
}
: require {
dup dup
'.sf + source
ns? not ifelse { "Namespace " . . " not loaded." p }
{ "Namespace " . . " loaded." p }
}
/ Other startup files.
*sallyforth-dir* "/" "io.sf" + + source *sallyforth-dir* "/" "io.sf" + + source
*sallyforth-dir* "/" "list.sf" + + source *sallyforth-dir* "/" "list.sf" + + source

136
sallyforth/ast_utils.py Normal file
View file

@ -0,0 +1,136 @@
from ast import *
import ast
forth_stack_ast = Attribute(value=Name(id='forth', ctx=Load()),
attr='stack', ctx=Load())
forth_push_ast = Attribute(value=forth_stack_ast, attr='push', ctx=Load())
def push_ast(val_ast):
return call(func=forth_push_ast, args=[val_ast], keywords=[])
def value_ast(value):
print("value ast:", value)
if isinstance(value, str):
return Str(value)
elif isinstance(value, int):
return Num(value)
elif isinstance(value, float):
return Num(value)
else:
return None
def push_value_ast(value, name='constant'):
vast = value_ast(value)
if vast:
result = FunctionDef(
name=name,
vararg=None,
kw_defaults=[],
decorator_list=[],
args=arguments(args=[arg(arg='forth', annotation=None)], vararg=None, kwonlyargs=[], kw_defaults=[], defaults=[]),
body=[Expr(value=push_ast(vast))])
fix_missing_locations(result)
return result
return None
def dump(x):
#print("dump", x, type(x))
if x == None:
print("None!")
elif isinstance(x,str):
print("String:", x)
elif isinstance(x,list) or isinstance(x, tuple):
for el in x:
print("List dump:")
dump(el)
else:
ast.dump(x)
def indent(s, level=0):
spaces = " " * level
return spaces + str(s)
def nl(s):
return s + "\n"
def dump_coll(kind, ast, level):
n = str(len(ast))
result = nl(indent(kind + "(" + n + ") =>", level))
for x in ast:
result += dump(x, level+1)
return result
def dump_tuple(ast, level=0):
return dump_coll("tuple", ast, level)
def dump_list(ast, level=0):
return dump_coll("list", ast, level)
def dump_plain_str(x, level=0):
return nl(indent("str:" + x, level))
def dump_expr(x, level=0):
return nl(indent("expr!!", level))
def dump_name(x, level=0):
return nl(indent(f'name({x.id})', level))
def ast_dump(x, level=0):
return nl(indent(ast.dump(x), level))
def dump_expr(x, level=0):
return nl(indent("Expr:", level)) \
+ dump(x.value, level+1)
def dump_module(m, level=0):
return nl(indent("Module:", level)) + \
dump_coll("body", m.body, level+1)
def dump_assign(a, level=0):
return nl(indent("Assign:", level)) + \
dump_coll("targets", a.targets, level+1) + \
dump(a.value, level+1)
def dump_call(c, level=0):
return nl(indent("Call", level)) + \
dump(c.func, level+1) + \
dump_coll("Args:", c.args, level+1)
def dump_fdef(fd, level=0):
return nl(indent("FunctionDef", level)) + \
nl(indent(fd.name, level+1)) + \
dump_coll(str(type(fd.body)), fd.body, level+1)
def dump_attr(a, level=0):
return nl(indent("Attr", level)) + \
dump(a.attr, level+1) + \
dump(a.value, level+1)
switcher = {
list: dump_list,
tuple: dump_tuple,
str: dump_plain_str,
Name: dump_name,
Expr: dump_expr,
FunctionDef: dump_fdef,
Module: dump_module,
Assign: dump_assign,
Attribute: dump_attr,
Call: dump_call}
def dump(ast, level=0):
print(">>Dump", ast)
if ast == None:
return nl(indent("None", level))
t = type(ast)
if t in switcher:
f = switcher[t]
return f(ast, level)
else:
print("?????", ast)
return str(ast)

View file

@ -1,16 +1,34 @@
import tokenstream as ts import tokenstream as ts
from wrappers import noop from wrappers import noop
from namespace import Namespace
from util import word, native_word from util import word, native_word
from unique import Unique from unique import Unique
import python_compiler as pc
import inliner
import importlib import importlib
from pprint import pprint from pprint import pprint
@word()
def compile(forth):
name = forth.stack.pop()
var = forth.ns[name]
word_f = var.value
new_f = pc.compile_word_f(word_f, name)
forth.set(name, new_f)
@word()
def inline(forth):
name = forth.stack.pop()
var = forth.ns[name]
word_f = var.value
new_f = inliner.compile_word_f(word_f, name)
forth.set(name, new_f)
@word() @word()
def dynamic(forth): def dynamic(forth):
name = forth.stack.pop() name = forth.stack.pop()
isdyn = forth.stack.pop() isdyn = forth.stack.pop()
var = forth.ns[name] var = forth.ns[name]
print(f'name: {name} var: {var} dyn: {isdyn}')
var.dynamic = isdyn var.dynamic = isdyn
@word() @word()
@ -26,6 +44,11 @@ def native(forth):
wrapped_f = native_word(native_f, name, n, has_return) wrapped_f = native_word(native_f, name, n, has_return)
forth.set(name, wrapped_f) forth.set(name, wrapped_f)
@word("go!")
def exec_word(forth):
func = forth.stack.pop()
func(forth)
@word("function") @word("function")
def function_word(forth): def function_word(forth):
name = forth.stack.pop() name = forth.stack.pop()
@ -54,13 +77,11 @@ def readtoken(forth):
def w_call(forth): def w_call(forth):
func = forth.stack.pop() func = forth.stack.pop()
args = forth.stack.pop() args = forth.stack.pop()
#print('f', f, 'args', args)
try: try:
result = func(*args) result = func(*args)
except: except:
print(f'Error executing {func}({args})') print(f'Error executing {func}({args})')
raise raise
#print('result', result)
forth.stack.push(result) forth.stack.push(result)
@word() @word()
@ -144,28 +165,48 @@ def splat(forth):
def stack(forth): def stack(forth):
print(forth.stack) print(forth.stack)
@word() @word('debug-ns')
def ns(forth): def debug_ns(forth):
print('debug ns')
print(forth.ns.name) print(forth.ns.name)
pprint(forth.ns.includes)
pprint(forth.ns.contents) pprint(forth.ns.contents)
@word('*ns*')
def star_ns_star(forth):
forth.stack.push(forth.ns)
@word('new-ns')
def new_ns(forth):
name = forth.stack.pop()
core = forth.namespaces['core']
namespace = Namespace(name, [core])
forth.namespaces[name] = namespace
@word('include')
def include_ns(forth):
name = forth.stack.pop()
included = forth.namespaces[name]
forth.ns.include_ns(included)
@word('set-ns')
def set_ns_word(forth):
name = forth.stack.pop()
forth.set_ns(name)
@word('ns?')
def ns_question(forth):
name = forth.stack.pop()
forth.stack.push(name in forth.namespaces)
@word(':', True) @word(':', True)
def colon(forth): def colon(forth):
name = forth.stream.get_token().value name = forth.stream.get_token().value
body = forth.compile_next() body = forth.compile_next()
forth.set(name, body) forth.set(name, body)
forth.set_constant('*last-word*', name) forth.core.set_constant('*last-word*', name)
return noop return noop
@word()
def inline(forth):
name = forth.stack.pop()
print('name', name)
var = forth.ns[name]
value = var.value
if not value.forth_primitive:
value.forth_inline = True
@word() @word()
def current_stream(forth): def current_stream(forth):
forth.stack.push(forth.stream) forth.stack.push(forth.stream)
@ -196,27 +237,26 @@ def word_bang(forth):
def w_while(forth): def w_while(forth):
cond = forth.compile_next() cond = forth.compile_next()
body = forth.compile_next() body = forth.compile_next()
#print("cond:", cond)
#print("body", body)
def dowhile(xforth): def dowhile(xforth):
b = fresult(xforth, cond) b = fresult(xforth, cond)
while b: while b:
body(xforth) body(xforth)
b = fresult(xforth, cond) b = fresult(xforth, cond)
dowhile.forth_inline = False dowhile.operation_type = 'while'
dowhile.forth_primitive = True dowhile.immediate = False
dowhile.forth_immediate = False
return dowhile return dowhile
@word('if', True) @word('if', True)
def w_if(forth): def w_if(forth):
compiled = forth.compile_next() compiled = forth.compile_next()
print("compiled", compiled)
def doif(forth): def doif(forth):
value = forth.stack.pop() value = forth.stack.pop()
if value: if value:
compiled(forth) compiled(forth)
doif.forth_inline = False doif.operation_type = 'if'
doif.forth_primitive = True doif.immediate = False
doif.forth_immediate = False
return doif return doif
@word('ifelse', True) @word('ifelse', True)
@ -229,7 +269,6 @@ def ifelse(forth):
compiled_true(forth) compiled_true(forth)
else: else:
compiled_false(forth) compiled_false(forth)
doif.forth_inline = False doif.operation_type = 'ifelse'
doif.forth_primitive = True doif.immediate = False
doif.forth_immediate = False
return doif return doif

View file

@ -1,9 +0,0 @@
: doc { <. $? '__doc__ .> }
'power builtins/pow 2 true native
'abs builtins/abs 1 true native
'round builtins/round 1 true native
\ 'open builtins/open 2 true native

View file

@ -1,105 +0,0 @@
from tokenstream import Token
from wrappers import value_f, inner_f, inner2_f, inner3_f, noop
from recoder import concat_functions
LBrace = Token('word', '{')
RBrace = Token('word', '}')
def composite_function(contents):
asts = []
for f in contents:
ast = getattr(f, 'ast', None)
if not ast:
print("No ast for:", f)
return None
asts.append(ast)
return concat_functions(asts)
def compile_word(forth, w):
name = w.value
var = forth.ns[name]
value = var.value
if value.forth_immediate:
return value(forth)
elif var.dynamic:
return var
else:
return value
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 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 compile_block(forth, stream, wrap_block):
contents = []
t = stream.get_token()
while t != RBrace:
compile_value(contents, compile_next(forth, stream, t))
t = stream.get_token()
if len(contents) == 0:
f = noop
elif len(contents) == 1:
f = contents[0]
elif len(contents) == 2:
f = inner2_f(contents[0], contents[1])
elif len(contents) == 3:
f = inner3_f(contents[0], contents[1], contents[2])
else:
f = inner_f(contents)
if wrap_block:
f = value_f(f)
return f
def xxx_compile_block(forth, stream, wrap_block):
contents = []
t = stream.get_token()
while t != RBrace:
compile_value(contents, compile_next(forth, stream, t))
t = stream.get_token()
f = composite_function(contents)
if not f:
f = inner_f(contents)
if wrap_block:
f = value_f(f)
return f
def compile_next(forth, stream, current_token=None, wrap_block=False):
if current_token:
t = current_token
else:
t = stream.get_token()
if t == None:
return None
if t != LBrace:
return compile_token(forth, t)
return compile_block(forth, stream, wrap_block)
def eval_stream(forth, stream):
t = stream.get_token()
while t:
compiled = compile_next(forth, stream, t, True)
#print(f"*** compiled {t} => {compiled}")
compiled(forth)
t = stream.get_token()

View file

@ -5,7 +5,7 @@ from unique import Unique
def w_bounded_list(forth): def w_bounded_list(forth):
"""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])
""" """
marker = forth.stack.pop() marker = forth.stack.pop()
l = [] l = []

19
sallyforth/inliner.py Normal file
View file

@ -0,0 +1,19 @@
from wrappers import inner_f
def compile_f(contents, name):
new_contents = []
for f in contents:
sub_contents = getattr(f, "contents", None)
if sub_contents:
new_contents.extend(sub_contents)
else:
new_contents.append(f)
new_func = inner_f(new_contents)
new_func.name = name
return new_func
def compile_word_f(f, name=None):
contents = getattr(f, 'contents', None)
if contents and len(contents) > 1:
return compile_f(contents, name)
return f

View file

@ -1,8 +1,8 @@
'io namespace
: 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 { open dup <. $? 'read .> !!0 swap close }
: read-lines (path -- contents) { open dup <. $? 'readlines .> !!0 swap close } : read-lines { open dup <. $? 'readlines .> !!0 swap close }
: read-line (prompt -- input-line) { builtins/input !!1 } : read-line { builtins/input !!1 }

View file

@ -7,65 +7,136 @@ from namespace import Namespace
#import operator_words #import operator_words
#import data_words #import data_words
import tokenstream as ts import tokenstream as ts
import compiler import threaded_compiler as compiler
from wrappers import value_f from wrappers import value_f
class Forth: class Forth:
"""
A class to represent a SallyForth execution context.
An instance of the Forth class is all you need to execute
SallyForth code.
Attributes
----------
stack : Stack
Data stack used by most every word.
namespaces : String -> Namespace dictionary
All of the Forth namespaces indexed by ns name.
ns : Namespace
The currently active Namespace.
core : Namespace
The core namespace. Has all the Forth built-in words.
user : Namespace
The more or less empty default namespace.
"""
def __init__(self): def __init__(self):
"""
Construct a new SallyForth execution environment.
"""
self.stack = Stack() self.stack = Stack()
self.stream = None self.stream = None
self.ns = Namespace('core') core = Namespace('core')
user = Namespace('user', [core])
user.include_ns(core)
self.namespaces = {}
self.namespaces[core.name] = core
self.namespaces[user.name] = user
self.ns = core
self.core = core
self.set_constant('forth', self) self.set_constant('forth', self)
self.set_constant('nil', None) self.set_constant('nil', None)
self.set_constant('true', True) self.set_constant('true', True)
self.set_constant('false', False) self.set_constant('false', False)
self.set_constant('*source*', '<<input>>') self.set_constant('*source*', '<<input>>')
self.set_constant('*last-word*', None) self.set_constant('*last-word*', None)
self.set_constant('*sallyforth-dir*', sally_dir = os.path.dirname(os.path.abspath(__file__))
os.path.dirname(os.path.abspath(__file__))) self.set_constant('*sallyforth-dir*', sally_dir)
self.ns.import_from_module('basic_words') self.ns.import_from_module('basic_words')
self.ns.import_from_module('stack_words') self.ns.import_from_module('stack_words')
self.ns.import_from_module('operator_words') self.ns.import_from_module('operator_words')
self.ns.import_from_module('data_words') self.ns.import_from_module('data_words')
self.eval_file(f'{sally_dir}/0.sf')
self.ns = user
def set_constant(self, name, value): def set_constant(self, name, value):
return self.ns.set(name, value_f(value)) """
Sets name in the current namespace to a function that will push value onto the stack.
"""
return self.ns.set_constant(name, value)
def set(self, name, fvalue): def set(self, name, fvalue):
"""
Sets name in the current namespace to the given function.
"""
return self.ns.set(name, fvalue) return self.ns.set(name, fvalue)
def get(self, name, def_value=None): def get(self, name, def_value=None):
"""
Get the value associated with name in the current namespace (and it's includes).
"""
if name in self.ns: if name in self.ns:
return self.ns[name] return self.ns[name]
return def_value return def_value
def alias(self, new_name, old_name): def alias(self, new_name, old_name):
"""
Given an existing value in the current namespace an additional name.
"""
self.ns.alias(new_name, old_name) self.ns.alias(new_name, old_name)
def set_ns(self, new_ns_name):
"""
Set the current namespace.
"""
self.ns = self.namespaces[new_ns_name]
def compile_next(self, current_token=None): def compile_next(self, current_token=None):
"""
Compile the next token, either the one passed in or the next one on the current token stream.
"""
return compiler.compile_next(self, self.stream, current_token) return compiler.compile_next(self, self.stream, current_token)
def eval_stream(self, stream): def eval_stream(self, stream):
"""
Evaluate the contents of the given token stream.
"""
old_stream = self.stream old_stream = self.stream
self.stream = stream self.stream = stream
compiler.eval_stream(self, stream) compiler.eval_stream(self, stream)
self.stream = old_stream self.stream = old_stream
def eval_file(self, path): def eval_file(self, path):
"""
Evaluate the contents of the given file as Forth source code.
"""
old_source = self.ns['*source*'] old_source = self.ns['*source*']
old_ns = self.ns
with open(path) as f: with open(path) as f:
fns = ts.file_token_stream(f) fns = ts.file_token_stream(f)
return self.eval_stream(fns) result = self.eval_stream(fns)
self.ns['*source*'] = old_source self.ns = old_ns
self.ns['*source*'] = old_source
return result
def eval_string(self, s): def eval_string(self, s):
"""
Evaluate a string as Forth source code.
"""
self.eval_stream(ts.string_token_stream(s)) self.eval_stream(ts.string_token_stream(s))
def eval_string_r(self, s): def eval_string_r(self, s):
"""
Evaluate a string and return the top of the resulting stack.
"""
self.eval_string(s) self.eval_string(s)
return self.stack.pop() return self.stack.pop()
def lookup(self, name): def lookup(self, name):
"""
Return the value of the given name in the current namespace.
"""
return self.ns[name] return self.ns[name]
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -1,6 +1,11 @@
from collections import UserString from collections import UserString
class Keyword(UserString): class Keyword(UserString):
"""
A Keyword is more or less a specialized string. The main difference
between strings and keywords is that Keyswords, when called as a function
with a dictionary as an argument will look themselves up in the dictionary.
"""
def __init__(self, value): def __init__(self, value):
value = value[1::] value = value[1::]
UserString.__init__(self, value) UserString.__init__(self, value)

View file

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

View file

@ -4,6 +4,7 @@ import ast
import copy import copy
from pprint import pprint from pprint import pprint
from util import word from util import word
#from ast_utils import dump
class FunctionVisitor(ast.NodeVisitor): class FunctionVisitor(ast.NodeVisitor):
def __init__(self): def __init__(self):
@ -53,12 +54,16 @@ def load_module(name):
return m return m
def build_composite_function(function_asts, name='generated_function'): def build_composite_function(function_asts, name='generated_function'):
print("*** name:", name)
#dump(function_asts)
new_body = [] new_body = []
for other_f in function_asts: for other_f in function_asts:
print("Other f:") print("Other f:", type(other_f))
ast.dump(other_f) #dump(other_f.body)
new_body.extend(other_f.body) new_body.extend(other_f.body)
new_f = copy.deepcopy(function_asts[0]) new_f = copy.deepcopy(function_asts[0])
new_f.forth_primitive = False
new_f.forth_immediate = False
new_f.name = name new_f.name = name
new_f.body = new_body new_f.body = new_body
return new_f return new_f
@ -72,13 +77,18 @@ def concat_functions(function_asts, name='generated_function'):
of arguments as the first function on the list. of arguments as the first function on the list.
Returns None if it's unable to build the new function. Returns None if it's unable to build the new function.
""" """
print(name)
new_f = build_composite_function(function_asts, name) new_f = build_composite_function(function_asts, name)
new_m = ast.Module([new_f]) new_m = ast.Module([new_f])
print("===== ", name, "====")
#dump(new_m)
code = compile(new_m, "*generated*", "exec") code = compile(new_m, "*generated*", "exec")
eval(code) eval(code)
f = locals()[name] f = locals()[name]
f.ast = new_m.body[0] f.ast = new_m.body[0]
f.forth_primitive = True
f.forth_immediate = False
f.forth_inline = False
print("generated function:", f)
return f return f
#m = load_module('m1') #m = load_module('m1')

View file

@ -1,50 +1,64 @@
from util import get_attribute from util import get_attribute
from wrappers import value_f from wrappers import value_f
from recoder import load_module from module_loader import load_module
class Var: class Var:
"""
A Var is a named container for a value.
Vars contain the name, the value and a dynamic flag,
which indicates if the value is static or should be looked
up anew for each use.
Since the major use for Vars is to store the functions
associated with Forth words, Vars can also proxy many
of the methods of a Forth word.
"""
def __init__(self, name, value, dynamic=True): def __init__(self, name, value, dynamic=True):
self.name = name self.name = name
self.value = value self.value = value
self.dynamic = dynamic self.dynamic = dynamic
def __call__(self, forth): def __call__(self, forth):
#print("indirect call on", self.name)
return self.value(forth) return self.value(forth)
@property @property
def forth_immediate(self): def immediate(self):
return self.value.forth_immediate return self.value.immediate
@property @property
def forth_contents(self): def contents(self):
#print("indirect contents on", self.name) return self.value.contents
return self.value.forth_contents
@property @property
def forth_primitive(self): def operation_type(self):
return self.value.forth_primitive return self.value.operation_type
@property
def forth_name(self):
#print("indirect name on", self.name)
return self.value.forth_name
@property
def forth_inline(self):
return self.value.forth_inline
def __str__(self): def __str__(self):
return f'[[[[Var({self.name}/{self.dynamic}::{self.value})]]]' return f' Var({self.name}/{self.dynamic}::{self.value}) '
def __repr__(self): def __repr__(self):
return str(self) return str(self)
class Namespace: class Namespace:
def __init__(self, name): """
A Namespace is basically a string name -> function dictionary.
Namespaces also know about includes which are a list of other
namespaces.
When you look up a name in a namespace it first looks in its
own dictionary (contents) and then searchs its includes, in
the order in which they were included.
"""
def __init__(self, name, includes=[]):
self.includes = includes.copy()
self.contents = {} self.contents = {}
self.name = name self.name = name
def include_ns(self, other):
self.includes.append(other)
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]
@ -55,11 +69,9 @@ class Namespace:
into this namespace. Removes the prefix. into this namespace. Removes the prefix.
""" """
m = load_module(module_name) m = load_module(module_name)
#print(m)
names = dir(m) names = dir(m)
for name in names: for name in names:
value = getattr(m, name) value = getattr(m, name)
#print("IMP", name, value, '=>', getattr(value, 'ast', None))
if get_attribute(value, 'forth_word'): if get_attribute(value, 'forth_word'):
forth_name = value.forth_name or name forth_name = value.forth_name or name
var = self.set(forth_name, value, False) var = self.set(forth_name, value, False)
@ -68,13 +80,11 @@ class Namespace:
if not alias: if not alias:
alias = m.__name__ alias = m.__name__
alias = alias.replace(".", "/") alias = alias.replace(".", "/")
#print(m, alias)
names = dir(m) names = dir(m)
for name in names: for name in names:
localname = f'{alias}/{name}' localname = f'{alias}/{name}'
val = getattr(m, name) val = getattr(m, name)
#print("setting", localname)
var = self.set(localname, value_f(val), False) var = self.set(localname, value_f(val), False)
def set(self, key, value, dynamic=True): def set(self, key, value, dynamic=True):
@ -87,12 +97,29 @@ class Namespace:
var.dynamic = dynamic var.dynamic = dynamic
return var return var
def set_constant(self, key, value):
return self.set(key, value_f(value))
def keys(self): def keys(self):
return self.contents.keys() return self.contents.keys()
def __contains__(self, key): def all_keys(self):
result = set(self.contents.keys())
for h in self.includes:
result = result.union(set(h.keys()))
return result
def private_contains(self, key):
return self.contents.__contains__(key) return self.contents.__contains__(key)
def __contains__(self, key):
if key in self.contents:
return True
for h in self.includes:
if key in h:
return True
return False
def __delattr__(self, key): def __delattr__(self, key):
return self.contents.__delattr__(key) return self.contents.__delattr__(key)
@ -102,9 +129,16 @@ class Namespace:
def __iter__(self): def __iter__(self):
return self.contents.__iter__() return self.contents.__iter__()
def private_lookup(self, key):
return self.contents[key]
def __getitem__(self, key): def __getitem__(self, key):
#print("getitem:", key) if key in self.contents:
return self.contents.__getitem__(key) return self.contents[key]
for h in self.includes:
if key in h:
return h[key]
raise KeyError(key)
def __str__(self): def __str__(self):
return f'Namespace({self.name})' return f'Namespace({self.name})'

View file

@ -0,0 +1,61 @@
#from ast import Attribute, Name, Call, dump, Load, fix_missing_locations, Module, parse, Expr, Expression, FunctionDef, arguments, arg, Interactive, Str
from ast import *
from pprint import pprint
import ast_utils
def fdef_ast(name, body):
return FunctionDef(name=name,
args=arguments(args=[arg(arg='forth', annotation=None)],
vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
body=body, decorator_list=[], returns=None)
def call_ast(fname):
r = Expr(
value=Call(
func=Name(id=fname, ctx=Load()),
args=[Name(id='forth', ctx=Load())],
keywords=[]))
return r
def print_ast(name):
name = name or "generated function"
r = Expr(
value=Call(
func=Name(id="print", ctx=Load()),
args=[Str(s=name)],
keywords=[]))
return r
def compile_f(contents, name):
d = locals().copy()
exprs = []
for i, val in enumerate(contents):
fname = f'f_{i}'
d[fname] = val
exprs.append(call_ast(fname))
f_ast = fdef_ast('generated_function', exprs)
m = Module(body=[f_ast])
fix_missing_locations(m)
code = compile(m, 'source', 'exec')
exec(code, d)
f = d['generated_function']
f.immediate = False
f.operation_type = 'compiled'
f.name = name
f.contents = contents
return f
def compile_word_f(f, name=None):
"""
Given a Forth word function return an equivalent function.
Compile_word_f works by building up a Python AST for a function
that executes all of the content functions and then compiling
it.
The idea is that compiled functions skip all of the overhead
of running thru the contents array at runtime.
"""
contents = getattr(f, 'contents', None)
if contents and len(contents) > 1:
return compile_f(contents, name)
return f

View file

@ -3,19 +3,23 @@ import sys
import atexit import atexit
import readline import readline
import traceback import traceback
import argparse
from kernel import Forth from kernel import Forth
from tokenstream import prompt_token_stream from tokenstream import prompt_token_stream
HistoryFile=".sallyforth" HistoryFile='.sallyforth'
hist_file = os.path.join(os.path.expanduser("~"), HistoryFile) hist_file = os.path.join(os.path.expanduser('~'), HistoryFile)
class Completer: class Completer:
"""
Supply the list of words available in the current namespace.
"""
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 = \ self.matching_words = \
[w for w in self.f.ns.keys() if w.startswith(prefix)] [w for w in self.f.ns.all_keys() if w.startswith(prefix)]
try: try:
return self.matching_words[index] return self.matching_words[index]
except IndexError: except IndexError:
@ -27,40 +31,52 @@ def setup_readline(history_path, f):
readline.read_history_file(history_path) readline.read_history_file(history_path)
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_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)
atexit.register(save_history) atexit.register(save_history)
def setup_forth(): def setup_forth(run_startups, additional_scripts):
source_dir = os.path.dirname(os.path.abspath(__file__))
startup_file = f'{source_dir}/0.sf'
f = Forth() f = Forth()
if os.path.exists(startup_file): for s in additional_scripts:
f.eval_file(startup_file) f.eval_file(s)
f.eval_string(": *i-cmd* { 'Icmd: p p }")
return f return f
def repl(f): def repl(f):
print('Sally welcomes you!')
while True: while True:
try: try:
prompt = f.eval_string_r('*prompt*') prompt = f.eval_string_r('*prompt*')
try: try:
line = input(prompt) line = input(prompt)
line += "\n"
except EOFError: except EOFError:
return return
try: try:
f.eval_string(line) if len(line) > 0 and line[0] == '/':
print('special handline:', line)
f.stack.push(line)
f.eval_string('*i-cmd*')
else:
f.eval_string(line)
except: except:
traceback.print_exc() traceback.print_exc()
except KeyboardInterrupt: except KeyboardInterrupt:
print() print()
if __name__ == "__main__": def process_args():
f = setup_forth() parser = argparse.ArgumentParser()
parser.add_argument('--nostartup', help='Skip startup scripts', action='store_true')
parser.add_argument('scripts', nargs='*')
args = parser.parse_args()
return (not args.nostartup), args.scripts
if __name__ == '__main__':
run_startup, scripts = process_args()
f = setup_forth(run_startup, scripts)
setup_readline(hist_file, f) setup_readline(hist_file, f)
repl(f) repl(f)
print("Bye!") print('Bye!')

View file

@ -1,6 +1,9 @@
from collections.abc import Sequence from collections.abc import Sequence
class Stack(Sequence): class Stack(Sequence):
"""
A Stack is a traditional last in, first out data stack.
"""
def __init__(self): def __init__(self):
self.contents = [] self.contents = []

View file

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

View file

@ -1,5 +1,5 @@
\ Test stack and arithmetic. / Test stack and arithmetic.
reset stackdepth 0 = "Stack starts empty." assert reset stackdepth 0 = "Stack starts empty." assert
reset 111 stackdepth 1 = "One item on stack" assert reset 111 stackdepth 1 = "One item on stack" assert
@ -27,7 +27,7 @@ reset 1 2 3 reset stackdepth 0 = "Reset empties the stack." assert
1 ++ 2 = "1++ is two." assert 1 ++ 2 = "1++ is two." assert
0 -- -1 = "0-- is -1." assert 0 -- -1 = "0-- is -1." assert
\ Booleans / Booleans
true "True is true." assert true "True is true." assert
true not false = "Not true is false" assert true not false = "Not true is false" assert
@ -43,7 +43,7 @@ true false and not "T and F is F." assert
false true and not "F and T is F." assert false true and not "F and T is F." assert
false false and not "F and F is F." assert false false and not "F and F is F." assert
\ Secondary words / Secondary words
: push8 8 : push8 8
push8 8 = "A word can push a number." assert push8 8 = "A word can push a number." assert
@ -54,7 +54,7 @@ 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 } } : 1-if-true { if { 1 } }
@ -66,17 +66,17 @@ reset false 1-if-true stackdepth 0 = "if does not fire on false." assert
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
\ built in functions / built in functions
123 str "123" = "Str turns numbers into strings." assert 123 str "123" = "Str turns numbers into strings." assert
"abcd" len 4 = "Len gets length of strings." assert "abcd" len 4 = "Len gets length of strings." 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
0 ->list len 0 = "->list with a lenght of 0 gives you empty list." assert 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 44 1 ->list len 1 = "->list with a lenght of 1 gives you 1 list." assert
@ -93,9 +93,11 @@ reset false 1-or-2 2 = "False part of ifelse fires." assert
[ 1 ] empty? not "Empty? knows a non-empty list." assert [ 1 ] empty? not "Empty? knows a non-empty list." assert
[ 1 2 ] empty? not "Empty? knows a non-empty list." assert [ 1 2 ] empty? not "Empty? knows a non-empty list." assert
\ Loop / Loop
: test-while ( n -- ) { -999 swap while { dup zero? } { -- } -888 } 'While p
: test-while { -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
@ -103,6 +105,6 @@ reset false 1-or-2 2 = "False part of ifelse fires." assert
888 zero-trip-while 888 = "While should handle zero trip case." assert 888 zero-trip-while 888 = "While should handle zero trip case." assert
\ Strings / Strings
reset "abc.def.h" dot-split [ "abc" "def" "h" ] = "Dot split splits" assert reset "abc.def.h" dot-split [ "abc" "def" "h" ] = "Dot split splits" assert

View file

@ -0,0 +1,62 @@
from tokenstream import Token
from wrappers import value_f, inner_f
LBrace = Token('word', '{')
RBrace = Token('word', '}')
def compile_word(forth, w):
name = w.value
var = forth.ns[name]
value = var.value
if value.immediate:
result = value(forth)
elif var.dynamic:
result = var
else:
result = value
return result
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 compile_value(contents, v):
contents.append(v)
return contents
def compile_block(forth, stream, wrap_block):
contents = []
t = stream.get_token()
while t != RBrace:
compile_value(contents, compile_next(forth, stream, t))
t = stream.get_token()
f = inner_f(contents)
if wrap_block:
f = value_f(f)
return f
def compile_next(forth, stream, current_token=None, wrap_block=False):
if current_token:
t = current_token
else:
t = stream.get_token()
if t == None:
return None
if t != LBrace:
return compile_token(forth, t)
return compile_block(forth, stream, wrap_block)
def eval_stream(forth, stream):
t = stream.get_token()
while t:
compiled = compile_next(forth, stream, t, True)
compiled(forth)
t = stream.get_token()

View file

@ -10,6 +10,10 @@ def to_number(token):
return None return None
class Token: class Token:
"""
A Token consists of a string, something like "123" or "dup"
and kind, also a string, something like "number" or "word".
"""
def __init__(self, kind, value): def __init__(self, kind, value):
self.kind = kind self.kind = kind
self.value = value self.value = value
@ -50,6 +54,7 @@ def stoken(value):
return Token('string', value) return Token('string', value)
class PromptInputStream: class PromptInputStream:
"A stream of characters from in input prompt."
def __init__(self, prompt_f): def __init__(self, prompt_f):
self.prompt_f = prompt_f self.prompt_f = prompt_f
self.buffer = [] self.buffer = []
@ -67,46 +72,72 @@ class PromptInputStream:
return '' return ''
class TokenStream: class TokenStream:
"""
A TokenStream reads and returns one token at a time.
To create a TokenStream instance you supply the constructor
with a function that returns one character at a time.
"""
def __init__(self, read_f): def __init__(self, read_f):
#print("Tokenstream", read_f)
self.read_f = read_f self.read_f = read_f
self.pushed_char = None
def special(self, ch):
return ch in ['(', ')', '{', '}']
def whitespace(self, ch): def whitespace(self, ch):
return ch in [' ', '\t', '\n'] return ch in [' ', '\t', '\n']
def ender(self, ch):
return self.whitespace(ch) or self.special(ch)
def get_token(self): def get_token(self):
t = self.do_get_token() t = self.do_get_token()
#print("GET token:", t)
return t return t
def next_ch(self):
if self.pushed_char:
ch = self.pushed_char
self.pushed_char = None
return ch
return self.read_f()
def unread(self, ch):
self.pushed_char = ch
def number_or_word(self, s):
n = to_number(s)
if n != None:
return Token('number', n)
else:
return Token('word', s)
def do_get_token(self): def do_get_token(self):
state = 'start' state = 'start'
token = '' token = ''
while True: while True:
ch = self.read_f() ch = self.next_ch()
#print(f'ch: {ch} typech {type(ch)} state {state}')
if ch in ['', None]: if ch in ['', None]:
if state in ['sqstring', 'dqstring']: if state in ['sqstring', 'dqstring']:
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': if state == 'number':
return Token('number', token) return self.number_or_word(token)
#print("x get returning NONE")
return None return None
elif state == 'start' and self.special(ch):
return Token('word', ch)
elif state == 'start' and ch == ':': elif state == 'start' and ch == ':':
token = ch token = ch
state = 'keyword' state = 'keyword'
elif state == 'start' and ch in "+-0123456789": elif state == 'start' and ch in "+-0123456789":
token = ch token = ch
state = 'number' state = 'number'
elif state == 'start' and ch == '\\':
state = 'lcomment'
elif state == 'lcomment' and ch == '\n': elif state == 'lcomment' and ch == '\n':
state = 'start' state = 'start'
elif state == 'start' and ch == '(': elif state == 'start' and ch == '/':
state = 'icomment' state = 'icomment'
elif state == 'icomment' and ch == ')': elif state == 'icomment' and ch in ['\n', '/']:
state = 'start' state = 'start'
elif state == 'start' and self.whitespace(ch): elif state == 'start' and self.whitespace(ch):
continue continue
@ -119,19 +150,17 @@ class TokenStream:
elif state == 'start': elif state == 'start':
state = 'word' state = 'word'
token += ch token += ch
elif state == 'number' and self.whitespace(ch): elif state == 'number' and self.ender(ch):
n = to_number(token) self.unread(ch)
if n != None: return self.number_or_word(token)
#print("returning number", n) elif state == 'word' and self.ender(ch):
return Token('number', n) self.unread(ch)
else:
return Token('word', token)
elif state == 'word' and self.whitespace(ch):
return Token('word', token) return Token('word', token)
elif state == 'sqstring' and self.whitespace(ch): elif state == 'sqstring' and self.whitespace(ch):
self.unread(ch)
return Token('string', token) return Token('string', token)
elif state == 'keyword' and self.whitespace(ch): elif state == 'keyword' and self.ender(ch):
state = 'start' self.unread(ch)
if token in [':']: if token in [':']:
return Token('word', token) return Token('word', token)
return Token('keyword', token) return Token('keyword', token)
@ -139,6 +168,11 @@ class TokenStream:
token += ch token += ch
class MacroTokenStream: class MacroTokenStream:
"""
MacroTokenStream adds a bit of preprocessing to a regular
token stream. Specifically it turns tokens of the form #aa.bb.cc
into a sequence of tokens of the form <. aa 'bb 'cc .>.
"""
def __init__(self, stream): def __init__(self, stream):
self.stream = stream self.stream = stream
self.tokens = [] self.tokens = []
@ -166,7 +200,6 @@ class MacroTokenStream:
return None return None
def file_token_stream(f): def file_token_stream(f):
#print("file token stream:", f)
return MacroTokenStream(TokenStream(lambda : f.read(1))) return MacroTokenStream(TokenStream(lambda : f.read(1)))
def string_token_stream(s): def string_token_stream(s):
@ -179,7 +212,6 @@ def prompt_token_stream(prompt_f):
if __name__ == "__main__": if __name__ == "__main__":
x = 0 x = 0
def pmt(): def pmt():
global x global x
x += 1 x += 1

View file

@ -1,3 +1,6 @@
class Unique: class Unique:
"""
Simple untility class that only exists to be different.
"""
def __str__(self): def __str__(self):
return f'Unique[{id(self)}]' return f'Unique[{id(self)}]'

View file

@ -12,15 +12,12 @@ class word:
f.forth_name = self.name f.forth_name = self.name
else: else:
f.forth_name = f.__name__ f.forth_name = f.__name__
f.forth_primitive = True f.immediate = self.immediate
f.forth_inline = False
f.forth_immediate = self.immediate
return f return f
def wrap_native_f(f, n, hasreturn): def wrap_native_f(f, n, hasreturn):
if n > 0 and hasreturn: if n > 0 and hasreturn:
def wrapper(forth): def wrapper(forth):
print("both")
args = [] args = []
for i in range(n): for i in range(n):
args.append(forth.stack.pop()) args.append(forth.stack.pop())
@ -37,7 +34,6 @@ def wrap_native_f(f, n, hasreturn):
forth.stack.push(f(*args)) forth.stack.push(f(*args))
else: else:
def wrapper(forth): def wrapper(forth):
print("nothing")
f() f()
return wrapper return wrapper
@ -50,9 +46,8 @@ def determine_nargs(f, n):
def native_word(f, name=None, nargs=None, hasreturn=False): def native_word(f, name=None, nargs=None, hasreturn=False):
nargs = determine_nargs(f, nargs) nargs = determine_nargs(f, nargs)
f = wrap_native_f(f, nargs, hasreturn) f = wrap_native_f(f, nargs, hasreturn)
f.forth_type = 'wrapped_primitive' f.operation_type = 'wrapped_primitive'
f.forth_inline = False f.immediate = False
f.forth_immediate = False
return f return f

View file

@ -1,49 +1,67 @@
import ast
import ast_utils
def value_f(value): def value_f(value):
def push_constant(f): def push_constant(f):
f.stack.push(value) f.stack.push(value)
push_constant.forth_inline = False push_constant.immediate = False
push_constant.forth_primitive = True push_constant.operation_type = 'pushv'
push_constant.forth_name = 'pushv' push_constant.value = value
push_constant.forth_immediate = False
return push_constant return push_constant
def inner_f(contents): def inner_f(contents):
def inner(forth): if len(contents) == 0:
f = noop
elif len(contents) == 1:
f = contents[0]
elif len(contents) == 2:
f = inner_f2(contents)
elif len(contents) == 3:
f = inner_f3(contents)
else:
f = inner_fn(contents)
#print("f", f)
return f
def inner_fn(contents):
def i_n(forth):
#print("inner_fn:", contents)
for fn in contents: for fn in contents:
fn(forth) fn(forth)
inner.forth_primitive = False #print("i_n", i_n)
inner.forth_immediate = False i_n.immediate = False
inner.forth_contents = contents i_n.operation_type = 'inner'
inner.forth_inline = False i_n.contents = contents
return inner return i_n
def inner2_f(f1, f2): def inner_f2(contents):
def inner2(forth): f1 = contents[0]
f2 = contents[1]
def i_2(forth):
#print('inner2:', f1, f2) #print('inner2:', f1, f2)
f1(forth) f1(forth)
f2(forth) f2(forth)
inner2.forth_primitive = False i_2.immediate = False
inner2.forth_contents = [f1, f2] i_2.operation_type = 'inner'
inner2.forth_primitive = True i_2.contents = contents
inner2.forth_immediate = False return i_2
inner2.forth_inline = False
return inner2
def inner3_f(f1, f2, f3): def inner_f3(contents):
def inner3(forth): f1 = contents[0]
f2 = contents[1]
f3 = contents[2]
def i_3(forth):
f1(forth) f1(forth)
f2(forth) f2(forth)
f3(forth) f3(forth)
inner3.forth_primitive = False i_3.immediate = False
inner3.forth_contents = [f1, f2, f3] i_3.operation_type = 'inner'
inner3.forth_immediate = False i_3.contents = contents
inner3.forth_inline = False return i_3
return inner3
def noop(value): def noop(value):
pass pass
noop.forth_inline = False noop.immediate = False
noop.forth_primitive = True noop.operation_type = 'noop'
noop.forth_immediate = False