-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathline-counter.lisp
executable file
·102 lines (87 loc) · 3.22 KB
/
line-counter.lisp
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
(in-package :cl-graphql)
(defstruct line-counted
val line col filename readtable)
(defun count-lines (lstream &optional (line 1) (col 1) (filename "STDIN"))
(cond ((null lstream)
nil)
((member (head lstream) '(#\Newline #\Return))
(stream-cons (make-line-counted :val (head lstream)
:line line
:col col
:filename filename)
(count-lines (tail lstream) (1+ line) 1 filename)))
(t
(stream-cons (make-line-counted :val (head lstream)
:line line
:col col
:filename filename)
(count-lines (tail lstream) line (1+ col) filename)))))
(defun lazy-read (lisp-stream)
"Lazily reads characters from the LISP-STREAM, closing the stream when the end of file is reached."
(stream-cons
(read-char lisp-stream)
(if (eq (peek-char nil lisp-stream nil :eof) :eof)
(progn
(close lisp-stream)
nil)
(lazy-read lisp-stream))))
(defgeneric raw-ch (ch))
(defmethod raw-ch ((ch line-counted))
(line-counted-val ch))
(defmethod raw-ch ((ch character))
ch)
(defmethod raw-ch ((ch null))
nil)
(defun ch= (ch1 ch2)
(eql (raw-ch ch1)
(raw-ch ch2)))
(defun normalize-newlines (lazy-stream)
(cond ((stream-null? lazy-stream)
nil)
((and (ch= (head lazy-stream) #\Return)
(not (stream-null? (tail lazy-stream)))
(ch= (head (tail lazy-stream)) #\Newline))
(tail lazy-stream))
((ch= (head lazy-stream) #\Return)
(stream-cons #\Newline (tail lazy-stream)))
(t (stream-cons (head lazy-stream)
(normalize-newlines (tail lazy-stream))))))
(defun count-lines-from-file (filename)
"Opens the text file FILENAME and returns a lazy stream. The elements of the stream are all the struct
LINE-COUNTED, which wraps each character (the VAL member of the struct) in an object that describes exactly
where the character was read from."
(let ((stream (open filename :element-type 'character)))
(count-lines (normalize-newlines (lazy-read stream)) 1 1 filename)))
(defun strip-line-info (counted-stream)
"Returns a lazy stream containing just the characters from a COUNT-LINES stream."
(stream-map #'raw-ch counted-stream))
(defun counted-stream->string (counted-stream)
(coerce (mapcar #'raw-ch (stream->list counted-stream)) 'string))
(defun copy-line-count-info (value line-counted)
"Wraps the VALUE in a LINE-COUNTED object with the same line, column, file, and readtable as the one provided."
(if (line-counted-p line-counted)
(make-line-counted
:val value
:line (line-counted-line line-counted)
:col (line-counted-col line-counted)
:filename (line-counted-filename line-counted)
:readtable (line-counted-readtable line-counted))
value))
(defun hash->alist (h)
(let ((result nil))
(maphash (lambda (k v)
(push (list k v) result))
h)
result))
(defmacro as-> (initial var &body body)
"Evaluates each form in the BODY with VAR bound to the value of the previous form.
The first form will be evaluated with VAR bound to INITIAL. The final value of the
form is that of the last form in the BODY."
(if (null body)
initial
`(let ((,var ,initial))
(as-> ,(car body) ,var
,@(cdr body)))))
(defmacro aif (expr if-true &optional if-false)
`(let ((it ,expr))
(if it ,if-true ,if-false)))