def let(se: SExpression, stg: LexicalVarStorage) -> SExpression: """ The ``let`` macro binds variables to a local scope: the expressions inside the macro. Like a function, a ``let`` returns the last expression in its body. Once the ``let`` returns, the variables are unbound, and cannot be accessed anymore. For example:: (let ((a (+ f g)) (b 11) (c 12)) (+ a (- b c))) In the example above, ``a`` was bound to whatever ``(+ f g)`` evaluates to, b to ``11``, and ``c`` to ``12``. Notice how the above was equivalent to another expression:: ((lambda (a b c) (+ a (- b c))) (+ f g) 11 12) You should do a syntax translation from the input to something like that. >>> from slyther.types import * >>> from slyther.parser import lisp >>> from slyther.evaluator import lisp_eval >>> stg = LexicalVarStorage( ... {'let': Variable(let), ... 'lambda': Variable(lambda_func), ... 'print': Variable(print_), ... 'x': Variable(10)}) >>> lisp_eval(lisp('(let ((x 20) (y 30)) (print x) (print y))'), stg) 20 30 NIL >>> lisp_eval(Symbol('x'), stg) 10 """ new_environ = {**stg.environ} new_local = {**stg.local} new_lex_var = LexicalVarStorage(new_environ) new_lex_var.local = new_local for item in se.car: new_lex_var.put(item.car, lisp_eval(item.cdr.car, stg)) return_val = NIL for item in se.cdr: return_val = lisp_eval(item, new_lex_var) return return_val
def setbang(se: SExpression, stg: LexicalVarStorage): """ ``set!`` is the assigment macro. Unlike ``define``, ``set!`` **does not create new variables.** Instead, it changes the value of existing variables **only**. If given a variable which has not been defined, it should raise a ``KeyError``. For example:: (set! <name> <value>) Should just eval ``value`` and call ``stg[name].set`` on it. Return ``NIL``. >>> from slyther.types import * >>> from slyther.parser import lisp >>> stg = LexicalVarStorage({'foo': Variable(12), 'bar': Variable(15)}) >>> old_foo = stg['foo'] >>> setbang(lisp('(foo 15)'), stg) NIL >>> stg['foo'].value 15 >>> stg['foo'] is old_foo True >>> setbang(lisp('(baz 15)'), stg) Traceback (most recent call last): ... KeyError: 'Undefined variable baz' """ try: stg[se.car].set(lisp_eval(se.cdr.car, stg)) return NIL except KeyError as ex: raise KeyError("Undefined variable {}".format(str(se.car))) from ex
def if_expr(se: SExpression, stg: LexicalVarStorage): """ An ``if`` expression looks like this:: (if <predicate> <consequent> <alternative>) If the predicate evaluates to something truthy, return the consequent, otherwise return the alternative. An example:: (if (< x 10) (print "x is less than 10") (print "x is greater than or equal to 10")) >>> from slyther.types import * >>> from slyther.parser import lisp >>> se = lisp('((< x 10)' ... ' (print "x is less than 10")' ... ' (print "x is greater than or equal to 10"))') >>> stg = LexicalVarStorage({}) >>> stg.put('<', lt) >>> stg.put('x', 9) >>> if_expr(se, stg) (print "x is less than 10") >>> stg['x'].set(10) >>> if_expr(se, stg) (print "x is greater than or equal to 10") """ if lisp_eval(se.car, stg): return se.cdr.car else: return se.cdr.cdr.car
def __call__(self, *args): """ Call the function with arguments ``args``. Make use of ``lisp_eval``. Note that a fully working ``lisp_eval`` implementation will require this function to work properly, and this will require a working ``lisp_eval`` to work properly, so you must write both before you can test it. Warning: Do not make any attempt to modify ``environ`` here. That is not how lexical scoping works. Instead, construct a new ``LexicalVarStorage`` from the existing environ. """ # avoid circular imports from slyther.evaluator import lisp_eval storage = LexicalVarStorage(self.environ) for x, y in zip(args, self.params): storage.put(y, x) r = NIL length = len(self.body) for index, z in enumerate(self.body): if index == length - 1: return (z, storage) else: r = lisp_eval(z, storage) return r
def define(se: SExpression, stg: LexicalVarStorage): """ Define a variable or a function to a value, calling ``put`` on the storage:: ; variable (define var-name evaluate-me) ; lambda function (define func-name (lambda (args...) (body1) ... (bodyN))) ; SE-named function (define (func-name args...) (body1) ... (bodyN)) .. note:: If defining a function (either by ``lambda`` or by SE-named syntax), the definition **must** be made visible from within that function's ``environ``, or recursion will not work! >>> from slyther.types import * >>> from slyther.parser import lisp >>> se = lisp('((twirl alpha beta) (print alpha) (print beta))') >>> name = Symbol('twirl') >>> args = lisp('(alpha beta)') >>> body = lisp('((print alpha) (print beta))') >>> stg = LexicalVarStorage({}) >>> stg.put('NIL', NIL) >>> stg.put('define', define) >>> stg.put('lambda', lambda_func) >>> from slyther.evaluator import lisp_eval >>> lisp_eval(define(cons(cons(name, args), body), stg), stg) NIL >>> lisp_eval(define(lisp('(x 10)'), stg), stg) NIL >>> stg[name].value (lambda (alpha beta) (print alpha) (print beta)) >>> stg[name].value.environ['twirl'].value (lambda (alpha beta) (print alpha) (print beta)) >>> stg['x'].value 10 >>> stg[name].value.environ['x'].value Traceback (most recent call last): ... KeyError: 'x' """ key = se.car value = se.cdr if isinstance(key, SExpression): function = UserFunction(params=key.cdr, body=value, environ=stg.fork()) key = key.car function.environ[key] = Variable(function) stg.put(key, function) elif isinstance(key, Symbol): stg.put(key, lisp_eval(value.car, stg)) else: stg.put(key, value)
def eval(self, expr): """ Eval a single (parsed) lisp expression. """ try: return lisp_eval(expr, self.stg) except RecursionError as e: raise RecursionError( "Maximum recursion depth exceeded while evaluating {!r}" .format(expr)) from e
def and_(se: SExpression, stg: LexicalVarStorage): """ Compute an ``and`` expression, like this:: (and (< x 10) (> y 15) (foo? z)) Evaluate left to right, and return the first result which produces a falsy value. Note that the result need not be a boolean, but you should test its falsiness(parse(lex(''' ... (cond ... ((< x 5) (print "x < 5")) ... ((< x 10) (print "5 <= x < 10")) ... ((< x 15) (print "10 <= x < 15")) ... (#t (print "x >= 15")))'''))), and return the result (even if it's not a boolean). Note that you could return the last expression unevaluated if all the previous are truthy, as your ``lisp_eval`` should eval it for you. This could be useful for tail call optimization. >>> from slyther.types import * >>> from slyther.parser import lisp >>> from slyther.evaluator import lisp_eval >>> stg = LexicalVarStorage( ... {'and': Variable(and_), ... '#f': Variable(Boolean(False)), ... '#t': Variable(Boolean(True)), ... 'print': Variable(print_), ... '+': Variable(add), ... 'x': Variable(0), ... 'y': Variable(1), ... 'z': Variable(2)}) >>> lisp_eval(lisp('(and #t (print (+ x y z)) #f)'), stg) 3 NIL >>> lisp_eval(lisp('(and #f (print (+ x y z)) #f)'), stg) #f >>> lisp_eval(lisp('(and #t x (print (+ x y z)) y foo)'), stg) 0 >>> lisp_eval(lisp('(and (+ x y) y z)'), stg) 2 >>> lisp_eval(lisp('(and)'), stg) NIL """ res = NIL length = len(se) for index, x in enumerate(se): if index == length - 1: return x y = lisp_eval(x, stg) res = y if not y: return y return res
def and_(se: SExpression, stg: LexicalVarStorage): """ Compute an ``and`` expression, like this:: (and (< x 10) (> y 15) (foo? z)) Evaluate left to right, and return the first result which produces a falsy value. Note that the result need not be a boolean, but you should test its falsiness, and return the result (even if it's not a boolean). Note that you could return the last expression unevaluated if all the previous are truthy, as your ``lisp_eval`` should eval it for you. This could be useful for tail call optimization. >>> from slyther.types import * >>> from slyther.parser import lisp >>> from slyther.evaluator import lisp_eval >>> stg = LexicalVarStorage( ... {'and': Variable(and_), ... '#f': Variable(Boolean(False)), ... '#t': Variable(Boolean(True)), ... 'print': Variable(print_), ... '+': Variable(add), ... 'x': Variable(0), ... 'y': Variable(1), ... 'z': Variable(2)}) >>> lisp_eval(lisp('(and #t (print (+ x y z)) #f)'), stg) 3 NIL >>> lisp_eval(lisp('(and #f (print (+ x y z)) #f)'), stg) #f >>> lisp_eval(lisp('(and #t x (print (+ x y z)) y foo)'), stg) 0 >>> lisp_eval(lisp('(and (+ x y) y z)'), stg) 2 >>> lisp_eval(lisp('(and)'), stg) NIL """ ''' if(se.car is NIL): return NIL ''' return_val = NIL for item in se.cells(): if item.cdr is not NIL: return_val = lisp_eval(item.car, stg) else: return item.car if not return_val: return return_val return return_val '''
def test_function_return(): environ = stg.fork() func = UserFunction(params=lisp('(a b)'), body=lisp('''((collect 'add-result (add a b)) (add a b))'''), environ=environ.copy()) assert environ == func.environ stg.put('func', func) result = lisp_eval(lisp('(func 10 50)'), stg) # If this fails, then calling the function modified it's environ # dictionary. This should NEVER happen. assert environ == func.environ assert collected['add-result'] == 60 assert result == 60
def eval_(se: SExpression, stg: LexicalVarStorage): """ :: (eval <expr>) Evaluate ``expr``, if it produces a ``ConsList``, then upgrade that ``ConsList`` to an ``SExpression`` (recursively) and return it. Your ``lisp_eval`` will take care of actually evaluating the expression, as this is a macro. >>> from slyther.types import * >>> from slyther.parser import lisp >>> from slyther.evaluator import lisp_eval >>> stg = LexicalVarStorage( ... {'eval': Variable(eval_), ... '#t': Variable(Boolean(True)), ... 'print': Variable(print_), ... 'x': Variable(0), ... 'y': Variable(1), ... 'z': Variable(lisp("(print x)"))}) >>> lisp_eval(lisp("(eval '(print x))"), stg) 0 NIL >>> lisp_eval(lisp("(eval ''(print x))"), stg) (list print x) >>> lisp_eval(lisp('(eval #t)'), stg) #t >>> lisp_eval(lisp("(eval '''#t)"), stg) '#t >>> lisp_eval(lisp("(eval 'y)"), stg) 1 >>> lisp_eval(lisp("(eval ''y)"), stg) y >>> lisp_eval(lisp("(eval z)"), stg) 0 NIL """ expression = lisp_eval(se.car, stg) if isinstance(expression, ConsList): expression = SExpression.from_iterable( eval_(SExpression(x), stg) for x in expression) return expression
def or_(se: SExpression, stg: LexicalVarStorage): """ Similar to ``and`` above, but compute an ``or`` instead. Return the first truthy value rather than falsy. >>> from slyther.types import * >>> from slyther.parser import lisp >>> from slyther.evaluator import lisp_eval >>> stg = LexicalVarStorage( ... {'or': Variable(or_), ... '#f': Variable(Boolean(False)), ... '#t': Variable(Boolean(True)), ... 'print': Variable(print_), ... '+': Variable(add), ... 'x': Variable(0), ... 'y': Variable(1), ... 'z': Variable(2)}) >>> lisp_eval(lisp('(or #t (print (+ x y z)) #f)'), stg) #t >>> lisp_eval(lisp('(or #f (print (+ x y z)) #f)'), stg) 3 #f >>> lisp_eval(lisp('(or #f x (print (+ x y z)) y foo)'), stg) 3 1 >>> lisp_eval(lisp('(or (+ y -1) x z)'), stg) 2 >>> lisp_eval(lisp('(or)'), stg) NIL """ ''' if(se.car is NIL): return NIL ''' return_val = NIL for item in se.cells(): if item.cdr is not NIL: return_val = lisp_eval(item.car, stg) else: return item.car if return_val: return return_val return return_val '''
def or_(se: SExpression, stg: LexicalVarStorage): """ Similar to ``and`` above, but compute an ``or`` instead. Return the first truthy value rather than falsy. >>> from slyther.types import * >>> from slyther.parser import lisp >>> from slyther.evaluator import lisp_eval >>> stg = LexicalVarStorage( ... {'or': Variable(or_), ... '#f': Variable(Boolean(False)), ... '#t': Variable(Boolean(True)), ... 'print': Variable(print_), ... '+': Variable(add), ... 'x': Variable(0), ... 'y': Variable(1), ... 'z': Variable(2)}) >>> lisp_eval(lisp('(or #t (print (+ x y z)) #f)'), stg) #t >>> lisp_eval(lisp('(or #f (print (+ x y z)) #f)'), stg) 3 #f >>> lisp_eval(lisp('(or #f x (print (+ x y z)) y foo)'), stg) 3 1 >>> lisp_eval(lisp('(or (+ y -1) x z)'), stg) 2 >>> lisp_eval(lisp('(or)'), stg) NIL """ res = NIL length = len(se) for index, x in enumerate(se): if index == length - 1: return x y = lisp_eval(x, stg) res = y if y: return y return res
def cond(se: SExpression, stg: LexicalVarStorage): """ ``cond`` is similar to ``if``, but it lists a series of predicates and consequents, similar to how guards work in Haskell. For example:: (cond ((< x 5) (print "x < 5")) ((< x 10) (print "5 <= x < 10")) ((< x 15) (print "10 <= x < 15")) (#t (print "x >= 15"))) >>> from slyther.types import * >>> from slyther.parser import lex, parse >>> def test_cond(x): ... expr = next(parse(lex(''' ... (cond ... ((< x 5) (print "x < 5")) ... ((< x 10) (print "5 <= x < 10")) ... ((< x 15) (print "10 <= x < 15")) ... (#t (print "x >= 15")))'''))) ... stg = LexicalVarStorage({}) ... stg.put('<', lt) ... stg.put('#t', Boolean(True)) ... stg.put('x', x) ... return cond(expr.cdr, stg) >>> test_cond(4) (print "x < 5") >>> test_cond(5) (print "5 <= x < 10") >>> test_cond(10) (print "10 <= x < 15") >>> test_cond(15) (print "x >= 15") """ while se is not NIL: if (lisp_eval(se.car.car, stg)): return se.car.cdr.car se = se.cdr
def __call__(self, *args): """ Call the function with arguments ``args``. Make use of ``lisp_eval``. Note that a fully working ``lisp_eval`` implementation will require this function to work properly, and this will require a working ``lisp_eval`` to work properly, so you must write both before you can test it. Warning: Do not make any attempt to modify ``environ`` here. That is not how lexical scoping works. Instead, construct a new ``LexicalVarStorage`` from the existing environ. """ # avoid circular imports from slyther.evaluator import lisp_eval new_lex_var = LexicalVarStorage(self.environ) param_list = [] for item in self.params: param_list.append(item) # keyword argument handling code start param_dict = {} count = 0 for item in param_list: param_dict[item] = count count = count + 1 new_args = [0 for i in range(len(param_list))] found_keyword_args = False for index, item in enumerate(args): s = str(item) if ('=' in s): equal_index = index_of_equal_sign(s) new_args[param_dict[s[:equal_index]]] = int(s[equal_index + 1:]) found_keyword_args = True else: if (found_keyword_args): raise BaseException( 'positional argument follows keyword argument') new_args[index] = item if (found_keyword_args): args = new_args # key word argument handling done for p, a in zip(param_list, args): new_lex_var.put(p, a) for cell in self.body.cells(): if isinstance(cell.cdr, NilType): return (cell.car, new_lex_var) lisp_eval(cell.car, new_lex_var) return NIL
def define(se: SExpression, stg: LexicalVarStorage): """ Define a variable or a function to a value, calling ``put`` on the storage:: ; variable (define var-name evaluate-me) ; lambda function (define func-name (lambda (args...) (body1) ... (bodyN))) ; SE-named function (define (func-name args...) (body1) ... (bodyN)) .. note:: If defining a function (either by ``lambda`` or by SE-named syntax), the definition **must** be made visible from within that function's ``environ``, or recursion will not work! >>> from slyther.types import * >>> from slyther.parser import lisp >>> se = lisp('((twirl alpha beta) (print alpha) (print beta))') >>> name = Symbol('twirl') >>> args = lisp('(alpha beta)') >>> body = lisp('((print alpha) (print beta))') >>> stg = LexicalVarStorage({}) >>> stg.put('NIL', NIL) >>> stg.put('define', define) # so that you can return a define if you want >>> stg.put('lambda', lambda_func) >>> from slyther.evaluator import lisp_eval >>> lisp_eval(define(cons(cons(name, args), body), stg), stg) NIL >>> lisp_eval(define(lisp('(x 10)'), stg), stg) NIL >>> stg[name].value (lambda (alpha beta) (print alpha) (print beta)) >>> stg[name].value.environ['twirl'].value (lambda (alpha beta) (print alpha) (print beta)) >>> stg['x'].value 10 >>> stg[name].value.environ['x'].value Traceback (most recent call last): ... KeyError: 'x' """ ''' if isinstance(se.car, Symbol): if isinstance(se.cdr, Variable): stg.put(str(se.car), se.cdr) else: f= lambda_func(se.cdr, stg) f.environ.put(str(se.car), f) else: f= UserFunction(se.cdr, se.cdr.cdr, stg) f.environ.put(str(se.car), f) ''' ''' if isinstance(se.car, Symbol): stg.put(str(se.car), se.cdr) elif callable(se.car): f= lambda_func(se.cdr, stg) f.environ.put(str(se.car), f) stg.put(str(se.car.car), f) elif callable(se.car.car): f= UserFunction(se.cdr, se.cdr.cdr, stg) f.environ.put(str(se.car), f) stg.put(str(se.car.car), f) ''' if isinstance(se.car, SExpression): f = UserFunction(se.car.cdr, se.cdr, stg.fork()) f.environ[str(se.car.car)] = Variable(f) stg.put(str(se.car.car), f) else: f = lisp_eval(se.cdr.car, stg) if isinstance(f, UserFunction): f.environ[str(se.car)] = Variable(f) stg.put(str(se.car), f) else: stg.put(str(se.car), f) """