Exported 'loop' from Parenscript package, moved utility functions to src/utils.lisp.
[clinton/parenscript.git] / src / lib / ps-loop.lisp
CommitLineData
e4ee1b86
DG
1(in-package :parenscript)
2
e4ee1b86
DG
3(defun complex-js-expr? (expr)
4 (if (symbolp expr)
5 (find #\. (symbol-name expr))
6 (consp expr)))
7
8(defvar *loop-clauses* '(:for :do :when :unless :initially :finally :first-time :last-time :while :until))
9(defvar *loop-keywords*
10 (append *loop-clauses* '(:from :to :below :downto :above :by :in :across :index := :then :sum :into)))
11
12(defun normalize-loop-keywords (args)
13 (mapcar (lambda (x)
14 (or (find-if (lambda (key) (eq x (intern (string key))))
15 *loop-keywords*)
16 x))
17 args))
18
19(defun parse-js-loop (terms)
20 (let (prologue
21 init-step-forms end-test-forms
22 initially finally
23 first-time last-time
24 body)
25 (macrolet ((with-local-var ((name expr) &body body)
26 (once-only (expr)
27 `(let ((,name (aif (and (complex-js-expr? ,expr) (ps-gensym))
28 (progn (push (list 'var it ,expr) prologue)
29 it)
30 ,expr)))
31 ,@body))))
32 (labels ((next ()
33 (car terms))
34 (next? (term)
35 (eq (next) term))
36 (err (expected got)
37 (error "JS-LOOP expected ~s, got ~s." expected got))
38 (consume (&optional what)
39 (let ((term (pop terms)))
40 (when (and what (not (eq what term)))
41 (err what term))
42 term))
43 (consume-atom ()
44 (if (atom (next))
45 (consume)
46 (err "an atom" (next))))
47 (consume-progn ()
48 (cons 'progn (loop :collect (if (consp (next))
49 (consume)
50 (err "a compound form" (next)))
51 :until (atom (next)))))
52 (consume-if (term)
53 (when (next? term)
54 (consume)
55 (consume)))
56 (body-clause (term)
57 (case term
58 ((:when :unless) (list (intern (symbol-name term))
59 (consume)
60 (body-clause (consume-atom))))
61 (:sum (let ((sum-expr (consume)))
62 (consume :into)
63 (let ((sum-var (consume-atom)))
64 (push `(var ,sum-var 0) prologue)
65 `(incf ,sum-var ,sum-expr))))
66 (:do (consume-progn))
67 (otherwise (err "a JS-LOOP keyword" term))))
68 (for-from (var)
69 (let ((start (consume))
70 (op '+)
71 (test nil)
72 (by nil)
73 (end nil))
74 (loop while (member (next) '(:to :below :downto :above :by)) do
75 (let ((term (consume)))
76 (if (eq term :by)
77 (setf by (consume))
78 (setf op (case term ((:downto :above) '-) (otherwise '+))
79 test (case term (:to '>) (:below '>=) (:downto '<) (:above '<=))
80 end (consume)))))
81 (push `(,var ,start (,op ,var ,(or by 1))) init-step-forms)
82 (when test
83 (with-local-var (end-var end)
84 (push (list test var end-var) end-test-forms)))))
85 (for-= (var)
86 (let ((start (consume))
87 (then (consume-if :then)))
88 (push (list var start (or then start)) init-step-forms)))
89 (for-in (var)
90 (with-local-var (arr (consume))
91 (let* ((index (or (consume-if :index) (ps-gensym)))
92 (equiv `(:for ,index :from 0 :below (length ,arr)
93 :for ,var := (aref ,arr ,index))))
94 (setf terms (append equiv terms))
95 (clause)
96 (clause))))
97 (for-clause ()
98 (let ((var (consume-atom))
99 (term (consume-atom)))
100 (case term
101 (:from (for-from var))
102 (:= (for-= var))
103 ((:in :across) (for-in var))
104 (otherwise (error "FOR ~s ~s is not valid in JS-LOOP." var term)))))
105 (clause ()
106 (let ((term (consume-atom)))
107 (case term
108 (:for (for-clause))
109 (:while (push `(unless ,(consume) break) body))
110 (:until (push `(when ,(consume) break) body))
111 (:initially (push (consume-progn) initially))
112 (:finally (push (consume-progn) finally))
113 (:first-time (push (consume-progn) first-time))
114 (:last-time (push (consume-progn) last-time))
115 (otherwise (push (body-clause term) body))))))
116 (if terms
117 (loop :while terms :do (clause))
118 (err "loop definition" nil))
119 (values (nreverse prologue)
120 (nreverse init-step-forms)
121 (aif (nreverse end-test-forms)
122 (if (cdr it)
123 (list (cons 'or it))
124 it)
125 (list nil))
126 (nreverse initially)
127 (nreverse finally)
128 (nreverse first-time)
129 (nreverse last-time)
130 (nreverse body))))))
131
132(defpsmacro loop (&rest args)
133 (multiple-value-bind (prologue
134 init-step-forms end-test-forms
135 initially finally
136 first-time last-time
137 body)
138 (parse-js-loop (normalize-loop-keywords args))
139 (let ((first-guard (and first-time (ps-gensym)))
140 (last-guard (and last-time (ps-gensym))))
141 `(progn ,@(when first-time `((var ,first-guard t)))
142 ,@(when last-time `((var ,last-guard nil)))
143 ,@prologue
144 ,@initially
ee03fd80
DG
145 (do* ,init-step-forms
146 ,end-test-forms
e4ee1b86
DG
147 ,@(when first-time
148 `((when ,first-guard
149 ,@first-time
150 (setf ,first-guard nil))))
151 ,@body
152 ,@(when last-time
153 `((setf ,last-guard t))))
154 ,@(when last-time `((when ,last-guard ,@last-time)))
155 ,@finally))))