Skip to content

Commit

Permalink
add try/catch/else
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
simeonschaub committed Sep 10, 2021
1 parent d7028da commit 0f651d7
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 29 deletions.
5 changes: 4 additions & 1 deletion base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2131,12 +2131,15 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
elseif head === :line && 1 <= nargs <= 2
show_linenumber(io, args...)

elseif head === :try && 3 <= nargs <= 4
elseif head === :try && 3 <= nargs <= 5
iob = IOContext(io, beginsym=>false)
show_block(iob, "try", args[1], indent, quote_level)
if is_expr(args[3], :block)
show_block(iob, "catch", args[2] === false ? Any[] : args[2], args[3]::Expr, indent, quote_level)
end
if nargs >= 5 && is_expr(args[5], :block)
show_block(iob, "else", Any[], args[5]::Expr, indent, quote_level)
end
if nargs >= 4 && is_expr(args[4], :block)
show_block(iob, "finally", Any[], args[4]::Expr, indent, quote_level)
end
Expand Down
7 changes: 7 additions & 0 deletions src/ast.scm
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@
"\n"
(indented-block (cdr (cadddr e)) ilvl))
"")
(if (length> e 5)
(let ((els (cadddddr e)))
(if (and (pair? els) (eq? (car els) 'block))
(string (string.rep " " ilvl) "else\n"
(indented-block (cdr els) ilvl))
""))
"")
(if (length> e 4)
(let ((fin (caddddr e)))
(if (and (pair? fin) (eq? (car fin) 'block))
Expand Down
34 changes: 25 additions & 9 deletions src/julia-parser.scm
Original file line number Diff line number Diff line change
Expand Up @@ -1509,29 +1509,33 @@
(let loop ((nxt (peek-token s))
(catchb #f)
(catchv #f)
(finalb #f))
(finalb #f)
(elseb #f))
(take-token s)
(cond
((eq? nxt 'end)
(list* 'try try-block (or catchv '(false))
(or catchb (if finalb '(false) (error "try without catch or finally")))
(if finalb (list finalb) '())))
(or catchb (if (or finalb elseb) '(false) (error "try without catch, else or finally")))
(cond (elseb (list (or finalb '(false)) elseb))
(finalb (list finalb))
(else '()))))
((and (eq? nxt 'catch)
(not catchb))
(let ((nl (memv (peek-token s) '(#\newline #\;))))
(if (eqv? (peek-token s) #\;)
(take-token s))
(if (memq (require-token s) '(end finally))
(if (memq (require-token s) '(end finally else))
(loop (require-token s)
'(block)
#f
finalb)
finalb
elseb)
(let* ((loc (line-number-node s))
(var (if nl #f (parse-eq* s)))
(var? (and (not nl) (or (symbol? var)
(and (length= var 2) (eq? (car var) '$))
(error (string "invalid syntax \"catch " (deparse var) "\"")))))
(catch-block (if (eq? (require-token s) 'finally)
(catch-block (if (memq (require-token s) '(finally else))
`(block ,(line-number-node s))
(parse-block s))))
(loop (require-token s)
Expand All @@ -1543,16 +1547,28 @@
'()
(cdr catch-block))))
(if var? var '(false))
finalb)))))
finalb
elseb)))))
((and (eq? nxt 'finally)
(not finalb))
(let ((fb (if (eq? (require-token s) 'catch)
(let ((fb (if (eq? (require-token s) '(catch else))
'(block)
(parse-block s))))
(loop (require-token s)
catchb
catchv
fb)))
fb
elseb)))
((and (eq? nxt 'else)
(not elseb))
(let ((eb (if (eq? (require-token s) '(catch finally))
'(block)
(parse-block s))))
(loop (require-token s)
catchb
catchv
finalb
eb)))
(else (expect-end-error nxt 'try))))))
((return) (let ((t (peek-token s)))
(if (or (eqv? t #\newline) (closing-token? t))
Expand Down
52 changes: 33 additions & 19 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -1332,25 +1332,29 @@
(let ((tryb (cadr e))
(var (caddr e))
(catchb (cadddr e)))
(cond ((length= e 5)
(cond ((and (length> e 4) (not (equal? (caddddr e) '(false))))
(if (has-unmatched-symbolic-goto? tryb)
(error "goto from a try/finally block is not permitted"))
(let ((finalb (cadddr (cdr e))))
(let ((finalb (caddddr e)))
(expand-forms
`(tryfinally
,(if (not (equal? catchb '(false)))
`(try ,tryb ,var ,catchb)
`(scope-block ,tryb))
,(if (and (equal? catchb '(false)) (length= e 5))
`(scope-block ,tryb)
`(try ,tryb ,var ,catchb (false) ,@(cdddddr e)))
(scope-block ,finalb)))))
((length= e 4)
(expand-forms
(if (symbol-like? var)
`(trycatch (scope-block ,tryb)
(scope-block
(block (= ,var (the_exception))
,catchb)))
`(trycatch (scope-block ,tryb)
(scope-block ,catchb)))))
((length> e 3)
(and (length> e 6) (error "invalid \"try\" form"))
(let ((elseb (if (length= e 6) (cdddddr e) '())))
(expand-forms
`(,(if (null? elseb) 'trycatch 'trycatchelse)
(scope-block ,tryb)
(scope-block
,(if (symbol-like? var)
`(scope-block
(block (= ,var (the_exception))
,catchb))
`(scope-block ,catchb)))
,@elseb))))
(else
(error "invalid \"try\" form")))))

Expand Down Expand Up @@ -3588,7 +3592,7 @@ f(x) = yt(x)
((eq? (car e) 'symboliclabel)
(kill)
#t)
((memq (car e) '(if elseif trycatch tryfinally))
((memq (car e) '(if elseif trycatch tryfinally trycatchelse))
(let ((prev (table.clone live)))
(if (eager-any (lambda (e) (begin0 (visit e)
(kill)))
Expand Down Expand Up @@ -3654,7 +3658,7 @@ f(x) = yt(x)
(and cv (vinfo:asgn cv) (vinfo:capt cv)))))

(define (toplevel-preserving? e)
(and (pair? e) (memq (car e) '(if elseif block trycatch tryfinally))))
(and (pair? e) (memq (car e) '(if elseif block trycatch tryfinally trycatchelse))))

(define (map-cl-convert exprs fname lam namemap defined toplevel interp opaq)
(if toplevel
Expand Down Expand Up @@ -4446,9 +4450,10 @@ f(x) = yt(x)
;; (= tok (enter L)) - push handler with catch block at label L, yielding token
;; (leave n) - pop N exception handlers
;; (pop_exception tok) - pop exception stack back to state of associated enter
((trycatch tryfinally)
((trycatch tryfinally trycatchelse)
(let ((handler-token (make-ssavalue))
(catch (make-label))
(els (and (eq? (car e) 'trycatchelse) (make-label)))
(endl (make-label))
(last-finally-handler finally-handler)
(finally (if (eq? (car e) 'tryfinally) (new-mutable-var) #f))
Expand All @@ -4465,11 +4470,20 @@ f(x) = yt(x)
;; handler block postfix
(if (and val v1) (emit-assignment val v1))
(if tail
(begin (if v1 (emit-return v1))
(begin (if els
(begin (if (and (not val) v1) (emit v1))
(emit '(leave 1)))
(if v1 (emit-return v1)))
(if (not finally) (set! endl #f)))
(begin (emit '(leave 1))
(emit `(goto ,endl))))
(emit `(goto ,(or els endl)))))
(set! handler-level (- handler-level 1))
;; emit else block
(if els
(begin (mark-label els)
(let ((v3 (compile (cadddr e) break-labels value tail))) ;; emit else block code
(if val (emit-assignment val v3)))
(emit `(goto ,endl))))
;; emit either catch or finally block
(mark-label catch)
(emit `(leave 1))
Expand Down
1 change: 1 addition & 0 deletions src/utils.scm
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@

(define (caddddr x) (car (cdr (cdr (cdr (cdr x))))))
(define (cdddddr x) (cdr (cdr (cdr (cdr (cdr x))))))
(define (cadddddr x) (car (cdddddr x)))

(define (table.clone t)
(let ((nt (table)))
Expand Down
81 changes: 81 additions & 0 deletions test/syntax.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2966,3 +2966,84 @@ end

@generated g25678(x) = return :x
@test g25678(7) === 7

@testset "try else" begin
fails(f) = try f() catch; true else false end
@test fails(error)
@test !fails(() -> 1 + 2)

err = try
try
1 + 2
else
error("foo")
end
catch e
e
end
@test err == ErrorException("foo")

x = 0
err = try
try
1 + 2
else
error("foo")
finally
x += 1
end
catch e
e
end
@test err == ErrorException("foo")
@test x == 1

x = 0
err = try
try
1 + 2
else
3 + 4
finally
x += 1
end
catch e
e
end
@test err == 3 + 4
@test x == 1

x = 0
err = try
try
1 + 2
catch
5 + 6
else
3 + 4
finally
x += 1
end
catch e
e
end
@test err == 3 + 4
@test x == 1

x = 0
err = try
try
error()
catch
5 + 6
else
3 + 4
finally
x += 1
end
catch e
e
end
@test err == 5 + 6
@test x == 1
end

0 comments on commit 0f651d7

Please sign in to comment.