"""Nominal primitive + structural compound types and conservative effect analysis.""" import math,re from .runtime import ICLError,BUILTINS BASIC={'Int64','Float64','Bool','String','Observer','Context','ProvenanceRef','RecoveryWitness','Capability'} def split_types(s): out=[];start=0;d=0 for i,c in enumerate(s): if c in '[{':d+=1 elif c in ']}':d-=1 elif c==',' and d==0:out.append(s[start:i]);start=i+1 out.append(s[start:]);return out def valid_type(t): if not isinstance(t,str) or len(t)>8192:return False if t in BASIC:return True if t.startswith('Record{') and t.endswith('}'): try: pairs=[x.split(':',1) for x in split_types(t[7:-1])] if not pairs or any(len(x)!=2 for x in pairs):return False names=[x[0] for x in pairs] return len(set(names))==len(names) and names==sorted(names) and all(re.fullmatch(r'[A-Za-z_][A-Za-z_0-9]*',k) and valid_type(v) for k,v in pairs) except (ValueError,RecursionError):return False if t.startswith('Tensor['): try:d=[int(x) for x in t[7:-1].split(',')];return t.endswith(']') and 1<=len(d)<=3 and all(x>0 for x in d) and math.prod(d)<=4096 except ValueError:return False for g in ('List','Option','Result','Representation','Exact','Approx','Tuple'): if t.startswith(g+'[') and t.endswith(']'): ts=split_types(t[len(g)+1:-1]);return len(ts)>=1 and all(valid_type(x) for x in ts) and (g=='Tuple' or len(ts)==1) return False def shape(t): if not t.startswith('Tensor['):raise ICLError('TYPE','Tensor required') return [int(x) for x in t[7:-1].split(',')] def tt(s):return 'Tensor['+','.join(map(str,s))+']' def check(program): fs={};effects=set();ops=[];shapes=[] for f in program['functions']: if f['name'] in fs or f['name'] in BUILTINS:raise ICLError('NAME','duplicate/reserved function '+f['name'],f.get('span')) if not valid_type(f['return_type']):raise ICLError('TYPE','unsupported return type '+f['return_type']) if len({n for n,t in f['params']})!=len(f['params']):raise ICLError('NAME','duplicate parameter') for n,t in f['params']: if not valid_type(t):raise ICLError('TYPE','unsupported parameter type '+t) fs[f['name']]=f def same(a,b,span=None): if a!=b:raise ICLError('TYPE',f'{a} incompatible with {b}',span) def ex(x,env,expected=None): k=x['kind'];sp=x.get('span') if k=='literal': v=x['value'];t='Bool' if type(v)==bool else 'Int64' if type(v)==int else 'Float64' if type(v)==float else 'String' if type(v)==int and not -(1<<63)<=v<(1<<63):raise ICLError('OVERFLOW','literal outside Int64',sp) if type(v)==float and not math.isfinite(v):raise ICLError('NONFINITE','nonfinite literal',sp) elif k=='var': if x['name'] not in env:raise ICLError('NAME','undefined name '+x['name'],sp) t=env[x['name']][0] elif k=='unary': # Smallest Int64 is expressed by unary minus of its magnitude. if x['op']=='-' and x['expr']['kind']=='literal' and type(x['expr']['value'])==int and x['expr']['value']==(1<<63): x['expr']['inferred_type']='Int64';t='Int64' else: a=ex(x['expr'],env);t='Bool' if x['op']=='!' else a if x['op']=='!':same(a,'Bool',sp) elif a not in ('Int64','Float64'):raise ICLError('TYPE','numeric negation only',sp) elif k=='binary': a=ex(x['left'],env);b=ex(x['right'],env);same(a,b,sp);op=x['op'] if op in ('&&','||'):same(a,'Bool',sp);t='Bool' elif op in ('==','!='):t='Bool' elif op in ('<','<=','>','>='): if a not in ('Int64','Float64','String'):raise ICLError('TYPE','ordered scalar required',sp) t='Bool' else: if a not in ('Int64','Float64') and not(op=='+' and a=='String'):raise ICLError('TYPE','numeric arithmetic only',sp) t=a elif k in ('list','tuple'): inner=expected[5:-1] if k=='list' and isinstance(expected,str) and expected.startswith('List[') and valid_type(expected) else None ts=[ex(y,env,inner) for y in x['items']] if not ts: if k=='list' and inner is not None: x['inferred_type']=expected return expected raise ICLError('TYPE','empty list requires a contextual List type',sp) if k=='list': for a in ts:same(a,ts[0],sp) t='List['+ts[0]+']' else:t='Tuple['+','.join(ts)+']' elif k=='record':t='Record{'+','.join(n+':'+ex(v,env) for n,v in sorted(x['fields'].items()))+'}' elif k=='field': a=ex(x['target'],env) if not a.startswith('Record{'):raise ICLError('TYPE','record required',sp) fields=dict(item.split(':',1) for item in split_types(a[7:-1])) if x['name'] not in fields:raise ICLError('NAME','no such record field',sp) t=fields[x['name']] elif k=='index': a=ex(x['target'],env);same(ex(x['index'],env),'Int64',sp) if a.startswith('List['):t=a[5:-1] elif a=='String':t='String' elif a.startswith('Tuple['): ix=x['index'] if ix['kind']!='literal' or type(ix['value'])!=int:raise ICLError('TYPE','tuple index must be literal',sp) ts=split_types(a[6:-1]);i=ix['value'] if i<0 or i>=len(ts):raise ICLError('INDEX','tuple index out of range',sp) t=ts[i] else:raise ICLError('TYPE','indexable value required',sp) elif k=='call': nm=x['name'];args=x['args'] expected_args=[t for _,t in fs[nm]['params']] if nm in fs else [] ts=[ex(y,env,expected_args[i] if i0 elif tag=='Exact': valid=value.get('tag')==tag and fields(['value','witness_bytes','scope']) and type(value['witness_bytes'])==int and value['witness_bytes']>=0 and isinstance(value['scope'],str) if valid:valid=validate_value(value['value'],inner,ctx) elif tag=='Approx': valid=value.get('tag')==tag and fields(['value','error_bound']) and type(value['error_bound'])==float and value['error_bound']>=0 if valid:valid=validate_value(value['value'],inner,ctx) elif tag=='Observer':valid=value.get('tag')==tag and isinstance(value.get('id'),str) elif tag=='Context':valid=value.get('tag')==tag and isinstance(value.get('frame'),str) and isinstance(value.get('unit'),str) elif tag=='ProvenanceRef':valid=value.get('tag')==tag and isinstance(value.get('source_id'),str) and value.get('verified') is False elif tag=='RecoveryWitness': valid=value.get('tag')==tag and all(type(value.get(k))==int for k in ('quotient','modulus','remainder','payload_bytes')) and value.get('modulus',0)>0 and value.get('payload_bytes',-1)>=0 and isinstance(value.get('sha256'),str) elif tag=='Capability':valid=False # no transferable authority values in v0.1 if not valid:raise ICLError('TYPE','input does not match '+t) return True