from .lexer import lex from .parser import parse from .exceptions import * from .typeclass import TypeEnum, is_subtype_of from .structs import * from copy import deepcopy def interpret(exprs, env, ns=None): ret = None for expr in exprs: ret = evaluate(expr, env, ns) return ret def evaluate(expr, env, ns=None): if isinstance(expr, Literal) or isinstance(expr, Callable) or isinstance(expr, TypeWrap) or isinstance(expr, List) or isinstance(expr, Handle): return expr elif isinstance(expr, Symbol) or isinstance(expr, Type): if isinstance(expr, Symbol) and expr.quoted: return Symbol(expr.name, expr.line) if env.contains(expr.name): if isinstance(expr, Type) and expr.inner is not None: typecopy = deepcopy(env.get(expr.name)) inner = env.get(f"{expr.inner}") typecopy.name.inner = inner return evaluate(typecopy, env, ns) else: return evaluate(env.get(expr.name), env, ns) elif ns is not None and env.contains(f"{ns}/{expr.name}"): if isinstance(expr, Type) and expr.inner is not None: typecopy = deepcopy(env.get(f"{ns}/{expr.name}")) inner = env.get(f"{expr.inner}") typecopy.name.inner = inner return evaluate(typecopy, env, ns) else: return evaluate(env.get(f"{ns}/{expr.name}"), env, ns) else: if isinstance(expr, Symbol): raise NebPanic(f"no such symbol: {expr}") else: raise NebPanic(f"no such type {expr}") # if it's an empty list, return it elif len(expr.args) == 0: return expr elif isinstance(expr.args[0], Expr): ev = evaluate(expr.args[0], env, ns) return evaluate(Expr([ev] + expr.args[1:]), env, ns) elif isinstance(expr.args[0], Callable): return expr.args[0].call(expr, env, ns) elif not isinstance(expr.args[0], Symbol): raise NebPanic("can't evaluate without a symbol") name = expr.args[0].name if env.contains(name): return env.get(name).call(expr, env, ns) elif ns is not None and env.contains(f"{ns}/{name}"): return env.get(f"{ns}/{name}").call(expr, env, ns) else: raise InterpretPanic(expr.args[0], "unable to evaluate") # THINGS # - functions/syntax can have multiple arity definitions # - functions can have multiple same-arity, different-type definitions # callable # - name # - implementations # - signature (arguments (name and type), return type) class Signature: def __init__(self, *args, many=None, return_type=None): self.args = args self.many = many if return_type is None: self.return_type = Type(":any") def compatable_arity(self, arity): if arity < len(self.args): return False elif self.many is None and arity > len(self.args): return False else: return True def arity_str(self): out = f"{len(self.args)}" if self.many is not None: out += "+" return out def get_type_by_idx(self, idx): #print(f"getting type by idx: {idx}") if idx < len(self.args): return self.args[idx] else: return self.many def __str__(self): out = "" for arg in self.args: out = f"{out}{arg}" if self.many is not None: out = f"{out}{self.many}" return out class Callable: def __init__(self, name, params, body, args=None, many=None): self.name = name self.params = params self.body = body self.args = args self.many = many #self.type_ = TypeEnum.ANY # TODO no it's not self.type_ = Type(":any") # TODO no it's not self.return_type = Type(":any") if args is None: self.sig = Signature(many=many) else: self.sig = Signature(*args, many=many) def describe(self, name=None): if name is None: name = self.name out = [f"({name}"] out.append(string_args(self.args, self.many)) return " ".join(out).strip() + f") => {self.return_type}" def arity_check(self, symbol, params): min_arity = len([a for a in self.args if not a.optional]) max_arity = -1 if self.many is not None else len(self.args) if len(params) < min_arity or (max_arity >= 0 and len(params) > max_arity): if max_arity < 0: fmt = f"{min_arity}+" elif min_arity != max_arity: fmt = f"{min_arity}-{max_arity}" else: fmt = f"{min_arity}" raise InterpretPanic(symbol, f"expected [{fmt}] arguments, received {len(params)}") return True def call(self, expr, env): pass class MultiFunction(Callable): def __init__(self, name): super().__init__(name, None, None) self.impls = {} def __str__(self): builtin_count = len(list(x for x in self.impls.values() if isinstance(x, Builtin))) userfunc_count = len(list(x for x in self.impls.values() if isinstance(x, UserFunction))) b = "" uf = "" if builtin_count != 0: b = f"{builtin_count} builtin" if userfunc_count != 0: uf = f"{userfunc_count} user defined" if b != "" and uf != "": desc = f"{b}, {uf}" else: desc = b if b != "" else uf return f"function {self.name} ({desc})" def describe(self): return [i.describe() for i in self.impls.values()] def user_impl(self): return len(list(x for x in self.impls.values() if isinstance(x, UserFunction))) != 0 def register(self, impl): self.impls[f"{impl.sig}"] = impl def call(self, expr, env, ns): symbol = expr.args[0] params = expr.args[1:] #print("in resolve!") #print(f"{self.impls}") #for f in self.impls.values(): # print(f"{type(f)}") # get compatable arities compatable_arities = [k for k,v in self.impls.items() if v.sig.compatable_arity(len(params))] if len(compatable_arities) == 0: fmt = "|".join(f"{v.sig.arity_str()}" for v in self.impls.values()) raise InterpretPanic(symbol, f"expected [{fmt}] arguments, received {len(params)}") #print(f"compatable arities: {compatable_arities}") ret = [] prev_candidates = [] current_types = [] next_candidates = compatable_arities for param_idx, param in enumerate(params): # evaluate the parameter ev = evaluate(param, env, ns) # reset the types we may be looking for current_types = [] prev_candidates = next_candidates[:] next_candidates = [] # loop through candidate functions for candidate in prev_candidates: func = self.impls[candidate] exp = func.sig.get_type_by_idx(param_idx) expected_type = evaluate(exp.type_, env, ns) #print(f"expected type: {expected_type}") current_types.append(expected_type) valid = expected_type.validate_type(ev, env, ns) if valid.value: next_candidates.append(candidate) # if we have no more good functions, panic if len(next_candidates) == 0: fmt = "|".join(f"{t}" for t in current_types) rec = f"{ev.type_}" raise InterpretPanic(symbol, f"received {rec}, expected {fmt}", ev) else: ret.append(ev) if len(next_candidates) != 1: raise InterpretPanic(symbol, "ambiguous definition!") #return(f"returning!") return self.impls[next_candidates[0]].call(Expr([symbol] + ret), env, ns) class Special(Callable): def __init__(self, name, params, body, args=None, many=None): super().__init__(name, params, body, args, many) class NebSyntax(Special): def __init__(self, name, callable_, args=None, many=None, return_type=None): super().__init__(name, None, callable_, args, many) if return_type is not None: self.return_type = return_type def __str__(self): return f"syntax function {self.name}" def call(self, expr, env, ns): #self.arity_check(expr.args[0], expr.args[1:]) return self.body(expr.args[0], expr.args[1:], env, ns) class Function(Callable): def __init__(self, name, params, body, args=None, many=None): super().__init__(name, params, body, args, many) def precall(self, symbol, params, env, ns): ret = [] for idx, param in enumerate(params): if idx < len(self.args): arg = self.args[idx] else: arg = self.many ev = evaluate(param, env, ns) expected_type = evaluate(arg.type_, env, ns) valid = expected_type.validate_type(ev, env, ns) if not valid.value: exp = f"{arg.type_}" rec = f"{ev.type_}" raise InterpretPanic(symbol, f"received {rec}, expected {exp}", ev) ret.append(ev) return ret #class Builtin(Function): class Builtin(Callable): def __init__(self, name, callable_, args=None, many=None, return_type=None): super().__init__(name, None, callable_, args, many) if return_type is not None: self.return_type = return_type def __str__(self): return f"builtin function {self.name}" def call(self, expr, env, ns): ''' self.arity_check(expr.args[0], expr.args[1:]) evaluated_args = self.precall(expr.args[0], expr.args[1:], env, ns) return self.body(expr.args[0], evaluated_args, env, ns) ''' #print(type(expr.args[1])) return self.body(expr.args[0], expr.args[1:], env, ns) #class UserFunction(Function): class UserFunction(Callable): def __init__(self, name, params, body): newparams, args, many = self.process_params(name, params) super().__init__(name, newparams, body, args, many) def __str__(self): out = f"(func {self.name} {self.return_type} (" args_list = [f"{a.name} {a.type_}" for a in self.args] if self.many: args_list.append(f"{self.many.name} {self.many.type_}") out = out + " ".join(args_list) + ") " for expr in self.body: out = out + f"{expr} " return out.strip() + ")" def process_params(self, name, params): newparams = [] args = [] many = None prev_type = False first = True for param in params: if isinstance(param, Symbol): if many is not None: raise NebPanic("& must be last argument") if param.name == "&": many = Arg(param.name, TypeEnum.ANY) else: newparams.append(param) args.append(Arg(param.name, TypeEnum.ANY)) prev_type = False elif isinstance(param, Type) and not prev_type and not first: if many is None: #args[-1].type_ = param.name args[-1].type_ = param else: #many.type_ = param.name many.type_ = param prev_type = True else: raise NebPanic("invalid :func signature", param) first = False return newparams, args, many def call(self, expr, env, ns): #self.arity_check(expr.args[0], expr.args[1:]) #evaluated_args = self.precall(expr.args[0], expr.args[1:], env, ns) evaluated_args = expr.args[1:] this_env = Environment(env) for idx, param in enumerate(self.params): this_env.register(param.name, evaluated_args[idx]) # if we got "many", wrap the rest in a list if self.many: this_env.register(self.many.name, List(evaluated_args[len(self.params):])) return interpret(self.body, env=this_env, ns=ns) class TypeWrap: def __init__(self, name, parent, is_func): self.name = ALL_TYPES[name] self.parent = parent self.is_func = is_func self.type_ = Type(":type") def validate_type(self, target, env, ns): # if it's an any type, it's valid if self.parent is None: return Bool(True) if isinstance(self.is_func, Function) or isinstance(self.is_func, MultiFunction): valid = self.is_func.call(Expr([self.name, target]), env, ns) else: valid = self.is_func(self.name, [target], env, ns) if valid.value == True: return valid parent_type = env.get(target.type_.name) while valid.value != True and parent_type.parent is not None: parent_type = env.get(parent_type.parent.name.name) valid = Bool(self.name == parent_type.name) # TODO wrong return valid def __str__(self): return f"{self.name}" class NebType(TypeWrap): pass class UserType(TypeWrap): def __init__(self, name, parent, is_func): if name in ALL_TYPES: raise NebPanic(f"already a type called {name}") ALL_TYPES[name] = Type(name) super().__init__(name, parent, is_func)