-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathiter2.el
1010 lines (914 loc) · 62.2 KB
/
iter2.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; iter2.el --- Reimplementation of Elisp generators -*- lexical-binding: t -*-
;;; Copyright (C) 2017-2025 Paul Pogonyshev
;; Author: Paul Pogonyshev <[email protected]>
;; Maintainer: Paul Pogonyshev <[email protected]>
;; Version: 2.3snapshot
;; Keywords: elisp, extensions
;; Homepage: https://github.com/doublep/iter2
;; Package-Requires: ((emacs "25.1"))
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation, either version 3 of
;; the License, or (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see http://www.gnu.org/licenses.
;;; Commentary:
;; Fully compatible fast reimplementation of `generator' built-in
;; Emacs package. This library provides `iter2-defun` and
;; `iter2-lambda` forms that can be used in place of `iter-defun` and
;; `iter-lambda`. Form `iter2-next` is a replacement for `iter-next`
;; (see its documentation for the reasons), though `iter-next` will
;; work too.
;;
;; Other functions and macros (`iter-yield`, `iter-yield-from`,
;; `iter-do` and `iter-close`) are intentionally not duplicated: just
;; use the ones from the original package.
;;; Code:
;; For `iter-yield' etc. from the original `generator' package.
(require 'generator)
(require 'macroexp)
(require 'subr-x)
(require 'bytecomp)
;; See the usage below.
(defvar byte-compile-bound-variables)
(defgroup iter2 nil
"Reimplementation of Elisp generators"
:group 'lisp)
(defcustom iter2-detect-nested-lambda-yields nil
"If non-nil, detect non-working yields in nested lambdas.
Due to the way `iter2' (or original `generator', for that matter)
works, it is impossible to yield from nested lambdas: only from
the main function. Such `iter-yield' invocations will fail at
runtime.
When this variable is set, `iter2' tries to detect such problems
during conversion. However, this test is not enabled by default
because it might, in principle, give false positives if you never
call said lambda. It also negatively affects performance.
On the other hand, this might be useful, as it is very easy to
accidentally try to yield from a macro-generated lambda, e.g.:
# Replace each element with what `iter-yield' returns. Not
# obvious that this form will not work.
(setf list (--map (iter-yield it) list))"
:type 'boolean
:group 'iter2)
(defvar iter2-generate-tracing-functions nil
"Set to non-nil to always generate tracing functions.
Normally, only `iter2-tracing-defun' and `iter2-tracing-lambda'
will do this. But if this flag is set, `iter2-defun' and
`iter2-lambda' will behave similarly.")
(defvar iter2-tracing-print-level t
"Value used for `print-level' when tracing generator functions.
If set to t, value of `print-level' at the time of tracing is
preserved. Otherwise, it is overwritten with the value of this
variable.")
(defvar iter2-tracing-print-length t
"Value used for `print-length' when tracing generator functions.
If set to t, value of `print-length' at the time of tracing is
preserved. Otherwise, it is overwritten with the value of this
variable.")
(defvar iter2-tracing-function #'iter2-log-message
"Function called to trace iterator execution.
The function must accept the same arguments as built-in
`message', but is not restricted in what it does with the
messages. If the value is nil, tracing is disabled even for
generator functions that are supposed to trace.")
(defvar iter2--tracing-depth 0)
(defvar iter2--value nil)
(defvar iter2--continuations nil)
(defvar iter2--cleanups nil)
(defvar iter2--stack nil)
(defvar iter2--yielding nil)
(defvar iter2--done nil)
(defvar iter2--stack-state nil)
(defvar iter2--catcher nil)
(defvar iter2--cleanups-used nil)
(defvar iter2--converter-depth 0)
(defmacro iter2-defun (name arglist &rest body)
"Create a generator function NAME.
When called, generator returns an iterator object. Successive
values can be retrieved from an iterator using `iter-next'.
From generator perspective, BODY is executed whenever a new
iterator object is created. At each point `iter-yield' is called
in the body, evaluation stops, and is resumed at exactly the same
point with the same state (both local and global variables) if
`iter-next' is used to query another value.
Iterator objects must be closed with `iter-close' unless they are
fully exhausted, i.e. signal `iter-end-of-sequence'. Calling
`iter-close' on an exhausted iterator object or even several
times is not an error."
(declare (debug defun) (indent 2) (doc-string 3))
(let ((parsed-body (macroexp-parse-body body)))
`(defun ,name ,arglist
,@(car parsed-body)
,(iter2--convert-function-body (cdr parsed-body) iter2-generate-tracing-functions))))
(defmacro iter2-lambda (arglist &rest body)
"Create an anonymous generator function.
See `iter2-defun' for details."
(declare (debug lambda) (indent 1) (doc-string 2))
(let ((parsed-body (macroexp-parse-body body)))
`(lambda ,arglist
,@(car parsed-body)
,(iter2--convert-function-body (cdr parsed-body) iter2-generate-tracing-functions))))
(defmacro iter2-tracing-defun (name arglist &rest body)
"Create a tracing generator function NAME.
See `iter2-defun' for details."
(declare (debug defun) (indent 2) (doc-string 3))
(let ((iter2-generate-tracing-functions t))
(macroexpand-1 `(iter2-defun ,name ,arglist ,@body))))
(defmacro iter2-tracing-lambda (arglist &rest body)
"Create a tracing anonymous generator function NAME.
See `iter2-defun' for details."
(declare (debug lambda) (indent 1) (doc-string 2))
(let ((iter2-generate-tracing-functions t))
(macroexpand-1 `(iter2-lambda ,arglist ,@body))))
(defmacro iter2-next (iterator &optional yield-result)
"Similar to `iter-next', but can inject errors into the ITERATOR.
Can only be used with `iter2' iterators, not those of original
`generator' package (`iter-next' works with either, but has less
functionality).
If form YIELD-RESULT exits nonlocally (using `signal', `throw' or
any derived mechanism), this nonlocal exit is “injected” into the
generator function. It then works as if the exit was triggered
by the last issued `(iter-yield ...)' form. If the function has
an appropriate `condition-case' or `catch' handler, the control
is subsequently transferred to it.
If YIELD-RESULT never exits nonlocally, behavior of this macro is
identical to that of `iter-next' (with the exception that the
iterator must be created with `iter2')."
`(funcall ,iterator :iter2-next (lambda () ,@(macroexp-unprogn yield-result))))
(defun iter2--literalp (x)
"Determine if X involves no evaluation."
(if (atom x)
(or (not (symbolp x))
(memq x '(nil t))
(keywordp x))
;; Don't check for wrong forms here: just let it fail later in such a case.
(memq (car x) '(quote function))))
(defun iter2--literal-or-variable-p (x)
(or (atom x) (memq (car x) '(quote function))))
;; During byte-compilation, function `special-variable-p' won't give intended results for
;; variables declared in the file being compiled. E.g. byte-compiler itself used (pre-29)
;; to use `byte-compile-not-lexical-var-p' to decide how to compile let-bindings. Of
;; course, they had to remove the function, even if it wass not private according to Elisp
;; naming conventions. It seems we still can use variable `byte-compile-bound-variables'
;; at least.
(defun iter2--special-variable-p (var &optional warn)
(cond ((boundp 'byte-compile-bound-variables)
(or (special-variable-p var) (memq var byte-compile-bound-variables)))
(t
(when warn
(warn "Variable `byte-compile-bound-variables' is missing, special variable bindings in byte-compiled `iter2-defun' might misbehave"))
(special-variable-p var))))
(defmacro iter2--convert-progn (forms)
(declare (debug (form)))
`(iter2--convert-form (macroexp-progn ,forms)))
(defmacro iter2--add-converted-form (place converted-form)
(declare (debug (place form)))
`(setf ,place (nconc (reverse (macroexp-unprogn ,converted-form)) ,place)))
(defmacro iter2--finish-chunk (converted-chunks converted &rest next-chunk-forms)
(declare (debug (place place &rest form)))
`(setf ,converted-chunks (cons (macroexp-progn (nreverse ,converted)) ,converted-chunks)
,converted ,(when next-chunk-forms `(list ,@next-chunk-forms))))
(defsubst iter2--identity-bind (value)
"Create an `identity'-like function and bind it with given VALUE."
(lambda () value))
(defun iter2--convert-function-body (body &optional tracing)
(unless lexical-binding
(error "Generator functions require lexical binding"))
(let* ((iter2-generate-tracing-functions tracing)
(iter2--value (make-symbol "$value"))
(iter2--continuations (make-symbol "$continuations"))
(iter2--cleanups (make-symbol "$cleanups"))
(iter2--stack (make-symbol "$stack"))
(iter2--yielding (make-symbol "$yielding"))
(iter2--done (make-symbol "$done"))
(iter2--stack-state (make-symbol "$stack-state"))
(iter2--catcher (make-symbol "$catcher"))
(iter2--cleanups-used nil)
(apply-debugger (lambda (&rest forms) forms)))
(pcase body
(`((edebug-enter ,edebug-name ,edebug-args (function (lambda () . ,real-body))))
;; This is a hack, but since Emacs code (Edebug in this case) is pretty stable, I'm
;; sure it will keep working. The idea is to invoke `edebug-enter' not when the
;; function is first called (this creates and returns an iterator object and
;; doesn't involve user code at all), but instead when it receives control after
;; `iter-next' or `iter-yield' call. This also solves the issue with form
;; conversion: normally `iter2--convert-form' doesn't recurse into nested lambdas.
(setq body real-body
apply-debugger (lambda (&rest forms) `((edebug-enter ,edebug-name ,edebug-args (function (lambda () ,@forms))))))))
;; Need to convert the body now, since this affects at least `iter2--cleanups-used'.
(let ((converted (iter2--convert-progn body)))
`(let (,iter2--continuations
,@(when iter2--cleanups-used (list iter2--cleanups))
,iter2--stack
,iter2--yielding)
;; Must not be moved to `let' above, since the lambda accesses the other
;; variables declared there.
(setq ,iter2--continuations (list (lambda (,iter2--value) ,@(macroexp-unprogn (iter2--merge-continuation-form converted)))))
(lambda (operation value)
(cond ((or (eq operation :iter2-next) ; Make it faster for our own extension.
(when (eq operation :next) ; But keep it compatible with `generator'.
(setq value (iter2--identity-bind value))
;; This t is only to make byte-compiled code a bit more efficient, because otherwise
;; (as of Emacs 29) it doesn't understand that this branch is always truthy.
t))
,@(funcall apply-debugger
;; Rewritten in a somewhat weird form to maximize performance.
`(while (progn (setq value ,(iter2--continuation-invocation-form 'value `(or (pop ,iter2--continuations)
(signal 'iter-end-of-sequence (funcall value)))))
(not ,iter2--yielding))
(setq value (iter2--identity-bind value)))
`(setq ,iter2--yielding nil)
`value))
((eq operation :close)
,@(funcall apply-debugger
(if iter2--cleanups-used
`(let ((cleanups ,iter2--cleanups))
(setq ,iter2--continuations nil
,iter2--cleanups nil
,iter2--stack nil)
(if cleanups
(iter2--do-clean-up cleanups)))
`(setq ,iter2--continuations nil
,iter2--stack nil))))
(t (error "Unknown iterator operation %S" operation))))))))
(defsubst iter2--do-macroexpand (form)
;; Prevent `macroexpand' from expanding macros for which we have special handling.
(macroexpand form '((save-match-data . nil))))
;; Returns (CONVERTED-FORM . CONTINUATION-FORM)
;;
;; if CONVERTED-FORM never yields, CONTINUATION-FORM is nil. CONTINUATION-FORM itself
;; never yields.
;;
;; Since this function is recursive, it can certainly run out of stack
;; on complicated forms if not byte-compiled.
(defun iter2--convert-form (form)
(if (atom form)
;; Speed optimizations, also simplifies debugging a bit.
(cons form nil)
(let ((body (macroexp-unprogn form))
can-yield
converted
converted-chunks)
(while body
(let ((form (iter2--do-macroexpand (pop body))))
;; Simplify certain forms, rewrite certain others using
;; special forms that we handle below.
(while (let ((rewritten-form
(pcase form
(`(and) t)
(`(or) nil)
(`(,(or 'and 'or) ,only-condition) (iter2--do-macroexpand only-condition))
(`(cond) nil)
(`(cond (,only-condition)) (iter2--do-macroexpand only-condition))
(`(cond (,only-condition . ,body)) `(if ,only-condition ,(macroexp-progn body)))
(`(,(or 'let 'let*) () . ,let-body) (setq body (append (cdr let-body) body)) (iter2--do-macroexpand (car let-body)))
(`(,(or 'progn 'inline)) nil)
(`(,(or 'progn 'inline 'prog1) ,only-form) (iter2--do-macroexpand only-form))
(`(,(or 'progn 'inline) ,first . ,others) (setq body (append others body)) (iter2--do-macroexpand first))
(`(prog1 ,value . ,rest) (if body
;; This value is not going to be used anyway,
;; so just inline this into `body'.
(progn (setq body (append rest body)) (iter2--do-macroexpand value))
;; Do nothing.
form))
(`(prog2 ,first ,second . ,others) `(prog1 (progn ,first ,second) ,@others))
(`(unwind-protect ,body-form) (iter2--do-macroexpand body-form))
(`(condition-case ,_ ,body-form) (iter2--do-macroexpand body-form))
(_ form))))
(if (eq form rewritten-form)
nil
(setq form rewritten-form))))
(pcase form
;; Handle nested lambdas; optionally check them for yields.
(`(function (lambda ,_lambda-args . ,lambda-body))
;; Could write a faster function here, but probably not performance-critical.
(when (and iter2-detect-nested-lambda-yields (cdr (iter2--convert-progn lambda-body)))
(error "Nested anonymous function %S yields, which will fail at runtime" (cadr form)))
(push form converted))
;; Handle quoting ('_ and #'_): just pass it through.
(`(,(or 'quote 'function) ,_)
(push form converted))
;; Handle (and CONDITIONS...) and (or CONDITIONS...).
(`(,(and (or 'and 'or) operator) . ,conditions)
(let (plain-conditions)
(while conditions
(let* ((converted-condition (iter2--convert-form (pop conditions)))
(converted-condition-form (car converted-condition)))
(if (cdr converted-condition)
(progn
(if conditions
(let ((converted-continuation (iter2--convert-form `(,operator ,(cdr converted-condition) ,@conditions))))
(setq converted-condition-form `(progn ,(iter2--continuation-adding-form (list (iter2--merge-continuation-form converted-continuation)))
,@(macroexp-unprogn converted-condition-form))))
(setq converted-condition-form (iter2--merge-continuation-form converted-condition)))
(when plain-conditions
(setq converted-condition-form `(,operator ,@(nreverse plain-conditions) ,converted-condition-form)))
(iter2--add-converted-form converted converted-condition-form)
(setq can-yield t
conditions nil))
(push converted-condition-form plain-conditions))))
(unless can-yield
(push `(,operator ,@(nreverse plain-conditions)) converted))))
;; Handle (if CONDITION THEN [ELSE...]).
(`(if ,condition ,then . ,else)
(let ((converted-condition (iter2--convert-form condition))
(converted-then (iter2--convert-form then))
(converted-else (iter2--convert-progn else)))
(if (cdr converted-condition)
(progn (iter2--add-converted-form converted (car converted-condition))
(iter2--finish-chunk converted-chunks converted
`(if ,(cdr converted-condition)
,(iter2--merge-continuation-form converted-then)
,@(when else (macroexp-unprogn (iter2--merge-continuation-form converted-else))))))
(push `(if ,(car converted-condition)
,(iter2--merge-continuation-form converted-then)
,@(when else (macroexp-unprogn (iter2--merge-continuation-form converted-else))))
converted))
(setq can-yield (or (cdr converted-then) (cdr converted-else)))))
;; Handle (cond [CLAUSES...]).
(`(cond . ,clauses)
(let (converted-clauses
conditions-can-yield)
(while clauses
(let* ((clause (pop clauses))
(converted-condition (iter2--convert-form (car clause)))
(converted-condition-form (car converted-condition))
(clause-body (cdr clause)))
(if (cdr converted-condition)
(let ((converted-continuation (iter2--convert-form `(cond (,(cdr converted-condition) ,@clause-body) ,@clauses))))
(setq converted-condition-form `(progn ,(iter2--continuation-adding-form (list (iter2--merge-continuation-form converted-continuation)))
,@(macroexp-unprogn converted-condition-form)))
(when converted-clauses
(setq converted-condition-form `(cond ,@(nreverse converted-clauses) (t ,@(macroexp-unprogn converted-condition-form)))))
(iter2--add-converted-form converted converted-condition-form)
(setq conditions-can-yield t
clauses nil))
(let ((converted-body (when clause-body (iter2--convert-progn clause-body))))
(push `(,(car converted-condition) ,@(when clause-body (macroexp-unprogn (iter2--merge-continuation-form converted-body))))
converted-clauses)
(when (cdr converted-body)
(setq can-yield t))))))
(if conditions-can-yield
(setq can-yield t)
(push `(cond ,@(nreverse converted-clauses)) converted))))
;; Handle (while CONDITION [WHILE-BODY...]).
(`(while ,condition . ,while-body)
(let* ((converted-condition (iter2--convert-form condition))
(converted-while-body (when while-body (iter2--convert-progn while-body))))
(if (or (cdr converted-condition) (cdr converted-while-body))
;; One special case is optimized: if there is no `while' body and the condition yields as
;; its last operator.
(let ((special-empty-body (and (null while-body) (equal (cdr converted-condition) `(funcall ,iter2--value)))))
(when while-body
(setq converted-while-body (iter2--merge-continuation-form converted-while-body)))
(if (cdr converted-condition)
;; Condition yields; whether body yields too is not relevant.
(let ((inner-form `(if ,(cdr converted-condition)
(progn (setq ,iter2--continuations (cons (car ,iter2--stack) ,iter2--continuations))
,@(macroexp-unprogn (if special-empty-body (car converted-condition) converted-while-body)))
(setq ,iter2--stack (cdr ,iter2--stack)))))
(push (iter2--continuation-adding-form (list (if special-empty-body
inner-form
`(progn ,(iter2--continuation-adding-form (list inner-form))
,@(macroexp-unprogn (car converted-condition)))))
iter2--stack)
converted))
;; Only body yields.
(push (iter2--continuation-adding-form (list `(if ,(car converted-condition)
(progn (setq ,iter2--continuations (cons (car ,iter2--stack) ,iter2--continuations))
,@(macroexp-unprogn converted-while-body))
(setq ,iter2--stack (cdr ,iter2--stack))))
iter2--stack)
converted))
;; Initial value is important in some cases, see above.
(push (iter2--continuation-invocation-form (when special-empty-body '(lambda () t)) `(car ,iter2--stack)) converted)
(setq can-yield t))
;; Nothing yields, the simplest case.
(push `(while ,(car converted-condition) ,@(when while-body (macroexp-unprogn (car converted-while-body)))) converted))))
;; Handle (let (BINDINGS) LET-BODY) and (let* (BINDINGS) LET-BODY).
(`(,(and (or 'let 'let*) let-kind) ,bindings . ,let-body)
(let ((plain-let (eq let-kind 'let))
converted-bindings
catcher-outer-bindings
catcher-inner-bindings
catcher-value-storing-forms
;; The rest are unused for `let*'.
next-continuation-bindings
values-to-save-on-stack
(num-stack-values 0))
(while bindings
(let* ((binding (pop bindings))
var
value)
(pcase binding
((pred symbolp)
(setq var binding))
(`(,(and (pred symbolp) variable))
(setq var variable))
(`(,(and (pred symbolp) variable) ,value-form)
(setq var variable
value value-form))
(_
(error "Erroneous binding %S" binding)))
(let ((special (iter2--special-variable-p var))
(literal (iter2--literalp value)))
(cond (literal
(push binding converted-bindings)
(when plain-let
(push binding next-continuation-bindings)))
((eq value iter2--stack-state)
;; This is our private internal flag that means "take a
;; previously computed value from the stack". Stack is only
;; used for plain `let', never for `let*'.
(push `(,var (pop ,iter2--stack)) converted-bindings)
(push `(,var ,iter2--stack-state) next-continuation-bindings)
(setq num-stack-values (1+ num-stack-values)))
(t
(let* ((converted-value (iter2--convert-form value))
(converted-value-form (car converted-value)))
(if (cdr converted-value)
(progn (if (or plain-let (null converted-bindings))
;; Yielding before anything is bound.
(progn (when values-to-save-on-stack
(push (iter2--stack-adding-form (nreverse values-to-save-on-stack)) converted))
(iter2--add-converted-form converted converted-value-form)
(iter2--finish-chunk converted-chunks converted
(iter2--merge-continuation-form (iter2--convert-form `(,let-kind (,@(nreverse next-continuation-bindings)
(,var ,(cdr converted-value))
,@bindings)
,@let-body)))))
;; We need to bind already converted values now.
(push (iter2--let*-yielding-form catcher-outer-bindings catcher-inner-bindings catcher-value-storing-forms
(iter2--merge-continuation-form (iter2--convert-form `(let* ((,var (funcall ,iter2--value)) ,@bindings) ,@let-body)))
(iter2--merge-continuation-form converted-value))
converted))
(setq bindings nil
can-yield t))
(push `(,var ,converted-value-form) converted-bindings)
(when plain-let
(push `(,var ,iter2--stack-state) next-continuation-bindings)
(push converted-value-form values-to-save-on-stack))))))
(if special
(let ((private-storage-var (make-symbol (format "$%s" (symbol-name var)))))
(push `(,private-storage-var ,(nth 1 (car converted-bindings))) catcher-outer-bindings)
(push `(,var ,private-storage-var) catcher-inner-bindings)
(push `(setq ,private-storage-var ,var) catcher-value-storing-forms))
(push (car converted-bindings) catcher-outer-bindings))
;; This is a marker we use to separate bindings for different
;; catchers for `let*'.
(when (and special (not plain-let))
(push nil catcher-outer-bindings)))))
(unless can-yield
(when (> num-stack-values 1)
(push (iter2--stack-head-reversing-form num-stack-values) converted))
(let* ((converted-let-body (iter2--convert-progn let-body))
(converted-let-body-form (iter2--merge-continuation-form converted-let-body)))
(if (or (null (cdr converted-let-body)) (null catcher-inner-bindings))
(progn (push `(,let-kind (,@(nreverse converted-bindings)) ,@(macroexp-unprogn converted-let-body-form)) converted)
(setq can-yield (cdr converted-let-body)))
;; Let body yields and we have special (dynamic) bindings.
(push (if plain-let
`(let (,@(nreverse catcher-outer-bindings))
,(iter2--catcher-continuation-adding-form `(let (,@(nreverse catcher-inner-bindings))
(prog1 (unwind-protect
,(iter2--continuation-invocation-form iter2--value)
,@(nreverse catcher-value-storing-forms))
(unless (eq ,iter2--continuations ,iter2--done)
(push ,iter2--catcher ,iter2--continuations))))
converted-let-body-form))
(iter2--let*-yielding-form catcher-outer-bindings catcher-inner-bindings catcher-value-storing-forms converted-let-body-form))
converted)
(setq can-yield t))))))
;; Handle (prog1 VALUE [BODY...]).
(`(prog1 ,value . ,rest)
(let ((converted-value (iter2--convert-form value))
(converted-rest (iter2--convert-progn rest)))
(if (or (cdr converted-value) (cdr converted-rest))
(progn (when (cdr converted-value)
(iter2--add-converted-form converted (car converted-value))
(iter2--finish-chunk converted-chunks converted))
(if (cdr converted-rest)
(progn (push `(push ,(or (cdr converted-value) (car converted-value)) ,iter2--stack) converted)
(iter2--add-converted-form converted (car converted-rest))
(iter2--finish-chunk converted-chunks converted
;; See comments about `iter2-next' elsewhere.
`(pop ,iter2--stack) `(funcall ,iter2--value)))
(push `(prog1 ,(cdr converted-value) ,@rest) converted)))
(push `(prog1 ,(car converted-value) ,@(macroexp-unprogn (car converted-rest))) converted))))
;; Handle (unwind-protect BODY-FORM CLEANUP-FORMS...).
(`(unwind-protect ,body-form . ,cleanup-forms)
(let ((converted-body-form (iter2--convert-form body-form))
(converted-cleanup-forms (iter2--convert-progn cleanup-forms)))
(when (cdr converted-cleanup-forms)
(error "Yielding from cleanup forms in `unwind-protect' is not allowed: %S" cleanup-forms))
(if (cdr converted-body-form)
(progn
(push `(setq ,iter2--cleanups (cons (lambda () ,@(macroexp-unprogn (car converted-cleanup-forms))) ,iter2--cleanups)) converted)
;; No need to use private variable names as we don't include any user code.
(push (iter2--catcher-continuation-adding-form `(let (result)
(unwind-protect
(prog1 ,(iter2--continuation-invocation-form iter2--value)
(setq result (if (eq ,iter2--continuations ,iter2--done) ,iter2--yielding 'continuing)))
(if result
(push (if (eq result t)
;; Completed body, but yielded. Clean up when control is regained.
(lambda (,iter2--value) ,(iter2--cleanup-invocation-body) (funcall ,iter2--value))
;; Continuing. Re-add self.
,iter2--catcher)
,iter2--continuations)
;; Either exited non-locally, or completed body and haven't yielded.
,(iter2--cleanup-invocation-body))))
(iter2--merge-continuation-form converted-body-form))
converted)
(setq can-yield t
iter2--cleanups-used t))
(push `(unwind-protect ,(car converted-body-form) ,@(macroexp-unprogn (car converted-cleanup-forms))) converted))))
;; Handle (catch TAG BODY).
(`(catch ,tag . ,catch-body)
(let* ((converted-tag (iter2--convert-form tag))
(converted-tag-form (car converted-tag)))
(when (cdr converted-tag)
(iter2--add-converted-form converted converted-tag-form)
(iter2--finish-chunk converted-chunks converted)
(setq converted-tag-form (cdr converted-tag)))
(let ((converted-catch-body (iter2--convert-progn catch-body)))
(if (cdr converted-catch-body)
(let ((literal-tag (iter2--literalp converted-tag-form)))
;; No need to use private variable names as we don't include any user code.
(push (iter2--catcher-continuation-adding-form `(let (completed-normally)
(prog1 (catch ,(if literal-tag converted-tag-form 'tag)
(prog1 ,(iter2--continuation-invocation-form iter2--value)
(setq completed-normally t)
(unless (eq ,iter2--continuations ,iter2--done)
(push ,iter2--catcher ,iter2--continuations))))
(unless completed-normally
(setq ,iter2--continuations ,iter2--done
,iter2--stack ,iter2--stack-state))))
(iter2--merge-continuation-form converted-catch-body)
`(,iter2--stack-state ,iter2--stack)
(unless literal-tag `(tag ,converted-tag-form)))
converted)
(setq can-yield t))
(push `(catch ,converted-tag-form ,@(macroexp-unprogn (car converted-catch-body))) converted)))))
;; Handle (condition-case VAR BODY-FORM HANDLERS...).
(`(condition-case ,var ,body-form . ,handlers)
(let ((converted-body (iter2--convert-form body-form))
converted-handlers
success-handler)
(dolist (handler handlers)
(pcase handler
(`(,condition . ,handler-body)
(let* ((converted-handler (iter2--convert-progn handler-body))
(converted-handler-form `(,condition ,@(macroexp-unprogn (iter2--merge-continuation-form converted-handler)))))
;; Emacs 28 adds special handler called `:success' invoked when the main body doesn't
;; signal. It has to be handled specially, but only if the body yields.
(if (and (eq condition :success) (cdr converted-body))
(progn (when success-handler
(error "Duplicate special handler `:success' in %S" form))
(setf success-handler converted-handler-form))
(push converted-handler-form converted-handlers))
(when (cdr converted-handler)
(setq can-yield t))))
(_
(error "Invalid `condition-case' error handler: %S" handler))))
(setq converted-handlers (nreverse converted-handlers))
(if (cdr converted-body)
(let (success-var
condition-case-body)
(if success-handler
(setf success-var (make-symbol "$success")
condition-case-body `(prog1 ,(iter2--continuation-invocation-form iter2--value)
(if (eq ,iter2--continuations ,iter2--done)
;; The reason for `success-var' and not evaluating `success-handler'
;; is not directly here is that it must not be placed inside
;; `condition-case' so that it never triggers other handlers.
(setq ,success-var t)
(push ,iter2--catcher ,iter2--continuations))))
(setf condition-case-body `(prog1 ,(iter2--continuation-invocation-form iter2--value)
(unless (eq ,iter2--continuations ,iter2--done)
(push ,iter2--catcher ,iter2--continuations)))))
(let ((condition-case-form condition-case-body))
;; This check may look strange, but if the only handler is `:success', `converted-handlers' will be nil.
(when converted-handlers
(setf condition-case-form `(condition-case ,var
,condition-case-body
,@(mapcar (lambda (handler)
`(,(car handler)
(setq ,iter2--continuations ,iter2--done ,iter2--stack ,iter2--stack-state)
,@(cdr handler)))
converted-handlers))))
(when success-handler
(let ((body-value-var (make-symbol "$body-value")))
(setf condition-case-form `(let* (,success-var
(,body-value-var ,condition-case-form))
(if ,success-var
,(iter2--merge-continuation-form (cdr success-handler))
,body-value-var)))))
(push (iter2--catcher-continuation-adding-form condition-case-form
(iter2--merge-continuation-form converted-body)
`(,iter2--stack-state ,iter2--stack))
converted))
(setq can-yield t))
(push `(condition-case ,var ,(car converted-body) ,@converted-handlers) converted))))
;; Handle (iter-yield VALUE).
(`(iter-yield . ,rest)
(unless (and (consp rest) (null (cdr rest)))
(error "Form `iter-yield' must be used with exactly one argument: %S" form))
(let* ((converted-value (iter2--convert-form (car rest)))
(converted-value-form (car converted-value)))
(when iter2-generate-tracing-functions
(setq converted-value-form `(let ((,iter2--value ,converted-value-form))
(iter2--do-trace "yielding %S" ,iter2--value)
,iter2--value)))
(when (cdr converted-value)
(iter2--add-converted-form converted converted-value-form)
(iter2--finish-chunk converted-chunks converted)
(setq converted-value-form (cdr converted-value)))
(if (iter2--literal-or-variable-p converted-value-form)
(progn (push `(setq ,iter2--yielding t) converted)
(push converted-value-form converted))
;; It might be unsafe to set yielding flag first: what if the value
;; form exits non-locally?
(push `(prog1 ,converted-value-form (setq ,iter2--yielding t)) converted))
(setq can-yield t)))
;; Handle `save-excursion'.
(`(save-excursion . ,body)
(let ((converted-body (iter2--convert-progn body)))
(if (cdr converted-body)
(progn (push (iter2--catcher-continuation-adding-form `(save-excursion
;; Byte compiler gives a dumb warning here,
;; suggesting to use `with-current-buffer'.
(with-no-warnings (set-buffer buffer))
(goto-char point)
(prog1 ,(iter2--continuation-invocation-form iter2--value)
(unless (eq ,iter2--continuations ,iter2--done)
(setq buffer (current-buffer)
point (point))
(push ,iter2--catcher ,iter2--continuations))))
(iter2--merge-continuation-form converted-body)
'(buffer (current-buffer))
'(point (point)))
converted)
(setq can-yield t))
(push `(save-excursion ,@(macroexp-unprogn (car converted-body))) converted))))
;; Handle `save-current-buffer'.
(`(save-current-buffer . ,body)
(let ((converted-body (iter2--convert-progn body)))
(if (cdr converted-body)
(progn (push (iter2--catcher-continuation-adding-form `(save-current-buffer
(set-buffer buffer)
(prog1 ,(iter2--continuation-invocation-form iter2--value)
(unless (eq ,iter2--continuations ,iter2--done)
(setq buffer (current-buffer))
(push ,iter2--catcher ,iter2--continuations))))
(iter2--merge-continuation-form converted-body)
'(buffer (current-buffer)))
converted)
(setq can-yield t))
(push `(save-current-buffer ,@(macroexp-unprogn (car converted-body))) converted))))
;; Handle `save-restriction'.
(`(save-restriction . ,body)
(let ((converted-body (iter2--convert-progn body)))
(if (cdr converted-body)
(progn (push (iter2--catcher-continuation-adding-form `(save-restriction
(narrow-to-region point-min point-max)
(prog1 ,(iter2--continuation-invocation-form iter2--value)
(unless (eq ,iter2--continuations ,iter2--done)
(setq point-min (point-min)
point-max (point-max))
(push ,iter2--catcher ,iter2--continuations))))
(iter2--merge-continuation-form converted-body)
'(point-min (point-min))
'(point-max (point-max)))
converted)
(setq can-yield t))
(push `(save-restriction ,@(macroexp-unprogn (car converted-body))) converted))))
;; Handle `save-match-data' macro: not a special form, but without special
;; handling doesn't produce intended results in generator functions.
(`(save-match-data . ,body)
(let ((converted-body (iter2--convert-progn body)))
(if (cdr converted-body)
(progn (push (iter2--catcher-continuation-adding-form `(save-match-data
(set-match-data match-data)
(prog1 ,(iter2--continuation-invocation-form iter2--value)
(unless (eq ,iter2--continuations ,iter2--done)
(match-data nil match-data)
(push ,iter2--catcher ,iter2--continuations))))
(iter2--merge-continuation-form converted-body)
'(match-data (match-data)))
converted)
(setq can-yield t))
(push `(save-match-data ,@(macroexp-unprogn (car converted-body))) converted))))
;; Handle `with-no-warnings': while not a special form, it requires special handling.
(`(with-no-warnings . ,body)
(let ((converted-body (iter2--convert-progn body)))
(push `(with-no-warnings ,@(macroexp-unprogn (iter2--merge-continuation-form converted-body))) converted)
(setq can-yield (cdr converted-body))))
;; Handle all other non-atomic forms.
(`(,name . ,arguments)
;; Several special forms are handled more-or-less like function calls.
(when (and (special-form-p name) (not (memq name '(setq setq-default throw))))
(error "Special form %S incorrect or not supported" form))
(let (converted-arguments
next-continuation-arguments
arguments-to-save-on-stack
(num-stack-arguments 0))
(while arguments
(let ((argument (pop arguments)))
(cond ((eq argument iter2--stack-state)
;; This is our private internal flag that means "take a
;; previously computed value from the stack".
(push `(pop ,iter2--stack) converted-arguments)
(push iter2--stack-state next-continuation-arguments)
(setq num-stack-arguments (1+ num-stack-arguments)))
((or (iter2--literalp argument) (and (memq name '(setq setq-default)) (= (% (length converted-arguments) 2) 0)))
(push argument converted-arguments)
(push argument next-continuation-arguments))
(t
(let ((converted-argument (iter2--convert-form argument)))
(if (cdr converted-argument)
(progn (when arguments-to-save-on-stack
(push (iter2--stack-adding-form (nreverse arguments-to-save-on-stack)) converted))
(iter2--add-converted-form converted (car converted-argument))
(push `(,name ,@(nreverse next-continuation-arguments) ,(cdr converted-argument) ,@arguments) body)
(setq arguments nil
can-yield t))
(push (car converted-argument) converted-arguments)
(push iter2--stack-state next-continuation-arguments)
(push (car converted-argument) arguments-to-save-on-stack)))))))
(unless can-yield
(when (> num-stack-arguments 1)
(push (iter2--stack-head-reversing-form num-stack-arguments) converted))
(push (cons name (nreverse converted-arguments)) converted))))
(_
(push form converted)))
(when can-yield
(iter2--finish-chunk converted-chunks converted
;; With the addition of `iter2-next', we need to resolve the incoming values
;; (wrapped in lambdas), even if the result is unused. The reason is that
;; the lambdas can exit nonlocally or have a side effect. This is not
;; possible with `iter-next', because the lambdas in this case just return a
;; precomputed value, see `iter2--identity-bind'. However, with `iter2-next'
;; they can include arbitrary forms.
`(funcall ,iter2--value))
(setq can-yield nil))))
(setq converted (nreverse converted))
(if converted-chunks
(progn (setq converted-chunks (nreverse converted-chunks))
(cons (if (cdr converted-chunks)
`(progn ,(iter2--continuation-adding-form (reverse (cdr converted-chunks)))
,@(macroexp-unprogn (car converted-chunks)))
(car converted-chunks))
(if converted (macroexp-progn converted) `(funcall ,iter2--value))))
(cons (macroexp-progn converted) nil)))))
(defun iter2--convert-form-tracer (function form)
(let ((result (funcall function form)))
(if (or (atom form) (null iter2-tracing-function))
result
(let ((indentation (make-string (* iter2--converter-depth 4) ? ))
(iter2--converter-depth (1+ iter2--converter-depth)))
(funcall iter2-tracing-function "%s" (replace-regexp-in-string "^" indentation (format "FORM: %s" (iter2--pp-to-string form 60 6)) t t))
(funcall iter2-tracing-function "%s" (replace-regexp-in-string "^" indentation (format "--->: %s\n+: %s"
(iter2--pp-to-string (car result) 60 6)
(iter2--pp-to-string (cdr result) 60 6))
t t))
result))))
(defun iter2--pp-to-string (object &optional max-single-line-length indent-by)
(let* ((print-quoted t)
(string (prin1-to-string object)))
(when (and max-single-line-length (> (length string) max-single-line-length))
(setq string (string-trim-right (pp-to-string object))))
(when (and indent-by (> indent-by 0))
(setq string (replace-regexp-in-string "\n" (concat "\n" (make-string indent-by ? )) string t t)))
string))
(defun iter2--debug-converter (enable)
(if enable
(advice-add #'iter2--convert-form :around #'iter2--convert-form-tracer)
(advice-remove #'iter2--convert-form #'iter2--convert-form-tracer)))
(defun iter2--continuation-invocation-form (value &optional lambda)
(if iter2-generate-tracing-functions
`(let ((function ,(or lambda `(pop ,iter2--continuations))))
(iter2--do-trace "invoking %S with value %S" function ,value)
(let ((iter2--tracing-depth (1+ iter2--tracing-depth)))
(funcall function ,value)))
`(funcall ,(or lambda `(pop ,iter2--continuations)) ,value)))
(defun iter2--cleanup-invocation-body ()
(if iter2-generate-tracing-functions
`(let ((function (pop ,iter2--cleanups)))
(iter2--do-trace "cleaning up using %S" function)
(funcall function))
`(funcall (pop ,iter2--cleanups))))
(defun iter2--merge-continuation-form (converted &optional var)
(let ((converted-form (car converted))
(continuation-form (cdr converted)))
(if continuation-form
`(progn ,(iter2--continuation-adding-form (list continuation-form) var) ,@(macroexp-unprogn converted-form))
converted-form)))
(defun iter2--continuation-adding-form (new-continuations &optional var)
(let ((value (or var iter2--continuations)))
(while new-continuations
(let ((continuation (pop new-continuations)))
(setq value (if (equal continuation `(funcall ,iter2--value))
;; With the addition of `iter2-next', we often use a `(lambda (x) (funcall x))' to
;; resolve incoming lambdas into values (for nonlocal exits and side effects), only
;; then to ignore the result. This is really needed, but instead of creating tons of
;; such lambdas, just use `funcall' directly: it is exactly the same here.
`(cons 'funcall ,value)
`(cons (lambda (,iter2--value) ,@(macroexp-unprogn continuation)) ,value)))))
`(setq ,(or var iter2--continuations) ,value)))
(defun iter2--catcher-continuation-adding-form (catcher-body next-continuation &rest additional-catcher-outer-bindings)
`(setq ,iter2--continuations
(cons (let ((,iter2--done ,iter2--continuations)
,@(delq nil additional-catcher-outer-bindings)
,iter2--catcher)
(setq ,iter2--catcher (lambda (,iter2--value) ,@(macroexp-unprogn catcher-body))))
(cons (lambda (,iter2--value) ,@(macroexp-unprogn next-continuation))
,iter2--continuations))))
(defun iter2--let*-yielding-form (catcher-outer-bindings catcher-inner-bindings catcher-value-storing-forms continuation &optional form-before-continuation)
(let (main-bindings)
(while (and catcher-outer-bindings (car catcher-outer-bindings))
(push (pop catcher-outer-bindings) main-bindings))
(setq catcher-outer-bindings (cdr catcher-outer-bindings))
(let ((form `(,(iter2--continuation-adding-form (list continuation))
,@(when form-before-continuation (macroexp-unprogn form-before-continuation)))))
(setq form (if main-bindings `(let* (,@main-bindings) ,@form) `(progn ,@form)))
(while catcher-inner-bindings
(let (outer-bindings)
(while (and catcher-outer-bindings (car catcher-outer-bindings))
(push (pop catcher-outer-bindings) outer-bindings))
(setq catcher-outer-bindings (cdr catcher-outer-bindings))
(setq form (iter2--catcher-continuation-adding-form `(let (,(pop catcher-inner-bindings))
(prog1 (unwind-protect
,(iter2--continuation-invocation-form iter2--value)
,(pop catcher-value-storing-forms))
(unless (eq ,iter2--continuations ,iter2--done)
(push ,iter2--catcher ,iter2--continuations))))
form))
(when outer-bindings
(setq form `(let* (,@outer-bindings)
,form)))))
form)))
(defun iter2--stack-adding-form (new-elements)
(let ((value iter2--stack))
(while new-elements
(setq value `(cons ,(pop new-elements) ,value)))
`(setq ,iter2--stack ,value)))
(defun iter2--stack-head-reversing-form (n)
(pcase n
(2 `(iter2--reverse-stack-head-2 ,iter2--stack))
(3 `(iter2--reverse-stack-head-3 ,iter2--stack))
(_ `(setq ,iter2--stack (iter2--reverse-stack-head-n ,iter2--stack ,(1- n))))))
;; Internal helpers for generated functions.
(defun iter2--do-clean-up (cleanups)
(if (cdr cleanups)
(unwind-protect
(funcall (car cleanups))
(iter2--do-clean-up (cdr cleanups)))
(funcall (car cleanups))))
(defun iter2--reverse-stack-head-2 (stack)
(let ((x (car stack))
(link-2 (cdr stack)))
(setcar stack (car link-2))
(setcar link-2 x)))
(defun iter2--reverse-stack-head-3 (stack)
(let ((x (car stack))
(link-3 (cddr stack)))
(setcar stack (car link-3))
(setcar link-3 x)))
(defun iter2--reverse-stack-head-n (stack n-1)
(let* ((last-head-cons (nthcdr n-1 stack))
(stack-tail (cdr last-head-cons)))
(setcdr last-head-cons nil)
(nconc (nreverse stack) stack-tail)))
(defun iter2--do-trace (format-string &rest arguments)
(when iter2-tracing-function
(let ((print-level (if (eq iter2-tracing-print-level t) print-level iter2-tracing-print-level))
(print-length (if (eq iter2-tracing-print-length t) print-length iter2-tracing-print-length)))
(apply iter2-tracing-function (concat "%siter2: " format-string) (cons (make-string (* iter2--tracing-depth 4) ? ) arguments)))))
(defun iter2-log-message (format-string &rest arguments)
"Like built-in `message', but only write to `*Messages*' buffer."
(let ((inhibit-message (or inhibit-message (not noninteractive))))
(apply #'message format-string arguments)))
;; Make sure that we are still compatible with `generator'. I couldn't make it work like
;; I wanted with fewer `eval's.
(eval-after-load 'iter2
(eval `(unless (let* ((it (funcall (iter2-lambda () (iter-yield 1)))))
(and (equal (iter-next it) 1) (condition-case error (progn (iter-next it 2) nil) (iter-end-of-sequence (equal (cdr error) 2)))))
(warn "Compatibility of `iter2' with `generator' package appears broken; please report this to maintainer (Emacs version: %s)" (emacs-version)))
t))
;; Issue a warning if compatibility is broken yet again.
(iter2--special-variable-p nil t)
;; Work around missing Edebug specification for `iter-do' macro on older Emacs versions.
(when (and (fboundp 'iter-do) (null (get 'iter-do 'edebug-form-spec)))