Commit | Line | Data |
---|---|---|
dea4d055 MW |
1 | ;;; -*-lisp-*- |
2 | ;;; | |
3 | ;;; Scanner protocol definitions. | |
4 | ;;; | |
5 | ;;; (c) 2009 Straylight/Edgeware | |
6 | ;;; | |
7 | ||
8 | ;;;----- Licensing notice --------------------------------------------------- | |
9 | ;;; | |
10 | ;;; This file is part of the Sensble Object Design, an object system for C. | |
11 | ;;; | |
12 | ;;; SOD is free software; you can redistribute it and/or modify | |
13 | ;;; it under the terms of the GNU General Public License as published by | |
14 | ;;; the Free Software Foundation; either version 2 of the License, or | |
15 | ;;; (at your option) any later version. | |
16 | ;;; | |
17 | ;;; SOD is distributed in the hope that it will be useful, | |
18 | ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | ;;; GNU General Public License for more details. | |
21 | ;;; | |
22 | ;;; You should have received a copy of the GNU General Public License | |
23 | ;;; along with SOD; if not, write to the Free Software Foundation, | |
24 | ;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
25 | ||
26 | (cl:in-package #:sod-parser) | |
27 | ||
28 | ;;;-------------------------------------------------------------------------- | |
29 | ;;; Scanner context protocol. | |
30 | ||
31 | (export 'parser-scanner) | |
32 | (defgeneric parser-scanner (context) | |
33 | (:documentation | |
34 | "Return the symbol naming the CONTEXT's run-time scanner.")) | |
35 | ||
36 | (export 'scanner-context) | |
37 | (defclass scanner-context () | |
38 | ((scanner :initarg :scanner :type symbol :reader parser-scanner)) | |
39 | (:documentation | |
40 | "Base class for scanner contexts. | |
41 | ||
42 | A scanner is simply an object maintaining the run-time state of a parsing | |
43 | operation, in the same way as a parser context maintains the compile-time | |
44 | state. So the scanner context is a compile-time context which expands to | |
45 | calls to use the run-time scanner. See? | |
46 | ||
bf090e02 MW |
47 | This class provides common compile-time behaviour for `parser-at-eof-p' |
48 | and friends by invoking corresponding methods on the scanner object at | |
dea4d055 MW |
49 | run-time.")) |
50 | ||
51 | ;;;-------------------------------------------------------------------------- | |
52 | ;;; Basic scanner protocol. | |
53 | ||
54 | (export 'scanner-at-eof-p) | |
55 | (defgeneric scanner-at-eof-p (scanner) | |
56 | (:documentation | |
57 | "Answer whether the SCANNER is at end-of-file. | |
58 | ||
59 | It is an error to query the current item when at end-of-file.")) | |
60 | ||
61 | (export 'scanner-step) | |
62 | (defgeneric scanner-step (scanner) | |
63 | (:documentation | |
64 | "Advance the SCANNER to the next item. | |
65 | ||
66 | The precise nature of the items isn't known at this level, so a protocol | |
67 | for accessing them is left for later.")) | |
68 | ||
69 | ;;;-------------------------------------------------------------------------- | |
70 | ;;; Scanner place-capture protocol. | |
71 | ||
72 | (export 'scanner-capture-place) | |
73 | (defgeneric scanner-capture-place (scanner) | |
74 | (:documentation | |
75 | "Capture the SCANNER's current place and return it.") | |
76 | (:method (scanner) | |
77 | (error "Scanner ~S doesn't support rewinding." scanner))) | |
78 | ||
79 | (export 'scanner-restore-place) | |
80 | (defgeneric scanner-restore-place (scanner place) | |
81 | (:documentation | |
82 | "`Rewind' the SCANNER to the captured PLACE. | |
83 | ||
84 | The place was previously captured by `scanner-capture-place'.")) | |
85 | ||
86 | (export 'scanner-release-place) | |
87 | (defgeneric scanner-release-place (scanner place) | |
88 | (:documentation | |
89 | "Release a PLACE captured from the SCANNER. | |
90 | ||
91 | The place was previously captured by `scanner-capture-place'.") | |
92 | (:method (scanner place) nil)) | |
93 | ||
94 | (export 'with-scanner-place) | |
95 | (defmacro with-scanner-place ((place scanner) &body body) | |
96 | "Evaluate BODY with PLACE bound to the captured current place. | |
97 | ||
98 | Automatically releases the place when the BODY finishes. Note that | |
99 | if you wanted to circumvent the cleanup then you should have used | |
100 | `with-parser-place', which does all of this in the meta-level." | |
101 | (once-only (scanner) | |
102 | `(let ((,place (scanner-capture-place ,scanner))) | |
103 | (unwind-protect (progn ,@body) | |
104 | (scanner-release-place ,scanner ,place))))) | |
105 | ||
106 | ;;;-------------------------------------------------------------------------- | |
107 | ;;; Character scanner protocol. | |
108 | ||
109 | (export 'character-scanner) | |
110 | (defclass character-scanner () | |
111 | () | |
112 | (:documentation "Base class for character scanners.")) | |
113 | ||
114 | (export 'character-scanner-context) | |
115 | (defclass character-scanner-context | |
116 | (scanner-context character-parser-context) | |
117 | () | |
118 | (:documentation | |
119 | "A context for a richer character-oriented scanner.")) | |
120 | ||
121 | (export 'scanner-current-char) | |
122 | (defgeneric scanner-current-char (scanner) | |
123 | (:documentation | |
124 | "Returns the SCANNER's current character. | |
125 | ||
126 | You advance to the next one using `scanner-step'.")) | |
127 | ||
128 | (export 'scanner-unread) | |
129 | (defgeneric scanner-unread (scanner char) | |
130 | (:documentation | |
131 | "Rewind SCANNER by one character, specifically CHAR. | |
132 | ||
133 | CHAR must be the character most recently stepped over by `scanner-step' -- | |
134 | it is an error to unread before the first call to `scanner-step'. It is | |
135 | also an error to unread after encountering end-of-file.")) | |
136 | ||
137 | (export 'scanner-interval) | |
138 | (defgeneric scanner-interval (scanner place-a &optional place-b) | |
139 | (:documentation | |
140 | "Return the characters from PLACE-A up to (but not including) PLACE-B. | |
141 | ||
142 | The characters are returned as a string. If PLACE-B is omitted, return | |
143 | the characters up to (but not including) the current position. It is an | |
144 | error if PLACE-B precedes PLACE-A or they are from different scanners.")) | |
145 | ||
146 | (export '(scanner-filename scanner-line scanner-column)) | |
147 | (defgeneric scanner-filename (scanner) | |
148 | (:documentation "Return the filename backing the SCANNER.") | |
149 | (:method (scanner) nil)) | |
150 | (defgeneric scanner-line (scanner) | |
151 | (:documentation "Return the SCANNER's current line number.") | |
152 | (:method (scanner) nil)) | |
153 | (defgeneric scanner-column (scanner) | |
154 | (:documentation "Return the SCANNER's current column number.") | |
155 | (:method (scanner) nil)) | |
156 | ||
157 | (defun scanner-file-location (scanner) | |
158 | "Capture the current location of the SCANNER. | |
159 | ||
160 | This uses the generic functions `scanner-filename', `scanner-line' and | |
161 | `scanner-column' to compute its result. There are default methods on | |
162 | these functions which make up dummy results. | |
163 | ||
164 | There is a method for `file-location' defined on `character-scanner' which | |
165 | simply calls this function; but since some scanners are structure-objects | |
166 | rather than standard-objects they can't include `character-scanner' as a | |
167 | superclass." | |
168 | (make-file-location (scanner-filename scanner) | |
169 | (scanner-line scanner) | |
170 | (scanner-column scanner))) | |
171 | ||
172 | ;;;-------------------------------------------------------------------------- | |
173 | ;;; Token scanner protocol. | |
174 | ||
dea4d055 MW |
175 | ;; The token scanner base class and parser context. |
176 | ||
177 | (export '(token-scanner token-type token-value)) | |
178 | (defclass token-scanner () | |
179 | ((type :reader token-type) | |
180 | (value :reader token-value) | |
181 | (captures :initform 0 :type fixnum) | |
182 | (tail :initform nil :type (or token-scanner-place null)) | |
bf090e02 | 183 | (filename :initarg :filename :type string :reader scanner-filename) |
dea4d055 MW |
184 | (line :initarg :line :initform 1 :type fixnum :accessor scanner-line) |
185 | (column :initarg :column :initform 0 | |
186 | :type fixnum :accessor scanner-column)) | |
187 | (:documentation | |
188 | "A rewindable scanner for tokenizing. | |
189 | ||
190 | The scanner should be used via the parser protocol; see also the token | |
191 | scanner protocol, which explains the model. | |
192 | ||
193 | Subclasses must provide the detailed scanning behaviour -- most notably | |
9ec578d9 MW |
194 | the `scanner-token' generic function -- and also implement a method on |
195 | `file-location' to return the location. The `scanner-token' method should | |
196 | also update the `line' and `column' slots to track the position in the | |
197 | underlying source, if appropriate. This class will handle the remaining | |
198 | details, such as dealing correctly with rewinding.")) | |
dea4d055 MW |
199 | |
200 | (export 'token-scanner-context) | |
201 | (defclass token-scanner-context (scanner-context token-parser-context) | |
202 | () | |
203 | (:documentation | |
204 | "A parser context for a richer token-based scanners.")) | |
205 | ||
1d087117 MW |
206 | ;; A place marker. |
207 | ||
208 | (export '(token-scanner-place token-scanner-place-p)) | |
209 | (defstruct token-scanner-place | |
210 | "A link in the chain of lookahead tokens; capturable as a place. | |
211 | ||
212 | If the scanner's place is captured, it starts to maintain a list of | |
213 | lookahead tokens. The list contains internal links -- it works out | |
214 | slightly easier that way. This is basically a simpler version of the | |
215 | charbuf scanner (q.v.); most significantly, the chain links here do double | |
216 | duty as place markers. | |
217 | ||
218 | The details of this structure are not a defined part of the token scanner | |
219 | protocol." | |
220 | ||
221 | (scanner nil :type token-scanner :read-only t) | |
222 | (next nil :type (or token-scanner-place null)) | |
223 | (type nil :read-only t) | |
224 | (value nil :read-only t) | |
225 | (line 1 :type (or fixnum null) :read-only t) | |
226 | (column 0 :type (or fixnum null) :read-only t)) | |
227 | ||
dea4d055 MW |
228 | ;; Protocol. |
229 | ||
230 | (export 'scanner-token) | |
231 | (defgeneric scanner-token (scanner) | |
232 | (:documentation | |
233 | "Internal protocol: read the next token from the SCANNER. | |
234 | ||
235 | This function is called by `scanner-step' to actually read the next token | |
236 | if necessary. It should return two values: the token's `type' and its | |
237 | `value'.")) | |
238 | ||
239 | ;;;-------------------------------------------------------------------------- | |
240 | ;;; Character scanner streams. | |
241 | ;;; | |
242 | ;;; This seems like an abstraction inversion, but it's important if we're to | |
243 | ;;; `read' from a character scanner. | |
244 | ||
245 | (export 'character-scanner-stream) | |
246 | (defclass character-scanner-stream (fundamental-character-input-stream) | |
247 | ((scanner :initarg :scanner)) | |
248 | (:documentation | |
249 | "A stream which reads from a character scanner. | |
250 | ||
251 | The SCANNER must implement the character scanner protcol, including | |
252 | `scanner-current-char', `scanner-step', and `scanner-unread'; it is not | |
253 | necessary that the scanner implement the place-capture protocol. | |
254 | ||
255 | The stream can be made more efficient by implementing | |
256 | `stream-read-sequence' and `stream-read-line' in a scanner-specific | |
257 | manner.")) | |
258 | ||
bf090e02 MW |
259 | (export 'make-scanner-stream) |
260 | (defgeneric make-scanner-stream (scanner) | |
261 | (:documentation | |
262 | "Return a stream which reads from the SCANNER. | |
263 | ||
264 | The default method simply constructs a `character-scanner-stream' | |
265 | instance. Subclasses of `character-scanner' can override this method in | |
266 | order to return instances of more efficient stream subclasses.") | |
267 | (:method ((scanner character-scanner)) | |
268 | (make-instance 'character-scanner-stream :scanner scanner))) | |
269 | ||
dea4d055 | 270 | ;;;----- That's all, folks -------------------------------------------------- |