-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathsource-tracking-reader.lisp
96 lines (89 loc) · 3.55 KB
/
source-tracking-reader.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
(defpackage :source-tracking-reader
(:use :cl)
(:export
#:read-tracking-source))
(in-package :source-tracking-reader)
;; character counting stream
(defclass char-counting-stream
(slynk-gray:fundamental-character-input-stream)
((char-count :initform 0 :reader char-count)
(understream :initarg :understream :initform (error "required!"))))
(defmethod slynk-gray:stream-read-char
((s char-counting-stream))
"Read one character from stream S."
(with-slots (char-count understream) s
(prog1 (read-char understream nil :eof)
(incf char-count))))
(defmethod slynk-gray:stream-unread-char
((s char-counting-stream) char)
"Read one character from stream S."
(with-slots (char-count understream) s
(prog1 (unread-char char understream)
(decf char-count))))
(defmethod slynk-gray:stream-read-char-no-hang
((s char-counting-stream))
"Read one character from stream S."
(with-slots (char-count understream) s
(let ((retval (read-char-no-hang understream nil :eof)))
(when retval
(incf char-count)
retval))))
(defun char-counting-stream (understream)
(make-instance 'char-counting-stream :understream understream))
;; substitution-table
(defun substitution-table (original-rt wrapper)
(flet ((read-token-fallback (stream first-char)
(unread-char first-char stream)
(let ((*readtable* original-rt))
(read-preserving-whitespace stream nil nil nil)))
(wrap (char rt fun non-terminating-p)
(dolist (c (list (char-upcase char)
(char-downcase char)))
(set-macro-character
c
(lambda (&rest whatever)
(funcall wrapper
(lambda () (apply fun whatever))))
non-terminating-p
rt)))
(standard-whitespace-p (char)
(member char '(#\Tab #\Newline
#\Linefeed #\Page
#\Return #\Space))))
(loop with rt = (copy-readtable original-rt)
for i from 0 upto 96
for char = (code-char i)
for (fun non-terminating-p)
= (multiple-value-list (get-macro-character char))
;; SBCL has nil entries for constituent
;; characters, whitespace or not. Give the
;; latter a default `READ-TOKEN-FALLBACK' entry
;; which should do the right thing, except if
;; they're whitespace, so check for that here.
for macrofun = (or fun
(and (not (standard-whitespace-p char))
#'read-token-fallback))
when macrofun
do (wrap char rt
macrofun
(if fun non-terminating-p t))
finally
(return rt))))
;; entry point
(defun read-tracking-source
(&optional (stream *standard-input*)
(eof-error-p t) eof-value
recursive-p (observer (lambda (&rest ignore)
(declare (ignore ignore)))))
(let* ((ccs (char-counting-stream stream))
(*readtable*
(substitution-table
*readtable*
(lambda (shadowed-entry)
(let ((start (1- (char-count ccs)))
(results (multiple-value-list (funcall shadowed-entry)))
(end (char-count ccs)))
(multiple-value-prog1 (apply #'values results)
(when results
(funcall observer (car results) start end))))))))
(read ccs eof-error-p eof-value recursive-p)))