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
Exemple #4
0
    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)
Exemple #6
0
 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
    '''
Exemple #9
0
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
Exemple #10
0
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
    '''
Exemple #12
0
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
Exemple #13
0
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
Exemple #14
0
    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)
    """