Skip to content

Commit 0f651d7

Browse files
committed
add try/catch/else
This PR allows the use of `else` inside try-blocks, which is only taken if no exception was caught inside `try`. It is still combinable with `finally` as well. If an error is thrown inside the `else` block, the current semantics are that the error does not get caught and is thrown like normal, but the `finally` block is still run afterwards. This seemed like the most sensible option to me. I am not very confident about the implementation of linearization for `trycatchelse` here, so I would appreciate it if @JeffBezanson could give this a thorough review, so that I don't miss any edge cases. I thought we had an issue for this already, but I couldn't find anything. `else` might also not be the best keyword here, so maybe we can come up with something clearer. But it of course has the advantage that it is already a Julia keyword, so we don't need to add a new one.
1 parent d7028da commit 0f651d7

File tree

6 files changed

+151
-29
lines changed

6 files changed

+151
-29
lines changed

base/show.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2131,12 +2131,15 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
21312131
elseif head === :line && 1 <= nargs <= 2
21322132
show_linenumber(io, args...)
21332133

2134-
elseif head === :try && 3 <= nargs <= 4
2134+
elseif head === :try && 3 <= nargs <= 5
21352135
iob = IOContext(io, beginsym=>false)
21362136
show_block(iob, "try", args[1], indent, quote_level)
21372137
if is_expr(args[3], :block)
21382138
show_block(iob, "catch", args[2] === false ? Any[] : args[2], args[3]::Expr, indent, quote_level)
21392139
end
2140+
if nargs >= 5 && is_expr(args[5], :block)
2141+
show_block(iob, "else", Any[], args[5]::Expr, indent, quote_level)
2142+
end
21402143
if nargs >= 4 && is_expr(args[4], :block)
21412144
show_block(iob, "finally", Any[], args[4]::Expr, indent, quote_level)
21422145
end

src/ast.scm

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,13 @@
209209
"\n"
210210
(indented-block (cdr (cadddr e)) ilvl))
211211
"")
212+
(if (length> e 5)
213+
(let ((els (cadddddr e)))
214+
(if (and (pair? els) (eq? (car els) 'block))
215+
(string (string.rep " " ilvl) "else\n"
216+
(indented-block (cdr els) ilvl))
217+
""))
218+
"")
212219
(if (length> e 4)
213220
(let ((fin (caddddr e)))
214221
(if (and (pair? fin) (eq? (car fin) 'block))

src/julia-parser.scm

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1509,29 +1509,33 @@
15091509
(let loop ((nxt (peek-token s))
15101510
(catchb #f)
15111511
(catchv #f)
1512-
(finalb #f))
1512+
(finalb #f)
1513+
(elseb #f))
15131514
(take-token s)
15141515
(cond
15151516
((eq? nxt 'end)
15161517
(list* 'try try-block (or catchv '(false))
1517-
(or catchb (if finalb '(false) (error "try without catch or finally")))
1518-
(if finalb (list finalb) '())))
1518+
(or catchb (if (or finalb elseb) '(false) (error "try without catch, else or finally")))
1519+
(cond (elseb (list (or finalb '(false)) elseb))
1520+
(finalb (list finalb))
1521+
(else '()))))
15191522
((and (eq? nxt 'catch)
15201523
(not catchb))
15211524
(let ((nl (memv (peek-token s) '(#\newline #\;))))
15221525
(if (eqv? (peek-token s) #\;)
15231526
(take-token s))
1524-
(if (memq (require-token s) '(end finally))
1527+
(if (memq (require-token s) '(end finally else))
15251528
(loop (require-token s)
15261529
'(block)
15271530
#f
1528-
finalb)
1531+
finalb
1532+
elseb)
15291533
(let* ((loc (line-number-node s))
15301534
(var (if nl #f (parse-eq* s)))
15311535
(var? (and (not nl) (or (symbol? var)
15321536
(and (length= var 2) (eq? (car var) '$))
15331537
(error (string "invalid syntax \"catch " (deparse var) "\"")))))
1534-
(catch-block (if (eq? (require-token s) 'finally)
1538+
(catch-block (if (memq (require-token s) '(finally else))
15351539
`(block ,(line-number-node s))
15361540
(parse-block s))))
15371541
(loop (require-token s)
@@ -1543,16 +1547,28 @@
15431547
'()
15441548
(cdr catch-block))))
15451549
(if var? var '(false))
1546-
finalb)))))
1550+
finalb
1551+
elseb)))))
15471552
((and (eq? nxt 'finally)
15481553
(not finalb))
1549-
(let ((fb (if (eq? (require-token s) 'catch)
1554+
(let ((fb (if (eq? (require-token s) '(catch else))
15501555
'(block)
15511556
(parse-block s))))
15521557
(loop (require-token s)
15531558
catchb
15541559
catchv
1555-
fb)))
1560+
fb
1561+
elseb)))
1562+
((and (eq? nxt 'else)
1563+
(not elseb))
1564+
(let ((eb (if (eq? (require-token s) '(catch finally))
1565+
'(block)
1566+
(parse-block s))))
1567+
(loop (require-token s)
1568+
catchb
1569+
catchv
1570+
finalb
1571+
eb)))
15561572
(else (expect-end-error nxt 'try))))))
15571573
((return) (let ((t (peek-token s)))
15581574
(if (or (eqv? t #\newline) (closing-token? t))

src/julia-syntax.scm

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1332,25 +1332,29 @@
13321332
(let ((tryb (cadr e))
13331333
(var (caddr e))
13341334
(catchb (cadddr e)))
1335-
(cond ((length= e 5)
1335+
(cond ((and (length> e 4) (not (equal? (caddddr e) '(false))))
13361336
(if (has-unmatched-symbolic-goto? tryb)
13371337
(error "goto from a try/finally block is not permitted"))
1338-
(let ((finalb (cadddr (cdr e))))
1338+
(let ((finalb (caddddr e)))
13391339
(expand-forms
13401340
`(tryfinally
1341-
,(if (not (equal? catchb '(false)))
1342-
`(try ,tryb ,var ,catchb)
1343-
`(scope-block ,tryb))
1341+
,(if (and (equal? catchb '(false)) (length= e 5))
1342+
`(scope-block ,tryb)
1343+
`(try ,tryb ,var ,catchb (false) ,@(cdddddr e)))
13441344
(scope-block ,finalb)))))
1345-
((length= e 4)
1346-
(expand-forms
1347-
(if (symbol-like? var)
1348-
`(trycatch (scope-block ,tryb)
1349-
(scope-block
1350-
(block (= ,var (the_exception))
1351-
,catchb)))
1352-
`(trycatch (scope-block ,tryb)
1353-
(scope-block ,catchb)))))
1345+
((length> e 3)
1346+
(and (length> e 6) (error "invalid \"try\" form"))
1347+
(let ((elseb (if (length= e 6) (cdddddr e) '())))
1348+
(expand-forms
1349+
`(,(if (null? elseb) 'trycatch 'trycatchelse)
1350+
(scope-block ,tryb)
1351+
(scope-block
1352+
,(if (symbol-like? var)
1353+
`(scope-block
1354+
(block (= ,var (the_exception))
1355+
,catchb))
1356+
`(scope-block ,catchb)))
1357+
,@elseb))))
13541358
(else
13551359
(error "invalid \"try\" form")))))
13561360

@@ -3588,7 +3592,7 @@ f(x) = yt(x)
35883592
((eq? (car e) 'symboliclabel)
35893593
(kill)
35903594
#t)
3591-
((memq (car e) '(if elseif trycatch tryfinally))
3595+
((memq (car e) '(if elseif trycatch tryfinally trycatchelse))
35923596
(let ((prev (table.clone live)))
35933597
(if (eager-any (lambda (e) (begin0 (visit e)
35943598
(kill)))
@@ -3654,7 +3658,7 @@ f(x) = yt(x)
36543658
(and cv (vinfo:asgn cv) (vinfo:capt cv)))))
36553659

36563660
(define (toplevel-preserving? e)
3657-
(and (pair? e) (memq (car e) '(if elseif block trycatch tryfinally))))
3661+
(and (pair? e) (memq (car e) '(if elseif block trycatch tryfinally trycatchelse))))
36583662

36593663
(define (map-cl-convert exprs fname lam namemap defined toplevel interp opaq)
36603664
(if toplevel
@@ -4446,9 +4450,10 @@ f(x) = yt(x)
44464450
;; (= tok (enter L)) - push handler with catch block at label L, yielding token
44474451
;; (leave n) - pop N exception handlers
44484452
;; (pop_exception tok) - pop exception stack back to state of associated enter
4449-
((trycatch tryfinally)
4453+
((trycatch tryfinally trycatchelse)
44504454
(let ((handler-token (make-ssavalue))
44514455
(catch (make-label))
4456+
(els (and (eq? (car e) 'trycatchelse) (make-label)))
44524457
(endl (make-label))
44534458
(last-finally-handler finally-handler)
44544459
(finally (if (eq? (car e) 'tryfinally) (new-mutable-var) #f))
@@ -4465,11 +4470,20 @@ f(x) = yt(x)
44654470
;; handler block postfix
44664471
(if (and val v1) (emit-assignment val v1))
44674472
(if tail
4468-
(begin (if v1 (emit-return v1))
4473+
(begin (if els
4474+
(begin (if (and (not val) v1) (emit v1))
4475+
(emit '(leave 1)))
4476+
(if v1 (emit-return v1)))
44694477
(if (not finally) (set! endl #f)))
44704478
(begin (emit '(leave 1))
4471-
(emit `(goto ,endl))))
4479+
(emit `(goto ,(or els endl)))))
44724480
(set! handler-level (- handler-level 1))
4481+
;; emit else block
4482+
(if els
4483+
(begin (mark-label els)
4484+
(let ((v3 (compile (cadddr e) break-labels value tail))) ;; emit else block code
4485+
(if val (emit-assignment val v3)))
4486+
(emit `(goto ,endl))))
44734487
;; emit either catch or finally block
44744488
(mark-label catch)
44754489
(emit `(leave 1))

src/utils.scm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979

8080
(define (caddddr x) (car (cdr (cdr (cdr (cdr x))))))
8181
(define (cdddddr x) (cdr (cdr (cdr (cdr (cdr x))))))
82+
(define (cadddddr x) (car (cdddddr x)))
8283

8384
(define (table.clone t)
8485
(let ((nt (table)))

test/syntax.jl

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2966,3 +2966,84 @@ end
29662966

29672967
@generated g25678(x) = return :x
29682968
@test g25678(7) === 7
2969+
2970+
@testset "try else" begin
2971+
fails(f) = try f() catch; true else false end
2972+
@test fails(error)
2973+
@test !fails(() -> 1 + 2)
2974+
2975+
err = try
2976+
try
2977+
1 + 2
2978+
else
2979+
error("foo")
2980+
end
2981+
catch e
2982+
e
2983+
end
2984+
@test err == ErrorException("foo")
2985+
2986+
x = 0
2987+
err = try
2988+
try
2989+
1 + 2
2990+
else
2991+
error("foo")
2992+
finally
2993+
x += 1
2994+
end
2995+
catch e
2996+
e
2997+
end
2998+
@test err == ErrorException("foo")
2999+
@test x == 1
3000+
3001+
x = 0
3002+
err = try
3003+
try
3004+
1 + 2
3005+
else
3006+
3 + 4
3007+
finally
3008+
x += 1
3009+
end
3010+
catch e
3011+
e
3012+
end
3013+
@test err == 3 + 4
3014+
@test x == 1
3015+
3016+
x = 0
3017+
err = try
3018+
try
3019+
1 + 2
3020+
catch
3021+
5 + 6
3022+
else
3023+
3 + 4
3024+
finally
3025+
x += 1
3026+
end
3027+
catch e
3028+
e
3029+
end
3030+
@test err == 3 + 4
3031+
@test x == 1
3032+
3033+
x = 0
3034+
err = try
3035+
try
3036+
error()
3037+
catch
3038+
5 + 6
3039+
else
3040+
3 + 4
3041+
finally
3042+
x += 1
3043+
end
3044+
catch e
3045+
e
3046+
end
3047+
@test err == 5 + 6
3048+
@test x == 1
3049+
end

0 commit comments

Comments
 (0)