Commit | Line | Data |
---|---|---|
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)))) |