diff options
| author | mryouse | 2022-06-21 01:49:29 +0000 |
|---|---|---|
| committer | mryouse | 2022-06-21 01:49:29 +0000 |
| commit | 776fe3193b515c028b5ac69326baed51d760d32f (patch) | |
| tree | db111c3fe7a20143b2f058259f86dbbecae4cbd6 | |
| parent | b1550660adaca68bb38541aed371e36b7000e124 (diff) | |
refactor: break stdlib into several files
| -rw-r--r-- | neb.py | 28 | ||||
| -rw-r--r-- | neb/__init__.py | 170 | ||||
| -rw-r--r-- | neb/interpreter.py | 870 | ||||
| -rw-r--r-- | neb/parser.py | 2 | ||||
| -rw-r--r-- | neb/std/__init__.py | 11 | ||||
| -rw-r--r-- | neb/std/boolean.py | 47 | ||||
| -rw-r--r-- | neb/std/core.py | 184 | ||||
| -rw-r--r-- | neb/std/fs.py | 61 | ||||
| -rw-r--r-- | neb/std/functools.py | 46 | ||||
| -rw-r--r-- | neb/std/lists.py | 116 | ||||
| -rw-r--r-- | neb/std/math.py | 79 | ||||
| -rw-r--r-- | neb/std/repl.py | 24 | ||||
| -rw-r--r-- | neb/std/strings.py | 53 | ||||
| -rw-r--r-- | neb/std/sys.py | 35 | ||||
| -rw-r--r-- | neb/std/term.py | 33 | ||||
| -rw-r--r-- | neb/std/types.py | 60 | ||||
| -rw-r--r-- | neb/structs.py | 134 |
17 files changed, 1074 insertions, 879 deletions
@@ -1,8 +1,28 @@ -from neb import lex, parse, interpret, NebPanic +from neb import lex, parse, interpret, NebPanic, Environment +from neb.std import * import sys import readline +def build_std(repl=False): + GLOBALS = Environment() + global_dict = { **BOOLEAN.environment, + **CORE.environment, + **FUNCTOOLS.environment, + **FS.environment, + **LISTS.environment, + **MATH.environment, + **STRINGS.environment, + **SYS.environment, + **TERM.environment, + **TYPES.environment } + if repl: + global_dict = { **global_dict, + **REPL.environment } + GLOBALS.environment = global_dict + return GLOBALS + + def debug(prev_lexed, prev_parsed): if prev_lexed is not None: acc = " ".join([f"{l}" for l in prev_lexed]) @@ -13,6 +33,7 @@ def debug(prev_lexed, prev_parsed): def repl(): + env = build_std(True) print("### neb :)(:") print("version: < 0") idx = 1 @@ -31,7 +52,7 @@ def repl(): prev_lexed = lexed parsed = parse(lexed) prev_parsed = parsed - inter = interpret(parsed) + inter = interpret(parsed, env) print(f"=> {inter}") prev_idx = idx idx += 1 @@ -43,13 +64,14 @@ def repl(): def run_file(filename): + env = build_std() with open(filename, "r") as fil: data = fil.read() try: lexed = lex(data) parsed = parse(lexed) - ev = interpret(parsed) + ev = interpret(parsed, env) except NebPanic as ne: print(f"panic! {ne}") except Exception as e: diff --git a/neb/__init__.py b/neb/__init__.py index f5afe60..583bef8 100644 --- a/neb/__init__.py +++ b/neb/__init__.py @@ -1,6 +1,166 @@ -from .structs import * -from .lexer import * -from .parser import * -from .interpreter import * +from .lexer import lex +from .parser import parse from .exceptions import * -from .typeclass 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, Type): + return expr + elif isinstance(expr, Symbol): + 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: + raise NebPanic(f"no such symbol: {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}"] + 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__("<builtin>", 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) diff --git a/neb/interpreter.py b/neb/interpreter.py deleted file mode 100644 index 6565adf..0000000 --- a/neb/interpreter.py +++ /dev/null @@ -1,870 +0,0 @@ -from .structs import * -from .exceptions import * -from .lexer import lex -from .parser import parse -from .typeclass import TypeEnum, is_subtype_of -from pathlib import Path -from glob import glob -import subprocess -import shlex -import random -import sys -import math - -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__("<builtin>", 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) - -GLOBALS = Environment() - -def interpret(exprs, *, env=GLOBALS, 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, Type): - return expr - elif isinstance(expr, Symbol): - 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: - raise NebPanic(f"no such symbol: {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") - -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, lazy=True) -GLOBALS.register("or", Builtin(interpretOr, [or_arg, or_arg], or_arg)) - -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) - -GLOBALS.register("and", Builtin(interpretAnd, [or_arg, or_arg], or_arg)) - -def interpretEq(symbol, args, env, ns): - # NOTE this currently only works for literals - # compare types because 0 != #false in neb - if type(args[0]) == type(args[1]) and args[0].value == args[1].value: - return Bool(True) - else: - return Bool(False) - -eq_arg = Arg("value", TypeEnum.LITERAL) -GLOBALS.register("eq?", Builtin(interpretEq, [eq_arg, eq_arg])) - -def interpretGreaterThan(symbol, args, env, ns): - return Bool(args[0].value > args[1].value) - -compare_arg = Arg("num", TypeEnum.NUMBER) -GLOBALS.register(">", Builtin(interpretGreaterThan, [compare_arg, compare_arg])) - -def interpretGreaterThanEqual(symbol, args, env, ns): - return Bool(args[0].value >= args[1].value) - -GLOBALS.register(">=", Builtin(interpretGreaterThanEqual, [compare_arg, compare_arg])) - -def interpretLessThan(symbol, args, env, ns): - return Bool(args[0].value < args[1].value) - -GLOBALS.register("<", Builtin(interpretLessThan, [compare_arg, compare_arg])) - -def interpretLessThanEqual(symbol, args, env, ns): - return Bool(args[0].value <= args[1].value) - -GLOBALS.register("<=", Builtin(interpretLessThanEqual, [compare_arg, compare_arg])) - -def interpretAddition(symbol, args, env, ns): - res = 0 - for arg in args: - res += arg.value - if isinstance(res, float): - return Float(res) - else: - return Int(res) - -term_arg = Arg("term", TypeEnum.NUMBER) -GLOBALS.register("+", Builtin(interpretAddition, [term_arg], term_arg)) - -def interpretSubtraction(symbol, args, env, ns): - if len(args) == 1: - res = -args[0].value - else: - res = args[0].value - for arg in args[1:]: - res -= arg.value - if isinstance(res, float): - return Float(res) - else: - return Int(res) - -GLOBALS.register("-", Builtin(interpretSubtraction, [term_arg], term_arg)) - -def interpretMultiplication(symbol, args, env, ns): - res = args[0].value - for arg in args[1:]: - res = res * arg.value - if isinstance(res, float): - return Float(res) - else: - return Int(res) - -factor_arg = Arg("factor", TypeEnum.NUMBER) -GLOBALS.register("*", Builtin(interpretMultiplication, [factor_arg, factor_arg], factor_arg)) - -def interpretDivision(symbol, args, env, ns): - ret = args[0].value / args[1].value - if int(ret) == ret: - return Int(int(ret)) - else: - return Float(ret) - -GLOBALS.register("/", Builtin(interpretDivision, [factor_arg, factor_arg])) - -def interpretNot(symbol, args, env, ns): - return Bool(not args[0].value) - -not_arg = Arg("not", TypeEnum.BOOL) -GLOBALS.register("not", Builtin(interpretNot, [not_arg])) - -def interpretIf(symbol, args, env, ns): - if args[0].value: - return evaluate(args[1], env, ns) - elif len(args) == 3: - return evaluate(args[2], env, ns) - return List([]) - -cond = Arg("cond", TypeEnum.BOOL) -t_branch = Arg("t-branch", TypeEnum.ANY, lazy=True) -f_branch = Arg("f-branch", TypeEnum.ANY, optional=True, lazy=True) -GLOBALS.register("if", Builtin(interpretIf, [cond, t_branch, f_branch])) - -def interpretPrint(symbol, args, env, ns): - print(args[0].value) - return List([]) # print returns nothing - -GLOBALS.register("print", Builtin(interpretPrint, [Arg("arg", TypeEnum.STRING)])) - -def interpretDef(symbol, args, env, ns): - - if not isinstance(args[0], Symbol): - raise InterpretPanic(symbol, "requires a :string name", args[0]) - name = args[0].name # NOTE: we are not evaluating the name!! - if not isinstance(name, str): - raise InterpretPanic(symbol, "requires a :string name") - - env.register(name, args[1]) # TODO since this isn't lazily evaluated, side effects are allowed (bad!) - - return List([]) - -def_name_arg = Arg("name", TypeEnum.ANY, lazy=True) -def_val_arg = Arg("value", TypeEnum.ANY) -GLOBALS.register("def", Builtin(interpretDef, [def_name_arg, def_val_arg])) - -def interpretRedef(symbol, args, env, ns): - if not isinstance(args[0], Symbol): - raise InterpretPanic(symbol, "requires a :string name", 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]) - - env.reregister(name, args[1]) - return List([]) - -GLOBALS.register("redef", Builtin(interpretRedef, [def_name_arg, def_val_arg])) - -def interpretLambda(symbol, args, env, ns): - if len(args[0].args) != 0: - func = UserFunction("<lambda>", args[0].args, args[1:]) - else: - func = UserFunction("<lambda>", [], args[1:]) - return func - -lambda_args_arg = Arg("args", TypeEnum.ANY, lazy=True) -lambda_body_arg = Arg("body", TypeEnum.ANY, lazy=True) -GLOBALS.register("lambda", Builtin(interpretLambda, [lambda_args_arg, lambda_body_arg], lambda_body_arg)) - -def interpretToString(symbol, args, env, ns): - item = args[0] - if isinstance(item, String): - return item - elif isinstance(item, Literal): - return String(str(item)) - else: - return String(f"{item}") - -GLOBALS.register("->string", Builtin(interpretToString, [Arg("arg", TypeEnum.ANY)])) - -def interpretConcat(symbol, args, env, ns): - out = "" - for arg in args: - out += arg.value - return String(out) - -string_arg = Arg("arg", TypeEnum.STRING) -GLOBALS.register("concat", Builtin(interpretConcat, [string_arg, string_arg], string_arg)) - -def interpretForCount(symbol, args, env, ns): - new_env = Environment(env) - ret = None - for idx in range(0, args[0].value): - new_env.register("idx", Int(idx + 1)) - for arg in args[1:]: - ret = evaluate(arg, new_env, ns) - if ret is None: - return List([]) - return ret - -for_count_arg = Arg("count", TypeEnum.INT) -for_body_arg = Arg("body", TypeEnum.ANY, lazy=True) -GLOBALS.register("for-count", Builtin(interpretForCount, [for_count_arg, for_body_arg], for_body_arg)) - -def interpretForEach(symbol, args, env, ns): - new_env = Environment(env) - ret = None - for item in args[0].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 List([]) - return ret - -for_each_arg = Arg("list", TypeEnum.LIST) -GLOBALS.register("for-each", Builtin(interpretForEach, [for_each_arg, for_body_arg], for_body_arg)) - -def interpretPipe(symbol, args, env, ns): - new_env = Environment(env) - pipe = None - for arg in args: - if pipe is not None: - new_env.register("items", pipe) - pipe = evaluate(arg, new_env, ns) - if pipe is None: - return List([]) - return pipe - -# TODO -GLOBALS.register("|", Builtin(interpretPipe, 2)) - -def interpretBranch(symbol, args, env, ns): - for arg in args: - if len(arg.args) != 2: - raise InterpretPanic(symbol, "each branch requires two expressions") - 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 List([]) - -GLOBALS.register("branch", Builtin(interpretBranch, [for_body_arg], for_body_arg)) - -def interpretFunc(symbol, args, env, ns): - if not isinstance(args[0], Symbol): - raise InterpretPanic(symbol, "requires a :string name") - name = args[0].name # NOTE: we are not evaluating the name!! - - if ns is not None: - name = f"{ns}/{name}" - - # compose a lambda - func = interpretLambda(None, args[1:], env, ns) - - # add the name to the function - func.name = name - - env.register(name, func) - return List([]) - -GLOBALS.register("func", Builtin(interpretFunc, [def_name_arg, lambda_args_arg, lambda_body_arg], lambda_body_arg)) - -def interpretReadLines(symbol, args, env, ns): - target_file_name = args[0].value - target_file = Path(target_file_name).resolve() - if not target_file.exists(): - raise InterpretPanic(symbol, "no such file", target_file) - with open(target_file, "r") as fil: - data = fil.readlines() - out = List([String(d) for d in data]) # all lines are strings - return out - -GLOBALS.register("read-lines", Builtin(interpretReadLines, [Arg("filename", TypeEnum.STRING)])) - -def interpretStrip(symbol, args, env, ns): - return String(args[0].value.strip()) - -GLOBALS.register("strip", Builtin(interpretStrip, [Arg("filename", TypeEnum.STRING)])) - -# - string->int and string->float -def interpretStringToInt(symbol, args, env, ns): - try: - val = int(args[0].value) - return Int(val) - except: - raise InterpretPanic(symbol, "can't convert to an :int", args[0]) - -GLOBALS.register("string->int", Builtin(interpretStringToInt, [Arg("arg", TypeEnum.STRING)])) - -def interpretSplit(symbol, args, env, ns): - target = args[0] - if len(args) == 1: - return List([String(char) for char in target.value]) - splitter = args[1] - ret = target.value.split(splitter.value) - return List([String(r) for r in ret]) - -GLOBALS.register("split", Builtin(interpretSplit, [Arg("target", TypeEnum.STRING)], Arg("splitter", TypeEnum.STRING, optional=True))) - -def interpretListLength(symbol, args, env, ns): - return Int(len(args[0].args)) - -GLOBALS.register("list-length", Builtin(interpretListLength, [Arg("arg", TypeEnum.LIST)])) - -def interpretFirst(symbol, args, env, ns): - if len(args[0].args) == 0: - raise InterpretPanic(symbol, "list is empty") - return evaluate(args[0].args[0], env, ns) - -GLOBALS.register("first", Builtin(interpretFirst, [Arg("arg", TypeEnum.LIST, )])) - -def interpretRest(symbol, args, env, ns): - # TODO do we know it's not evaluated? - return List(args[0].args[1:]) # we don't evaluate the remainder of the list - -GLOBALS.register("rest", Builtin(interpretRest, [Arg("arg", TypeEnum.LIST)])) - -def interpretMap(symbol, args, env, ns): - func = args[0] - if not isinstance(func, Function): - raise InterpretPanic(symbol, "requires a :func as its first argument", func) - lst = args[1] - if not isinstance(lst, List): - raise InterpretPanic(symbol, "requires a :list as its second argument", lst) - out = [] - for arg in lst.args: - ev = func.call(Expr([func, arg]), env, ns) - out.append(ev) - return List(out) - -GLOBALS.register("map", Builtin(interpretMap, [Arg("func", TypeEnum.ANY), Arg("list", TypeEnum.LIST)])) - -def interpretZip(symbol, args, env, ns): - z1 = args[0] - z2 = args[1] - if len(z1.args) != len(z2.args): - raise InterpretPanic(symbol, "requires two :lists of the same size") - out = [] - for idx in range(len(z1.args)): - f = z1.args[idx] - s = z2.args[idx] - out.append(List([f, s])) - return List(out) - -zip_arg = Arg("list", TypeEnum.LIST) -GLOBALS.register("zip", Builtin(interpretZip, [zip_arg, zip_arg])) - -def interpretList(symbol, args, env, ns): - return List(args) - -GLOBALS.register("list", Builtin(interpretList, [], Arg("item", TypeEnum.ANY))) - -def interpretListReverse(symbol, args, env, ns): - new_args = args[0].args[:] # make a copy of the args - new_args.reverse() - return List(new_args) - -GLOBALS.register("list-reverse", Builtin(interpretListReverse, [Arg("list", TypeEnum.LIST)])) - -def interpretApply(symbol, args, env, ns): - # TODO: to support lambdas, we can't assume the func is defined - func = args[0] - if not isinstance(func, Symbol): - raise InterpretPanic(symbol, "requires a symbol as its first argument", func) - new_lst = List([func] + args[1].args) - return evaluate(new_lst, env, ns) - -GLOBALS.register("apply", Builtin(interpretApply, [Arg("func", TypeEnum.ANY, lazy=True), Arg("list", TypeEnum.LIST)])) - -def interpretGlob(symbol, args, env, ns): - items = glob(args[0].value) - return List([String(item) for item in items]) - -GLOBALS.register("glob", Builtin(interpretGlob, [Arg("regex", TypeEnum.STRING)])) - -def interpretShell(symbol, args, env, ns): - ret = subprocess.run(shlex.split(args[0].value), capture_output=True) - return List([String(r) for r in ret.stdout.decode("utf-8").split("\n")]) - -GLOBALS.register("$", Builtin(interpretShell, [Arg("command", TypeEnum.STRING)])) - -def interpretEmpty(symbol, args, env, ns): - return Bool(len(args[0].args) == 0) - -GLOBALS.register("empty?", Builtin(interpretEmpty, [Arg("list", TypeEnum.LIST)])) - -def interpretShuf(symbol, args, env, ns): - items = args[0].args[:] - random.shuffle(items) - return List(items) - -GLOBALS.register("shuf", Builtin(interpretShuf, [Arg("list", TypeEnum.LIST)])) - -def interpretIsList(symbol, args, env, ns): - return Bool(isinstance(args[0], List)) - -GLOBALS.register("list?", Builtin(interpretIsList, [Arg("arg", TypeEnum.ANY)])) - -def interpretBlock(symbol, args, env, ns): - ret = List([]) - for arg in args: - ret = evaluate(arg, env, ns) - return ret - -block_arg = Arg("expr", TypeEnum.ANY, lazy=True) -GLOBALS.register("block", Builtin(interpretBlock, [block_arg], block_arg)) - -def interpretExit(symbol, args, env, ns): - status = 0 if len(args) == 0 else args[0].value - sys.exit(status) - return List([]) - -exit_arg = Arg("status", TypeEnum.INT, optional=True) -GLOBALS.register("exit", Builtin(interpretExit, [exit_arg])) - -def interpretUnlink(symbol, args, env, ns): - target_path = Path(args[0].value).resolve() - if not target_path.exists(): - raise InterpretPanic(symbol, "target file does not exist", target_path) - target_path.unlink() - return List([]) - -GLOBALS.register("unlink", Builtin(interpretUnlink, [Arg("filename", TypeEnum.STRING)])) - -def interpretArgv(symbol, args, env, ns): - out = [] - for arg in sys.argv[1:]: - out.append(String(arg)) - return List(out) - -GLOBALS.register("argv", Builtin(interpretArgv, [])) - -def interpretIn(symbol, args, env, ns): - target = args[0] - lst = args[1] - for arg in lst.args: - if type(arg) == type(target) and arg.value == target.value: - return Bool(True) - return Bool(False) - -in_target_arg = Arg("target", TypeEnum.LITERAL) -in_list_arg = Arg("list", TypeEnum.LIST) -GLOBALS.register("in?", Builtin(interpretIn, [in_target_arg, in_list_arg])) - -def interpretLast(symbol, args, env, ns): - if len(args[0].args) == 0: - raise InterpretPanic("List is empty") - return evaluate(args[0].args[-1], env, ns) - -GLOBALS.register("last", Builtin(interpretLast, [Arg("list", TypeEnum.LIST)])) - -def interpretJoin(symbol, args, env, ns): - lst = args[0] - target = args[1] - return String(target.value.join([a.value for a in lst.args])) - -join_list_arg = Arg("list", TypeEnum.LIST) -join_string_arg = Arg("joiner", TypeEnum.STRING) -GLOBALS.register("join", Builtin(interpretJoin, [join_list_arg, join_string_arg])) - -def interpretWithWrite(symbol, args, env, ns): - target_file = args[0] - new_env = Environment(env) - target_path = Path(target_file.value).resolve() - ret = Literal([]) - with open(str(target_path), "w") as fil: - new_env.register("_file_", List([fil])) # TODO wrong! - for arg in args[1:]: - ret = evaluate(arg, new_env, ns) - return ret - -GLOBALS.register("with-write", Builtin(interpretWithWrite, [Arg("filename", TypeEnum.STRING)], Arg("exprs", TypeEnum.ANY, lazy=True))) - -def interpretWrite(symbol, args, env, ns): - # write :string :filehandle - line = args[0] - handle = args[1] - handle.args[0].write(line.value) # TODO wrong! how do we evaluate a handle? - return Literal([]) - -GLOBALS.register("write", Builtin(interpretWrite, [Arg("string", TypeEnum.STRING), Arg("filename", TypeEnum.LIST)])) - -def interpretNewline(symbol, args, env, ns): - return String("\n") - -GLOBALS.register("newline", Builtin(interpretNewline, [])) - -def interpretExists(symbol, args, env, ns): - return Bool(Path(args[0].value).resolve().exists()) - -GLOBALS.register("exists?", Builtin(interpretExists, [Arg("filename", TypeEnum.STRING)])) - -def interpretFirstChar(symbol, args, env, ns): - if len(args[0].value) == 0: - raise InterpretPanic(symbol, ":string is empty", ev) - return String(args[0].value[0]) - -GLOBALS.register("first-char", Builtin(interpretFirstChar, [Arg("string", TypeEnum.STRING)])) - -def interpretRestChar(symbol, args, env, ns): - return String(args[0].value[1:]) - -GLOBALS.register("rest-char", Builtin(interpretRestChar, [Arg("string", TypeEnum.STRING)])) - -def interpretSlice(symbol, args, env, ns): - lst = args[0] - idx = args[1] - if len(args) == 2: - return List(lst.args[idx.value - 1:]) - length = args[2] - diff = idx.value - 1 + length.value - return List(lst.args[idx.value - 1:diff]) - -slice_list_arg = Arg("list", TypeEnum.LIST) -slice_idx_arg = Arg("idx", TypeEnum.INT) -slice_length_arg = Arg("length", TypeEnum.INT, optional=True) -GLOBALS.register("slice", Builtin(interpretSlice, [slice_list_arg, slice_idx_arg, slice_length_arg])) - -def interpretClear(symbol, args, env, ns): - subprocess.run(["clear"]) - return List([]) - -GLOBALS.register("clear", Builtin(interpretClear, [])) - -def interpretReadLine(symbol, args, env, ns): - ret = input(args[0].value) - return String(ret) - -GLOBALS.register("read-line", Builtin(interpretReadLine, [Arg("prompt", TypeEnum.STRING)])) - -def interpretReadChar(symbol, args, env, ns): - import termios, tty - fd = sys.stdin.fileno() - old = termios.tcgetattr(fd) - try: - tty.setraw(fd) - ch = sys.stdin.buffer.read1(4) # some keys are >1 bytes - except Exception: - raise - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old) - return String(ch.decode("utf-8")) - -GLOBALS.register("read-char", Builtin(interpretReadChar, [])) - -def interpretAppend(symbol, args, env, ns): - lst = args[0] - val = args[1] - items = lst.args[:] - return List(items + [val]) - -GLOBALS.register("append", Builtin(interpretAppend, [Arg("list", TypeEnum.LIST), Arg("item", TypeEnum.ANY)])) - -# TODO: this is actually for records/structs/whatever they're called -def interpretRemove(symbol, args, env, ns): - lst = args[0] - key = args[1] - out = [] - for arg in lst.args: - if arg.args[0].value != key.value: - out.append(arg) - return List(out) - -GLOBALS.register("remove", Builtin(interpretRemove, [Arg("list", TypeEnum.LIST), Arg("key", TypeEnum.ANY)])) - -def interpretWhile(symbol, args, env, ns): - cond = args[0] - ret = List([]) - while True: - ev = evaluate(cond, env, ns) - if not isinstance(ev, Bool): - raise InterpretPanic(symbol, "expects a :bool condition", ev) - if not ev.value: - break - for arg in args[1:]: - ret = evaluate(arg, env, ns) - return ret - -GLOBALS.register("while", Builtin(interpretWhile, [Arg("cond", TypeEnum.BOOL, lazy=True)], Arg("expr", TypeEnum.ANY, lazy=True))) - -def interpretUse(symbol, args, env, ns): - target_file_name = args[0].value - target_file = Path(target_file_name).resolve() - if not target_file.exists(): - raise InterpretPanic(symbol, "no such file", target_file) - with open(target_file, "r") as fil: - data = fil.read() - interpret(parse(lex(data))) - return List([]) - -GLOBALS.register("use", Builtin(interpretUse, [Arg("filename", TypeEnum.STRING)])) - -def interpretAssert(symbol, args, env, ns): - if args[0].value != True: - raise InterpretPanic(symbol, "assertion failed") - return List([]) - -GLOBALS.register("assert", Builtin(interpretAssert, [Arg("cond", TypeEnum.BOOL)])) - -def interpretHowTo(symbol, args, env, ns): - if not isinstance(args[0], Function): - raise InterpretPanic(symbol, "expects a :func", args[0]) - print(args[0].describe()) - return List([]) - -GLOBALS.register("howto", Builtin(interpretHowTo, [Arg("symbol", TypeEnum.ANY)])) - -def interpretSymbols(symbol, args, env, ns): - keys = [Symbol(k, -1) for k,v in env.environment.items()] - return List(keys) - -GLOBALS.register("symbols", Builtin(interpretSymbols, [])) - -def interpretUseAs(symbol, args, env, ns): - target_file_name = args[0].value - target_file = Path(target_file_name).resolve() - if not target_file.exists(): - raise InterpretPanic(symbol, "no such file", target_file) - with open(target_file, "r") as fil: - data = fil.read() - interpret(parse(lex(data)), ns=args[1].name) - return List([]) - -GLOBALS.register("use-as", Builtin(interpretUseAs, [Arg("filename", TypeEnum.STRING), Arg("namespace", TypeEnum.ANY, lazy=True)])) - -def interpretFloor(symbol, args, env, ns): - return Int(math.floor(args[0].value)) - -GLOBALS.register("floor", Builtin(interpretFloor, [Arg("floor", TypeEnum.NUMBER)])) - -def interpretFilter(symbol, args, env, ns): - func = args[0] - if not isinstance(func, Function): - raise InterpretPanic(symbol, "requires a :func as its first argument", func) - lst = args[1] - out = [] - for arg in lst.args: - ev = func.call(Expr([func, arg]), env, ns) - if not isinstance(ev, Bool): - raise InterpretPanic(symbol, "function must return :bool", ev) - if ev.value: - out.append(arg) - return List(out) - -GLOBALS.register("filter", Builtin(interpretFilter, [Arg("func", TypeEnum.ANY), Arg("list", TypeEnum.LIST)])) - -def interpretTypeOf(symbol, args, env, ns): - return Type(f"{args[0].type_}") - -GLOBALS.register("typeof", Builtin(interpretTypeOf, [Arg("candidate", TypeEnum.ANY)])) - -def interpretIsString(symbol, args, env, ns): - return Bool(isinstance(args[0], String)) - -GLOBALS.register("string?", Builtin(interpretIsString, [Arg("arg", TypeEnum.ANY)])) - -def interpretIsInt(symbol, args, env, ns): - return Bool(isinstance(args[0], Int)) - -GLOBALS.register("int?", Builtin(interpretIsInt, [Arg("arg", TypeEnum.ANY)])) - -def interpretIsFloat(symbol, args, env, ns): - return Bool(isinstance(args[0], Float)) - -GLOBALS.register("float?", Builtin(interpretIsFloat, [Arg("arg", TypeEnum.ANY)])) - -def interpretIsNumber(symbol, args, env, ns): - ret = isinstance(args[0], Int) or isinstance(args[0], Float) - return Bool(ret) - -GLOBALS.register("number?", Builtin(interpretIsNumber, [Arg("arg", TypeEnum.ANY)])) - -def interpretIsBool(symbol, args, env, ns): - return Bool(isinstance(args[0], Bool)) - -GLOBALS.register("bool?", Builtin(interpretIsBool, [Arg("arg", TypeEnum.ANY)])) - -def interpretUserSymbols(symbol, args, env, ns): - keys = [Symbol(k, -1) for k,v in env.environment.items() if isinstance(v, UserFunction) or isinstance(v, Literal)] - return List(keys) - -GLOBALS.register("user-symbols", Builtin(interpretUserSymbols, [])) - -def interpretQuote(symbol, args, env, ns): - return args[0] - -quote_arg = Arg("arg", TypeEnum.ANY, lazy=True) -GLOBALS.register("quote", Builtin(interpretQuote, [quote_arg])) - -def interpretEval(symbol, args, env, ns): - return evaluate(args[0], env, ns) # TODO why do i have to explicitly evaluate here? - -eval_arg = Arg("arg", TypeEnum.ANY) -GLOBALS.register("eval", Builtin(interpretEval, [eval_arg])) diff --git a/neb/parser.py b/neb/parser.py index ea875fb..8129fc9 100644 --- a/neb/parser.py +++ b/neb/parser.py @@ -22,7 +22,7 @@ def parseExpression(token, prev, tokens): idx += inc prev = token - return List(args), idx + 2 # parens + return Expr(args), idx + 2 # parens def parseSymbol(token, prev, tokens): return Symbol(token.text, token.line), 1 diff --git a/neb/std/__init__.py b/neb/std/__init__.py new file mode 100644 index 0000000..b6a4151 --- /dev/null +++ b/neb/std/__init__.py @@ -0,0 +1,11 @@ +from .boolean import BOOLEAN +from .core import CORE +from .functools import FUNCTOOLS +from .fs import FS +from .lists import LISTS +from .math import MATH +from .repl import REPL +from .strings import STRINGS +from .sys import SYS +from .term import TERM +from .types import TYPES diff --git a/neb/std/boolean.py b/neb/std/boolean.py new file mode 100644 index 0000000..b851d7d --- /dev/null +++ b/neb/std/boolean.py @@ -0,0 +1,47 @@ +from .. import TypeEnum, Environment, Arg, Builtin, evaluate +from ..structs import * + +BOOLEAN = Environment() + +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, lazy=True) +BOOLEAN.register("or", Builtin(interpretOr, [or_arg, or_arg], or_arg)) + +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) + +BOOLEAN.register("and", Builtin(interpretAnd, [or_arg, or_arg], or_arg)) + +def interpretEq(symbol, args, env, ns): + # NOTE this currently only works for literals + # compare types because 0 != #false in neb + if type(args[0]) == type(args[1]) and args[0].value == args[1].value: + return Bool(True) + else: + return Bool(False) + +eq_arg = Arg("value", TypeEnum.LITERAL) +BOOLEAN.register("eq?", Builtin(interpretEq, [eq_arg, eq_arg])) + + +def interpretNot(symbol, args, env, ns): + return Bool(not args[0].value) + +not_arg = Arg("not", TypeEnum.BOOL) +BOOLEAN.register("not", Builtin(interpretNot, [not_arg])) diff --git a/neb/std/core.py b/neb/std/core.py new file mode 100644 index 0000000..d481daa --- /dev/null +++ b/neb/std/core.py @@ -0,0 +1,184 @@ +from .. import TypeEnum, Environment, Arg, Builtin, UserFunction, evaluate, interpret, parse, lex +from ..structs import * +from pathlib import Path + +CORE = Environment() + +def interpretIf(symbol, args, env, ns): + if args[0].value: + return evaluate(args[1], env, ns) + elif len(args) == 3: + return evaluate(args[2], env, ns) + return List([]) + +cond = Arg("cond", TypeEnum.BOOL) +t_branch = Arg("t-branch", TypeEnum.ANY, lazy=True) +f_branch = Arg("f-branch", TypeEnum.ANY, optional=True, lazy=True) +CORE.register("if", Builtin(interpretIf, [cond, t_branch, f_branch])) + +def interpretDef(symbol, args, env, ns): + + if not isinstance(args[0], Symbol): + raise InterpretPanic(symbol, "requires a :string name", args[0]) + name = args[0].name # NOTE: we are not evaluating the name!! + if not isinstance(name, str): + raise InterpretPanic(symbol, "requires a :string name") + + env.register(name, args[1]) # TODO since this isn't lazily evaluated, side effects are allowed (bad!) + + return List([]) + +def_name_arg = Arg("name", TypeEnum.ANY, lazy=True) +def_val_arg = Arg("value", TypeEnum.ANY) +CORE.register("def", Builtin(interpretDef, [def_name_arg, def_val_arg])) + +def interpretRedef(symbol, args, env, ns): + if not isinstance(args[0], Symbol): + raise InterpretPanic(symbol, "requires a :string name", 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]) + + env.reregister(name, args[1]) + return List([]) + +CORE.register("redef", Builtin(interpretRedef, [def_name_arg, def_val_arg])) + +def interpretLambda(symbol, args, env, ns): + if len(args[0].args) != 0: + func = UserFunction("<lambda>", args[0].args, args[1:]) + else: + func = UserFunction("<lambda>", [], args[1:]) + return func + +lambda_args_arg = Arg("args", TypeEnum.ANY, lazy=True) +lambda_body_arg = Arg("body", TypeEnum.ANY, lazy=True) +CORE.register("lambda", Builtin(interpretLambda, [lambda_args_arg, lambda_body_arg], lambda_body_arg)) + +def interpretForCount(symbol, args, env, ns): + new_env = Environment(env) + ret = None + for idx in range(0, args[0].value): + new_env.register("idx", Int(idx + 1)) + for arg in args[1:]: + ret = evaluate(arg, new_env, ns) + if ret is None: + return List([]) + return ret + +for_count_arg = Arg("count", TypeEnum.INT) +for_body_arg = Arg("body", TypeEnum.ANY, lazy=True) +CORE.register("for-count", Builtin(interpretForCount, [for_count_arg, for_body_arg], for_body_arg)) + +def interpretForEach(symbol, args, env, ns): + new_env = Environment(env) + ret = None + for item in args[0].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 List([]) + return ret + +for_each_arg = Arg("list", TypeEnum.LIST) +CORE.register("for-each", Builtin(interpretForEach, [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") + 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 List([]) + +CORE.register("branch", Builtin(interpretBranch, [for_body_arg], for_body_arg)) + +def interpretFunc(symbol, args, env, ns): + if not isinstance(args[0], Symbol): + raise InterpretPanic(symbol, "requires a :string name") + name = args[0].name # NOTE: we are not evaluating the name!! + + if ns is not None: + name = f"{ns}/{name}" + + # compose a lambda + func = interpretLambda(None, args[1:], env, ns) + + # add the name to the function + func.name = name + + env.register(name, func) + return List([]) + +CORE.register("func", Builtin(interpretFunc, [def_name_arg, lambda_args_arg, lambda_body_arg], lambda_body_arg)) + +def interpretBlock(symbol, args, env, ns): + ret = List([]) + for arg in args: + ret = evaluate(arg, env, ns) + return ret + +block_arg = Arg("expr", TypeEnum.ANY, lazy=True) +CORE.register("block", Builtin(interpretBlock, [block_arg], block_arg)) + +def interpretWhile(symbol, args, env, ns): + cond = args[0] + ret = List([]) + while True: + ev = evaluate(cond, env, ns) + if not isinstance(ev, Bool): + raise InterpretPanic(symbol, "expects a :bool condition", ev) + if not ev.value: + break + for arg in args[1:]: + ret = evaluate(arg, env, ns) + return ret + +CORE.register("while", Builtin(interpretWhile, [Arg("cond", TypeEnum.BOOL, lazy=True)], Arg("expr", TypeEnum.ANY, lazy=True))) + +def interpretUse(symbol, args, env, ns): + target_file_name = args[0].value + target_file = Path(target_file_name).resolve() + if not target_file.exists(): + raise InterpretPanic(symbol, "no such file", target_file) + with open(target_file, "r") as fil: + data = fil.read() + interpret(parse(lex(data)), env, ns) + return List([]) + +CORE.register("use", Builtin(interpretUse, [Arg("filename", TypeEnum.STRING)])) + +def interpretAssert(symbol, args, env, ns): + if args[0].value != True: + raise InterpretPanic(symbol, "assertion failed") + return List([]) + +CORE.register("assert", Builtin(interpretAssert, [Arg("cond", TypeEnum.BOOL)])) + +def interpretUseAs(symbol, args, env, ns): + target_file_name = args[0].value + target_file = Path(target_file_name).resolve() + if not target_file.exists(): + raise InterpretPanic(symbol, "no such file", target_file) + with open(target_file, "r") as fil: + data = fil.read() + interpret(parse(lex(data)), ns=args[1].name) + return List([]) + +CORE.register("use-as", Builtin(interpretUseAs, [Arg("filename", TypeEnum.STRING), Arg("namespace", TypeEnum.ANY, lazy=True)])) + +def interpretQuote(symbol, args, env, ns): + return args[0] + +quote_arg = Arg("arg", TypeEnum.ANY, lazy=True) +CORE.register("quote", Builtin(interpretQuote, [quote_arg])) + +def interpretEval(symbol, args, env, ns): + return evaluate(args[0], env, ns) # TODO why do i have to explicitly evaluate here? + +eval_arg = Arg("arg", TypeEnum.ANY) +CORE.register("eval", Builtin(interpretEval, [eval_arg])) diff --git a/neb/std/fs.py b/neb/std/fs.py new file mode 100644 index 0000000..2265719 --- /dev/null +++ b/neb/std/fs.py @@ -0,0 +1,61 @@ +from .. import TypeEnum, Environment, Arg, Builtin, evaluate +from ..structs import * +from pathlib import Path +from glob import glob + +FS = Environment() + +def interpretExists(symbol, args, env, ns): + return Bool(Path(args[0].value).resolve().exists()) + +FS.register("exists?", Builtin(interpretExists, [Arg("filename", TypeEnum.STRING)])) + +def interpretGlob(symbol, args, env, ns): + items = glob(args[0].value) + return List([String(item) for item in items]) + +FS.register("glob", Builtin(interpretGlob, [Arg("regex", TypeEnum.STRING)])) + +def interpretUnlink(symbol, args, env, ns): + target_path = Path(args[0].value).resolve() + if not target_path.exists(): + raise InterpretPanic(symbol, "target file does not exist", target_path) + target_path.unlink() + return List([]) + +FS.register("unlink", Builtin(interpretUnlink, [Arg("filename", TypeEnum.STRING)])) + +def interpretWithWrite(symbol, args, env, ns): + target_file = args[0] + new_env = Environment(env) + target_path = Path(target_file.value).resolve() + ret = Literal([]) + with open(str(target_path), "w") as fil: + new_env.register("_file_", List([fil])) # TODO wrong! + for arg in args[1:]: + ret = evaluate(arg, new_env, ns) + return ret + +FS.register("with-write", Builtin(interpretWithWrite, [Arg("filename", TypeEnum.STRING)], Arg("exprs", TypeEnum.ANY, lazy=True))) + +def interpretWrite(symbol, args, env, ns): + # write :string :filehandle + line = args[0] + handle = args[1] + handle.args[0].write(line.value) # TODO wrong! how do we evaluate a handle? + return Literal([]) + +FS.register("write", Builtin(interpretWrite, [Arg("string", TypeEnum.STRING), Arg("filename", TypeEnum.LIST)])) + +def interpretReadLines(symbol, args, env, ns): + target_file_name = args[0].value + target_file = Path(target_file_name).resolve() + if not target_file.exists(): + raise InterpretPanic(symbol, "no such file", target_file) + with open(target_file, "r") as fil: + data = fil.readlines() + out = List([String(d) for d in data]) # all lines are strings + return out + +FS.register("read-lines", Builtin(interpretReadLines, [Arg("filename", TypeEnum.STRING)])) + diff --git a/neb/std/functools.py b/neb/std/functools.py new file mode 100644 index 0000000..59f4a2a --- /dev/null +++ b/neb/std/functools.py @@ -0,0 +1,46 @@ +from .. import TypeEnum, Environment, Arg, Builtin, Function, evaluate +from ..structs import * + +FUNCTOOLS = Environment() + +def interpretFilter(symbol, args, env, ns): + func = args[0] + if not isinstance(func, Function): + raise InterpretPanic(symbol, "requires a :func as its first argument", func) + lst = args[1] + out = [] + for arg in lst.args: + ev = func.call(Expr([func, arg]), env, ns) + if not isinstance(ev, Bool): + raise InterpretPanic(symbol, "function must return :bool", ev) + if ev.value: + out.append(arg) + return List(out) + +FUNCTOOLS.register("filter", Builtin(interpretFilter, [Arg("func", TypeEnum.ANY), Arg("list", TypeEnum.LIST)])) + +def interpretMap(symbol, args, env, ns): + func = args[0] + if not isinstance(func, Function): + raise InterpretPanic(symbol, "requires a :func as its first argument", func) + lst = args[1] + if not isinstance(lst, List): + raise InterpretPanic(symbol, "requires a :list as its second argument", lst) + out = [] + for arg in lst.args: + ev = func.call(Expr([func, arg]), env, ns) + out.append(ev) + return List(out) + +FUNCTOOLS.register("map", Builtin(interpretMap, [Arg("func", TypeEnum.ANY), Arg("list", TypeEnum.LIST)])) + +def interpretApply(symbol, args, env, ns): + # TODO: to support lambdas, we can't assume the func is defined + func = args[0] + if not isinstance(func, Symbol): + raise InterpretPanic(symbol, "requires a symbol as its first argument", func) + new_lst = List([func] + args[1].args) + return evaluate(new_lst, env, ns) + +FUNCTOOLS.register("apply", Builtin(interpretApply, [Arg("func", TypeEnum.ANY, lazy=True), Arg("list", TypeEnum.LIST)])) + diff --git a/neb/std/lists.py b/neb/std/lists.py new file mode 100644 index 0000000..d5c18c2 --- /dev/null +++ b/neb/std/lists.py @@ -0,0 +1,116 @@ +from .. import TypeEnum, Environment, Arg, Builtin, evaluate +from ..structs import * +import random + +LISTS = Environment() + +def interpretListLength(symbol, args, env, ns): + return Int(len(args[0].args)) + +LISTS.register("list-length", Builtin(interpretListLength, [Arg("arg", TypeEnum.LIST)])) + +def interpretFirst(symbol, args, env, ns): + if len(args[0].args) == 0: + raise InterpretPanic(symbol, "list is empty") + return evaluate(args[0].args[0], env, ns) + +LISTS.register("first", Builtin(interpretFirst, [Arg("arg", TypeEnum.LIST, )])) + +def interpretRest(symbol, args, env, ns): + # TODO do we know it's not evaluated? + return List(args[0].args[1:]) # we don't evaluate the remainder of the list + +LISTS.register("rest", Builtin(interpretRest, [Arg("arg", TypeEnum.LIST)])) + +def interpretListReverse(symbol, args, env, ns): + new_args = args[0].args[:] # make a copy of the args + new_args.reverse() + return List(new_args) + +LISTS.register("list-reverse", Builtin(interpretListReverse, [Arg("list", TypeEnum.LIST)])) + +def interpretEmpty(symbol, args, env, ns): + return Bool(len(args[0].args) == 0) + +LISTS.register("empty?", Builtin(interpretEmpty, [Arg("list", TypeEnum.LIST)])) + +def interpretShuf(symbol, args, env, ns): + items = args[0].args[:] + random.shuffle(items) + return List(items) + +LISTS.register("shuf", Builtin(interpretShuf, [Arg("list", TypeEnum.LIST)])) + +def interpretIn(symbol, args, env, ns): + target = args[0] + lst = args[1] + for arg in lst.args: + if type(arg) == type(target) and arg.value == target.value: + return Bool(True) + return Bool(False) + +in_target_arg = Arg("target", TypeEnum.LITERAL) +in_list_arg = Arg("list", TypeEnum.LIST) +LISTS.register("in?", Builtin(interpretIn, [in_target_arg, in_list_arg])) + +def interpretLast(symbol, args, env, ns): + if len(args[0].args) == 0: + raise InterpretPanic("List is empty") + return evaluate(args[0].args[-1], env, ns) + +LISTS.register("last", Builtin(interpretLast, [Arg("list", TypeEnum.LIST)])) + +def interpretSlice(symbol, args, env, ns): + lst = args[0] + idx = args[1] + if len(args) == 2: + return List(lst.args[idx.value - 1:]) + length = args[2] + diff = idx.value - 1 + length.value + return List(lst.args[idx.value - 1:diff]) + +slice_list_arg = Arg("list", TypeEnum.LIST) +slice_idx_arg = Arg("idx", TypeEnum.INT) +slice_length_arg = Arg("length", TypeEnum.INT, optional=True) +LISTS.register("slice", Builtin(interpretSlice, [slice_list_arg, slice_idx_arg, slice_length_arg])) + +def interpretAppend(symbol, args, env, ns): + lst = args[0] + val = args[1] + items = lst.args[:] + return List(items + [val]) + +LISTS.register("append", Builtin(interpretAppend, [Arg("list", TypeEnum.LIST), Arg("item", TypeEnum.ANY)])) + +# TODO: this is actually for records/structs/whatever they're called +def interpretRemove(symbol, args, env, ns): + lst = args[0] + key = args[1] + out = [] + for arg in lst.args: + if arg.args[0].value != key.value: + out.append(arg) + return List(out) + +LISTS.register("remove", Builtin(interpretRemove, [Arg("list", TypeEnum.LIST), Arg("key", TypeEnum.ANY)])) + +def interpretZip(symbol, args, env, ns): + z1 = args[0] + z2 = args[1] + if len(z1.args) != len(z2.args): + raise InterpretPanic(symbol, "requires two :lists of the same size") + out = [] + for idx in range(len(z1.args)): + f = z1.args[idx] + s = z2.args[idx] + out.append(List([f, s])) + return List(out) + +zip_arg = Arg("list", TypeEnum.LIST) +LISTS.register("zip", Builtin(interpretZip, [zip_arg, zip_arg])) + +def interpretList(symbol, args, env, ns): + return List(args) + +LISTS.register("list", Builtin(interpretList, [], Arg("item", TypeEnum.ANY))) + diff --git a/neb/std/math.py b/neb/std/math.py new file mode 100644 index 0000000..85d4f55 --- /dev/null +++ b/neb/std/math.py @@ -0,0 +1,79 @@ +from .. import TypeEnum, Environment, Arg, Builtin, evaluate +from ..structs import * +import math + +MATH = Environment() + +def interpretLessThanEqual(symbol, args, env, ns): + return Bool(args[0].value <= args[1].value) + +compare_arg = Arg("num", TypeEnum.NUMBER) +MATH.register("<=", Builtin(interpretLessThanEqual, [compare_arg, compare_arg])) + +def interpretGreaterThan(symbol, args, env, ns): + return Bool(args[0].value > args[1].value) + +compare_arg = Arg("num", TypeEnum.NUMBER) +MATH.register(">", Builtin(interpretGreaterThan, [compare_arg, compare_arg])) + +def interpretGreaterThanEqual(symbol, args, env, ns): + return Bool(args[0].value >= args[1].value) + +MATH.register(">=", Builtin(interpretGreaterThanEqual, [compare_arg, compare_arg])) + +def interpretLessThan(symbol, args, env, ns): + return Bool(args[0].value < args[1].value) + +MATH.register("<", Builtin(interpretLessThan, [compare_arg, compare_arg])) + +def interpretAddition(symbol, args, env, ns): + res = 0 + for arg in args: + res += arg.value + if isinstance(res, float): + return Float(res) + else: + return Int(res) + +term_arg = Arg("term", TypeEnum.NUMBER) +MATH.register("+", Builtin(interpretAddition, [term_arg], term_arg)) + +def interpretSubtraction(symbol, args, env, ns): + if len(args) == 1: + res = -args[0].value + else: + res = args[0].value + for arg in args[1:]: + res -= arg.value + if isinstance(res, float): + return Float(res) + else: + return Int(res) + +MATH.register("-", Builtin(interpretSubtraction, [term_arg], term_arg)) + +def interpretMultiplication(symbol, args, env, ns): + res = args[0].value + for arg in args[1:]: + res = res * arg.value + if isinstance(res, float): + return Float(res) + else: + return Int(res) + +factor_arg = Arg("factor", TypeEnum.NUMBER) +MATH.register("*", Builtin(interpretMultiplication, [factor_arg, factor_arg], factor_arg)) + +def interpretDivision(symbol, args, env, ns): + ret = args[0].value / args[1].value + if int(ret) == ret: + return Int(int(ret)) + else: + return Float(ret) + +MATH.register("/", Builtin(interpretDivision, [factor_arg, factor_arg])) + +def interpretFloor(symbol, args, env, ns): + return Int(math.floor(args[0].value)) + +MATH.register("floor", Builtin(interpretFloor, [Arg("floor", TypeEnum.NUMBER)])) diff --git a/neb/std/repl.py b/neb/std/repl.py new file mode 100644 index 0000000..48a7811 --- /dev/null +++ b/neb/std/repl.py @@ -0,0 +1,24 @@ +from .. import TypeEnum, Environment, Arg, Builtin, UserFunction, evaluate +from ..structs import * + +REPL = Environment() + +def interpretHowTo(symbol, args, env, ns): + if not isinstance(args[0], Function): + raise InterpretPanic(symbol, "expects a :func", args[0]) + print(args[0].describe()) + return List([]) + +REPL.register("howto", Builtin(interpretHowTo, [Arg("symbol", TypeEnum.ANY)])) + +def interpretSymbols(symbol, args, env, ns): + keys = [Symbol(k, -1) for k,v in env.environment.items()] + return List(keys) + +REPL.register("symbols", Builtin(interpretSymbols, [])) + +def interpretUserSymbols(symbol, args, env, ns): + keys = [Symbol(k, -1) for k,v in env.environment.items() if isinstance(v, UserFunction) or isinstance(v, Literal)] + return List(keys) + +REPL.register("user-symbols", Builtin(interpretUserSymbols, [])) diff --git a/neb/std/strings.py b/neb/std/strings.py new file mode 100644 index 0000000..0d5ebdb --- /dev/null +++ b/neb/std/strings.py @@ -0,0 +1,53 @@ +from .. import TypeEnum, Environment, Arg, Builtin, evaluate +from ..structs import * + +STRINGS = Environment() + +def interpretConcat(symbol, args, env, ns): + out = "" + for arg in args: + out += arg.value + return String(out) + +string_arg = Arg("arg", TypeEnum.STRING) +STRINGS.register("concat", Builtin(interpretConcat, [string_arg, string_arg], string_arg)) + +def interpretStrip(symbol, args, env, ns): + return String(args[0].value.strip()) + +STRINGS.register("strip", Builtin(interpretStrip, [Arg("filename", TypeEnum.STRING)])) +def interpretStrip(symbol, args, env, ns): + return String(args[0].value.strip()) + +STRINGS.register("strip", Builtin(interpretStrip, [Arg("filename", TypeEnum.STRING)])) + +def interpretSplit(symbol, args, env, ns): + target = args[0] + if len(args) == 1: + return List([String(char) for char in target.value]) + splitter = args[1] + ret = target.value.split(splitter.value) + return List([String(r) for r in ret]) + +STRINGS.register("split", Builtin(interpretSplit, [Arg("target", TypeEnum.STRING)], Arg("splitter", TypeEnum.STRING, optional=True))) + +def interpretJoin(symbol, args, env, ns): + lst = args[0] + target = args[1] + return String(target.value.join([a.value for a in lst.args])) + +join_list_arg = Arg("list", TypeEnum.LIST) +join_string_arg = Arg("joiner", TypeEnum.STRING) +STRINGS.register("join", Builtin(interpretJoin, [join_list_arg, join_string_arg])) + +def interpretFirstChar(symbol, args, env, ns): + if len(args[0].value) == 0: + raise InterpretPanic(symbol, ":string is empty", ev) + return String(args[0].value[0]) + +STRINGS.register("first-char", Builtin(interpretFirstChar, [Arg("string", TypeEnum.STRING)])) + +def interpretRestChar(symbol, args, env, ns): + return String(args[0].value[1:]) + +STRINGS.register("rest-char", Builtin(interpretRestChar, [Arg("string", TypeEnum.STRING)])) diff --git a/neb/std/sys.py b/neb/std/sys.py new file mode 100644 index 0000000..252a0b0 --- /dev/null +++ b/neb/std/sys.py @@ -0,0 +1,35 @@ +from .. import TypeEnum, Environment, Arg, Builtin, evaluate +from ..structs import * +import shlex +import subprocess +import sys + +SYS = Environment() + +def interpretArgv(symbol, args, env, ns): + out = [] + for arg in sys.argv[1:]: + out.append(String(arg)) + return List(out) + +SYS.register("argv", Builtin(interpretArgv, [])) + +def interpretShell(symbol, args, env, ns): + ret = subprocess.run(shlex.split(args[0].value), capture_output=True) + return List([String(r) for r in ret.stdout.decode("utf-8").split("\n")]) + +SYS.register("$", Builtin(interpretShell, [Arg("command", TypeEnum.STRING)])) + +def interpretExit(symbol, args, env, ns): + status = 0 if len(args) == 0 else args[0].value + sys.exit(status) + return List([]) + +exit_arg = Arg("status", TypeEnum.INT, optional=True) +SYS.register("exit", Builtin(interpretExit, [exit_arg])) + +def interpretPrint(symbol, args, env, ns): + print(args[0].value) + return List([]) # print returns nothing + +SYS.register("print", Builtin(interpretPrint, [Arg("arg", TypeEnum.STRING)])) diff --git a/neb/std/term.py b/neb/std/term.py new file mode 100644 index 0000000..e706005 --- /dev/null +++ b/neb/std/term.py @@ -0,0 +1,33 @@ +from .. import TypeEnum, Environment, Arg, Builtin, evaluate +from ..structs import * +import subprocess +import sys + +TERM = Environment() + +def interpretClear(symbol, args, env, ns): + subprocess.run(["clear"]) + return List([]) + +TERM.register("clear", Builtin(interpretClear, [])) + +def interpretReadLine(symbol, args, env, ns): + ret = input(args[0].value) + return String(ret) + +TERM.register("read-line", Builtin(interpretReadLine, [Arg("prompt", TypeEnum.STRING)])) + +def interpretReadChar(symbol, args, env, ns): + import termios, tty + fd = sys.stdin.fileno() + old = termios.tcgetattr(fd) + try: + tty.setraw(fd) + ch = sys.stdin.buffer.read1(4) # some keys are >1 bytes + except Exception: + raise + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old) + return String(ch.decode("utf-8")) + +TERM.register("read-char", Builtin(interpretReadChar, [])) diff --git a/neb/std/types.py b/neb/std/types.py new file mode 100644 index 0000000..3cf55ac --- /dev/null +++ b/neb/std/types.py @@ -0,0 +1,60 @@ +from .. import TypeEnum, Environment, Arg, Builtin, evaluate +from ..structs import * + +TYPES = Environment() + +def interpretIsBool(symbol, args, env, ns): + return Bool(isinstance(args[0], Bool)) + +TYPES.register("bool?", Builtin(interpretIsBool, [Arg("arg", TypeEnum.ANY)])) + +def interpretIsFloat(symbol, args, env, ns): + return Bool(isinstance(args[0], Float)) + +TYPES.register("float?", Builtin(interpretIsFloat, [Arg("arg", TypeEnum.ANY)])) + +def interpretIsNumber(symbol, args, env, ns): + ret = isinstance(args[0], Int) or isinstance(args[0], Float) + return Bool(ret) + +TYPES.register("number?", Builtin(interpretIsNumber, [Arg("arg", TypeEnum.ANY)])) + +def interpretIsInt(symbol, args, env, ns): + return Bool(isinstance(args[0], Int)) + +TYPES.register("int?", Builtin(interpretIsInt, [Arg("arg", TypeEnum.ANY)])) + +def interpretIsList(symbol, args, env, ns): + return Bool(isinstance(args[0], List)) + +TYPES.register("list?", Builtin(interpretIsList, [Arg("arg", TypeEnum.ANY)])) + +def interpretIsString(symbol, args, env, ns): + return Bool(isinstance(args[0], String)) + +TYPES.register("string?", Builtin(interpretIsString, [Arg("arg", TypeEnum.ANY)])) + +def interpretStringToInt(symbol, args, env, ns): + try: + val = int(args[0].value) + return Int(val) + except: + raise InterpretPanic(symbol, "can't convert to an :int", args[0]) + +TYPES.register("string->int", Builtin(interpretStringToInt, [Arg("arg", TypeEnum.STRING)])) + +def interpretToString(symbol, args, env, ns): + item = args[0] + if isinstance(item, String): + return item + elif isinstance(item, Literal): + return String(str(item)) + else: + return String(f"{item}") + +TYPES.register("->string", Builtin(interpretToString, [Arg("arg", TypeEnum.ANY)])) + +def interpretTypeOf(symbol, args, env, ns): + return Type(f"{args[0].type_}") + +TYPES.register("typeof", Builtin(interpretTypeOf, [Arg("candidate", TypeEnum.ANY)])) diff --git a/neb/structs.py b/neb/structs.py index c8e7e8b..cba0c03 100644 --- a/neb/structs.py +++ b/neb/structs.py @@ -2,6 +2,7 @@ from dataclasses import dataclass from enum import Enum, auto from typing import Any from .typeclass import TypeEnum +#from . import Function # tokens and types # NOTE: this can probably be simplified @@ -166,3 +167,136 @@ class Environment: 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__("<builtin>", 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) +''' |
