;; Author: Nicolas Petton <nicolas@petton.fr>
;; Keywords: stream, laziness, sequences
-;; Version: 1.0
-;; Package-Requires: ((emacs "25.1"))
+;; Version: 2.1.0
+;; Package-Requires: ((emacs "25"))
;; Package: stream
;; Maintainer: nicolas@petton.fr
(eval-when-compile (require 'cl-lib))
(require 'seq)
+(require 'thunk)
(eval-and-compile
(defconst stream--identifier '--stream--
"Symbol internally used to identify streams."))
-(defmacro stream--delay (&rest body)
- "Delay the evaluation of BODY."
- (declare (debug t))
- (let ((forced (make-symbol "forced"))
- (val (make-symbol "val")))
- `(let (,forced ,val)
- (lambda ()
- (unless ,forced
- (setf ,val (progn ,@body))
- (setf ,forced t))
- ,val))))
-
-(defun stream--force (delayed)
- "Force the evaluation of DELAYED."
- (funcall delayed))
-
(defmacro stream-make (&rest body)
"Return a stream built from BODY.
-BODY must return nil or a cons cell, which cdr is itself a
+BODY must return nil or a cons cell whose cdr is itself a
stream."
(declare (debug t))
- `(list ',stream--identifier (stream--delay ,@body)))
+ `(list ',stream--identifier (thunk-delay ,@body)))
(defmacro stream-cons (first rest)
"Return a stream built from the cons of FIRST and REST.
(cl-defmethod stream ((buffer buffer) &optional pos)
"Return a stream of the characters of the buffer BUFFER.
-BUFFER-OR-NAME may be a buffer or a string (buffer name).
+BUFFER may be a buffer or a string (buffer name).
The sequence starts at POS if non-nil, 1 otherwise."
(with-current-buffer buffer
(unless pos (setq pos (point-min)))
(char-after (point)))))
(stream buffer (1+ pos)))))
+(defun stream-regexp (buffer regexp)
+ (stream-make
+ (let (match)
+ (with-current-buffer buffer
+ (setq match (re-search-forward regexp nil t)))
+ (when match
+ (cons (match-data) (stream-regexp buffer regexp))))))
+
(defun stream-range (&optional start end step)
"Return a stream of the integers from START to END, stepping by STEP.
If START is nil, it defaults to 0. If STEP is nil, it defaults to
(stream-range (+ start step) end step))))
\f
-(defun stream-p (stream)
+(defun streamp (stream)
"Return non-nil if STREAM is a stream, nil otherwise."
(and (consp stream)
(eq (car stream) stream--identifier)))
(defun stream-empty ()
"Return an empty stream."
- (list stream--identifier (stream--delay nil)))
+ (list stream--identifier (thunk-delay nil)))
(defun stream-empty-p (stream)
"Return non-nil is STREAM is empty, nil otherwise."
- (null (stream--force (cadr stream))))
+ (null (thunk-force (cadr stream))))
(defun stream-first (stream)
"Return the first element of STREAM."
- (car (stream--force (cadr stream))))
+ (car (thunk-force (cadr stream))))
(defun stream-rest (stream)
"Return a stream of all but the first element of STREAM."
- (cdr (stream--force (cadr stream))))
+ (or (cdr (thunk-force (cadr stream)))
+ (stream-empty)))
+
+(defmacro stream-pop (stream)
+ "Return the first element of STREAM and set the value of STREAM to its rest."
+ (unless (symbolp stream)
+ (error "STREAM must be a symbol"))
+ `(prog1
+ (stream-first ,stream)
+ (setq ,stream (stream-rest ,stream))))
\f
;;; cl-generic support for streams
-(defvar stream--generalizer
- (cl-generic-make-generalizer
- 11
- (lambda (name)
- `(when (stream-p ,name)
- 'stream))
- (lambda (tag)
- (when (eq tag 'stream)
- '(stream)))))
+(cl-generic-define-generalizer stream--generalizer
+ 11
+ (lambda (name)
+ `(when (streamp ,name)
+ 'stream))
+ (lambda (tag)
+ (when (eq tag 'stream)
+ '(stream))))
(cl-defmethod cl-generic-generalizers ((_specializer (eql stream)))
"Support for `stream' specializers."
;;; Implementation of seq.el generic functions
-(cl-defgeneric seq-p ((_stream stream))
+(cl-defmethod seqp ((_stream stream))
t)
-(cl-defgeneric seq-elt ((stream stream) n)
+(cl-defmethod seq-elt ((stream stream) n)
"Return the element of STREAM at index N."
(while (> n 0)
(setq stream (stream-rest stream))
(setq n (1- n)))
(stream-first stream))
-(cl-defgeneric seq-length ((stream stream))
+(cl-defmethod seq-length ((stream stream))
"Return the length of STREAM.
This function will eagerly consume the entire stream."
(let ((len 0))
(setq stream (stream-rest stream)))
len))
-(cl-defgeneric seq-subseq ((stream stream) start end)
+(cl-defmethod seq-subseq ((stream stream) start end)
(seq-take (seq-drop stream start) (- end start)))
-(cl-defgeneric seq-into-sequence ((stream stream))
- "Convert STREAM into a sequence"
+(cl-defmethod seq-into-sequence ((stream stream))
+ "Convert STREAM into a sequence."
(let ((list))
(seq-doseq (elt stream)
(push elt list))
(nreverse list)))
-(cl-defgeneric seq-into ((stream stream) type)
+(cl-defmethod seq-into ((stream stream) type)
"Convert STREAM into a sequence of type TYPE."
(seq-into (seq-into-sequence stream) type))
-(cl-defgeneric seq-into ((stream stream) (_type (eql stream)))
+(cl-defmethod seq-into ((stream stream) (_type (eql stream)))
stream)
-(cl-defgeneric seq-into ((seq sequence) (_type (eql stream)))
+(cl-defmethod seq-into ((seq sequence) (_type (eql stream)))
(stream seq))
-(cl-defgeneric seq-take ((stream stream) n)
+(cl-defmethod seq-take ((stream stream) n)
"Return a stream of the first N elements of STREAM."
- (if (zerop n)
+ (if (or (zerop n)
+ (stream-empty-p stream))
(stream-empty)
(stream-cons
(stream-first stream)
(seq-take (stream-rest stream) (1- n)))))
-(cl-defgeneric seq-drop ((stream stream) n)
+(cl-defmethod seq-drop ((stream stream) n)
"Return a stream of STREAM without its first N elements."
(stream-make
(while (not (or (stream-empty-p stream) (zerop n)))
(cons (stream-first stream)
(stream-rest stream)))))
-(cl-defgeneric seq-take-while (pred (stream stream))
+(cl-defmethod seq-take-while (pred (stream stream))
"Return a stream of the successive elements for which (PRED elt) is non-nil in STREAM."
(stream-make
(when (funcall pred (stream-first stream))
(cons (stream-first stream)
(seq-take-while pred (stream-rest stream))))))
-(cl-defgeneric seq-drop-while (pred (stream stream))
+(cl-defmethod seq-drop-while (pred (stream stream))
"Return a stream from the first element for which (PRED elt) is nil in STREAM."
(stream-make
(while (not (or (stream-empty-p stream)
(cons (stream-first stream)
(stream-rest stream)))))
-(cl-defgeneric seq-map (function (stream stream))
- "Return a stream.
-The elements of the produced sequence consist of the application
-of FUNCTION to each element of STREAM."
- (if (stream-empty-p stream)
- stream
- (stream-cons
- (funcall function (stream-first stream))
- (seq-map function (stream-rest stream)))))
+(cl-defmethod seq-map (function (stream stream))
+ "Return a stream representing the mapping of FUNCTION over STREAM.
+The elements of the produced stream are the results of the
+applications of FUNCTION on each element of STREAM in succession."
+ (stream-make
+ (when (not (stream-empty-p stream))
+ (cons (funcall function (stream-first stream))
+ (seq-map function (stream-rest stream))))))
-(cl-defgeneric seq-do (function (stream stream))
+(cl-defmethod seq-do (function (stream stream))
"Evaluate FUNCTION for each element of STREAM eagerly, and return nil.
-`seq-do' should never be used on infinite streams."
+`seq-do' should never be used on infinite streams without some
+kind of nonlocal exit."
(while (not (stream-empty-p stream))
(funcall function (stream-first stream))
(setq stream (stream-rest stream))))
-(cl-defgeneric seq-filter (pred (stream stream))
+(cl-defmethod seq-filter (pred (stream stream))
"Return a stream of the elements for which (PRED element) is non-nil in STREAM."
(if (stream-empty-p stream)
stream
(cons (stream-first stream)
(seq-filter pred (stream-rest stream)))))))
-(cl-defgeneric seq-copy ((stream stream))
+(cl-defmethod seq-copy ((stream stream))
"Return a shallow copy of STREAM."
(stream-cons (stream-first stream)
(stream-rest stream)))