from dataclasses import dataclass from enum import Enum, auto from typing import Any from .typeclass import TypeEnum from .exceptions import NebPanic #from . import Function # tokens and types # NOTE: this can probably be simplified class TokenType(Enum): OPEN_PAREN = auto() CLOSE_PAREN = auto() EOF = auto() # literals INT = auto() FLOAT = auto() STRING = auto() TRUE = auto() FALSE = auto() # keywords IF = auto() FOR_COUNT = auto() DEF = auto() LAMBDA = auto() FUNC = auto() # symbols SYMBOL = auto() # types INT_TYPE = auto() FLOAT_TYPE = auto() NUMBER_TYPE = auto() STRING_TYPE = auto() ANY_TYPE = auto() LIST_TYPE = auto() LITERAL_TYPE = auto() BOOL_TYPE = auto() USER_TYPE = auto() MANY = auto() @dataclass class Token: type_: TokenType text: str value: Any line: int def __str__(self): return f"{self.type_.name} {self.text} {self.line}" class Literal: def __init__(self, value, type_=None): self.value = value if type_ is None: self.type_ = TypeEnum.ANY else: self.type_ = type_ def __str__(self): return f"{self.value}:literal" class Int(Literal): def __init__(self, value): super().__init__(value, TypeEnum.INT) def __str__(self): return f"{self.value}" class Float(Literal): def __init__(self, value): super().__init__(value, TypeEnum.FLOAT) def __str__(self): return f"{self.value}" class Bool(Literal): def __init__(self, value): super().__init__(value, TypeEnum.BOOL) def __str__(self): return f"#{str(self.value).lower()}" class String(Literal): def __init__(self, value): super().__init__(value, TypeEnum.STRING) def __str__(self): return f'"{repr(self.value)[1:-1]}"' class Type: def __init__(self, name): self.name = name def __str__(self): return self.name class Symbol: def __init__(self, name, line): self.name = name self.line = line self.type_ = TypeEnum.ANY # TODO no it's not def __str__(self): return f"{self.name}" class Expr: def __init__(self, args): self.args = args self.type_ = TypeEnum.ANY # TODO no it's not def __str__(self): return "(" + " ".join(f"{arg}" for arg in self.args) + ")" class List: def __init__(self, args): self.args = args self.type_ = TypeEnum.LIST def __str__(self): return "(" + " ".join(f"{arg}" for arg in self.args) + ")" # function things class Arg: def __init__(self, name, type_, *, optional=False, lazy=False): self.name = name self.type_ = type_ self.optional = optional self.lazy = lazy def __str__(self): opt = "?" if self.optional else "" lazy = "~" if self.lazy else "" return f"{lazy}{opt}{self.name} {self.type_}" def string_args(args, many): out = [f"{arg}" for arg in args] if many is not None: many.name = "&" out.append(f"{many}") return " ".join(out).strip() class Environment: def __init__(self, parent=None): self.parent = parent self.environment = {} def register(self, key, value): self.environment[key] = value def reregister(self, key, value): if not self.contains(key): raise NebPanic(f"undefined symbol: '{key}") if key in self.environment: self.register(key, value) else: self.parent.reregister(key, value) def contains(self, key): if key in self.environment: return True elif self.parent is not None: return self.parent.contains(key) else: return False def get(self, key): if not self.contains(key): raise NebPanic(f"undefined symbol: '{key}") if key in self.environment: return self.environment[key] else: return self.parent.get(key) def __str__(self): out = "" for k, v in self.environment.items(): out += f"{k}: {v}, " return out ''' 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}"] if self.args is not None: for arg in self.args: out.append(f"{arg}") if self.many is not None: out.append(f"{self.many}") return " ".join(out) + ")" 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) if not is_subtype_of(ev.type_, arg.type_): 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, callable_, args=None, many=None): super().__init__("", 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: typ = TypeEnum.__getattr__(param.name[1:].upper()) if many is None: args[-1].type_ = typ else: many.type_ = typ 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) '''