aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormryouse2022-06-21 01:49:29 +0000
committermryouse2022-06-21 01:49:29 +0000
commit776fe3193b515c028b5ac69326baed51d760d32f (patch)
treedb111c3fe7a20143b2f058259f86dbbecae4cbd6
parentb1550660adaca68bb38541aed371e36b7000e124 (diff)
refactor: break stdlib into several files
-rw-r--r--neb.py28
-rw-r--r--neb/__init__.py170
-rw-r--r--neb/interpreter.py870
-rw-r--r--neb/parser.py2
-rw-r--r--neb/std/__init__.py11
-rw-r--r--neb/std/boolean.py47
-rw-r--r--neb/std/core.py184
-rw-r--r--neb/std/fs.py61
-rw-r--r--neb/std/functools.py46
-rw-r--r--neb/std/lists.py116
-rw-r--r--neb/std/math.py79
-rw-r--r--neb/std/repl.py24
-rw-r--r--neb/std/strings.py53
-rw-r--r--neb/std/sys.py35
-rw-r--r--neb/std/term.py33
-rw-r--r--neb/std/types.py60
-rw-r--r--neb/structs.py134
17 files changed, 1074 insertions, 879 deletions
diff --git a/neb.py b/neb.py
index 7e95284..fa538ea 100644
--- a/neb.py
+++ b/neb.py
@@ -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)
+'''