aboutsummaryrefslogtreecommitdiff
path: root/neb
diff options
context:
space:
mode:
authormryouse2022-07-07 03:48:05 +0000
committermryouse2022-07-07 03:48:05 +0000
commit3412d4d0e35dfe1faac5254199aa34f0354e992b (patch)
treed8cb5b69a2cf110d65faa2d2ddf0b6486038015d /neb
parent46cefa721d145af17bee3696ef533d752989458c (diff)
refactor: add Macro structure under Callable, which is all core
Diffstat (limited to 'neb')
-rw-r--r--neb/__init__.py42
-rw-r--r--neb/std/core.py135
-rw-r--r--neb/std/repl.py4
3 files changed, 109 insertions, 72 deletions
diff --git a/neb/__init__.py b/neb/__init__.py
index b280914..6aeec88 100644
--- a/neb/__init__.py
+++ b/neb/__init__.py
@@ -11,7 +11,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):
@@ -57,18 +57,6 @@ class Callable:
out.append(string_args(self.args, self.many))
return " ".join(out).strip() + f") => {self.return_type}"
- def precall(self, symbol, params, env, ns):
- pass
-
- def call(self, expr, env):
- pass
-
-
-class Function(Callable):
-
- def __init__(self, name, params, body, args=None, many=None):
- super().__init__(name, params, body, args, many)
-
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)
@@ -83,6 +71,34 @@ class Function(Callable):
raise InterpretPanic(symbol, f"expected [{fmt}] arguments, received {len(params)}")
return True
+ def call(self, expr, env):
+ pass
+
+class Macro(Callable):
+
+ def __init__(self, name, params, body, args=None, many=None):
+ super().__init__(name, params, body, args, many)
+
+class NebMacro(Callable):
+
+ 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"builtin 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 = []
diff --git a/neb/std/core.py b/neb/std/core.py
index f6b5b06..a0f2cf6 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, NebMacro
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", NebMacro("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", NebMacro("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", NebMacro("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", NebMacro("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", NebMacro("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", NebMacro("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", NebMacro("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", NebMacro("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", NebMacro("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", NebMacro("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", NebMacro("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", NebMacro("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", NebMacro("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", NebMacro("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", NebMacro("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):
- raise InterpretPanic(symbol, f"no such type {args[1]}")
- parent = env.get(args[1].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,10 +236,10 @@ 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", NebMacro("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
@@ -230,8 +251,8 @@ def interpretOr(symbol, args, env, ns):
return ev
return Bool(False)
-or_arg = Arg("arg", TypeEnum.BOOL, lazy=True)
-CORE.register("or", Builtin("or", interpretOr, [or_arg, or_arg], or_arg, Type(":bool")))
+or_arg = Arg("arg", TypeEnum.BOOL)
+CORE.register("or", NebMacro("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
@@ -252,4 +273,4 @@ def interpretBench(symbol, args, env, ns):
print(f"bench [{symbol.line}]: {args[0]} => {after - before}")
return ret
-CORE.register("bench", Builtin("bench", interpretBench, [Arg("command", TypeEnum.ANY, lazy=True)], return_type=Type(":any")))
+CORE.register("bench", NebMacro("bench", interpretBench, [Arg("command", TypeEnum.ANY)], return_type=Type(":any")))
diff --git a/neb/std/repl.py b/neb/std/repl.py
index 16efb20..73f82d6 100644
--- a/neb/std/repl.py
+++ b/neb/std/repl.py
@@ -1,10 +1,10 @@
-from .. import TypeEnum, Environment, Arg, Builtin, UserFunction, Function, evaluate, InterpretPanic
+from .. import TypeEnum, Environment, Arg, Builtin, UserFunction, Function, evaluate, InterpretPanic, Callable
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([])