aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormryouse2022-07-09 02:39:27 +0000
committermryouse2022-07-09 02:39:27 +0000
commit4038aa87fddbe7e79c61603cf8d4514ebb47f3fe (patch)
tree8f5493f5fb8d9f9ea1de4cd1ade03b7d6c9447d9
parent7ffeef0faef3fbc389069df853109afc76260f0d (diff)
parent4fb8873f45c8596ba044c87060191778b8238952 (diff)
Merge branch 'master' into feature/listtypes
-rw-r--r--commands.txt75
-rw-r--r--docs.neb1
-rw-r--r--neb/__init__.py44
-rw-r--r--neb/std/boolean.py25
-rw-r--r--neb/std/core.py172
-rw-r--r--neb/std/fs.py13
-rw-r--r--neb/std/functools.py10
-rw-r--r--neb/std/repl.py28
-rw-r--r--neb/std/sys.py9
-rw-r--r--neb/structs.py11
-rw-r--r--neighborcat/neighborcat.neb9
-rw-r--r--repl.neb43
-rwxr-xr-xtests/runner.bash4
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
diff --git a/docs.neb b/docs.neb
index a057224..0353a53 100644
--- a/docs.neb
+++ b/docs.neb
@@ -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%)"