Merge pull request #1433 from Kodiologist/while-multistatement
Handle statements in the condition of `while`
This commit is contained in:
commit
a9621817f9
1
NEWS
1
NEWS
@ -51,6 +51,7 @@ Changes from 0.13.0
|
||||
* Multiple expressions are now allowed in the else clause of
|
||||
a for loop
|
||||
* `else` clauses in `for` and `while` are recognized more reliably
|
||||
* Statements in the condition of a `while` loop are repeated properly
|
||||
* Argument destructuring no longer interferes with function docstrings.
|
||||
* Multiple expressions are now allowed in `try`
|
||||
* `(yield-from)` is now a syntax error
|
||||
|
@ -52,5 +52,5 @@ html_context = dict(
|
||||
hy_descriptive_version = hy_descriptive_version)
|
||||
|
||||
intersphinx_mapping = dict(
|
||||
py2 = ('https://docs.python.org/2', None),
|
||||
py = ('https://docs.python.org/3', None))
|
||||
py2 = ('https://docs.python.org/2/', None),
|
||||
py = ('https://docs.python.org/3/', None))
|
||||
|
@ -1866,13 +1866,61 @@ following shows the expansion of the macro.
|
||||
while
|
||||
-----
|
||||
|
||||
``while`` is used to execute one or more blocks as long as a condition is met.
|
||||
The following example will output "Hello world!" to the screen indefinitely:
|
||||
``while`` compiles to a :py:keyword:`while` statement. It is used to execute a
|
||||
set of forms as long as a condition is met. The first argument to ``while`` is
|
||||
the condition, and any remaining forms constitute the body. The following
|
||||
example will output "Hello world!" to the screen indefinitely:
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
(while True (print "Hello world!"))
|
||||
|
||||
The last form of a ``while`` loop can be an ``else`` clause, which is executed
|
||||
after the loop terminates, unless it exited abnormally (e.g., with ``break``). So,
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
(setv x 2)
|
||||
(while x
|
||||
(print "In body")
|
||||
(-= x 1)
|
||||
(else
|
||||
(print "In else")))
|
||||
|
||||
prints
|
||||
|
||||
::
|
||||
|
||||
In body
|
||||
In body
|
||||
In else
|
||||
|
||||
If you put a ``break`` or ``continue`` form in the condition of a ``while``
|
||||
loop, it will apply to the very same loop rather than an outer loop, even if
|
||||
execution is yet to ever reach the loop body. (Hy compiles a ``while`` loop
|
||||
with statements in its condition by rewriting it so that the condition is
|
||||
actually in the body.) So,
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
(for [x [1]]
|
||||
(print "In outer loop")
|
||||
(while
|
||||
(do
|
||||
(print "In condition")
|
||||
(break)
|
||||
(print "This won't print.")
|
||||
True)
|
||||
(print "This won't print, either."))
|
||||
(print "At end of outer loop"))
|
||||
|
||||
prints
|
||||
|
||||
::
|
||||
|
||||
In outer loop
|
||||
In condition
|
||||
At end of outer loop
|
||||
|
||||
with
|
||||
----
|
||||
|
@ -1881,12 +1881,33 @@ class HyASTCompiler(object):
|
||||
@checkargs(min=2)
|
||||
def compile_while_expression(self, expr):
|
||||
expr.pop(0) # "while"
|
||||
ret = self.compile(expr.pop(0))
|
||||
cond = expr.pop(0)
|
||||
cond_compiled = self.compile(cond)
|
||||
|
||||
orel = Result()
|
||||
# (while cond body (else …))
|
||||
else_expr = None
|
||||
if ends_with_else(expr):
|
||||
else_expr = expr.pop()
|
||||
|
||||
if cond_compiled.stmts:
|
||||
# We need to ensure the statements for the condition are
|
||||
# executed on every iteration. Rewrite the loop to use a
|
||||
# single anonymous variable as the condition.
|
||||
def e(*x): return HyExpression(x)
|
||||
s = HySymbol
|
||||
cond_var = s(self.get_anon_var())
|
||||
return self.compile(e(
|
||||
s('do'),
|
||||
e(s('setv'), cond_var, 1),
|
||||
e(s('while'), cond_var,
|
||||
# Cast the condition to a bool in case it's mutable and
|
||||
# changes its truth value, but use (not (not ...)) instead of
|
||||
# `bool` in case `bool` has been redefined.
|
||||
e(s('setv'), cond_var, e(s('not'), e(s('not'), cond))),
|
||||
e(s('if*'), cond_var, e(s('do'), *expr)),
|
||||
*([else_expr] if else_expr is not None else []))).replace(expr)) # noqa
|
||||
|
||||
orel = Result()
|
||||
if else_expr is not None:
|
||||
for else_body in else_expr[1:]:
|
||||
orel += self.compile(else_body)
|
||||
orel += orel.expr_as_stmt()
|
||||
@ -1894,11 +1915,9 @@ class HyASTCompiler(object):
|
||||
body = self._compile_branch(expr)
|
||||
body += body.expr_as_stmt()
|
||||
|
||||
ret += asty.While(expr,
|
||||
test=ret.force_expr,
|
||||
body=body.stmts,
|
||||
orelse=orel.stmts)
|
||||
|
||||
ret = cond_compiled + asty.While(
|
||||
expr, test=cond_compiled.force_expr,
|
||||
body=body.stmts, orelse=orel.stmts)
|
||||
ret.contains_yield = body.contains_yield
|
||||
|
||||
return ret
|
||||
|
@ -346,6 +346,68 @@
|
||||
(assert (= a [2 "e"])))
|
||||
|
||||
|
||||
(defn test-while-multistatement-condition []
|
||||
|
||||
; The condition should be executed every iteration, before the body.
|
||||
; `else` should be executed last.
|
||||
(setv s "")
|
||||
(setv x 2)
|
||||
(while (do (+= s "a") x)
|
||||
(+= s "b")
|
||||
(-= x 1)
|
||||
(else
|
||||
(+= s "z")))
|
||||
(assert (= s "ababaz"))
|
||||
|
||||
; `else` should still be skipped after `break`.
|
||||
(setv s "")
|
||||
(setv x 2)
|
||||
(while (do (+= s "a") x)
|
||||
(+= s "b")
|
||||
(-= x 1)
|
||||
(when (= x 0)
|
||||
(break))
|
||||
(else
|
||||
(+= s "z")))
|
||||
(assert (= s "abab"))
|
||||
|
||||
; `continue` should jump to the condition.
|
||||
(setv s "")
|
||||
(setv x 2)
|
||||
(setv continued? False)
|
||||
(while (do (+= s "a") x)
|
||||
(+= s "b")
|
||||
(when (and (= x 1) (not continued?))
|
||||
(+= s "c")
|
||||
(setv continued? True)
|
||||
(continue))
|
||||
(-= x 1)
|
||||
(else
|
||||
(+= s "z")))
|
||||
(assert (= s "ababcabaz"))
|
||||
|
||||
; `break` in a condition applies to the `while`, not an outer loop.
|
||||
(setv s "")
|
||||
(for [x "123"]
|
||||
(+= s x)
|
||||
(setv y 0)
|
||||
(while (do (when (and (= x "2") (= y 1)) (break)) (< y 3))
|
||||
(+= s "y")
|
||||
(+= y 1)))
|
||||
(assert (= s "1yyy2y3yyy"))
|
||||
|
||||
; The condition is still tested appropriately if its last variable
|
||||
; is set to a false value in the loop body.
|
||||
(setv out [])
|
||||
(setv x 0)
|
||||
(setv a [1 1])
|
||||
(while (do (.append out 2) (setv x (and a (.pop a))) x)
|
||||
(setv x 0)
|
||||
(.append out x))
|
||||
(assert (= out [2 0 2 0 2]))
|
||||
(assert (is x a)))
|
||||
|
||||
|
||||
(defn test-branching []
|
||||
"NATIVE: test if branching"
|
||||
(if True
|
||||
|
Loading…
Reference in New Issue
Block a user