Merge pull request #1433 from Kodiologist/while-multistatement

Handle statements in the condition of `while`
This commit is contained in:
Tuukka Turto 2018-01-11 07:45:27 +02:00 committed by GitHub
commit a9621817f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 142 additions and 12 deletions

1
NEWS
View File

@ -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

View File

@ -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))

View File

@ -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
----

View File

@ -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

View File

@ -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