Skip to content

Commit 7acc02d

Browse files
junhyeokjunhyeok
authored andcommitted
macro: generate macro bodies for the running Lua version
1 parent d0ba265 commit 7acc02d

5 files changed

Lines changed: 819 additions & 4 deletions

File tree

Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
local tl = require('tl')
2+
local lua_generator = require('teal.gen.lua_generator')
3+
4+
-- Macro bodies are compiled for the Lua version this process is running on,
5+
-- because they are loaded into this VM. On Lua 5.4+ the control variable of a
6+
-- generic `for` is a constant, so a body that writes to one needs a shadowing
7+
-- local. These tests only exercise that path when the suite itself runs on
8+
-- Lua 5.4 or newer; on older versions they still assert the expansions.
9+
describe('macro bodies compiled for the running Lua version', function()
10+
local HI = "print('hi')"
11+
12+
local function repeated(n)
13+
local parts = {}
14+
for _ = 1, n do
15+
table.insert(parts, HI)
16+
end
17+
return table.concat(parts, '; ')
18+
end
19+
20+
local function expands_to(code, expected)
21+
local ast, errs = tl.parse(code)
22+
assert.same({}, errs)
23+
local out, err = lua_generator.generate(ast, '5.4')
24+
assert.is_nil(err)
25+
assert.same(expected, (out:gsub('^%s+', ''):gsub('%s+$', '')))
26+
end
27+
28+
describe('writes to a for-in control variable', function()
29+
it('accepts a write in the loop body', function()
30+
expands_to([[
31+
local macro m!(x: Expression)
32+
local out = block('statements')
33+
for e in ("a;b"):gmatch("[^;]+") do
34+
e = e .. "!"
35+
end
36+
table.insert(out, `$x`)
37+
return out
38+
end
39+
40+
m!(print('hi'))
41+
]], repeated(1))
42+
end)
43+
44+
it('accepts a write inside an if block', function()
45+
expands_to([[
46+
local macro m!(x: Expression)
47+
local out = block('statements')
48+
for e in ("a;b"):gmatch("[^;]+") do
49+
if #e > 0 then
50+
e = e .. "!"
51+
end
52+
table.insert(out, `$x`)
53+
end
54+
return out
55+
end
56+
57+
m!(print('hi'))
58+
]], repeated(2))
59+
end)
60+
61+
it('accepts writes in elseif and else branches', function()
62+
expands_to([[
63+
local macro m!(x: Expression)
64+
local out = block('statements')
65+
for e in ("a;b"):gmatch("[^;]+") do
66+
if e == "z" then
67+
e = "1"
68+
elseif e == "a" then
69+
e = "22"
70+
else
71+
e = "333"
72+
end
73+
for _ = 1, #e do
74+
table.insert(out, `$x`)
75+
end
76+
end
77+
return out
78+
end
79+
80+
m!(print('hi'))
81+
]], repeated(5))
82+
end)
83+
84+
it('accepts a write inside a while loop', function()
85+
expands_to([[
86+
local macro m!(x: Expression)
87+
local out = block('statements')
88+
for e in ("a;b"):gmatch("[^;]+") do
89+
while #e < 3 do
90+
e = e .. "!"
91+
end
92+
table.insert(out, `$x`)
93+
end
94+
return out
95+
end
96+
97+
m!(print('hi'))
98+
]], repeated(2))
99+
end)
100+
101+
it('accepts a write inside a repeat loop', function()
102+
expands_to([[
103+
local macro m!(x: Expression)
104+
local out = block('statements')
105+
for e in ("a;b"):gmatch("[^;]+") do
106+
repeat
107+
e = e .. "!"
108+
until #e > 1
109+
table.insert(out, `$x`)
110+
end
111+
return out
112+
end
113+
114+
m!(print('hi'))
115+
]], repeated(2))
116+
end)
117+
118+
it('accepts a write inside a do block', function()
119+
expands_to([[
120+
local macro m!(x: Expression)
121+
local out = block('statements')
122+
for e in ("a;b"):gmatch("[^;]+") do
123+
do
124+
e = e .. "!"
125+
end
126+
table.insert(out, `$x`)
127+
end
128+
return out
129+
end
130+
131+
m!(print('hi'))
132+
]], repeated(2))
133+
end)
134+
135+
it('accepts a write inside a nested numeric for', function()
136+
expands_to([[
137+
local macro m!(x: Expression)
138+
local out = block('statements')
139+
for e in ("a;b"):gmatch("[^;]+") do
140+
for _ = 1, 2 do
141+
e = e .. "!"
142+
end
143+
for _ = 1, #e do
144+
table.insert(out, `$x`)
145+
end
146+
end
147+
return out
148+
end
149+
150+
m!(print('hi'))
151+
]], repeated(6))
152+
end)
153+
154+
it('accepts writes to both control variables of nested for-ins', function()
155+
expands_to([[
156+
local macro m!(x: Expression)
157+
local out = block('statements')
158+
for e in ("a;b"):gmatch("[^;]+") do
159+
for f in ("c,d"):gmatch("[^,]+") do
160+
f = f .. "!"
161+
e = e .. f
162+
end
163+
for _ = 1, #e do
164+
table.insert(out, `$x`)
165+
end
166+
end
167+
return out
168+
end
169+
170+
m!(print('hi'))
171+
]], repeated(10))
172+
end)
173+
174+
it('accepts a write as part of a multiple assignment', function()
175+
expands_to([[
176+
local macro m!(x: Expression)
177+
local out = block('statements')
178+
for e in ("a;b"):gmatch("[^;]+") do
179+
local q
180+
q, e = 1, e .. "!"
181+
table.insert(out, `$x`)
182+
end
183+
return out
184+
end
185+
186+
m!(print('hi'))
187+
]], repeated(2))
188+
end)
189+
end)
190+
191+
describe('writes nested inside function values', function()
192+
it('accepts a write inside an anonymous function', function()
193+
expands_to([[
194+
local macro m!(x: Expression)
195+
local out = block('statements')
196+
local go = function()
197+
for e in ("a;b"):gmatch("[^;]+") do
198+
e = e .. "!"
199+
end
200+
end
201+
go()
202+
table.insert(out, `$x`)
203+
return out
204+
end
205+
206+
m!(print('hi'))
207+
]], repeated(1))
208+
end)
209+
210+
it('accepts a write inside a local function', function()
211+
expands_to([[
212+
local macro m!(x: Expression)
213+
local out = block('statements')
214+
local function go()
215+
for e in ("a;b"):gmatch("[^;]+") do
216+
e = e .. "!"
217+
end
218+
end
219+
go()
220+
table.insert(out, `$x`)
221+
return out
222+
end
223+
224+
m!(print('hi'))
225+
]], repeated(1))
226+
end)
227+
228+
it('accepts a write inside a function in a table constructor', function()
229+
expands_to([[
230+
local macro m!(x: Expression)
231+
local out = block('statements')
232+
local handlers = {
233+
go = function()
234+
for e in ("a;b"):gmatch("[^;]+") do
235+
e = e .. "!"
236+
end
237+
end,
238+
}
239+
handlers.go()
240+
table.insert(out, `$x`)
241+
return out
242+
end
243+
244+
m!(print('hi'))
245+
]], repeated(1))
246+
end)
247+
248+
it('accepts a write inside a function passed as a call argument', function()
249+
expands_to([[
250+
local macro m!(x: Expression)
251+
local out = block('statements')
252+
local function call(g)
253+
g()
254+
end
255+
call(function()
256+
for e in ("a;b"):gmatch("[^;]+") do
257+
e = e .. "!"
258+
end
259+
end)
260+
table.insert(out, `$x`)
261+
return out
262+
end
263+
264+
m!(print('hi'))
265+
]], repeated(1))
266+
end)
267+
268+
it('accepts a write inside a returned closure', function()
269+
expands_to([[
270+
local macro m!(x: Expression)
271+
local out = block('statements')
272+
local mk = function()
273+
return function()
274+
for e in ("a;b"):gmatch("[^;]+") do
275+
e = e .. "!"
276+
end
277+
end
278+
end
279+
mk()()
280+
table.insert(out, `$x`)
281+
return out
282+
end
283+
284+
m!(print('hi'))
285+
]], repeated(1))
286+
end)
287+
end)
288+
289+
describe('semantics of the shadowed control variable', function()
290+
it('keeps the write visible for the rest of the iteration', function()
291+
expands_to([[
292+
local macro m!(x: Expression)
293+
local out = block('statements')
294+
local n = 0
295+
for e in ("a;b"):gmatch("[^;]+") do
296+
e = e .. "!"
297+
n = n + #e
298+
end
299+
for _ = 1, n do
300+
table.insert(out, `$x`)
301+
end
302+
return out
303+
end
304+
305+
m!(print('hi'))
306+
]], repeated(4))
307+
end)
308+
309+
it('does not let the write disturb the iteration', function()
310+
expands_to([[
311+
local macro m!(x: Expression)
312+
local out = block('statements')
313+
for e in ("a;b;c"):gmatch("[^;]+") do
314+
e = "zzzz"
315+
table.insert(out, `$x`)
316+
end
317+
return out
318+
end
319+
320+
m!(print('hi'))
321+
]], repeated(3))
322+
end)
323+
324+
it('writes to a second control variable, which is not const', function()
325+
expands_to([[
326+
local macro m!(x: Expression)
327+
local out = block('statements')
328+
for _, v in ipairs({10, 20}) do
329+
v = v + 1
330+
table.insert(out, `$x`)
331+
end
332+
return out
333+
end
334+
335+
m!(print('hi'))
336+
]], repeated(2))
337+
end)
338+
339+
it('leaves a loop that never writes its control variable alone', function()
340+
expands_to([[
341+
local macro m!(x: Expression)
342+
local out = block('statements')
343+
local n = 0
344+
for e in ("a;b;c"):gmatch("[^;]+") do
345+
n = n + #e
346+
end
347+
for _ = 1, n do
348+
table.insert(out, `$x`)
349+
end
350+
return out
351+
end
352+
353+
m!(print('hi'))
354+
]], repeated(3))
355+
end)
356+
357+
it('does not treat a field assignment as a control-variable write', function()
358+
expands_to([[
359+
local macro m!(x: Expression)
360+
local out = block('statements')
361+
local t = { e = 0 }
362+
for e in ("a;b"):gmatch("[^;]+") do
363+
t.e = t.e + #e
364+
end
365+
for _ = 1, t.e do
366+
table.insert(out, `$x`)
367+
end
368+
return out
369+
end
370+
371+
m!(print('hi'))
372+
]], repeated(2))
373+
end)
374+
end)
375+
end)

0 commit comments

Comments
 (0)