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}