diff options
| author | mryouse | 2022-07-09 02:39:27 +0000 |
|---|---|---|
| committer | mryouse | 2022-07-09 02:39:27 +0000 |
| commit | 4038aa87fddbe7e79c61603cf8d4514ebb47f3fe (patch) | |
| tree | 8f5493f5fb8d9f9ea1de4cd1ade03b7d6c9447d9 | |
| parent | 7ffeef0faef3fbc389069df853109afc76260f0d (diff) | |
| parent | 4fb8873f45c8596ba044c87060191778b8238952 (diff) | |
Merge branch 'master' into feature/listtypes
| -rw-r--r-- | commands.txt | 75 | ||||
| -rw-r--r-- | docs.neb | 1 | ||||
| -rw-r--r-- | neb/__init__.py | 44 | ||||
| -rw-r--r-- | neb/std/boolean.py | 25 | ||||
| -rw-r--r-- | neb/std/core.py | 172 | ||||
| -rw-r--r-- | neb/std/fs.py | 13 | ||||
| -rw-r--r-- | neb/std/functools.py | 10 | ||||
| -rw-r--r-- | neb/std/repl.py | 28 | ||||
| -rw-r--r-- | neb/std/sys.py | 9 | ||||
| -rw-r--r-- | neb/structs.py | 11 | ||||
| -rw-r--r-- | neighborcat/neighborcat.neb | 9 | ||||
| -rw-r--r-- | repl.neb | 43 | ||||
| -rwxr-xr-x | tests/runner.bash | 4 |
13 files changed, 282 insertions, 162 deletions
diff --git a/commands.txt b/commands.txt index a9809ca..374d44d 100644 --- a/commands.txt +++ b/commands.txt @@ -1,59 +1,76 @@ -($ command :string) => :list (and arg :bool arg :bool & :bool) => :bool +(assert cond :bool) => :list +(bench command :any) => :any +(block expr :any & :any) => :any +(branch body :any & :any) => :any +(def name :any value :any) => :list +(eval arg :any) => :any +(for-count count :int body :any & :any) => :any +(for-each list :list body :any & :any) => :any +(func name :any args :any body :any & :any) => :list +(if cond :bool t-branch :any f-branch :any) => :any +(lambda args :any body :any & :any) => :any +(or arg :bool arg :bool & :bool) => :bool +(quote arg :any) => :any +(redef name :any value :any) => :list +(type name :any name :any func :any) => :any +(use filename :string) => :list +(use-as filename :string namespace :any) => :list +(while cond :bool & :any) => :any +($ command :string) => :list +(* factor :number factor :number & :number) => :number +(+ term :number & :number) => :number +(- term :number & :number) => :number +(->string arg :any) => :string +(/ factor :number factor :number) => :number +(< num :number num :number) => :bool +(<= num :number num :number) => :bool +(> num :number num :number) => :bool +(>= num :number num :number) => :bool (any? arg :any) => :bool (append list :list item :any) => :list (apply func :any list :list) => :any (argv) => :list -(assert cond :bool) => :list -(block expr :any & :any) => :any (bool? arg :any) => :bool -(branch body :any & :any) => :any (clear) => :list +(close handle :handle) => :list (concat arg :string arg :string & :string) => :string -(def name :any value :any) => :list (empty? list :list) => :bool +(env regex :string) => :string (eq? value :literal value :literal) => :bool -(eval arg :any) => :any (exists? filename :string) => :bool (exit status :int) => :list -(/ factor :number factor :number) => :number -(* factor :number factor :number & :number) => :number (filter func :any list :list) => :list (first arg :list) => :any (first-char string :string) => :string (float? arg :any) => :bool (floor floor :number) => :int -(for-count count :int body :any & :any) => :any -(for-each list :list body :any & :any) => :any -(func name :any args :any body :any & :any) => :list (funcs) => :list (glob regex :string) => :string +(handle? arg :any) => :bool (howto symbol :any) => :list -(if cond :bool t-branch :any f-branch :any) => :any -(int? arg :any) => :bool (in? target :literal list :list) => :bool +(int? arg :any) => :bool (join list :list joiner :string) => :string -(lambda args :any body :any & :any) => :any (last list :list) => :any +(length string :string) => :int (list & :any) => :list -(list? arg :any) => :bool (list-length arg :list) => :int (list-reverse list :list) => :list +(list? arg :any) => :bool (literal? arg :any) => :bool (map func :any list :list) => :list (not not :bool) => :bool (number? arg :any) => :bool -(< num :number num :number) => :bool -(<= num :number num :number) => :bool -(> num :number num :number) => :bool -(>= num :number num :number) => :bool -(or arg :bool arg :bool & :bool) => :bool +(open-append filename :string) => :handle +(open-read filename :string) => :handle +(open-write filename :string) => :handle +(parse-neb string :string) => :any (print arg :string) => :list -(quote arg :any) => :any +(read handle :handle) => :string (read-char) => :string (read-line prompt :string) => :string (read-lines filename :string) => :list -(redef name :any value :any) => :list (remove list :list key :any) => :list (rest arg :list) => :list (rest-char string :string) => :string @@ -61,20 +78,14 @@ (slice list :list idx :int length :int) => :list (sort-numbers list :list) => :list (split target :string & :string) => :list -(string? arg :any) => :bool -(->string arg :any) => :string (string->int arg :string) => :int +(string? arg :any) => :bool (strip filename :string) => :string (symbols) => :list -(+ term :number & :number) => :number -(- term :number & :number) => :number -(type name :any name :any func :any) => :any +(syntax) => :list +(type? arg :any) => :bool (typeof candidate :any) => :any (unlink filename :string) => :list -(use-as filename :string namespace :any) => :list -(use filename :string) => :list (user-symbols) => :list -(while cond :bool & :any) => :any -(with-write filename :string & :any) => :any -(write string :string filename :list) => :any +(write string :string handle :handle) => :list (zip list :list list :list) => :list @@ -1 +1,2 @@ +(for-each (syntax) (howto _item_)) (for-each (funcs) (howto _item_)) diff --git a/neb/__init__.py b/neb/__init__.py index 42e1d00..e3167b9 100644 --- a/neb/__init__.py +++ b/neb/__init__.py @@ -12,7 +12,7 @@ def interpret(exprs, env, ns=None): return ret def evaluate(expr, env, ns=None): - if isinstance(expr, Literal) or isinstance(expr, Function) or isinstance(expr, TypeWrap) or isinstance(expr, List) or isinstance(expr, Handle): + 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 env.contains(expr.name): @@ -51,7 +51,8 @@ def evaluate(expr, env, ns=None): else: raise InterpretPanic(expr.args[0], "unable to evaluate") -class Function: + +class Callable: def __init__(self, name, params, body, args=None, many=None): self.name = name @@ -84,7 +85,35 @@ class Function: raise InterpretPanic(symbol, f"expected [{fmt}] arguments, received {len(params)}") return True - def evaluate_args(self, symbol, params, env, ns): + def call(self, expr, env): + pass + +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): @@ -92,9 +121,6 @@ class Function: arg = self.args[idx] else: arg = self.many - if arg.lazy: - ret.append(param) - continue ev = evaluate(param, env, ns) expected_type = evaluate(arg.type_, env, ns) valid = expected_type.validate_type(ev, env, ns) @@ -105,8 +131,6 @@ class Function: ret.append(ev) return ret - def call(self, expr, env): - pass class Builtin(Function): @@ -120,7 +144,7 @@ class Builtin(Function): 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) + evaluated_args = self.precall(expr.args[0], expr.args[1:], env, ns) return self.body(expr.args[0], evaluated_args, env, ns) @@ -172,7 +196,7 @@ class UserFunction(Function): 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) + evaluated_args = self.precall(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]) diff --git a/neb/std/boolean.py b/neb/std/boolean.py index 973fa67..e716a87 100644 --- a/neb/std/boolean.py +++ b/neb/std/boolean.py @@ -3,31 +3,6 @@ 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("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) - -BOOLEAN.register("and", Builtin("and", interpretAnd, [or_arg, or_arg], or_arg, Type(":bool"))) - def interpretEq(symbol, args, env, ns): # NOTE this currently only works for literals # compare types because 0 != #false in neb diff --git a/neb/std/core.py b/neb/std/core.py index a44417c..9e2e743 100644 --- a/neb/std/core.py +++ b/neb/std/core.py @@ -1,48 +1,49 @@ -from .. import TypeEnum, Environment, Arg, Builtin, UserFunction, evaluate, interpret, parse, lex, InterpretPanic, TypeWrap, Function, UserType +from .. import TypeEnum, Environment, Arg, Builtin, UserFunction, evaluate, interpret, parse, lex, InterpretPanic, TypeWrap, Function, UserType, NebSyntax from ..structs import * from pathlib import Path CORE = Environment() def interpretIf(symbol, args, env, ns): - if args[0].value: + 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 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("if", interpretIf, [cond, t_branch, f_branch])) +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 interpretDef(symbol, args, env, ns): - if not isinstance(args[0], Symbol): - raise InterpretPanic(symbol, "requires a :string name", args[0]) + raise InterpretPanic(symbol, "requires a :symbol", 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!) - + res = evaluate(args[1], env, ns) + env.register(name, res) return List([]) -def_name_arg = Arg("name", TypeEnum.ANY, lazy=True) +def_name_arg = Arg("name", TypeEnum.ANY) def_val_arg = Arg("value", TypeEnum.ANY) -CORE.register("def", Builtin("def", interpretDef, [def_name_arg, def_val_arg], return_type=Type(":list"))) +CORE.register("def", NebSyntax("def", interpretDef, [def_name_arg, def_val_arg], return_type=Type(":list"))) def interpretRedef(symbol, args, env, ns): if not isinstance(args[0], Symbol): - raise InterpretPanic(symbol, "requires a :string name", args[0]) + 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]) - env.reregister(name, args[1]) + res = evaluate(args[1], env, ns) + env.reregister(name, res) return List([]) -CORE.register("redef", Builtin("redef", interpretRedef, [def_name_arg, def_val_arg], return_type=Type(":list"))) +CORE.register("redef", NebSyntax("redef", interpretRedef, [def_name_arg, def_val_arg], return_type=Type(":list"))) def interpretLambda(symbol, args, env, ns): new_args = args @@ -58,14 +59,17 @@ def interpretLambda(symbol, args, env, ns): func.return_type = return_type return func -lambda_args_arg = Arg("args", TypeEnum.ANY, lazy=True) -lambda_body_arg = Arg("body", TypeEnum.ANY, lazy=True) -CORE.register("lambda", Builtin("lambda", interpretLambda, [lambda_args_arg, lambda_body_arg], lambda_body_arg)) +lambda_args_arg = Arg("args", TypeEnum.ANY) +lambda_body_arg = Arg("body", TypeEnum.ANY) +CORE.register("lambda", NebSyntax("lambda", interpretLambda, [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, args[0].value): + 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) @@ -74,13 +78,16 @@ def interpretForCount(symbol, args, env, ns): return ret for_count_arg = Arg("count", TypeEnum.INT) -for_body_arg = Arg("body", TypeEnum.ANY, lazy=True) -CORE.register("for-count", Builtin("for-count", interpretForCount, [for_count_arg, for_body_arg], for_body_arg)) +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 args[0].args: + for item in coll.args: new_env.register("_item_", evaluate(item, env, ns)) for arg in args[1:]: ret = evaluate(arg, new_env, ns) @@ -89,12 +96,12 @@ def interpretForEach(symbol, args, env, ns): return ret for_each_arg = Arg("list", TypeEnum.LIST) -CORE.register("for-each", Builtin("for-each", interpretForEach, [for_each_arg, for_body_arg], for_body_arg)) +CORE.register("for-each", NebSyntax("for-each", 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") + 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) @@ -102,11 +109,11 @@ def interpretBranch(symbol, args, env, ns): return evaluate(arg.args[1], env, ns) return List([]) -CORE.register("branch", Builtin("branch", interpretBranch, [for_body_arg], for_body_arg)) +CORE.register("branch", NebSyntax("branch", 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") + raise InterpretPanic(symbol, "requires a :symbol") name = args[0].name # NOTE: we are not evaluating the name!! if ns is not None: @@ -121,7 +128,7 @@ def interpretFunc(symbol, args, env, ns): env.register(name, func) return List([]) -CORE.register("func", Builtin("func", interpretFunc, [def_name_arg, lambda_args_arg, lambda_body_arg], lambda_body_arg, Type(":list"))) +CORE.register("func", NebSyntax("func", interpretFunc, [def_name_arg, lambda_args_arg, lambda_body_arg], lambda_body_arg, Type(":list"))) def interpretBlock(symbol, args, env, ns): new_env = Environment(env) @@ -130,8 +137,8 @@ def interpretBlock(symbol, args, env, ns): ret = evaluate(arg, new_env, ns) return ret -block_arg = Arg("expr", TypeEnum.ANY, lazy=True) -CORE.register("block", Builtin("block", interpretBlock, [block_arg], block_arg)) +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) @@ -140,18 +147,21 @@ def interpretWhile(symbol, args, env, ns): while True: ev = evaluate(cond, new_env, ns) if not isinstance(ev, Bool): - raise InterpretPanic(symbol, "expects a :bool condition", ev) + 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", Builtin("while", interpretWhile, [Arg("cond", TypeEnum.BOOL, lazy=True)], Arg("expr", TypeEnum.ANY, lazy=True))) +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_file_name = args[0].value - target_file = Path(target_file_name).resolve() + 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) with open(target_file, "r") as fil: @@ -159,38 +169,48 @@ def interpretUse(symbol, args, env, ns): interpret(parse(lex(data)), env, ns) return List([]) -CORE.register("use", Builtin("use", interpretUse, [Arg("filename", TypeEnum.STRING)], return_type=Type(":list"))) +CORE.register("use", NebSyntax("use", interpretUse, [Arg("filename", TypeEnum.STRING)], return_type=Type(":list"))) +# NOTE this doesn't technically need to be a macro def interpretAssert(symbol, args, env, ns): - if args[0].value != True: + 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 List([]) -CORE.register("assert", Builtin("assert", interpretAssert, [Arg("cond", TypeEnum.BOOL)], return_type=Type(":list"))) +CORE.register("assert", NebSyntax("assert", interpretAssert, [Arg("cond", TypeEnum.BOOL)], return_type=Type(":list"))) def interpretUseAs(symbol, args, env, ns): - target_file_name = args[0].value - target_file = Path(target_file_name).resolve() + 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, args[1].name) + interpret(parse(lex(data)), env, new_ns) return List([]) -CORE.register("use-as", Builtin("use-as", interpretUseAs, [Arg("filename", TypeEnum.STRING), Arg("namespace", TypeEnum.ANY, lazy=True)], return_type=Type(":list"))) +CORE.register("use-as", NebSyntax("use-as", interpretUseAs, [Arg("filename", TypeEnum.STRING), Arg("namespace", TypeEnum.ANY)], return_type=Type(":list"))) def interpretQuote(symbol, args, env, ns): return args[0] -quote_arg = Arg("arg", TypeEnum.ANY, lazy=True) -CORE.register("quote", Builtin("quote", interpretQuote, [quote_arg])) +quote_arg = Arg("arg", TypeEnum.ANY) +CORE.register("quote", NebSyntax("quote", interpretQuote, [quote_arg])) def interpretEval(symbol, args, env, ns): - return evaluate(args[0], env, ns) # TODO why do i have to explicitly evaluate here? + ev = evaluate(args[0], env, ns) # TODO why do i have to evaluate twice? + return evaluate(ev, env, ns) eval_arg = Arg("arg", TypeEnum.ANY) -CORE.register("eval", Builtin("eval", interpretEval, [eval_arg])) +CORE.register("eval", NebSyntax("eval", interpretEval, [eval_arg])) def interpretType(symbol, args, env, ns): # (type typename parent func) @@ -201,13 +221,14 @@ def interpretType(symbol, args, env, ns): # TODO we may need to do namespace things here # also, we probably shouldn't be able to rename types - if not isinstance(args[1], TypeWrap): - raise InterpretPanic(symbol, "parent must be a valid type", args[1]) - elif not env.contains(args[1].name.name): - raise InterpretPanic(symbol, f"no such type {args[1]}") - parent = env.get(args[1].name.name) + parent_type = evaluate(args[1], env, ns) + if not isinstance(parent_type, TypeWrap): + raise InterpretPanic(symbol, "parent must be a valid type", parent_type) + elif not env.contains(parent_type.name): + raise InterpretPanic(symbol, f"no such type {parent_type}") + parent = env.get(parent_type.name) - func = args[2] + func = evaluate(args[2], env, ns) if not isinstance(func, Function): raise InterpretPanic(symbol, "validation must be a :func", func) @@ -215,8 +236,51 @@ def interpretType(symbol, args, env, ns): env.register(name, new_type) return List([]) -type_name_arg = Arg("name", TypeEnum.ANY, lazy=True) +type_name_arg = Arg("name", TypeEnum.ANY) type_parent_arg = Arg("name", TypeEnum.ANY) type_func_arg = Arg("func", TypeEnum.ANY) -CORE.register("type", Builtin("type", interpretType, [type_name_arg, type_parent_arg, type_func_arg])) +CORE.register("type", NebSyntax("type", interpretType, [type_name_arg, type_parent_arg, type_func_arg])) + +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"))) diff --git a/neb/std/fs.py b/neb/std/fs.py index d20cde9..144df0f 100644 --- a/neb/std/fs.py +++ b/neb/std/fs.py @@ -31,19 +31,6 @@ def interpretUnlink(symbol, args, env, ns): FS.register("unlink", Builtin("unlink", interpretUnlink, [Arg("filename", TypeEnum.STRING)], return_type=Type(":list"))) -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("with-write", interpretWithWrite, [Arg("filename", TypeEnum.STRING)], Arg("exprs", TypeEnum.ANY, lazy=True))) - def interpretWrite(symbol, args, env, ns): string = args[0] handle = args[1] diff --git a/neb/std/functools.py b/neb/std/functools.py index 9e426b8..f83c49d 100644 --- a/neb/std/functools.py +++ b/neb/std/functools.py @@ -34,13 +34,11 @@ def interpretMap(symbol, args, env, ns): FUNCTOOLS.register("map", Builtin("map", interpretMap, [Arg("func", TypeEnum.ANY), Arg("list", TypeEnum.LIST)], return_type=Type(":list"))) +# TODO I think this is wrong 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): + if not isinstance(func, Function): raise InterpretPanic(symbol, "requires a symbol as its first argument", func) - new_expr = Expr([func] + args[1].args) - return evaluate(new_expr, env, ns) - -FUNCTOOLS.register("apply", Builtin("apply", interpretApply, [Arg("func", TypeEnum.ANY, lazy=True), Arg("list", TypeEnum.LIST)])) + return func.call(Expr([func] + args[1].args), env, ns) +FUNCTOOLS.register("apply", Builtin("apply", interpretApply, [Arg("func", TypeEnum.ANY), Arg("list", TypeEnum.LIST)])) diff --git a/neb/std/repl.py b/neb/std/repl.py index 16efb20..6b6be83 100644 --- a/neb/std/repl.py +++ b/neb/std/repl.py @@ -1,10 +1,11 @@ -from .. import TypeEnum, Environment, Arg, Builtin, UserFunction, Function, evaluate, InterpretPanic +from .. import TypeEnum, Environment, Arg, Builtin, UserFunction, Function, evaluate, InterpretPanic, Callable, NebSyntax, lex, parse +from .core import interpretQuote from ..structs import * REPL = Environment() def interpretHowTo(symbol, args, env, ns): - if not isinstance(args[0], Function): + if not isinstance(args[0], Callable): raise InterpretPanic(symbol, "expects a :func", args[0]) print(args[0].describe()) return List([]) @@ -12,19 +13,36 @@ def interpretHowTo(symbol, args, env, ns): REPL.register("howto", Builtin("howto", interpretHowTo, [Arg("symbol", TypeEnum.ANY)], return_type=Type(":list"))) def interpretSymbols(symbol, args, env, ns): - keys = [Symbol(k, -1) for k,v in sorted(env.environment.items())] + keys = [Symbol(k, -1) for k,v in sorted(env.get_all().items())] return List(keys) REPL.register("symbols", Builtin("symbols", interpretSymbols, [], return_type=Type(":list"))) def interpretFuncs(symbol, args, env, ns): - keys = [Symbol(k, -1) for k,v in env.environment.items() if isinstance(v, Builtin)] + keys = [Symbol(k, -1) for k,v in sorted(env.get_all().items()) if isinstance(v, Function)] return List(keys) REPL.register("funcs", Builtin("funcs", interpretFuncs, [], return_type=Type(":list"))) 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)] + keys = [Symbol(k, -1) for k,v in env.get_all().items() if isinstance(v, UserFunction) or isinstance(v, Literal)] return List(keys) REPL.register("user-symbols", Builtin("user-symbols", interpretUserSymbols, [], return_type=Type(":list"))) + +def interpretUserFuncs(symbol, args, env, ns): + keys = [Symbol(k, -1) for k,v in env.get_all().items() if isinstance(v, UserFunction)] + return List(keys) + +REPL.register("user-funcs", Builtin("user-funcs", interpretUserFuncs, [], return_type=Type(":list"))) + +def interpretSyntax(symbol, args, env, ns): + keys = [Symbol(k, -1) for k,v in sorted(env.get_all().items()) if isinstance(v, NebSyntax)] + return List(keys) + +REPL.register("syntax", Builtin("syntax", interpretSyntax, [], return_type=Type(":list"))) + +def interpretParseNeb(symbol, args, env, ns): + return interpretQuote(None, [parse(lex(args[0].value))[0]], env, ns) + +REPL.register("parse-neb", Builtin("parse-neb", interpretParseNeb, [Arg("string", TypeEnum.STRING)], return_type=Type(":any"))) diff --git a/neb/std/sys.py b/neb/std/sys.py index 525895a..f92eb77 100644 --- a/neb/std/sys.py +++ b/neb/std/sys.py @@ -35,15 +35,6 @@ def interpretPrint(symbol, args, env, ns): SYS.register("print", Builtin("print", interpretPrint, [Arg("arg", TypeEnum.STRING)], return_type=Type(":list"))) -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 - -SYS.register("bench", Builtin("bench", interpretBench, [Arg("command", TypeEnum.ANY, lazy=True)], return_type=Type(":any"))) - def interpretEnv(symbol, args, env, ns): items = os.environ[args[0].value].split(":") return List([String(item) for item in items]) diff --git a/neb/structs.py b/neb/structs.py index c623ed3..45e69a7 100644 --- a/neb/structs.py +++ b/neb/structs.py @@ -112,7 +112,7 @@ class Type: else: return self.name -_native_types = ":any :literal :string :bool :number :int :float :[] :handle" +_native_types = ":any :literal :string :bool :number :int :float :[] :handle :type" ALL_TYPES = {x: Type(x) for x in _native_types.split(" ")} class Symbol: @@ -140,14 +140,13 @@ class List: # function things class Arg: - def __init__(self, name, type_, *, optional=False, lazy=False): + def __init__(self, name, type_, *, optional=False): self.name = name if f"{type_}" == ":list": self.type_ = ALL_TYPES[":[]"] else: self.type_ = ALL_TYPES[f"{type_}"] self.optional = optional - self.lazy = lazy def __str__(self): return f"{self.name} {self.type_}" @@ -194,6 +193,12 @@ class Environment: except: raise NebPanic(f"undefined symbol: '{key}") + def get_all(self): + if self.parent is None: + return self.environment + else: + return dict(self.parent.get_all(), **self.environment) + def __str__(self): out = "" for k, v in self.environment.items(): diff --git a/neighborcat/neighborcat.neb b/neighborcat/neighborcat.neb index 1c3b02a..5a2d5c2 100644 --- a/neighborcat/neighborcat.neb +++ b/neighborcat/neighborcat.neb @@ -75,9 +75,10 @@ ; go to the porch (def target-eats-stem (last (split target-eats "/"))) (def target-porch (user-from-path target-eats)) - (with-write (concat HOME target-porch PORCH NAME) - (write CAT _file_) - (write (concat "thank you for the " target-eats-stem "!\n") _file_))) + (def cat-file (open-write (concat HOME target-porch PORCH NAME))) + (write CAT cat-file) + (write (concat "thank you for the " target-eats-stem "!\n") cat-file) + (close cat-file)) (func init () ; create the new porch and bowl @@ -110,7 +111,7 @@ (block (print (concat "you already have " (first food) " in your bowl!")) (exit))) - (with-write (concat HOME USER BOWL "/" (first food))) + (close (open-write (concat HOME USER BOWL "/" (first food)))) (print (concat "you left some " (first food) " for " NAME "!"))) (func help () diff --git a/repl.neb b/repl.neb new file mode 100644 index 0000000..e8cb9b5 --- /dev/null +++ b/repl.neb @@ -0,0 +1,43 @@ +; repl.neb +; by mryouse +; +; a REPL for neb, written in neb + +(def _history_ (list)) + +(func history () + _history_) + +(func !! () + (def cmd (first (list-reverse (history)))) + (print (->string cmd)) + (eval (parse-neb cmd))) + +(func save-repl (filename :string) + (def fil (open-write filename)) + (write (concat (join (history) "\n") "\n") fil) + (close fil)) + +(func prompt (nxt) + (concat "#" (->string nxt) "> ")) + +(func repl () + (print "### neb :)(: IN NEB!") + (print "version: < 0") + + (def next-cmd-num 1) + (func evaluate-cmd (cmd) + (def evaluated (parse-neb cmd)) + (print (concat "=> " (->string evaluated))) + (redef next-cmd-num (+ 1 next-cmd-num)) + (redef _history_ (append _history_ cmd))) + + ; this is the actual loop part + (while #true + (def this-cmd (strip (read-line (prompt next-cmd-num)))) + (if (not (eq? "" this-cmd)) + (try + (evaluate-cmd this-cmd) + (print (concat "panic! " _panic_)))))) + +(repl) diff --git a/tests/runner.bash b/tests/runner.bash index 32af554..20634dd 100755 --- a/tests/runner.bash +++ b/tests/runner.bash @@ -6,9 +6,10 @@ do expected=$(tail -1 $item | sed 's/; //') actual=$(python3 ../neb.py $item) if [[ "$expected" == "$actual" ]]; then + echo -n "." ((passed=passed+1)) else - echo "$item... FAILED" + echo "$item FAILED" echo " Expected: $expected" echo " Actual: $actual" echo "" @@ -18,5 +19,6 @@ done pct=$(python -c "print('{:.2f}'.format($passed / float($total) * 100))") +echo "" echo "Total: $total" echo "Passed: $passed ($pct%)" |
