commit ff36917cb5336c02fec19637fd4116f1216d8309 Author: German Mikheev Date: Mon Dec 22 19:10:46 2025 +0300 Initial commit - fix problems & refactor code diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e96bdb --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__ +.venv +.idea diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/environment.py b/core/environment.py new file mode 100644 index 0000000..e0fabef --- /dev/null +++ b/core/environment.py @@ -0,0 +1,54 @@ +from __future__ import annotations +from typing import Any, Callable, Optional +from string import whitespace + +from core.primitives.symbol import symbolKey, isSymbol +from core.printer import Representation +from core.primitives.list import NIL + +class Environment: + """Environment for variable bindings with lexical scoping.""" + + frame: dict[str, Any] + parent: Environment | None + + def __init__(self, parent: Environment | None) -> None: + self.frame: dict[str, Any] = {} + self.parent: Environment | None = parent + + def add(self, symbol_key: Any, value: Any) -> None: + """Add a new binding to the current environment frame.""" + self.frame[symbolKey(symbol_key)] = value + + def set(self, symbol_key: Any, value: Any) -> None: + """Set a binding in the environment chain.""" + key: str = symbolKey(symbol_key) + currentEnvironment: Environment = self + while currentEnvironment is not None: + if key in currentEnvironment.frame: + currentEnvironment.frame[key] = value + return + assert currentEnvironment is Environment + currentEnvironment = currentEnvironment.parent + raise SyntaxError(f"no such symbol: {key}") + + def get(self, symbol_key: Any) -> Any: + """Get a binding from the environment chain.""" + key: str = symbolKey(symbol_key) + currentEnvironment: Environment | None = self + while currentEnvironment is not None: + if key in currentEnvironment.frame: + return currentEnvironment.frame[key] + currentEnvironment = currentEnvironment.parent + return symbol_key + +EVAL_NIL: Callable[[], Any] = lambda: NIL +ENV: Environment = Environment(None) + +MEANINGFUL: str = "\"();'" +IGNORE: str = whitespace + "," + +RepresentateAST: Callable[[Any], str] = lambda value: ( + value if type(value) is str and not isSymbol(value) else Representation(value) +) +TCO: bool = True diff --git a/core/evaluator/evaluate_binary_operator.py b/core/evaluator/evaluate_binary_operator.py new file mode 100644 index 0000000..c7ffecd --- /dev/null +++ b/core/evaluator/evaluate_binary_operator.py @@ -0,0 +1,24 @@ +from core.tokens import BinaryOperator +from core.exceptions import runtimeError +from core.environment import RepresentateAST + +def evalBinaryOperator(op: BinaryOperator, a, b): + try: + match op: + case BinaryOperator.ADD: + return a + b + case BinaryOperator.SUB: + return a - b + case BinaryOperator.MUL: + return a * b + case BinaryOperator.DIV: + if type(a) is type(b) is int: + return a // b + return a / b + case BinaryOperator.MOD: + return a % b + case BinaryOperator.STRCONCAT: + return RepresentateAST(a) + RepresentateAST(b) + + except Exception as e: + raise runtimeError(f'cannot apply {repr(op)} to {repr(a)} and {repr(b)}') from e \ No newline at end of file diff --git a/core/evaluator/evaluate_binary_predicate.py b/core/evaluator/evaluate_binary_predicate.py new file mode 100644 index 0000000..54fcb04 --- /dev/null +++ b/core/evaluator/evaluate_binary_predicate.py @@ -0,0 +1,14 @@ +from core.tokens import BinaryPredicate +from core.exceptions import runtimeError + +def evalBinaryPredicate(op: BinaryPredicate, a, b): + try: + match op: + case BinaryPredicate.LT: + return a < b + case BinaryPredicate.GT: + return a > b + case BinaryPredicate.EQ: + return a == b + except Exception as e: + raise runtimeError(f'cannot apply {repr(op)} to {repr(a)} and {repr(b)}') from e \ No newline at end of file diff --git a/core/evaluator/evaluate_call.py b/core/evaluator/evaluate_call.py new file mode 100644 index 0000000..9424251 --- /dev/null +++ b/core/evaluator/evaluate_call.py @@ -0,0 +1,53 @@ +from core.primitives.list import NIL, cons, car, cdr +from core.primitives.symbol import isSymbol, symbolName + +from core.exceptions import syntaxError + +from core.tokens import Lambda +from core.tail_recursion import tailRecursion + +# from core.evaluator.naively_evaluate import naivelyEvaluate +from core.evaluator.utils.getFirstN import getFirstN + +from core.environment import TCO + +def evalCall(head, tail, environment, environmentParent, strict): + from core.evaluator.naively_evaluate import naivelyEvaluate + args = head.args + + if args == NIL and tail != NIL: + raise syntaxError(f'more than zero args for {repr(head)}') + + while args != NIL: + firstArg = car(args) + if not isSymbol(firstArg): + raise syntaxError(f'cannot take non-symbol as argument: {repr(firstArg)}') + if symbolName(firstArg) == '.': + firstArg = getFirstN(args, 2)[1] + arr = [] + while tail != NIL: + arr.append(naivelyEvaluate(car(tail), environmentParent, True)) + tail = cdr(tail) + arg = NIL + for elem in reversed(arr): + arg = cons(elem, arg) + args = NIL + else: + if tail == NIL: + break + arg = naivelyEvaluate(car(tail), environmentParent, True) + args = cdr(args) + tail = cdr(tail) + environment.add(firstArg, arg) + + if tail != NIL: + raise syntaxError(f'extra args {repr(tail)}\nfor not variable lambda {repr(head)}') + + if args != NIL: + return Lambda(args, head.body, environment) + + if not TCO: + return naivelyEvaluate(head.body, environment, True) + + ret = tailRecursion(naivelyEvaluate, head.body, environment, False) + return tailRecursion.eval(ret) if strict else ret \ No newline at end of file diff --git a/core/evaluator/evaluate_macro.py b/core/evaluator/evaluate_macro.py new file mode 100644 index 0000000..013ae05 --- /dev/null +++ b/core/evaluator/evaluate_macro.py @@ -0,0 +1,49 @@ +from core.primitives.list import cons, car, cdr, NIL +from core.primitives.symbol import symbolName, symbolKey + +from core.tokens import Macro +from core.exceptions import syntaxError + +from core.tail_recursion import tailRecursion +from core.macro_expand import macroExpand + +# from core.evaluator.naively_evaluate import naivelyEvaluate +from core.evaluator.utils.getFirstN import getFirstN + +from core.environment import TCO + +def evalMacro(head, tail, environment, strict): + from core.evaluator.naively_evaluate import naivelyEvaluate + d = dict() + args = head.args + + if args == NIL and tail != NIL: + raise syntaxError(f'more than zero args for {repr(head)}') + + while args != NIL: + firstArg = car(args) + if symbolName(firstArg) == '.': + firstArg = getFirstN(args, 2)[1] + arg = tail + args = NIL + tail = NIL + else: + if tail == NIL: + break + arg = car(tail) + args = cdr(args) + tail = cdr(tail) + d[symbolKey(firstArg)] = arg + + if tail != NIL: + raise syntaxError(f'extra args {repr(tail)}\nfor not variable macro {repr(head)}') + expanded = macroExpand(head.body, d) + + if args != NIL: + return Macro(args, expanded) + + if not TCO: + return naivelyEvaluate(expanded, environment, True) + + ret = tailRecursion(naivelyEvaluate, expanded, environment, False) + return tailRecursion.eval(ret) if strict else ret \ No newline at end of file diff --git a/core/evaluator/evaluate_special_form.py b/core/evaluator/evaluate_special_form.py new file mode 100644 index 0000000..ff3f0b9 --- /dev/null +++ b/core/evaluator/evaluate_special_form.py @@ -0,0 +1,137 @@ +from core.tokens import ( + SpecialForm, + Lambda, + Macro, + Pure +) + +from core.primitives.list import ( + cons, + car, + cdr, + isList +) +from core.primitives.symbol import symbol + + +from core.environment import NIL, RepresentateAST +from core.primitives.symbol import isSymbol +from core.exceptions import ( + syntaxError, + runtimeError +) + +# from core.evaluator.naively_evaluate import naivelyEvaluate +from core.evaluator.utils.getFirstN import getFirstN +from core.parser.parse_expression import parseExpression + +def evalSpecialForm(head, tail, env, strict): + from core.evaluator.naively_evaluate import naivelyEvaluate + match head: + case SpecialForm.QUOTE: + return getFirstN(tail, 1)[0] + + case SpecialForm.EVAL: + return naivelyEvaluate(naivelyEvaluate(getFirstN(tail, 1)[0], env, True), env, strict) + + case SpecialForm.TYPEOF: + a = naivelyEvaluate(getFirstN(tail, 1)[0], env, True) + return 'List' if isList(a) else 'Symbol' if isSymbol(a) else str(a.__class__.__name__) + + case SpecialForm.CONS: + a, b = getFirstN(tail, 2) + evaluated_b = naivelyEvaluate(b, env, True) + if not isList(evaluated_b): + raise syntaxError('cannot add element to non-list') + return cons(naivelyEvaluate(a, env, True), evaluated_b) + + case SpecialForm.CAR: + a = naivelyEvaluate(getFirstN(tail, 1)[0], env, True) + if not isList(a): + return a # config moment + if a == NIL: + raise syntaxError("NIL's head???") # config moment + return car(a) + + case SpecialForm.CDR: + a = naivelyEvaluate(getFirstN(tail, 1)[0], env, True) + if not isList(a): + return NIL # config moment + if a == NIL: + raise syntaxError("NIL's tail???") # config moment + return cdr(a) + + case SpecialForm.IF: + a, b, c = getFirstN(tail, 3) # a ? b : c + evaled_a = naivelyEvaluate(a, env, True) + if type(evaled_a) is bool: + return naivelyEvaluate(b, env, strict) if evaled_a else naivelyEvaluate(c, env, strict) + raise syntaxError(f'not boolean {a} in (++ {repr(head)} {repr(tail)})') # TODO + + case SpecialForm.DO: + if tail == NIL: + return NIL + ev = NIL + while cdr(tail) != NIL: + ev = naivelyEvaluate(car(tail), env, True) + tail = cdr(tail) + return naivelyEvaluate(car(tail), env, strict) + + case SpecialForm.PRINT: + print(RepresentateAST(naivelyEvaluate(getFirstN(tail, 1)[0], env, True)), end='') + return NIL + + case SpecialForm.FLUSH: + print(end='', flush=True) + return NIL + + case SpecialForm.READ: + getFirstN(tail, 0) + s = input('\nreading: ') + return parseExpression(s)[0] + + case SpecialForm.SYMBOL: + a = naivelyEvaluate(getFirstN(tail, 1)[0], env, True) + if not isinstance(a, str): + raise syntaxError('cannot make symbol from non-string') + return symbol(a) + + case SpecialForm.DEF: + a, b = getFirstN(tail, 2) + if isSymbol(a): + env.add(a, naivelyEvaluate(b, env, True)) + return NIL + raise syntaxError('cannot define non-symbol') + + case SpecialForm.SET: + a, b = getFirstN(tail, 2) + if isSymbol(a): + env.set(a, naivelyEvaluate(b, env, True)) + return NIL + raise syntaxError('cannot set non-Symbol') + + case SpecialForm.LAMBDA: + a, b = getFirstN(tail, 2) + if isList(a): + return Lambda(a, b, env) + raise syntaxError(f'wrong args form for lambda: {repr(a)}') + + # some errors may be here + case SpecialForm.PURE: + a, b = getFirstN(tail, 2) + if isList(a): + return Pure(a, b) + raise syntaxError(f'wrong args form for pure: {repr(a)}') + + case SpecialForm.MACRO: + a, b = getFirstN(tail, 2) + if isList(a): + return Macro(a, b) + raise syntaxError(f'wrong args form for macro: {repr(a)}') + + case SpecialForm.RAISE: + cause = naivelyEvaluate(getFirstN(tail, 1)[0], env, True) + raise runtimeError(RepresentateAST(cause)) + + case _: + raise syntaxError \ No newline at end of file diff --git a/core/evaluator/naively_evaluate.py b/core/evaluator/naively_evaluate.py new file mode 100644 index 0000000..f2ffdb7 --- /dev/null +++ b/core/evaluator/naively_evaluate.py @@ -0,0 +1,73 @@ +from core.tokens import ( + BinaryOperator, + BinaryPredicate, + SpecialForm, + Lambda, + Pure, + Macro, +) + +# debug +import sys + +from core.primitives.symbol import ( + isSymbol +) +from core.primitives.list import ( + cons, car, cdr, + isList, + NIL +) +from core.environment import EVAL_NIL, Environment, RepresentateAST + +from core.evaluator.evaluate_binary_operator import evalBinaryOperator +from core.evaluator.evaluate_binary_predicate import evalBinaryPredicate +from core.evaluator.evaluate_special_form import evalSpecialForm +from core.evaluator.evaluate_call import evalCall +from core.evaluator.evaluate_macro import evalMacro + +from core.evaluator.utils.getFirstN import getFirstN + +from core.exceptions import error, syntaxError, typeError + +def naivelyEvaluate(value, localEnvironment, strict): + if isSymbol(value): + return localEnvironment.get(value) + + elif isList(value): + if value == NIL: + return EVAL_NIL() + head = naivelyEvaluate(car(value), localEnvironment, True) + tail = cdr(value) + + try: + if isinstance(head, (BinaryOperator, BinaryPredicate)): + try: + a, b = getFirstN(tail, 2) + evaledA = naivelyEvaluate(a, localEnvironment, True) + evaledB = naivelyEvaluate(b, localEnvironment, True) + if isinstance(head, BinaryOperator): + return evalBinaryOperator(head, evaledA, evaledB) + return evalBinaryPredicate(head, evaledA, evaledB) + except syntaxError as e: + raise syntaxError(f'wrong args for: {repr(value)}') from e + + elif isinstance(head, SpecialForm): + return evalSpecialForm(head, tail, localEnvironment, strict) + + elif isinstance(head, (Lambda, Pure)): + if isinstance(head, Lambda): + environment = Environment(head.env) + else: + environment = Environment(parent=localEnvironment) + return evalCall(head, tail, environment, localEnvironment, strict) + + elif isinstance(head, Macro): + return evalMacro(head, tail, localEnvironment, strict) + + except error as e: + raise syntaxError(f'wrong args for {repr(head)}: {repr(cdr(value))}') from e + + raise typeError(f'wrong head form: {RepresentateAST(head)}') + + return value \ No newline at end of file diff --git a/core/evaluator/utils/getFirstN.py b/core/evaluator/utils/getFirstN.py new file mode 100644 index 0000000..83642a0 --- /dev/null +++ b/core/evaluator/utils/getFirstN.py @@ -0,0 +1,32 @@ +from typing import Any +from core.primitives.list import cons, car, cdr, NIL +from core.exceptions import syntaxError +from core.printer import Representation + +def getFirstN(_list: Any, n: int) -> list[Any]: + """Get first N elements. + + Args: + _list: A list. + n: The expected number of elements. + + Returns: + A list of the extracted elements. + + Raises: + syntaxError: If the list has wrong number of elements. + """ + result: list[Any] = [] + current: Any = _list + + while current != NIL and len(result) < n: + result.append(car(current)) + current = cdr(current) + + if current != NIL: + raise syntaxError(f"extra elems: {Representation(current)}") + + if len(result) < n: + raise syntaxError("lack of elems") + + return result \ No newline at end of file diff --git a/core/exceptions.py b/core/exceptions.py new file mode 100644 index 0000000..43b86d8 --- /dev/null +++ b/core/exceptions.py @@ -0,0 +1,31 @@ +"""Exception classes for Lisp interpreter.""" + + +class error(Exception): + """Base exception for all Lisp-related errors.""" + + pass + + +class syntaxError(error): + """Raised when there is a syntax error in the Lisp code.""" + + pass + + +class runtimeError(error): + """Raised when there is a runtime error during evaluation.""" + + pass + + +class valueError(error): + """Raised when there is an invalid value.""" + + pass + + +class typeError(error): + """Raised when there is a type error during evaluation.""" + + pass diff --git a/core/macro_expand.py b/core/macro_expand.py new file mode 100644 index 0000000..b0dc3a7 --- /dev/null +++ b/core/macro_expand.py @@ -0,0 +1,28 @@ +from typing import Any + +from core.primitives.symbol import symbolKey, isSymbol +from core.primitives.list import NIL, isList, cons, car, cdr + +def macroExpand(body: Any, bindings: dict[str, Any]) -> Any: + """Expand a macro by substituting bindings into the body. + + Args: + body: The macro body to expand. + bindings: Dictionary of symbol-to-value bindings. + + Returns: + The expanded body. + """ + if isSymbol(body): + if symbolKey(body) in bindings: + return bindings[symbolKey(body)] # auto quote + return body + + if isList(body): + if body == NIL: + return NIL + return cons( + macroExpand(car(body), bindings), macroExpand(cdr(body), bindings) + ) + + return body \ No newline at end of file diff --git a/core/parse_evaluate.py b/core/parse_evaluate.py new file mode 100644 index 0000000..ae3d648 --- /dev/null +++ b/core/parse_evaluate.py @@ -0,0 +1,29 @@ +from core.parser.parse_expression import parseExpression +from core.evaluator.naively_evaluate import naivelyEvaluate +from core.exceptions import error + +from core.environment import ENV + +from traceback import print_exception + +def parseEvaluate(source: str, *, load: bool = False) -> str: + results: list[str] = [] + s = source + while s: + try: + expr, s = parseExpression(s) + value = naivelyEvaluate(expr, ENV, True) + results.append(repr(value)) + except error as e: + # Unwrap nested causes so that the original error message is shown + cause = e + while cause.__cause__ and isinstance(cause.__cause__, error): + cause = cause.__cause__ + print(cause) + break + except KeyboardInterrupt: + return "interrupted\n" + except Exception as exc: + print_exception(exc) + break + return ' '.join(results) \ No newline at end of file diff --git a/core/parser/__init__.py b/core/parser/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/parser/clean_string.py b/core/parser/clean_string.py new file mode 100644 index 0000000..fbb0016 --- /dev/null +++ b/core/parser/clean_string.py @@ -0,0 +1,10 @@ +from core.environment import IGNORE + +def cleanString(s: str, ignore: str = IGNORE) -> str: + s = s.lstrip(ignore) + while s and s[0] == ';': + line_end: int = s.find('\n') + if line_end < 0: + return '' + s = s[line_end:].lstrip(ignore) + return s \ No newline at end of file diff --git a/core/parser/find_prefix.py b/core/parser/find_prefix.py new file mode 100644 index 0000000..0f5de3e --- /dev/null +++ b/core/parser/find_prefix.py @@ -0,0 +1,9 @@ +from core.environment import IGNORE, MEANINGFUL +import re + +def findPrefix(s: str, separators: str = IGNORE + MEANINGFUL) -> str: + pattern = rf'^[^{separators}]*' + match = re.match(pattern, s) + if match: + return match.group(0) + return '' \ No newline at end of file diff --git a/core/parser/parse_expression.py b/core/parser/parse_expression.py new file mode 100644 index 0000000..cbf8b43 --- /dev/null +++ b/core/parser/parse_expression.py @@ -0,0 +1,52 @@ +from core.parser.clean_string import cleanString +from core.parser.parse_token import parseToken +from core.parser.find_prefix import findPrefix + +from core.primitives.list import ( + cons, + NIL +) +from core.exceptions import ( + syntaxError, + runtimeError +) + +from core.tokens import SpecialForm + +def parseExpression(s: str) -> tuple: + s = cleanString(s) + if not s: + return NIL, s + match s[0]: + case '"': # string + matching = s.find('"', 1) + if matching < 0: + raise syntaxError(f'the closing " is missing: {s}') + return s[1:matching], s[matching + 1:] + case '(': # list + cache = s # saving initial view for Error illustration + s = s[1:] + arr: list = [] + while s and s[0] != ')': + elem, s = parseExpression(s) + arr.append(elem) + s = cleanString(s) + if not s: + raise syntaxError(f'the closing ) is missing: {cache}') + result = NIL + for elem in reversed(arr): + result = cons(elem, result) + return result, s[1:] + case ')': + raise syntaxError(f'extra closing bracket: {s}') # TODO debug + case "'": + try: + v, rem = parseExpression(s[1:]) + except Exception as e: + raise runtimeError(f'invalid quote: {s}') from e + if v is None: + raise syntaxError(f'quote of nothing: {s}') + return cons(SpecialForm.QUOTE, cons(v, NIL)), rem + case _: # maybe we can find token + first = findPrefix(s) + return parseToken(first) if first else NIL, s[len(first):] \ No newline at end of file diff --git a/core/parser/parse_token.py b/core/parser/parse_token.py new file mode 100644 index 0000000..e907bb9 --- /dev/null +++ b/core/parser/parse_token.py @@ -0,0 +1,22 @@ +from core.tokens import ( + SpecialForm, + BinaryOperator, + BinaryPredicate +) +from core.primitives.symbol import symbol + +def parseToken(token: str): + for en in [SpecialForm, BinaryOperator, BinaryPredicate]: + if token in en: + return en(token) + if token in ['true', 'false']: + return token == 'true' + try: + return int(token) + except ValueError: + if token == 'inf': + return symbol(token) + try: + return float(token) + except ValueError: + return symbol(token) \ No newline at end of file diff --git a/core/primitives/__init__.py b/core/primitives/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/primitives/list.py b/core/primitives/list.py new file mode 100644 index 0000000..2a79ccc --- /dev/null +++ b/core/primitives/list.py @@ -0,0 +1,26 @@ +from typing import Any + +from core.exceptions import syntaxError +# from core.printer import Representation + +class LinkedList: + value = None + nxt = None + + def __init__(self, value_, nxt_): + self.value = value_ + self.nxt = nxt_ + +def car(lst): + return lst.value + +def cdr(lst): + return lst.nxt + +def cons(val, lst): + return LinkedList(val, lst) + +def isList(val): + return isinstance(val, LinkedList) + +NIL = cons(None, None) diff --git a/core/primitives/symbol.py b/core/primitives/symbol.py new file mode 100644 index 0000000..13c1839 --- /dev/null +++ b/core/primitives/symbol.py @@ -0,0 +1,25 @@ +from functools import lru_cache + +class Symbol: + _name: str + + def __init__(self, name): + self._name = name + + @property + def name(self): + return self._name + +@lru_cache(maxsize=None) +def symbol(name: str) -> Symbol: + return Symbol(name) + +def symbolName(sym: Symbol) -> str: + # return sym.name[1:] + return sym.name + +def symbolKey(sym: Symbol) -> str: + return sym.name + +def isSymbol(obj) -> bool: + return isinstance(obj, Symbol) diff --git a/core/printer.py b/core/printer.py new file mode 100644 index 0000000..0d33fac --- /dev/null +++ b/core/printer.py @@ -0,0 +1,55 @@ +from typing import Any + +from core.primitives.list import cons, car, cdr, isList +from core.primitives.symbol import isSymbol, symbolName +from core.tokens import BaseToken, Lambda, Dambda, Macro, SpecialForm +from core.primitives.list import NIL + + +def Representation(obj: Any) -> str: + """Return a showable string representation of an object in Lisp notation. + + Args: + obj: The object to represent. + + Returns: + A string that could be parsed back by the Lisp parser. + """ + if obj is None: + obj = NIL + + if isList(obj): + result_str: str = "" + is_first: bool = True + current_item: Any = obj + while current_item != NIL: + if is_first: + is_first = False + else: + result_str += " " + result_str += Representation(car(current_item)) + current_item = cdr(current_item) + return f"({result_str})" + + elif isSymbol(obj): + return symbolName(obj) # type: ignore + + elif isinstance(obj, str): + return f'"{obj}"' + + elif isinstance(obj, BaseToken): + return obj.value + + elif isinstance(obj, bool): + return "true" if obj else "false" + + elif isinstance(obj, Lambda): + return f"({Representation(SpecialForm.LAMBDA)} {Representation(obj.args)} {Representation(obj.body)})" + + elif isinstance(obj, Dambda): + return f"({Representation(SpecialForm.DAMBDA)} {Representation(obj.args)} {Representation(obj.body)})" + + elif isinstance(obj, Macro): + return f"({Representation(SpecialForm.MACRO)} {Representation(obj.args)} {Representation(obj.body)})" + + return str(obj) diff --git a/core/string_utils.py b/core/string_utils.py new file mode 100644 index 0000000..3fe3506 --- /dev/null +++ b/core/string_utils.py @@ -0,0 +1,38 @@ +from core.environment import IGNORE, MEANINGFUL +import re + +def processString(source: str) -> str: + """Remove blank characters and comments from source code. + + Args: + source: The source code string. + + Returns: + The cleaned source code. + """ + source = source.lstrip(IGNORE) + + while source and source[0] == ";": + line_end: int = source.find("\n") + + if line_end < 0: + return "" + + source = source[line_end:].lstrip(IGNORE) + + return source + + +def findPrefix(source: str, separators: str = IGNORE + MEANINGFUL) -> str: + """Find a meaningful prefix (token) in the source string. + + Args: + source: The source code string. + separators: Characters that mark token boundaries. + + Returns: + The prefix token found, or empty string if none. + """ + pattern: str = rf"^[^{separators}]*" + match = re.match(pattern, source) + return match.group(0) if match else "" \ No newline at end of file diff --git a/core/tail_recursion.py b/core/tail_recursion.py new file mode 100644 index 0000000..2fdfd90 --- /dev/null +++ b/core/tail_recursion.py @@ -0,0 +1,23 @@ +from typing import Callable, Any + +class tailRecursion: + """Tail-call optimization wrapper.""" + + f: Callable[..., Any] + args: tuple[Any, ...] + kwargs: dict[str, Any] + + def __init__(self, func: Callable[..., Any], *args: Any, **kwargs: Any) -> None: + self.f: Callable[..., Any] = func + self.args: tuple[Any, ...] = args + self.kwargs: dict[str, Any] = kwargs + + def __call__(self) -> Any: + return self.f(*self.args, **self.kwargs) + + @staticmethod + def eval(val: Any) -> Any: + """Evaluate tail calls recursively until completion.""" + while isinstance(val, tailRecursion): + val = val() + return val \ No newline at end of file diff --git a/core/tokens.py b/core/tokens.py new file mode 100644 index 0000000..04e65c3 --- /dev/null +++ b/core/tokens.py @@ -0,0 +1,87 @@ +from __future__ import annotations +from enum import Enum +from typing import Any, TYPE_CHECKING + +# from core.environment import Environment + +if TYPE_CHECKING: + from core.environment import Environment + +class Lambda: + """Lambda function with arguments, body, and closure environment""" + + args: Any + body: Any + env: Environment + + def __init__(self, args: Any, body: Any, env: Environment) -> None: + self.args: Any = args + self.body: Any = body + self.env: Environment = env + +class Dambda: + """Dambda function""" + + args: Any + body: Any + + def __init__(self, args: Any, body: Any) -> None: + self.args: Any = args + self.body: Any = body + +class Macro: + """Macro definition""" + + args: Any + body: Any + + def __init__(self, args: Any, body: Any) -> None: + self.args: Any = args + self.body: Any = body + +class Pure(): + """Pure definition""" + + args: Any + body: Any + + def __init__(self, args: Any, body: Any) -> None: + self.args: Any = args + self.body: Any = body + +class BaseToken(Enum): + """Base class for enumerations""" + +class SpecialForm(BaseToken): + EVAL = "eval" + IF = "if" + DO = "do" + PRINT = "print" + READ = "read" + SYMBOL = "symbol" + DEF = "def" + SET = "set!" + FLUSH = "flush" + PURE = "pure" + LAMBDA = "lambda" + DAMBDA = "dambda" + MACRO = "macro" + RAISE = "raise" + QUOTE = "quote" + CONS = "cons" + CAR = "car" + CDR = "cdr" + TYPEOF = "typeof" + +class BinaryOperator(BaseToken): + ADD = "_+_" + SUB = "_-_" + MUL = "_*_" + DIV = "_/_" + MOD = "_%_" + STRCONCAT = "_++_" + +class BinaryPredicate(BaseToken): + LT = "_<_" + GT = "_>_" + EQ = "_=_" diff --git a/examples/TR.py b/examples/TR.py new file mode 100644 index 0000000..e61a556 --- /dev/null +++ b/examples/TR.py @@ -0,0 +1,27 @@ +from typing import Callable + +class TR: + def __init__(self, f, *args, **kwargs): + self.f = f + self.args = args + self.kwargs = kwargs + def __call__(self): + return self.f(*self.args, **self.kwargs) + +def evalTR(value): + while isinstance(value, TR): + value = value() + return value + +def suman(n, acc): + return acc if n <= 0 else TR(suman, n - 1, acc + n) + +def isOdd(n): + return False if n == 0 else TR(isEven, n - 1) + +def isEven(n): + return True if n == 0 else TR(isOdd, n - 1) + +while n := int(input()): + print(evalTR(suman(n, 0))) + # print(evalTR(isEven(n)), evalTR(isOdd(n))) \ No newline at end of file diff --git a/examples/class.lsp b/examples/class.lsp new file mode 100644 index 0000000..033868c --- /dev/null +++ b/examples/class.lsp @@ -0,0 +1,20 @@ +; (def endl " +; ") + +(def acc-class + (lambda () + (do + (def get '(lambda () val)) + (def add + '(lambda (delta) + (do + (set! val (+ val delta)) + val))) + (def sub + '(lambda (delta) + (do + (def new (- val delta)) + (if (< new 0) (print (++ "аяяй" endl)) (set val new)) + val))) + (def acc-constructor (lambda (val) (lambda (method) (eval (eval method))))) + acc-constructor))) diff --git a/examples/factorial.lsp b/examples/factorial.lsp new file mode 100644 index 0000000..2dec039 --- /dev/null +++ b/examples/factorial.lsp @@ -0,0 +1,32 @@ +(def comment (macro (some) '())) + +(comment + (print "input a number") + (def n (read)) + (def res 1) + ; (print (_+_ n 1)) + (def f '(do + (set! res (_*_ res n)) + (set! n (_-_ n 1)) + (if (_<_ 0 n) (eval f) 0) + )) + (eval f) + (print res) + + (def t (macro (x) (do (def z 42) (print x) x))) + (def m (dambda (a) (do (print z) (_+_ a a) (print z)))) + (print (m (t 3))) + ) + + +(def dact (dambda (n self) ( + if (_<_ n 2) 1 + (_*_ n (self (_-_ n 1) self)) + ))) + +(def lact (lambda (n self) ( + if (_<_ n 2) 1 + (_*_ n (lact (_-_ n 1) self)) + ))) + +(def foo (lambda (f, n) (do (def lact 43) (def dact 42) (f n f)))) diff --git a/examples/factorial_dambda.lsp b/examples/factorial_dambda.lsp new file mode 100644 index 0000000..f6ecaa1 --- /dev/null +++ b/examples/factorial_dambda.lsp @@ -0,0 +1,5 @@ +(def fact (dambda (n) ( + if (_<_ n 2) 1 + (_*_ n (fact (_-_ n 1))) + ))) + diff --git a/examples/factorial_macro.lsp b/examples/factorial_macro.lsp new file mode 100644 index 0000000..a58e413 --- /dev/null +++ b/examples/factorial_macro.lsp @@ -0,0 +1,9 @@ +(defmacro fact (n) + ; (prints 'n \n) + (if (< n 3) n + (* n (fact (- n 1))))) + +(defmacro facc (n acc) + ; (prints 'acc \n) + (if (< n 2) acc + (facc (- n 1) (* n acc)))) diff --git a/examples/factorialambda.lsp b/examples/factorialambda.lsp new file mode 100644 index 0000000..50d2887 --- /dev/null +++ b/examples/factorialambda.lsp @@ -0,0 +1,6 @@ +(def fact (lambda (n) ( + if (_<_ n 2) 1 + (_*_ n (fact (_-_ n 1))) + ))) +; (def foo fact) +; (set! fact (lambda (n) (_+_ n 1))) diff --git a/examples/horsey.clj b/examples/horsey.clj new file mode 100644 index 0000000..f30d10d --- /dev/null +++ b/examples/horsey.clj @@ -0,0 +1,116 @@ +; path: list of points ((1 3) (3 5) (5 7)) +; ^ +; stack - список ещё не проверенных направлений +; + +; (((1 2) (1 -2) (-1 2) (-1 -2)) ((1 -2) (-1 2) (-1 -2)) ((-1 2) (-1 -2))) +; (( ... ) ((1 -2) (-1 2) (-1 -2)) ((1 -2) (-1 2) (-1 -2)) ((-1 2) (-1 -2))) +; (() ((1 -2) (-1 2) (-1 -2)) ((-1 2) (-1 -2))) +; ((2 5) (1 3) (3 5) (5 7)) + +;; Сделать так, чтобы код работал для доски 8x8 +;; Необходимо сделать Tail Call Optimization + +(def m 6) +(def n 6) +(def nm (* n m)) +(def start-position '(1 1)) + +;; + +;; (defn check-step? (step path) ; '(to from) +;; (cond (or (nil? path) (nil? (cdr path))) false +;; (= (take 2 path) step) true +;; (check-step? step (cdr path)))) + +;; (a b c d e a b f g) + +(defn in-borders? (p) (and (<= 1 (car p) n) (<= 1 (car (cdr p)) m))) + +;; (defn duplicated-step? (path) (check-step? (take 2 path) (cdr path))) + +;; (def all-dirs '((2 1) (2 -1) (-2 1) (-2 -1) (1 2) (1 -2) (-1 2) (-1 -2))) +;; (def all-dirs '((1 2) (2 1) (2 -1) (1 -2) (-1 -2) (-2 -1) (-2 1) (-1 2))) +(def all-dirs '((2 1) (1 2) (-1 2) (-2 1) (-2 -1) (-1 -2) (1 -2) (2 -1))) +;; @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + +(def all-cells + (concat (map (lambda (r) + (map (lambda (c) + (list r c)) + (range 1 (+ 1 m)))) + (range 1 (+ 1 n))))) + + +(def all-moves (concat (map (lambda (cell) + (filter (lambda (step) (in-borders? (car step))) + (map (lambda (delta-move) (list (zip-with + cell delta-move) cell)) + all-dirs))) + all-cells))) + + +(defn steps-set-gen () + (eval (cons do (map (lambda (step) (list def (symbol (++ step)) false)) all-moves))) + (defn add (x) (eval (list set! (symbol (++ x)) true))) + (defn has? (x) (eval (symbol (++ x)))) + (defn remove (x) (eval (list set! (symbol (++ x)) false))) + (lambda (cmd) (eval cmd)) +) + +(def steps-set (steps-set-gen)) + +(defn step-was? (step) ((steps-set 'has?) step)) + +;; @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + +(defmacro -= (x v) (set! x (- x v))) +(defmacro += (x v) (set! x (+ x v))) + +(defn cells-multiset-gen () + (eval (cons do (map (lambda (cell) (list def (symbol (++ cell)) 0)) all-cells))) + (def distinct 0) + (defn add (x) (do + (def name (symbol (++ x))) + (when (= (eval name) 0) (+= distinct 1)) + (eval (list += name 1)))) + (defn remove (x) (do + (def name (symbol (++ x))) + (when (= (eval name) 1) (-= distinct 1)) + (eval (list -= name 1)))) +;; (defn distinct () xxx) + (lambda (cmd) (eval cmd)) +) + +(def cells-multiset (cells-multiset-gen)) + +(defn path-isgood? (path) (and (= (car path) start-position) (= (cells-multiset 'distinct) nm))) + +;; @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + +(defn dfs-tail (path stack) + ;; (print path) + (if + ; success + (path-isgood? path) path (if + ; если мы выше начала - нет решения + (nil? path) false (if + ; если некуда ходить из текущей позиции + (nil? (car stack)) (do + (if (not (nil? (cdr path))) ((steps-set 'remove) (take 2 path)) nil) + ((cells-multiset 'remove) (car path)) + (dfs-tail (cdr path) (cdr stack))) + ; иначе нам есть что делать + (do (def next-pos (zip-with + (car path) (car (car stack)))) + (def step (list next-pos (car path))) + (def new-stack (cons (cdr (car stack)) (cdr stack))) + (if + ; если мы резко оказались вне доски + (not (in-borders? next-pos)) (dfs-tail path new-stack) (if + ; если этот ход мы уже делали + (step-was? step) (dfs-tail path new-stack) + (do + ((steps-set 'add) step) + ((cells-multiset 'add) next-pos) + (dfs-tail (cons next-pos path) (cons all-dirs new-stack)))))))))) + +(print (dfs-tail (list start-position) (list all-dirs))) diff --git a/examples/knight.clj b/examples/knight.clj new file mode 100644 index 0000000..a537a5e --- /dev/null +++ b/examples/knight.clj @@ -0,0 +1,79 @@ +(def n 5) +(def m 4) +(def start-position '(1 1)) + +;; path - '((1 1) (4 2) (0 0)) + +(def knight-moves '((2 1) (2 -1) (-2 1) (-2 -1) (1 2) (1 -2) (-1 2) (-1 -2))) + +(defn check-pos? (p) (and (<= 1 (car p) n) (<= 1 (car (cdr p)) m))) + + +(def all-cells + (concat (map (lambda (r) + (map (lambda (c) + (list r c)) + (range 1 (+ 1 m)))) + (range 1 (+ 1 n))))) + + +(def all-moves (concat (map (lambda (cell) + (filter (lambda (step) (check-pos? (car step))) + (map (lambda (delta-move) (list (zip-with + cell delta-move) cell)) + knight-moves))) + all-cells))) + + +(defn steps-set-gen () + (eval (cons do (map (lambda (step) (list def (symbol (++ step)) false)) all-moves))) + (defn add (x) (eval (list set! (symbol (++ x)) true))) + (defn has? (x) (eval (symbol (++ x)))) + (defn remove (x) (eval (list set! (symbol (++ x)) false))) + (lambda (cmd) (eval cmd)) +) + +(def steps-set (steps-set-gen)) + +(defn check-step? (step path) (not ((steps-set 'has?) step))) + +(defmacro -= (x v) (set! x (- x v))) +(defmacro += (x v) (set! x (+ x v))) + +(defn cells-multiset-gen () + (eval (cons do (map (lambda (cell) (list def (symbol (++ cell)) 0)) all-cells))) + (def distinct 0) + (defn add (x) (do + (def name (symbol (++ x))) + (when (= (eval name) 0) (+= distinct 1)) + (eval (list += name 1)))) + (defn remove (x) (do + (def name (symbol (++ x))) + (when (= (eval name) 1) (-= distinct 1)) + (eval (list -= name 1)))) +;; (defn distinct () xxx) + (lambda (cmd) (eval cmd)) +) + +(def cells-multiset (cells-multiset-gen)) + +(defn dfs (path) +;; (def current-position (car path)) +;; (print path) + (if (and (= (car path) start-position) (= (cells-multiset 'distinct) (* n m))) + path + (reduce (lambda (acc, x) + (if (!= acc false) acc + (do + (def new-pos (zip-with + x (car path))) + (def step (list new-pos (car path))) + (if (and (check-pos? new-pos) (check-step? step path)) + (do ((steps-set 'add) step) + ((cells-multiset 'add) new-pos) + (def res (dfs (cons new-pos path))) + ((steps-set 'remove) step) + ((cells-multiset 'remove) new-pos) + res + ) + false)))) + false knight-moves)) +) diff --git a/examples/oddeven.lsp b/examples/oddeven.lsp new file mode 100644 index 0000000..6802381 --- /dev/null +++ b/examples/oddeven.lsp @@ -0,0 +1,4 @@ +(defn maybeprint (n) (when (= (% n 50) 0) (do (prints n " ") (flush)))) +(defn odd? (n) (if (= n 0) false (do (maybeprint n) (even? (- n 1))))) +(defn even? (n) (if (= n 0) true (do (maybeprint n) (odd? (- n 1))))) +; (print (odd? (read))) \ No newline at end of file diff --git a/examples/oop.lsp b/examples/oop.lsp new file mode 100644 index 0000000..9869d18 --- /dev/null +++ b/examples/oop.lsp @@ -0,0 +1,50 @@ +"(def \n " +") +(def acc-class (lambda () + (do + (def get (dambda () val)) + (def add (dambda (delta) + (do + (set! val (_+_ val delta)) + val + ))) + (def sub (dambda (delta) + (do + (def new (_-_ val delta)) + (if (_<_ new 0) (print (_++_ "аяяй" \n)) (set! val new)) + val + ))) + (def acc-constructor (lambda (val) (lambda (method-name) (eval (eval method-name) )))) + acc-constructor +)))" + +(def nil '()) + +(def acc-class (lambda (initial-val) + (do + (def val initial-val) + (lambda (all-args) + (do + (def method (car all-args)) + (def args (cdr all-args)) + + (def get (dambda () val)) + (def add (dambda (delta) + (do + (set! val (_+_ val delta)) + val + ))) + (def sub (dambda (delta) + (do + (def new (_-_ val delta)) + (if (_<_ new 0) (print (_++_ "аяяй" "\n")) (set! val new)) + val + ))) + + (if (_==_ method (symbol "get")) + (get) + (if (_==_ method (symbol "add")) + (add (car args)) + (if (_==_ method (symbol "sub")) + (sub (car args)) + nil)))))))) diff --git a/examples/test.lsp b/examples/test.lsp new file mode 100644 index 0000000..4ee64d9 --- /dev/null +++ b/examples/test.lsp @@ -0,0 +1,2 @@ +(defun selfactorial (self, n) (* n (if (< n 3) 1 (f f (- n 1))))) +(defun factorial (n) (selfactorial selfactorial n)) diff --git a/main.py b/main.py new file mode 100644 index 0000000..b6700f9 --- /dev/null +++ b/main.py @@ -0,0 +1,59 @@ +import os +import sys + +from core.parse_evaluate import parseEvaluate + +def repl() -> None: + stdlib_path = 'stdlib.lsp' + if os.path.isfile(stdlib_path): + with open(stdlib_path, 'r', encoding='utf‑8') as f: + parseEvaluate(f.read(), load=True) + else: + print('>> Warning: standard library not found.') + + if not sys.stdin.isatty(): + parseEvaluate(sys.stdin.read(), load=True) + print('\ninput exhausted, exiting REPL...') + return + + prompt = '>>> ' + exit_cmds = {':q', ':quit', ':exit', 'exit', 'quit'} + load_cmds = {':l', ':load'} + + while True: + try: + line = input(prompt).strip() + except (KeyboardInterrupt, EOFError): + print("\nexiting REPL...") + break + + if not line: + continue + + parts = line.split(None, 1) + if not parts: + continue + + token = parts[0] + + if token in exit_cmds: + print("\nexiting REPL...") + break + + if token in load_cmds: + filename = parts[1].strip() if len(parts) > 1 else '' + if not filename: + print(prompt + "Error: no filename provided.") + continue + if not os.path.isfile(filename): + print(prompt + f'Error: no such file "{filename}"') + continue + with open(filename, 'r', encoding='utf-8') as f: + parseEvaluate(f.read(), load=True) + print() + continue + + print(parseEvaluate(line)) + +if __name__ == '__main__': + repl() diff --git a/stdlib.lsp b/stdlib.lsp new file mode 100644 index 0000000..d3ac584 --- /dev/null +++ b/stdlib.lsp @@ -0,0 +1,216 @@ +;;;; stdlib + +;;; useful stuff +(def \n " +") +(def nil '()) +(def fn lambda) +(def nil? (macro (l) (_=_ l nil))) +(def list (lambda (. args) args)) + +;;; easier functions +(def defmacro-defun-codegen + (lambda (head name args body) + (list def name + (list head args + (if (nil? body) nil + (if (nil? (cdr body)) (car body) + (cons do body))))))) +(def defmacro (macro (name args . body) (eval (defmacro-defun-codegen macro 'name 'args 'body)))) +(defmacro defun (name args . body) (eval (defmacro-defun-codegen lambda 'name 'args 'body))) +(def defn defun) + +;;; useful functions +(defmacro comment (. a) nil) + +(defun reduce (f a l) (if (nil? l) a (reduce f (f a (car l)) (cdr l)))) +(defun foldr (f a l) (if (nil? l) a (f (foldr f a (cdr l)) (car l)))) + +;;; boolean functions +(defun not (boo) (if boo false true)) +(defun or-codegen (args) (if (nil? args) false (list if (car args) true (or-codegen (cdr args))))) +(defun and-codegen (args) (if (nil? args) true (list if (car args) (and-codegen (cdr args)) false))) +(defmacro or (. args) (eval ( or-codegen 'args))) +(defmacro and (. args) (eval (and-codegen 'args))) ; (and 1 2 3) = (if 1 (if 2 3 false) false) + +(defun cond-codegen (args) + (if (nil? args) nil ; (raise "wrong arity of cond") + (if (nil? (cdr args)) (car args) + (list if (car args) (car (cdr args)) (cond-codegen (cdr (cdr args))))))) +(defmacro cond (. args) (eval (cond-codegen 'args))) ; (cond 1 2 3 4 5) = (if 1 2 (if 3 4 5)) + +;;; basic math operations +; (defun + (. args) (if (nil? args) 0 (reduce _+_ (car args) (cdr args)))) ; ability to interop + +(defun + (. args) (reduce _+_ 0 args)) ; (+ 1 2 3 4) = (_+_ 0 (_+_ 1 (_+_ 2 (_+_ 3 4)))) +(defun * (. args) (reduce _*_ 1 args)) +(defun ++ (. args) (reduce _++_ "" args)) + +(defun - (. args) + (cond (nil? args) (raise "zero args for -") + (nil? (cdr args)) (_-_ 0 (car args)) + (reduce _-_ (car args) (cdr args)))) +(defun / (. args) + (cond (nil? args) (raise "zero args for /") + (nil? (cdr args)) (_/_ 1 (car args)) + (reduce _/_ (car args) (cdr args)))) +(defun % (. args) + (cond (nil? args) (raise "zero args for %") + (nil? (cdr args)) (raise "one arg for %") + (reduce _%_ (car args) (cdr args)))) + +;;; basic binary predicates +(defun bp-core (bp a l) (if (nil? l) true (if (bp a (car l)) (bp-core bp (car l) (cdr l)) false ))) +(defun bp-pre-core (bp args) (if (nil? args) true (bp-core bp (car args) (cdr args)))) + +(defun < (. args) (bp-pre-core _<_ args)) +(defun > (. args) (bp-pre-core _>_ args)) + +(defun _>=_ (a b) (not (_<_ a b))) +(defun _<=_ (a b) (not (_>_ a b))) +(defun <= (. args) (bp-pre-core _<=_ args)) +(defun >= (. args) (bp-pre-core _>=_ args)) + +(defun three-op-eq (a b c) (if (_=_ a b) (_=_ b c) false)) +(defun bineq (a b) + (def type-a (typeof a)) + (def type-b (typeof b)) + (cond (three-op-eq type-a type-b "Symbol") (_=_ (++ a) (++ b)) + (three-op-eq type-a type-b "List") (_=_ (++ a) (++ b)) + (_=_ a b))) +(defun = (. args) (bp-pre-core bineq args)) +(defun != (. args) (not (bp-pre-core bineq args))) + +;;; list tools +(defun map (func lst) + (if (nil? lst) nil + (cons (func (car lst)) (map func (cdr lst))))) + +(defun reverse-core (lst acc) (if (nil? lst) acc (reverse-core (cdr lst) (cons (car lst) acc)))) +(defun reverse (lst) (reverse-core lst nil)) + +(defun map-reverse-core (func lst acc) + (if (nil? lst) acc + (map-reverse-core func (cdr lst) (cons (func (car lst)) acc)))) +(defun map-reverse (func lst) (map-reverse-core func lst nil)) +(defun map-tail (func lst) (reverse (map-reverse func lst))) + +(defun filter (pred lst) + (cond (nil? lst) nil + (pred (car lst)) (cons (car lst) (filter pred (cdr lst))) + (filter pred (cdr lst)))) + +(defun take (n lst) + (cond (< n 1) nil + (nil? lst) nil + (cons (car lst) (take (- n 1) (cdr lst))))) + +(defun drop (n lst) + (cond (< n 1) lst + (nil? lst) nil + (drop (- n 1) (cdr lst)))) + +(defun append-reverse (a b) + (if (nil? a) b (append-reverse (cdr a) (cons (car a) b)))) +(defun append (a b) (append-reverse (reverse a) b)) + +(defun concat (lsts) (reduce append nil lsts)) + +(defun length (lst) (reduce (lambda (a b) (+ a 1)) 0 lst)) + +(defun all? (pred lst) + (cond (nil? lst) true + (pred (car lst)) (all? pred (cdr lst)) + false)) + +(defun any? (pred lst) + (cond (nil? lst) false + (pred (car lst)) true + (any? pred (cdr lst)))) + +(defun indexof-from (elem lst idx) + (cond (nil? lst) -1 + (= elem (car lst)) idx + (indexof-from elem (cdr lst) (+ 1 idx)))) +(defun indexof (elem lst) (indexof-from elem lst 0)) + +(defun range (a b) ; [a b) + (if (< a b) (cons a (range (+ 1 a) b)) nil)) + +(defun range-tail-core (a b acc) + (if (< a b) (range-tail-core a (- b 1) (cons (- b 1) acc)) acc)) +(defun range-tail (a b) (range-tail-core a b nil)) + +(defun list-ref (n lst) + (def tmp (drop n lst)) + (if (nil? tmp) (raise (++ "index out of bounds: " n " of " lst)) + (car tmp))) + +(defun zip-with (f a b) + (if (or (nil? a) (nil? b)) nil + (cons (f (car a) (car b)) (zip-with f (cdr a) (cdr b))))) + +(defun flatten (lsts) + (cond (!= (typeof lsts) "List") (list lsts) + (nil? lsts) nil + (append (flatten (car lsts)) (flatten (cdr lsts))))) + +(defun distinct (lst) + (if (nil? lst) nil + (cons (car lst) (distinct (filter (lambda (v) (!= v (car lst))) (cdr lst)))))) + + +; (defn map-n (f . l) +; (cond (nil? l) nil +; (any? nil? l) nil +; (cons (eval (cons f (map car 'l))) (eval (cons map-n (cons f (map cdr 'l))))))) + + +(defmacro apply-1 (f args) + (eval (cons f args))) + +(defn map-n-core (f l) + (if (or (nil? l) (any? nil? l)) + nil + (cons (apply-1 f (map car l)) + (map-n-core f (map cdr l))))) + +(defn map-n (f . l) + (map-n-core f l)) + + +(defn cond-append (u v) (if (or (= false u) (= false v)) false (append u v))) ;; append-revers + +(defn match-codegen (p v) ;; check-pattern + (def type-p (typeof p)) + (cond (= type-p "Symbol") (if (= p '_) nil (list (list def p (list quote v)))) ;; TODO quote only lists + (= type-p "List") + (cond + (!= (typeof v) "List") false + (nil? p) (if (nil? v) nil false) + (= (car p) '.) (match-codegen (car (cdr p)) v) + (nil? v) false + (cond-append (match-codegen (car p) (car v)) + (match-codegen (cdr p) (cdr v)))) + (= p v) nil + false)) + +(defmacro when (c . body) (if c (eval (cons do 'body)) nil)) + +(defn check-pattern? (p) + (def syms (filter (lambda (e) (and (= (typeof e) "Symbol") (!= e '_) (!= e '.))) (flatten p))) + (= syms (distinct syms) syms)) + +(defn match-wrap (p v) + ; (when (not (check-pattern? p)) (raise (++ "bad pattern: " p))) + (def code (match-codegen p v)) + (if (= code false) false + (cons do (append code '(true))))) + +(defmacro match (p v) (eval (match-wrap 'p v))) + + +(defun prints (. args) (reduce (lambda (nothing arg) (print arg)) nil args)) + + + +(prints "stdlib loaded" \n) \ No newline at end of file