Skip to content

Commit 595054a

Browse files
committed
support for-else and while-else
This adopts the semantics discussed in #1289, namely the `else` block is executed whenever the loop never runs. Unlike Python, `break` and `continue` are irrelevant. Multidimensional loops are not supported since there is some ambiguity whether e.g. ```julia for i in 1:3, j in 1:0 print(1) else print(2) end ``` should print 2 once, thrice or maybe not even at all. Currently only supported in the flisp parser, so requires `JULIA_USE_FLISP_PARSER=1`. I could use some guidance on the necessary steps to add this to JuliaSyntax as well - AFAIU this would also require #56110 first. closes #1289
1 parent 67c93b9 commit 595054a

File tree

3 files changed

+112
-26
lines changed

3 files changed

+112
-26
lines changed

src/julia-parser.scm

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,14 +1397,41 @@
13971397
(if (eq? word 'quote)
13981398
(list 'quote blk)
13991399
blk))))
1400-
((while) (begin0 (list 'while (parse-cond s) (append (parse-block s) (list (line-number-node s))))
1401-
(expect-end s word)))
1400+
((while)
1401+
(let* ((con (parse-cond s))
1402+
(body (parse-block s))
1403+
(nxt (require-token s)))
1404+
(take-token s)
1405+
(case nxt
1406+
((end)
1407+
`(while ,con
1408+
,(append body (list (line-number-node s)))))
1409+
((else)
1410+
(let ((else-body (parse-block s)))
1411+
(expect-end s word)
1412+
`(while ,con
1413+
,body
1414+
,(append else-body (list (line-number-node s))))))
1415+
(else
1416+
(error (string "unexpected \"" nxt "\""))))))
14021417
((for)
14031418
(let* ((ranges (parse-comma-separated-iters s))
1404-
(body (parse-block s)))
1405-
(expect-end s word)
1406-
`(for ,(if (length= ranges 1) (car ranges) (cons 'block ranges))
1407-
,(append body (list (line-number-node s))))))
1419+
(ranges (if (length= ranges 1) (car ranges) (cons 'block ranges)))
1420+
(body (parse-block s))
1421+
(nxt (require-token s)))
1422+
(take-token s)
1423+
(case nxt
1424+
((end)
1425+
`(for ,ranges
1426+
,(append body (list (line-number-node s)))))
1427+
((else)
1428+
(let ((else-body (parse-block s)))
1429+
(expect-end s word)
1430+
`(for ,ranges
1431+
,body
1432+
,(append else-body (list (line-number-node s))))))
1433+
(else
1434+
(error (string "unexpected \"" nxt "\""))))))
14081435

14091436
((let)
14101437
(let ((binds (if (memv (peek-token s) '(#\newline #\;))

src/julia-syntax.scm

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1823,7 +1823,9 @@
18231823
(if ,g ,g
18241824
,(loop (cdr tail)))))))))))
18251825

1826-
(define (expand-for lhss itrs body)
1826+
(define (expand-for lhss itrs body . else-body)
1827+
(if (and (length> lhss 1) (not (null? else-body)))
1828+
(error "multi-dimensional for-loops are not allowed to have else-blocks"))
18271829
(define (outer? x) (and (pair? x) (eq? (car x) 'outer)))
18281830
(let ((copied-vars ;; variables not declared `outer` are copied in the innermost loop
18291831
;; TODO: maybe filter these to remove vars not assigned in the loop
@@ -1867,7 +1869,8 @@
18671869
(_do_while
18681870
(block ,body
18691871
(= ,next (call (top iterate) ,coll ,state)))
1870-
(call (top not_int) (call (core ===) ,next (null))))))))))))
1872+
(call (top not_int) (call (core ===) ,next (null))))
1873+
,@else-body))))))))
18711874

18721875
;; wrap `expr` in a function appropriate for consuming values from given ranges
18731876
(define (func-for-generator-ranges expr range-exprs flat outervars)
@@ -2134,10 +2137,17 @@
21342137
(list* (car e) (expand-condition (cadr e)) (map expand-forms (cddr e))))
21352138

21362139
(define (expand-while e)
2137-
`(break-block loop-exit
2138-
(_while ,(expand-condition (cadr e))
2139-
(break-block loop-cont
2140-
(scope-block ,(blockify (expand-forms (caddr e))))))))
2140+
(if (length= e 3)
2141+
`(break-block loop-exit
2142+
(_while ,(expand-condition (cadr e))
2143+
(break-block loop-cont
2144+
(scope-block ,(blockify (expand-forms (caddr e)))))))
2145+
`(break-block loop-exit
2146+
(if ,(expand-condition (cadr e))
2147+
(_do_while (break-block loop-cont
2148+
(scope-block ,(blockify (expand-forms (caddr e)))))
2149+
,(expand-condition (cadr e)))
2150+
(scope-block ,(blockify (expand-forms (cadddr e))))))))
21412151

21422152
(define (expand-vcat e
21432153
(vcat '((top vcat)))
@@ -2798,7 +2808,7 @@
27982808
(let ((ranges (if (eq? (car (cadr e)) 'block)
27992809
(cdr (cadr e))
28002810
(list (cadr e)))))
2801-
(expand-forms (expand-for (map cadr ranges) (map caddr ranges) (caddr e)))))
2811+
(expand-forms (apply expand-for (map cadr ranges) (map caddr ranges) (cddr e)))))
28022812

28032813
'&& (lambda (e) (expand-forms (expand-and e)))
28042814
'|\|\|| (lambda (e) (expand-forms (expand-or e)))

test/syntax.jl

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -537,18 +537,19 @@ end
537537
# make sure that incomplete tags are detected correctly
538538
# (i.e. error messages in src/julia-parser.scm must be matched correctly
539539
# by the code in base/client.jl)
540-
for (str, tag) in Dict("" => :none, "\"" => :string, "#=" => :comment, "'" => :char,
541-
"`" => :cmd, "begin;" => :block, "quote;" => :block,
542-
"let;" => :block, "for i=1;" => :block, "function f();" => :block,
543-
"f() do x;" => :block, "module X;" => :block, "mutable struct X;" => :block,
544-
"struct X;" => :block, "(" => :other, "[" => :other,
545-
"for" => :other, "function" => :other,
546-
"f() do" => :other, "module" => :other, "mutable struct" => :other,
547-
"struct" => :other,
548-
"quote" => using_JuliaSyntax ? :block : :other,
549-
"let" => using_JuliaSyntax ? :block : :other,
550-
"begin" => using_JuliaSyntax ? :block : :other,
551-
)
540+
@testset "incomplete tags: $str => $tag" for (str, tag) in Dict(
541+
"" => :none, "\"" => :string, "#=" => :comment, "'" => :char,
542+
"`" => :cmd, "begin;" => :block, "quote;" => :block,
543+
"let;" => :block, "for i=1;" => :other, "function f();" => :block,
544+
"f() do x;" => :block, "module X;" => :block, "mutable struct X;" => :block,
545+
"struct X;" => :block, "(" => :other, "[" => :other,
546+
"for" => :other, "function" => :other,
547+
"f() do" => :other, "module" => :other, "mutable struct" => :other,
548+
"struct" => :other,
549+
"quote" => using_JuliaSyntax ? :block : :other,
550+
"let" => using_JuliaSyntax ? :block : :other,
551+
"begin" => using_JuliaSyntax ? :block : :other,
552+
)
552553
@test Base.incomplete_tag(Meta.parse(str, raise=false)) == tag
553554
end
554555

@@ -2429,7 +2430,7 @@ end
24292430
@test x == 6
24302431

24312432
# issue #36196
2432-
@test_parseerror "(for i=1; println())" "\"for\" at none:1 expected \"end\", got \")\""
2433+
@test_parseerror "(for i=1; println())" "unexpected \")\""
24332434
@test_parseerror "(try i=1; println())" "\"try\" at none:1 expected \"end\", got \")\""
24342435

24352436
# issue #36272
@@ -3987,3 +3988,51 @@ end
39873988
@test f45494() === (0,)
39883989

39893990
@test_throws "\"esc(...)\" used outside of macro expansion" eval(esc(:(const x=1)))
3991+
3992+
@testset "for else" begin
3993+
a = Int[]
3994+
for i in 1:0
3995+
push!(a, 1)
3996+
else
3997+
push!(a, 2)
3998+
end
3999+
@test a == [2]
4000+
4001+
a = Int[]
4002+
for i in 1:3
4003+
push!(a, 1)
4004+
else
4005+
push!(a, 2)
4006+
end
4007+
@test a == [1, 1, 1]
4008+
4009+
@test_throws "multi-dimensional for-loops are not allowed to have else-blocks" eval(:(
4010+
for i in 1:0, j in 1:3
4011+
print(1)
4012+
else
4013+
print(2)
4014+
end
4015+
))
4016+
end
4017+
4018+
@testset "while else" begin
4019+
a = Int[]
4020+
i = 1
4021+
while i 0
4022+
push!(a, 1)
4023+
i += 1
4024+
else
4025+
push!(a, 2)
4026+
end
4027+
@test a == [2]
4028+
4029+
a = Int[]
4030+
i = 1
4031+
while i 3
4032+
push!(a, 1)
4033+
i += 1
4034+
else
4035+
push!(a, 2)
4036+
end
4037+
@test a == [1, 1, 1]
4038+
end

0 commit comments

Comments
 (0)