|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | +"""Test the Lispython dialect.""" |
| 3 | + |
| 4 | +# See the `mcpyrate` dialects user manual: |
| 5 | +# https://github.com/Technologicat/mcpyrate/blob/master/doc/dialects.md |
| 6 | + |
| 7 | +from ...dialects import dialects, Lispython # noqa: F401 |
| 8 | + |
| 9 | +# Can use macros, too. |
| 10 | +from ...syntax import macros, continuations, call_cc # noqa: F401 |
| 11 | + |
| 12 | +# `unpythonic` is effectively `lispython`'s stdlib; not everything gets imported by default. |
| 13 | +from ...fold import foldl |
| 14 | + |
| 15 | +# Of course, all of Python's stdlib is available too. |
| 16 | +# |
| 17 | +# So is **any** Python library; the ability to use arbitrary Python libraries in |
| 18 | +# a customized Python-based language is pretty much the whole point of dialects. |
| 19 | +# |
| 20 | +from operator import mul |
| 21 | + |
| 22 | +# TODO: use the test framework |
| 23 | + |
| 24 | +def main(): |
| 25 | + assert prod((2, 3, 4)) == 24 # noqa: F821, bye missing battery, hello new dialect builtin |
| 26 | + assert foldl(mul, 1, (2, 3, 4)) == 24 |
| 27 | + |
| 28 | + # cons, car, cdr, ll, llist are builtins (for more linked list utils, import them from unpythonic) |
| 29 | + c = cons(1, 2) # noqa: F821 |
| 30 | + assert tuple(c) == (1, 2) |
| 31 | + assert car(c) == 1 # noqa: F821 |
| 32 | + assert cdr(c) == 2 # noqa: F821 |
| 33 | + assert ll(1, 2, 3) == llist((1, 2, 3)) # noqa: F821 |
| 34 | + |
| 35 | + # all unpythonic.syntax let[], letseq[], letrec[] constructs are builtins |
| 36 | + # (including the decorator versions, let_syntax and abbrev) |
| 37 | + x = let[(a, 21) in 2 * a] # noqa: F821 |
| 38 | + assert x == 42 |
| 39 | + |
| 40 | + x = letseq[((a, 1), # noqa: F821 |
| 41 | + (a, 2 * a), # noqa: F821 |
| 42 | + (a, 2 * a)) in # noqa: F821 |
| 43 | + a] # noqa: F821 |
| 44 | + assert x == 4 |
| 45 | + |
| 46 | + # rackety cond |
| 47 | + a = lambda x: cond[x < 0, "nope", # noqa: F821 |
| 48 | + x % 2 == 0, "even", |
| 49 | + "odd"] |
| 50 | + assert a(-1) == "nope" |
| 51 | + assert a(2) == "even" |
| 52 | + assert a(3) == "odd" |
| 53 | + |
| 54 | + # auto-TCO (both in defs and lambdas), implicit return in tail position |
| 55 | + def fact(n): |
| 56 | + def f(k, acc): |
| 57 | + if k == 1: |
| 58 | + return acc # "return" still available for early return |
| 59 | + f(k - 1, k * acc) |
| 60 | + f(n, acc=1) |
| 61 | + assert fact(4) == 24 |
| 62 | + fact(5000) # no crash (and correct result, since Python uses bignums transparently) |
| 63 | + |
| 64 | + t = letrec[((evenp, lambda x: (x == 0) or oddp(x - 1)), # noqa: F821 |
| 65 | + (oddp, lambda x:(x != 0) and evenp(x - 1))) in # noqa: F821 |
| 66 | + evenp(10000)] # no crash # noqa: F821 |
| 67 | + assert t is True |
| 68 | + |
| 69 | + # lambdas are named automatically |
| 70 | + square = lambda x: x**2 |
| 71 | + assert square(3) == 9 |
| 72 | + assert square.__name__ == "square" |
| 73 | + |
| 74 | + # the underscore (NOTE: due to this, "f" is a reserved name in lispython) |
| 75 | + cube = f[_**3] # noqa: F821 |
| 76 | + assert cube(3) == 27 |
| 77 | + assert cube.__name__ == "cube" |
| 78 | + |
| 79 | + # lambdas can have multiple expressions and local variables |
| 80 | + # |
| 81 | + # If you need to return a literal list from a lambda, use an extra set of |
| 82 | + # brackets; the outermost brackets always enable multiple-expression mode. |
| 83 | + # |
| 84 | + test = lambda x: [local[y << 2 * x], # noqa: F821, local[name << value] makes a local variable |
| 85 | + y + 1] # noqa: F821 |
| 86 | + assert test(10) == 21 |
| 87 | + |
| 88 | + a = lambda x: [local[t << x % 2], # noqa: F821 |
| 89 | + cond[t == 0, "even", # noqa: F821 |
| 90 | + t == 1, "odd", |
| 91 | + None]] # cond[] requires an else branch |
| 92 | + assert a(2) == "even" |
| 93 | + assert a(3) == "odd" |
| 94 | + |
| 95 | + # actually the multiple-expression environment is an unpythonic.syntax.do[], |
| 96 | + # which can be used in any expression position. |
| 97 | + x = do[local[z << 2], # noqa: F821 |
| 98 | + 3 * z] # noqa: F821 |
| 99 | + assert x == 6 |
| 100 | + |
| 101 | + # do0[] is the same, but returns the value of the first expression instead of the last one. |
| 102 | + x = do0[local[z << 3], # noqa: F821 |
| 103 | + print("hi from do0, z is {}".format(z))] # noqa: F821 |
| 104 | + assert x == 3 |
| 105 | + |
| 106 | + # MacroPy #21; namedlambda must be in its own with block in the |
| 107 | + # dialect implementation or this particular combination will fail |
| 108 | + # (uncaught jump, __name__ not set). |
| 109 | + t = letrec[((evenp, lambda x: (x == 0) or oddp(x - 1)), # noqa: F821 |
| 110 | + (oddp, lambda x:(x != 0) and evenp(x - 1))) in # noqa: F821 |
| 111 | + [local[x << evenp(100)], # noqa: F821, multi-expression let body is a do[] environment |
| 112 | + (x, evenp.__name__, oddp.__name__)]] # noqa: F821 |
| 113 | + assert t == (True, "evenp", "oddp") |
| 114 | + |
| 115 | + with continuations: # should be skipped by the implicit tco inserted by the dialect |
| 116 | + k = None # kontinuation |
| 117 | + def setk(*args, cc): |
| 118 | + nonlocal k |
| 119 | + k = cc # current continuation, i.e. where to go after setk() finishes |
| 120 | + args # tuple means multiple-return-values |
| 121 | + def doit(): |
| 122 | + lst = ['the call returned'] |
| 123 | + *more, = call_cc[setk('A')] |
| 124 | + lst + list(more) |
| 125 | + assert doit() == ['the call returned', 'A'] |
| 126 | + # We can now send stuff into k, as long as it conforms to the |
| 127 | + # signature of the assignment targets of the "call_cc". |
| 128 | + assert k('again') == ['the call returned', 'again'] |
| 129 | + assert k('thrice', '!') == ['the call returned', 'thrice', '!'] |
| 130 | + |
| 131 | + print("All tests PASSED") |
| 132 | + |
| 133 | +if __name__ == '__main__': |
| 134 | + main() |
0 commit comments