File size: 4,129 Bytes
2e4432d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | """Independent recursive tree evaluator: no bytecode compiler or VM calls."""
from .runtime import ICLError,RuntimeContext,checked_binary,checked_unary,builtin,BUILTINS,digest
from .checker import validate_value
class Returned(Exception):
def __init__(self,value):self.value=value
class Scope:
def __init__(self,parent=None):self.parent=parent;self.values={}
def get(self,name):
if name in self.values:return self.values[name]
if self.parent is not None:return self.parent.get(name)
raise ICLError('NAME','undefined '+name)
def set(self,name,val):
if name in self.values:self.values[name]=val
elif self.parent is not None:self.parent.set(name,val)
else:raise ICLError('NAME','undefined assignment '+name)
def execute_tree(program,entry='main',args=None,limits=None,policy=None):
ctx=RuntimeContext(limits,policy);fs={f['name']:f for f in program['functions']};depth=0
def ex(x,scope):
chain=[];cur=scope
while cur is not None:chain.append(cur.values);cur=cur.parent
ctx.step('tree.'+x['kind'],inputs={'scope':chain,'prng':ctx.prng})
event=ctx.trace[-1]
try:
value=_ex(x,scope);event['output_hash']=digest(value);event['state_ref']=event['output_hash'];return value
except ICLError as error:event['error_code']=error.code;raise
def _ex(x,scope):
k=x['kind']
if k=='literal':return x['value']
if k=='var':return scope.get(x['name'])
if k=='unary':return checked_unary(x['op'],ex(x['expr'],scope))
if k=='binary':
a=ex(x['left'],scope)
if x['op']=='&&' and a is False:return False
if x['op']=='||' and a is True:return True
return ctx.check_value(checked_binary(x['op'],a,ex(x['right'],scope)))
if k=='call':
aa=[ex(v,scope) for v in x['args']]
return ctx.check_value(builtin(x['name'],aa,ctx) if x['name'] in BUILTINS else call(x['name'],aa))
if k in ('list','tuple'):return ctx.check_value([ex(v,scope) for v in x['items']])
if k=='record':return ctx.check_value({n:ex(x['fields'][n],scope) for n in sorted(x['fields'])})
if k=='index':
a=ex(x['target'],scope);i=ex(x['index'],scope)
if i<0 or i>=len(a):raise ICLError('BOUNDS','index out of bounds')
return a[i]
if k=='field':return ex(x['target'],scope)[x['name']]
raise ICLError('UNSUPPORTED_OP',k)
def block(ss,parent):
scope=Scope(parent)
for s in ss:
k=s['kind'];ctx.step('tree.'+k)
if k=='let':scope.values[s['name']]=ex(s['expr'],scope)
elif k=='assign':scope.set(s['name'],ex(s['expr'],scope))
elif k=='return':raise Returned(ex(s['expr'],scope))
elif k=='expr':ex(s['expr'],scope)
elif k=='if':block(s['then'] if ex(s['cond'],scope) else s['else'],scope)
elif k=='while':
count=0
while ex(s['cond'],scope):
if count>=s['limit']:raise ICLError('LOOP_LIMIT','declared iteration bound exceeded')
count+=1;block(s['body'],scope)
else:raise ICLError('UNSUPPORTED_OP',k)
def call(name,aa):
nonlocal depth
if name not in fs:raise ICLError('NAME','entry/function missing '+name)
f=fs[name]
if len(aa)!=len(f['params']):raise ICLError('TYPE','function argument count')
depth+=1
if depth>ctx.limits['max_stack']:raise ICLError('STACK_LIMIT','call stack budget')
env=Scope()
for (n,t),v in zip(f['params'],aa):validate_value(v,t,ctx);env.values[n]=v
try:block(f['body'],env)
except Returned as r:validate_value(r.value,f['return_type'],ctx);return r.value
finally:depth-=1
raise ICLError('RETURN','function reached end')
try:value=call(entry,args or [])
except RecursionError:raise ICLError('STACK_LIMIT','host recursion boundary reached')
ctx.check_value(value)
return {'value':value,'trace':ctx.trace,'effects':ctx.effects,'steps':ctx.steps}
|