Skip to content

Commit fd7fcc3

Browse files
committed
silly imperative pop-while construct for list handling
1 parent 81f1050 commit fd7fcc3

File tree

3 files changed

+83
-2
lines changed

3 files changed

+83
-2
lines changed

unpythonic/syntax/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
blet as _blet, bletseq as _bletseq, bletrec as _bletrec
2626
from .letsyntax import let_syntax_expr, let_syntax_block, block, expr
2727
from .nb import nb as _nb, dbg_block as _dbg_block, dbg_expr as _dbg_expr, \
28-
dbgprint_block, dbgprint_expr
28+
dbgprint_block, dbgprint_expr, pop_while as _pop_while
2929
from .prefix import prefix as _prefix
3030
from .tailtools import autoreturn as _autoreturn, tco as _tco, \
3131
continuations as _continuations, call_cc
@@ -1516,6 +1516,14 @@ def dbg(tree, args, **kw):
15161516

15171517
# -----------------------------------------------------------------------------
15181518

1519+
@macros.block
1520+
def pop_while(tree, args, *, gen_sym, **kw):
1521+
"""TODO: document""" # TODO: document
1522+
with dyn.let(gen_sym=gen_sym):
1523+
return _pop_while(body=tree, args=args)
1524+
1525+
# -----------------------------------------------------------------------------
1526+
15191527
@macros.block
15201528
def lazify(tree, *, gen_sym, **kw):
15211529
"""[syntax, block] Call-by-need for Python.

unpythonic/syntax/nb.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
from macropy.core.walkers import Walker
1717
from macropy.core import unparse
1818

19+
from ..dynassign import dyn
20+
1921
def nb(body, args):
2022
p = args[0] if args else q[print] # custom print function hook
2123
newbody = []
@@ -36,6 +38,8 @@ def nb(body, args):
3638

3739
# -----------------------------------------------------------------------------
3840

41+
# TODO: refactor dbg into its own module
42+
3943
def dbgprint_block(ks, vs, *, filename=None, lineno=None, sep=", ", **kwargs):
4044
"""Default debug printer for the ``dbg`` macro, block variant.
4145
@@ -154,3 +158,53 @@ def dbgprint_expr(k, v, *, filename, lineno):
154158
def dbg_expr(tree):
155159
ln = q[u[tree.lineno]] if hasattr(tree, "lineno") else q[None]
156160
return q[dbgprint_expr(u[unparse(tree)], ast_literal[tree], filename=__file__, lineno=ast_literal[ln])]
161+
162+
# -----------------------------------------------------------------------------
163+
164+
# TODO: refactor pop_while into its own module
165+
166+
# Imperative list handling tool::
167+
#
168+
# with pop_while(name, expr):
169+
# ...
170+
#
171+
# with pop_while(expr):
172+
# ...
173+
#
174+
# transforms into::
175+
#
176+
# name = expr # or (gensym) = expr in the 1-arg form
177+
# while name:
178+
# it = name.pop(0) # "it" is literal, visible in user code
179+
# ...
180+
#
181+
# The point is the user code may append to or extend the list ``name``;
182+
# this simplifies writing some algorithms.
183+
#
184+
def pop_while(body, args):
185+
gen_sym = dyn.gen_sym
186+
if len(args) == 1:
187+
theinput = args[0]
188+
thename = gen_sym("_tmp")
189+
elif len(args) == 2:
190+
theinput = args[1]
191+
thename = args[0]
192+
if type(thename) is not Name:
193+
assert False, "in the two-argument form, the first argument must be a bare name"
194+
thename = thename.id
195+
else:
196+
assert False, "pop_while takes exactly one or two arguments"
197+
198+
with q as newbody:
199+
__the_tmp = ast_literal[theinput]
200+
while __the_tmp:
201+
it = __the_tmp.pop(0)
202+
thewhile = newbody[-1]
203+
thewhile.body.extend(body)
204+
205+
@Walker
206+
def renametmp(tree, **kw):
207+
if type(tree) is Name and tree.id == "__the_tmp":
208+
tree.id = thename
209+
return tree
210+
return renametmp.recurse(newbody)

unpythonic/syntax/test/test_nb.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22

3-
from ...syntax import macros, nb, dbg
3+
from ...syntax import macros, nb, dbg, pop_while
44
from ...misc import call
55

66
def test():
@@ -65,6 +65,25 @@ def just_a_scope():
6565
x = dbg[2 + 3]
6666
assert x == ("(2 + 3)", 5)
6767

68+
# silly imperative pop-while construct for handling lists in cases
69+
# where the body needs to append to or extend the input (so that a
70+
# for-loop is not appropriate)
71+
lst1 = list(range(5))
72+
lst2 = []
73+
with pop_while(lst1):
74+
lst2.append(it)
75+
assert lst1 == []
76+
assert lst2 == list(range(5))
77+
78+
lst1 = list(range(5))
79+
lst2 = []
80+
with pop_while(mylist, lst1): # pop_while(name_as, expr)
81+
lst2.append(it)
82+
if it == 4:
83+
mylist.append(5)
84+
assert lst1 == []
85+
assert lst2 == list(range(6))
86+
6887
print("All tests PASSED")
6988

7089
if __name__ == '__main__':

0 commit comments

Comments
 (0)