Initial commit - fix problems & refactor code
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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 ''
|
||||
@@ -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):]
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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 ""
|
||||
@@ -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
|
||||
@@ -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 = "_=_"
|
||||
Reference in New Issue
Block a user