-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathtest_letsyntax.py
More file actions
205 lines (184 loc) · 7.86 KB
/
test_letsyntax.py
File metadata and controls
205 lines (184 loc) · 7.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# -*- coding: utf-8 -*-
"""Local **syntactic** bindings - splice code at macro expansion time.
**CAUTION**: a toy macro system within the real macro system. Read the docstrings."""
# Look at the various examples by surrounding them "with step_expansion:"
# to see the expanded code.
#
# from mcpyrate.debug import macros, step_expansion # noqa: F401
#
# Note let_syntax completely goes away at macro expansion time; it just instructs
# the expander to perform some substitutions in a particular section of code.
# At runtime (after macro expansion), let_syntax has zero performance overhead.
from ...syntax import macros, test, test_raises # noqa: F401
from ...test.fixtures import session, testset
from ...syntax import macros, let_syntax, abbrev # noqa: F401, F811
from ...syntax import block, expr, where
def runtests():
with testset("expression variant"):
evaluations = 0
def verylongfunctionname(x=1):
nonlocal evaluations
evaluations += 1
return x
y = let_syntax((f, verylongfunctionname))[[ # extra brackets: implicit do # noqa: F821, `let_syntax` defines `f` here.
f(), # noqa: F821
f(5)]] # noqa: F821
test[evaluations == 2]
test[y == 5]
# haskelly syntax
y = let_syntax[((f, verylongfunctionname)) # noqa: F821
in [f(), # noqa: F821
f(17)]] # noqa: F821
test[evaluations == 4]
test[y == 17]
y = let_syntax[[f(), # noqa: F821
f(23)], # noqa: F821
where((f, verylongfunctionname))] # noqa: F821
test[evaluations == 6]
test[y == 23]
# templates
# - positional parameters only, no default values
y = let_syntax((f(a), verylongfunctionname(2 * a)))[[ # noqa: F821
f(2), # noqa: F821
f(3)]] # noqa: F821
test[evaluations == 8]
test[y == 6]
# Renaming via let_syntax also affects attributes
class Silly:
realthing = 42
# This test will either pass, or error out with an AttributeError.
test[let_syntax[((alias, realthing)) in Silly.alias] == 42] # noqa: F821
with testset("block variant"):
with let_syntax:
with block as make123: # capture one or more statements
lst = []
lst.append(1)
lst.append(2)
lst.append(3)
make123
test_raises[NameError,
snd == 2, # noqa: F821, `snd` being undefined is the point of this test.
"snd should not be defined yet"]
test[lst == [1, 2, 3]]
with expr as snd: # capture a single expression
lst[1]
test[snd == 2]
with block as make456:
lst = []
lst.append(4)
lst.append(5)
lst.append(6)
if 42 % 2 == 0:
make456
else:
make123
test[lst == [4, 5, 6]]
test[snd == 5]
with let_syntax:
with block(a, b, c) as makeabc: # block template - parameters are expressions # noqa: F821, `let_syntax` defines `a`, `b`, `c` when we call `makeabc`.
lst = [a, b, c] # noqa: F821
makeabc(3 + 4, 2**3, 3 * 3)
test[lst == [7, 8, 9]]
with expr(n) as nth: # single-expression template # noqa: F821, `let_syntax` defines `n` when we call `nth`.
lst[n] # noqa: F821
test[nth(2) == 9]
# blocks may refer to ones defined previously in the same let_syntax
with let_syntax:
lst = []
with block as append123:
lst += [1, 2, 3]
with block as maketwo123s:
append123
append123
maketwo123s
test[lst == [1, 2, 3] * 2]
# Renaming via let_syntax also affects names of class and function definitions
with let_syntax:
# when the identifier "alias" is encountered, change it to "realthing"
with expr as alias:
realthing # noqa: F821
with expr as Alias:
Realthing # noqa: F821
class Alias:
x = 42
def alias():
return 42
# These tests will either pass, or error out with a NameError.
test[realthing() == 42] # noqa: F821
test[Realthing.x == 42] # noqa: F821
with testset("lexical scoping"):
with let_syntax:
with block as makelst:
lst = [1, 2, 3]
with let_syntax:
with block as makelst:
lst = [4, 5, 6]
makelst
test[lst == [4, 5, 6]]
makelst
test[lst == [1, 2, 3]]
with testset("block variant with templates"):
# even in block templates, parameters are always expressions
# - but there's a trick: names and calls are expressions
# - ...so a parameter can refer to a previously defined substitution
# - definition order is important!
# - all template substitutions are applied before any barename substitutions
# - within each kind (template, barename), substitutions are applied
# in the same order they are defined
with let_syntax:
# barenames (no parameters)
with block as append123:
lst += [1, 2, 3]
with block as append456:
lst += [4, 5, 6]
# template - applied before any barenames
with block(a) as twice: # noqa: F821
a # noqa: F821
a # noqa: F821
lst = []
twice(append123) # name of a barename substitution as a parameter
test[lst == [1, 2, 3] * 2]
lst = []
twice(append456)
test[lst == [4, 5, 6] * 2]
with let_syntax:
# in this example, both substitutions are templates, so they must be
# defined in the same order they are meant to be applied.
with block(a) as twice: # noqa: F821
a # noqa: F821
a # noqa: F821
with block(x, y, z) as appendxyz: # noqa: F821
lst += [x, y, z] # noqa: F821
lst = []
# template substitution invoked in a parameter
twice(appendxyz(7, 8, 9)) # a call is an expression, so as long as not yet expanded, this is ok
test[lst == [7, 8, 9] * 2]
with testset("abbrev (first-pass let_syntax)"):
# abbrev: like let_syntax, but expands in the first pass, outside in
# - no lexically scoped nesting
# - but can locally rename also macros (since abbrev itself expands before its body)
y = abbrev((f, verylongfunctionname))[[ # noqa: F821
f(), # noqa: F821
f(5)]] # noqa: F821
test[y == 5]
# haskelly syntax
y = abbrev[((f, verylongfunctionname)) # noqa: F821
in [f(), # noqa: F821
f(17)]] # noqa: F821
test[y == 17]
y = abbrev[[f(), # noqa: F821
f(23)], # noqa: F821
where((f, verylongfunctionname))] # noqa: F821
test[y == 23]
# in abbrev, outer expands first, so in the test,
# f -> longishname -> verylongfunctionname
with abbrev:
with expr as f:
longishname # noqa: F821
with abbrev:
with expr as longishname: # noqa: F841
verylongfunctionname
test[f(10) == 10]
if __name__ == '__main__': # pragma: no cover
with session(__file__):
runtests()