From 976ff84532729983a30a0ec26b148cf5e92a294f Mon Sep 17 00:00:00 2001 From: Timo Kluck Date: Mon, 14 Aug 2017 22:05:25 +0200 Subject: [PATCH 01/15] Parse + emit code for for/else and break-with-value This commit enables a combination of two language features that dovetail nicely together (as remarked by @StefanKarpinski in [1]): for/else and break-with-value, which together allow something like: name = for person in people if person.id == id break person.name end else generate_name() end The parsing patch is pretty straightforward. As for the code lowering part, I opted to make a second version of `'break-block`, called `'break-block-with-value`, which passes a target variable for the return value onto the `break-labels` stack. That's where the `'break` operation finds it. An alternative approach would be something more similar to the existing `replace-return` function: Instead of having an intermediate node representing `'break-block-with-value`,we could traverse the expression tree and replace `break-with-value` by an assignment followed by a break. I have no opinion either way; the current commit seemed like the obvious implementation to me, but that was before I saw `replace-return`'s prior art. This patch still has a few places marked `TODO`, where scoping issues for the `else` block need to be resolved. I'll tackle that after awaiting feedback. [1] https://github.com/JuliaLang/julia/issues/22891 --- src/jlfrontend.scm | 4 +++- src/julia-parser.scm | 31 ++++++++++++++++++------ src/julia-syntax.scm | 57 +++++++++++++++++++++++++++++++++++--------- 3 files changed, 73 insertions(+), 19 deletions(-) diff --git a/src/jlfrontend.scm b/src/jlfrontend.scm index a556310a881d0..94f9aa96ee3cb 100644 --- a/src/jlfrontend.scm +++ b/src/jlfrontend.scm @@ -43,7 +43,9 @@ tab))) ((lambda) tab) ((local) tab) - ((break-block) (find-possible-globals- (caddr e) tab)) + ((break-block) (find-possible-globals- (caddr e) tab)) + ;; TODO: elsebody in (cadddr e) + ((break-block-with-value) (find-possible-globals- (caddr e) tab)) ((module toplevel) '()) (else (for-each (lambda (x) (find-possible-globals- x tab)) diff --git a/src/julia-parser.scm b/src/julia-parser.scm index b7ca3a19577c6..a7d05548fdc57 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -1286,11 +1286,22 @@ (expect-end s word))) ((for) (let* ((ranges (parse-comma-separated-iters s)) - (body (parse-block s))) - (expect-end s word) - `(for ,(if (length= ranges 1) (car ranges) (cons 'block ranges)) - ,body))) - + (body (parse-block s)) + (nxt (take-token s))) + (case nxt + ((end) + `(for ,(if (length= ranges 1) (car ranges) (cons 'block ranges)) + ,body)) + ((else) + (let* ((elsebody (parse-block s))) + (expect-end s word) + `(for ,(if (length= ranges 1) (car ranges) (cons 'block ranges)) + ,body + ,elsebody))) + (otherwise + (error (string "\"" word "\" at " + current-filename ":" input-port-line + " expected \"end\", got \"" nxt "\"")))))) ((if elseif) (if (newline? (peek-token s)) (error (string "missing condition in \"if\" at " current-filename @@ -1481,14 +1492,20 @@ (if (or (eqv? t #\newline) (closing-token? t)) (list 'return '(null)) (list 'return (parse-eq s))))) - ((break continue) + ((continue) (let ((t (peek-token s))) (if (or (eof-object? t) (and (eq? t 'end) (not end-symbol)) (memv t '(#\newline #\; #\) :))) (list word) (error (string "unexpected \"" t "\" after " word))))) - + ((break) + (let ((t (peek-token s))) + (if (or (eof-object? t) + (and (eq? t 'end) (not end-symbol)) + (memv t '(#\newline #\; #\) :))) + (list word '(null)) + (list word (parse-eq s))))) ((module baremodule) (let* ((name (parse-unary-prefix s)) (loc (line-number-node s)) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 172478f3550fc..075e5f8a68fa6 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1669,7 +1669,7 @@ (if ,g ,g ,(loop (cdr tail))))))))))) -(define (expand-for while lhs X body) +(define (expand-for while lhs X elsebody body) ;; (for (= lhs X) body) (let ((coll (make-ssavalue)) (state (gensy))) @@ -1685,7 +1685,8 @@ #;,@(map (lambda (v) `(local ,v)) (lhs-vars lhs)) ,(lower-tuple-assignment (list lhs state) `(call (top next) ,coll ,state)) - ,body)))))))) + ,body)) + ,elsebody)))))) ;; convert an operator parsed as (op a b) to (call op a b) (define (syntactic-op-to-call e) @@ -2224,10 +2225,13 @@ 'while (lambda (e) `(scope-block - (break-block loop-exit + (break-block-with-value loop-exit (_while ,(expand-forms (cadr e)) (break-block loop-cont - ,(expand-forms (caddr e))))))) + ,(expand-forms (caddr e)))) + ,(if (length> e 3) + (expand-forms (cadddr e)) + '(null))))) 'inner-while (lambda (e) @@ -2238,9 +2242,9 @@ 'break (lambda (e) - (if (pair? (cdr e)) + (if (symbol? (cadr e)) e - '(break loop-exit))) + `(break loop-exit ,(cadr e)))) 'continue (lambda (e) '(break loop-cont)) @@ -2253,6 +2257,9 @@ (expand-for (if first 'while 'inner-while) (cadr (car ranges)) (caddr (car ranges)) + (if (and (length> e 3) first) + (cadddr e) ;; elsebody + '(null)) (if (null? (cdr ranges)) (caddr e) ;; body (nest (cdr ranges) #f))))) @@ -2524,6 +2531,8 @@ ((or (not (pair? e)) (quoted? e)) tab) ((memq (car e) '(lambda scope-block module toplevel)) tab) ((eq? (car e) 'break-block) (unbound-vars (caddr e) bound tab)) + ((eq? (car e) 'break-block-with-value) (unbound-vars (caddr e) bound tab)) + ;; TODO: elsebody in (cadddr e) ((eq? (car e) 'with-static-parameters) (unbound-vars (cadr e) bound tab)) (else (for-each (lambda (x) (unbound-vars x bound tab)) (cdr e)) @@ -2620,8 +2629,12 @@ ((eq? (car e) 'module) (error "module expression not at top level")) ((eq? (car e) 'break-block) - `(break-block ,(cadr e) ;; ignore type symbol of break-block expression + `(,(car e) ,(cadr e) ;; ignore type symbol of break-block expression ,(resolve-scopes- (caddr e) env outerglobals implicitglobals lam renames #f))) ;; body of break-block expression + ((eq? (car e) 'break-block-with-value) + `(,(car e) ,(cadr e) ;; ignore type symbol of break-block expression + ,(resolve-scopes- (caddr e) env outerglobals implicitglobals lam renames #f) ;; body of break-block expression + ,(resolve-scopes- (cadddr e) env outerglobals implicitglobals lam renames #f))) ;; elsebody of break-block-with-value expression ((eq? (car e) 'with-static-parameters) `(with-static-parameters ;; ignore list of sparams in break-block expression ,(resolve-scopes- (cadr e) env outerglobals implicitglobals lam renames #f) @@ -2646,6 +2659,8 @@ ((symbol? e) (put! tab e #t)) ((and (pair? e) (eq? (car e) 'outerref)) (put! tab (cadr e) #t)) ((and (pair? e) (eq? (car e) 'break-block)) (free-vars- (caddr e) tab)) + ;; TODO: elsebody in (cadddr e) + ((and (pair? e) (eq? (car e) 'break-block-with-value)) (free-vars- (caddr e) tab)) ((and (pair? e) (eq? (car e) 'with-static-parameters)) (free-vars- (cadr e) tab)) ((or (atom? e) (quoted? e)) tab) ((eq? (car e) 'lambda) @@ -3553,14 +3568,34 @@ f(x) = yt(x) value #f) (mark-label endl))) (if value (compile '(null) break-labels value tail))) + ((break-block-with-value) + (let ((endl (make-label)) + (valvar (if (or value tail) (new-mutable-var) #f))) + (compile (caddr e) + (cons (list (cadr e) endl handler-level valvar) + break-labels) + value #f) + (if (length> e 3) + (let ((elseval (compile (cadddr e) break-labels valvar #f))) + (if valvar (emit `(= ,valvar ,elseval)))) + (let ((elseval (compile '(null) break-labels valvar #f))) + (if (and valvar elseval) (emit `(= ,valvar ,elseval))))) + (mark-label endl) + (if tail (emit-return valvar) valvar))) ((break) (let ((labl (assq (cadr e) break-labels))) (if (not labl) (error "break or continue outside loop") - (begin - (if (> handler-level (caddr labl)) - (emit `(leave ,(- handler-level (caddr labl))))) - (emit `(goto ,(cadr labl))))))) + (let ((endl (cadr labl)) + (hlevel (caddr labl)) + (valtarget (if (length> labl 3) (cadddr labl) #f)) + (valexpr (if (length> e 2) (caddr e) '(null)))) + (let ((res (compile valexpr break-labels valtarget #f))) + (if valtarget + (emit `(= ,valtarget ,res)))) + (if (> handler-level hlevel) + (emit `(leave ,(- handler-level hlevel)))) + (emit `(goto ,endl)))))) ((label symboliclabel) (if (eq? (car e) 'symboliclabel) (if (has? label-level (cadr e)) From d8b2553ce82f3ace5da79ca2683b4e42126c1616 Mon Sep 17 00:00:00 2001 From: Timo Kluck Date: Mon, 14 Aug 2017 22:07:33 +0200 Subject: [PATCH 02/15] Implement findnext() in terms of for/else and break-with-value Because a new language feature should probably be used in the standard library, to encourage cargo-culting. --- base/array.jl | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/base/array.jl b/base/array.jl index 300512537ad8d..dcd3e5ded1fb4 100644 --- a/base/array.jl +++ b/base/array.jl @@ -1251,10 +1251,11 @@ julia> findnext(A,3) function findnext(A, start::Integer) for i = start:length(A) if A[i] != 0 - return i + break i end + else + 0 end - return 0 end """ @@ -1301,10 +1302,11 @@ julia> findnext(A,4,3) function findnext(A, v, start::Integer) for i = start:length(A) if A[i] == v - return i + break i end + else + 0 end - return 0 end """ findfirst(A, v) @@ -1350,10 +1352,11 @@ julia> findnext(isodd, A, 2) function findnext(testf::Function, A, start::Integer) for i = start:length(A) if testf(A[i]) - return i + break i end + else + 0 end - return 0 end """ @@ -1399,9 +1402,10 @@ julia> findprev(A,1) """ function findprev(A, start::Integer) for i = start:-1:1 - A[i] != 0 && return i + A[i] != 0 && break i + else + 0 end - return 0 end """ From 8a8d4cc450fea8d0c43dddf170b16f66ef3d9dfe Mon Sep 17 00:00:00 2001 From: Timo Kluck Date: Tue, 15 Aug 2017 22:37:02 +0200 Subject: [PATCH 03/15] Fix break-with-value in case the value expression is a symbol Before 976ff845327299, a parsed `break` expression `'(break)` was being augmented to `'(break