from .lexer import lex from .parser import parse from .exceptions import * from .typeclass import TypeEnum, is_subtype_of from .structs import * 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, Function) or isinstance(expr, TypeWrap) or isinstance(expr, List): return expr elif isinstance(expr, Symbol) or isinstance(expr, Type): if env.contains(expr.name): return evaluate(env.get(expr.name), env, ns) elif ns is not None and env.contains(f"{ns}/{expr.name}"): 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 if 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") class Function: 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 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() + ")" 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 evaluate_args(self, symbol, params, env, ns): self.arity_check(symbol, params) ret = [] for idx, param in enumerate(params): if idx < len(self.args): arg = self.args[idx] else: arg = self.many if arg.lazy: ret.append(param) continue ev = evaluate(param, env, ns) expected_name = f"{arg.type_}" expected_type = env.get(expected_name) 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 def call(self, expr, env): pass class Builtin(Function): def __init__(self, name, callable_, args=None, many=None): super().__init__(name, None, callable_, args, many) 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.evaluate_args(expr.args[0], expr.args[1:], env, ns) return self.body(expr.args[0], evaluated_args, env, ns) class UserFunction(Function): 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} (" 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 else: many.type_ = param.name 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.evaluate_args(expr.args[0], expr.args[1:], env, ns) 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 = name self.parent = parent self.is_func = is_func def validate_type(self, target, env, ns): # if it's an any type, it's valid if self.parent is None: return Bool(True) valid = self.is_func.call(List([None, target]), env, ns) if valid.value == True: return valid parent_type = env.get(f"{target.type_}") while valid.value != True and parent_type.parent is not None: parent_type = env.get(f"{parent_type.parent}") valid = Bool(self.name == parent_type.name) return valid def __str__(self): return f"{self.name}" class NebType(TypeWrap): pass class UserType(TypeWrap): pass