from .. import TypeEnum, Environment, Arg, Builtin, UserFunction, evaluate, interpret, parse, lex, InterpretPanic, TypeWrap, UserType, NebSyntax, MultiFunction from ..structs import * from pathlib import Path from datetime import datetime CORE = Environment() def interpretIf(symbol, args, env, ns): cond = evaluate(args[0], env, ns) if not isinstance(cond, Bool): raise InterpretPanic(symbol, "requires a :bool condition", cond) if cond.value: return evaluate(args[1], env, ns) elif len(args) == 3: return evaluate(args[2], env, ns) return Nil() cond = Arg("cond", TypeEnum.BOOL) t_branch = Arg("t-branch", TypeEnum.ANY) f_branch = Arg("f-branch", TypeEnum.ANY, optional=True) CORE.register("if", NebSyntax("if", interpretIf, [cond, t_branch, f_branch])) def_name_arg = Arg("name", TypeEnum.ANY) def_val_arg = Arg("value", TypeEnum.ANY) CORE.register("def", NebSyntax("def", None, [def_name_arg, def_val_arg], return_type=Type(":nil"))) def interpretRedef(symbol, args, env, ns): if not isinstance(args[0], Symbol): raise InterpretPanic(symbol, "requires a :symbol", args[0]) name = args[0].name # NOTE: we are not evaluating the name!! if not env.contains(name): raise InterpretPanic(symbol, "not previously defined", args[0]) res = evaluate(args[1], env, ns) env.reregister(name, res) return Nil() CORE.register("redef", NebSyntax("redef", interpretRedef, [def_name_arg, def_val_arg], return_type=Type(":nil"))) lambda_args_arg = Arg("args", TypeEnum.ANY) lambda_body_arg = Arg("body", TypeEnum.ANY) CORE.register("lambda", NebSyntax("lambda", None, [lambda_args_arg, lambda_body_arg], lambda_body_arg)) def interpretForCount(symbol, args, env, ns): num = evaluate(args[0], env, ns) if not isinstance(num, Int): raise InterpretPanic(symbol, "count must be an :int", num) new_env = Environment(env) ret = None for idx in range(0, num.value): new_env.register("_idx_", Int(idx + 1)) for arg in args[1:]: ret = evaluate(arg, new_env, ns) if ret is None: return Nil() return ret for_count_arg = Arg("count", TypeEnum.INT) for_body_arg = Arg("body", TypeEnum.ANY) CORE.register("for-count", NebSyntax("for-count", interpretForCount, [for_count_arg, for_body_arg], for_body_arg)) def interpretForEach(symbol, args, env, ns): coll = evaluate(args[0], env, ns) if not isinstance(coll, List): raise InterpretPanic(symbol, "coll must be a :list", coll) new_env = Environment(env) ret = None for item in coll.args: new_env.register("_item_", evaluate(item, env, ns)) for arg in args[1:]: ret = evaluate(arg, new_env, ns) if ret is None: return Nil() return ret for_each_arg = Arg("list", TypeEnum.LIST) CORE.register("for-each", NebSyntax("for-each", interpretForEach, [for_each_arg, for_body_arg], for_body_arg)) def interpretTakeWhile(symbol, args, env, ns): coll = evaluate(args[0], env, ns) if not isinstance(coll, List): raise InterpretPanic(symbol, "coll must be a :list", coll) new_env = Environment(env) ret = [] ev = None for item in coll.args: new_env.register("_item_", evaluate(item, env, ns)) for arg in args[1:]: ev = evaluate(arg, new_env, ns) if not isinstance(ev, Bool): raise InterpretPanic(symbol, "condition must evaluate to a :bool", ev) if ev.value is False: break ret.append(item) return List(ret) CORE.register("take-while", NebSyntax("take-while", interpretTakeWhile, [for_each_arg, for_body_arg], for_body_arg)) def interpretDropWhile(symbol, args, env, ns): coll = evaluate(args[0], env, ns) if not isinstance(coll, List): raise InterpretPanic(symbol, "coll must be a :list", coll) new_env = Environment(env) ev = None which_idx = None for idx, item in enumerate(coll.args): new_env.register("_item_", evaluate(item, env, ns)) for arg in args[1:]: ev = evaluate(arg, new_env, ns) if not isinstance(ev, Bool): raise InterpretPanic(symbol, "condition must evaluate to a :bool", ev) if ev.value is False: which_idx = idx break if which_idx is not None: return List(coll.args[which_idx:]) else: return Nil() CORE.register("drop-while", NebSyntax("drop-while", interpretDropWhile, [for_each_arg, for_body_arg], for_body_arg)) def interpretBranch(symbol, args, env, ns): for arg in args: if len(arg.args) != 2: raise InterpretPanic(symbol, "each branch requires two expressions", len(arg.args)) cond = evaluate(arg.args[0], env, ns) # this is the condition if not isinstance(cond, Bool): raise InterpretPanic(symbol, "branch condition must be :bool", cond) if cond.value: return evaluate(arg.args[1], env, ns) return Nil() CORE.register("branch", NebSyntax("branch", interpretBranch, [for_body_arg], for_body_arg)) CORE.register("func", NebSyntax("func", None, [def_name_arg, lambda_args_arg, lambda_body_arg], lambda_body_arg, Type(":nil"))) def interpretBlock(symbol, args, env, ns): new_env = Environment(env) ret = Nil() for arg in args: ret = evaluate(arg, new_env, ns) return ret block_arg = Arg("expr", TypeEnum.ANY) CORE.register("block", NebSyntax("block", interpretBlock, [block_arg], block_arg)) def interpretWhile(symbol, args, env, ns): new_env = Environment(env) cond = args[0] ret = Nil() while True: ev = evaluate(cond, new_env, ns) if not isinstance(ev, Bool): raise InterpretPanic(symbol, "requires a :bool condition", cond) if not ev.value: break for arg in args[1:]: ret = evaluate(arg, new_env, ns) return ret CORE.register("while", NebSyntax("while", interpretWhile, [Arg("cond", TypeEnum.BOOL)], Arg("expr", TypeEnum.ANY))) # NOTE this doesn't technically need to be a macro def interpretUse(symbol, args, env, ns): target = evaluate(args[0], env, ns) libs = Path("~/.local/share/neb/libs/").expanduser().glob("*.neb") target_file_list = [p for p in libs if p.stem == target.value] if len(target_file_list) == 1: target_file = target_file_list[0] else: target_file = Path(target.value).resolve() if not target_file.exists(): raise InterpretPanic(symbol, "no such file or lib", target_file) with open(target_file, "r") as fil: data = fil.read() interpret(parse(lex(data)), env, ns) return Nil() CORE.register("use", NebSyntax("use", interpretUse, [Arg("lib-or-file", TypeEnum.STRING)], return_type=Type(":nil"))) # NOTE this doesn't technically need to be a macro def interpretAssert(symbol, args, env, ns): cond = evaluate(args[0], env, ns) if not isinstance(cond, Bool): raise InterpretPanic(symbol, "requires a :bool condition", cond) if cond.value != True: raise InterpretPanic(symbol, "assertion failed") return Nil() CORE.register("assert", NebSyntax("assert", interpretAssert, [Arg("cond", TypeEnum.BOOL)], return_type=Type(":nil"))) def interpretUseAs(symbol, args, env, ns): target = evaluate(args[0], env, ns) if not isinstance(target, String): raise InterpretPanic(symbol, "filename must be a :string", target) target_file = Path(target.value).resolve() if not target_file.exists(): raise InterpretPanic(symbol, "no such file", target_file) if not isinstance(args[1], Symbol): raise InterpretPanic(symbol, "requires a :symbol", args[1]) new_ns = args[1].name with open(target_file, "r") as fil: data = fil.read() interpret(parse(lex(data)), env, new_ns) return Nil() CORE.register("use-as", NebSyntax("use-as", interpretUseAs, [Arg("filename", TypeEnum.STRING), Arg("namespace", TypeEnum.ANY)], return_type=Type(":nil"))) def interpretQuote(symbol, args, env, ns): return args[0] quote_arg = Arg("arg", TypeEnum.ANY) CORE.register("quote", NebSyntax("quote", interpretQuote, [quote_arg])) def interpretEval(symbol, args, env, ns): #ev = evaluate(args[0], env, ns) # TODO why do i have to evaluate twice? #return evaluate(ev, env, ns) return evaluate(args[0], env, ns) eval_arg = Arg("arg", TypeEnum.ANY) CORE.register("eval", NebSyntax("eval", interpretEval, [eval_arg])) def interpretType(symbol, args, env, ns): # (type typename parent func) if not isinstance(args[0], Type): raise InterpretPanic(symbol, "types must begin with a colon") name = args[0].name # NOTE: we are not evaluating the name!! # TODO we may need to do namespace things here # also, we probably shouldn't be able to rename types parent_type = evaluate(args[1], env, ns) if not isinstance(parent_type, TypeWrap): raise InterpretPanic(symbol, "parent must be a valid type", parent_type) func = evaluate(args[2], env, ns) if not isinstance(func, MultiFunction): raise InterpretPanic(symbol, "validation must be a :func", func) new_type = UserType(name, parent_type, func) env.register(name, new_type) return Nil() type_name_arg = Arg("name", TypeEnum.ANY) type_parent_arg = Arg("name", TypeEnum.ANY) type_func_arg = Arg("func", TypeEnum.ANY) CORE.register("type", NebSyntax("type", interpretType, [type_name_arg, type_parent_arg, type_func_arg], return_type=Type(":nil"))) def interpretOr(symbol, args, env, ns): # or returns true for the first expression that returns true for arg in args: ev = evaluate(arg, env, ns) if not isinstance(ev, Bool): raise InterpretPanic(symbol, "requires :bool arguments") if ev.value == True: return ev return Bool(False) or_arg = Arg("arg", TypeEnum.BOOL) CORE.register("or", NebSyntax("or", interpretOr, [or_arg, or_arg], or_arg, Type(":bool"))) def interpretAnd(symbol, args, env, ns): # and returns false for the first expression that returns false for arg in args: ev = evaluate(arg, env, ns) if not isinstance(ev, Bool): raise InterpretPanic(symbol, "requires :bool arguments") if ev.value == False: return ev return Bool(True) CORE.register("and", NebSyntax("and", interpretAnd, [or_arg, or_arg], or_arg, Type(":bool"))) def interpretBench(symbol, args, env, ns): before = datetime.now() ret = evaluate(args[0], env, ns) after = datetime.now() print(f"bench [{symbol.line}]: {args[0]} => {after - before}") return ret CORE.register("bench", NebSyntax("bench", interpretBench, [Arg("command", TypeEnum.ANY)], return_type=Type(":any"))) def interpretTry(symbol, args, env, ns): try: return evaluate(args[0], env, ns) except NebPanic as e: new_env = Environment(env) new_env.register("_panic_", String(f"{e}")) return evaluate(args[1], new_env, ns) CORE.register("try", NebSyntax("try", interpretTry, [Arg("expr", TypeEnum.ANY), Arg("except", TypeEnum.ANY)], return_type=Type(":any")))