It lives!
[sod] / pset.lisp
CommitLineData
abdf50aa
MW
1;;; -*-lisp-*-
2;;;
3;;; Collections of properties
4;;;
5;;; (c) 2009 Straylight/Edgeware
6;;;
7
8;;;----- Licensing notice ---------------------------------------------------
9;;;
10;;; This file is part of the Simple Object Definition system.
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)
27
28;;;--------------------------------------------------------------------------
77027cca 29;;; Property representation.
abdf50aa
MW
30
31(defun property-key (name)
32 "Convert NAME into a keyword.
33
34 If NAME isn't a symbol already, then flip its case (using FROB-CASE),
35 replace underscores by hyphens, and intern into the KEYWORD package."
36 (etypecase name
37 (symbol name)
38 (string (intern (substitute #\- #\_ (frob-case name)) :keyword))))
39
40(defun property-type (value)
77027cca 41 "Guess a sensible property type to use for VALUE."
1f1d88f5 42 (typecase value
abdf50aa
MW
43 (symbol :symbol)
44 (integer :integer)
45 (string :string)
1f1d88f5
MW
46 (character :char)
47 (c-fragment :frag)
48 (t :other)))
abdf50aa
MW
49
50(defstruct (property
51 (:conc-name p-)
52 (:constructor make-property
53 (name value
77027cca
MW
54 &key (type (property-type value))
55 ((:location %loc))
56 seenp
57 &aux (key (property-key name))
58 (location (file-location %loc)))))
abdf50aa
MW
59 "A simple structure for holding a property in a property set.
60
61 The main useful feature is the ability to tick off properties which have
77027cca
MW
62 been used, so that we can complain about unrecognized properties.
63
64 An explicit type tag is necessary because we need to be able to talk
65 distinctly about identifiers, strings and symbols, and we've only got two
66 obvious Lisp types to play with. Sad, but true."
67
68 (name nil :type (or string symbol))
abdf50aa
MW
69 (value nil :type t)
70 (type nil :type symbol)
71 (location (file-location nil) :type file-location)
72 (key nil :type symbol)
73 (seenp nil :type boolean))
74
abdf50aa
MW
75(defun string-to-symbol (string &optional (package *package*))
76 "Convert STRING to a symbol in PACKAGE.
77
78 If PACKAGE is nil, then parse off a `PACKAGE:' prefix from STRING to
79 identify the package. A doubled colon allows access to internal symbols,
80 and will intern if necessary. Note that escape characters are /not/
81 processed; don't put colons in package names if you want to use them from
82 SOD property sets."
83
abdf50aa
MW
84 (let* ((length (length string))
85 (colon (position #\: string)))
86 (multiple-value-bind (start internalp)
87 (cond ((not colon) (values 0 t))
88 ((and (< (1+ colon) length)
89 (char= (char string (1+ colon)) #\:))
90 (values (+ colon 2) t))
91 (t
92 (values (1+ colon) nil)))
93 (when colon
94 (let* ((package-name (subseq string 0 colon))
95 (found (find-package package-name)))
96 (unless found
97 (error "Unknown package `~A'" package-name))
98 (setf package found)))
99 (let ((name (subseq string start)))
100 (multiple-value-bind (symbol status)
101 (funcall (if internalp #'intern #'find-symbol) name package)
102 (cond ((or internalp (eq status :external))
103 symbol)
104 ((not status)
105 (error "Symbol `~A' not found in package `~A'"
106 name (package-name package)))
107 (t
108 (error "Symbol `~A' not external in package `~A'"
109 name (package-name package)))))))))
110
111(defgeneric coerce-property-value (value type wanted)
112 (:documentation
77027cca
MW
113 "Convert VALUE, a property of type TYPE, to be of type WANTED.
114
115 It's sensible to add additional methods to this function, but there are
116 all the ones we need.")
abdf50aa
MW
117
118 ;; If TYPE matches WANTED, we'll assume that VALUE already has the right
1f1d88f5
MW
119 ;; form. Otherwise, if nothing else matched, then I guess we'll have to
120 ;; say it didn't work.
abdf50aa
MW
121 (:method (value type wanted)
122 (if (eql type wanted)
123 value
124 (error "Incorrect type: expected ~A but found ~A" wanted type)))
125
1f1d88f5
MW
126 ;; If the caller asks for type T then give him the raw thing.
127 (:method (value type (wanted (eql t)))
128 value)
129
abdf50aa
MW
130 ;; Keywords.
131 (:method ((value symbol) (type (eql :symbol)) (wanted (eql :keyword)))
132 value)
133 (:method ((value string) (type (eql :id)) (wanted (eql :keyword)))
134 (string-to-symbol (substitute #\- #\_ (frob-case value)) :keyword))
135 (:method ((value string) (type (eql :string)) (wanted (eql :keyword)))
136 (string-to-symbol (frob-case value) :keyword))
137
138 ;; Symbols.
139 (:method ((value string) (type (eql :id)) (wanted (eql :symbol)))
140 (string-to-symbol (substitute #\- #\_ (frob-case value))))
141 (:method ((value string) (type (eql :string)) (wanted (eql :symbol)))
142 (string-to-symbol (frob-case value)))
143
144 ;; Identifiers.
145 (:method ((value symbol) (type (eql :symbol)) (wanted (eql :id)))
146 (substitute #\_ #\- (frob-case (symbol-name value)))))
147
77027cca
MW
148;;;--------------------------------------------------------------------------
149;;; Property set representation.
150;;;
151;;; There shouldn't be any code elsewhere which depends on the
152;;; representation. It's changed before; it may change again.
153
154(defstruct (pset (:constructor %make-pset)
155 (:conc-name %pset-))
156 "A property set.
157
158 Wrapped up in a structure so that we can define a print function."
159 (hash (make-hash-table) :type hash-table))
160
161(declaim (inline make-pset pset-get pset-store pset-map))
162
163(defun make-pset ()
164 "Constructor for property sets."
165 (%make-pset))
166
167(defun pset-get (pset key)
168 "Look KEY up in PSET and return what we find.
169
170 If there's no property by that name, return NIL."
171 (values (gethash key (%pset-hash pset))))
172
173(defun pset-store (pset prop)
174 "Store property PROP in PSET.
175
176 Overwrite or replace any previous property with the same name. Mutates
177 the property set."
178 (setf (gethash (p-key prop) (%pset-hash pset)) prop))
179
180(defun pset-map (func pset)
181 "Call FUNC for each property in PSET."
182 (maphash (lambda (key value) (declare (ignore key)) (funcall func value))
183 (%pset-hash pset)))
184
185;;;--------------------------------------------------------------------------
186;;; `Cooked' property set operations.
187
188(defun store-property
189 (pset name value &key (type (property-type value)) location)
190 "Store a property in PSET."
d9c15186
MW
191 (pset-store pset
192 (make-property name value :type type :location location)))
77027cca 193
abdf50aa
MW
194(defun get-property (pset name type &optional default)
195 "Fetch a property from a property set.
196
197 If a property NAME is not found in PSET, or if a property is found, but
198 its type doesn't match TYPE, then return DEFAULT and NIL; otherwise return
199 the value and its file location. In the latter case, mark the property as
200 having been used.
201
1f1d88f5
MW
202 The value returned depends on the TYPE argument provided. If you pass NIL
203 then you get back the entire PROPERTY object. If you pass T, then you get
204 whatever was left in the property set, uninterpreted. Otherwise the value
77027cca 205 is coerced to the right kind of thing (where possible) and returned.
abdf50aa 206
77027cca
MW
207 If PSET is nil, then return DEFAULT."
208
d9c15186 209 (let ((prop (and pset (pset-get pset (property-key name)))))
abdf50aa
MW
210 (with-default-error-location ((and prop (p-location prop)))
211 (cond ((not prop)
212 (values default nil))
213 ((not type)
214 (setf (p-seenp prop) t)
215 (values prop (p-location prop)))
216 (t
217 (setf (p-seenp prop) t)
218 (values (coerce-property-value (p-value prop)
219 (p-type prop)
220 type)
221 (p-location prop)))))))
222
77027cca
MW
223(defun add-property
224 (pset name value &key (type (property-type value)) location)
225 "Add a property to PSET.
226
227 If a property with the same NAME already exists, report an error."
228
229 (with-default-error-location (location)
230 (let ((existing (get-property pset name nil)))
231 (when existing
232 (error "Property ~S already defined~@[ at ~A~]"
233 name (p-location existing)))
234 (store-property pset name value :type type :location location))))
235
236(defun make-property-set (&rest plist)
237 "Make a new property set, with given properties.
238
239 This isn't the way to make properties when parsing, but it works well for
240 programmatic generation. The arguments should form a property list
241 (alternating keywords and values is good).
242
243 An attempt is made to guess property types from the Lisp types of the
244 values. This isn't always successful but it's not too bad. The
245 alternative is manufacturing a PROPERTY-VALUE object by hand and stuffing
246 into the set."
247
d9c15186 248 (do ((pset (make-pset))
77027cca
MW
249 (plist plist (cddr plist)))
250 ((endp plist) pset)
251 (add-property pset (car plist) (cadr plist))))
252
253(defmethod print-object ((pset pset) stream)
254 (print-unreadable-object (pset stream :type t)
255 (pprint-logical-block (stream nil)
256 (let ((firstp t))
d9c15186
MW
257 (pset-map (lambda (prop)
258 (cond (firstp (setf firstp nil))
259 (t (write-char #\space stream)
260 (pprint-newline :linear stream)))
261 (format stream "~:@<~S ~@_~S ~@_~S~:>"
262 (p-name prop) (p-type prop) (p-value prop)))
263 pset)))))
77027cca 264
abdf50aa
MW
265(defun check-unused-properties (pset)
266 "Issue errors about unused properties in PSET."
d9c15186
MW
267 (when pset
268 (pset-map (lambda (prop)
269 (unless (p-seenp prop)
270 (cerror*-with-location (p-location prop)
271 "Unknown property `~A'"
a07d8d00
MW
272 (p-name prop))
273 (setf (p-seenp prop) t)))
d9c15186 274 pset)))
abdf50aa
MW
275
276;;;--------------------------------------------------------------------------
77027cca 277;;; Expression parser.
abdf50aa
MW
278
279(defun parse-expression (lexer)
280 "Parse an expression from the LEXER.
281
1f1d88f5
MW
282 The return values are the expression's VALUE and TYPE; currently the types
283 are :ID, :INTEGER, :STRING, and :CHAR. If an error prevented a sane value
abdf50aa
MW
284 being produced, the TYPE :INVALID is returned.
285
286 Expression syntax is rather limited at the moment:
287
288 expression : term | expression `+' term | expression `-' term
289 term : factor | term `*' factor | term `/' factor
290 factor : primary | `+' factor | `-' factor
291 primary : integer | identifier | string
292 | `(' expression `)'
293 | `?' lisp-expression
294
295 Identifiers are just standalone things. They don't name values. The
296 operators only work on integer values at the moment. (Confusingly, you
297 can manufacture rational numbers using the division operator, but they
298 still get called integers.)"
299
300 (let ((valstack nil)
301 (opstack nil))
302
303 ;; The following is a simple operator-precedence parser: the
304 ;; recursive-descent parser I wrote the first time was about twice the
305 ;; size and harder to extend.
306 ;;
307 ;; The parser flips between two states, OPERAND and OPERATOR. It starts
308 ;; out in OPERAND state, and tries to parse a sequence of prefix
309 ;; operators followed by a primary expression. Once it's found one, it
310 ;; pushes the operand onto the value stack and flips to OPERATOR state;
311 ;; if it fails, it reports a syntax error and exits. The OPERAND state
312 ;; tries to read a sequence of postfix operators followed by an infix
313 ;; operator; if it fails, it assumes that it hit the stuff following the
314 ;; expression and stops.
315 ;;
316 ;; Each operator is pushed onto a stack consisting of lists of the form
317 ;; (FUNC PREC TY*). The PREC is a precedence -- higher numbers mean
318 ;; tighter binding. The TY* are operand types; operands are popped off
319 ;; the operand stack, checked against the requested types, and passed to
320 ;; the FUNC, which returns a new operand to be pushed in their place.
321 ;;
322 ;; Usually, when a binary operator is pushed, existing stacked operators
323 ;; with higher precedence are applied. Whether operators with /equal/
324 ;; precedence are also applied depends on the associativity of the
325 ;; operator: apply equal precedence operators for left-associative
326 ;; operators, don't apply for right-associative. When we reach the end
327 ;; of the expression, all the remaining operators on the stack are
328 ;; applied.
329 ;;
330 ;; Parenthesized subexpressions are implemented using a hack: when we
331 ;; find an open paren in operand position, a fake operator is pushed with
332 ;; an artificially low precedece, which protects the operators beneath
333 ;; from premature application. The fake operator's function reports an
334 ;; error -- this will be triggered only if we reach the end of the
335 ;; expression before a matching close-paren, because the close-paren
336 ;; handler will pop the fake operator before it does any harm.
337
338 (restart-case
339 (labels ((apply-op (op)
340 ;; Apply the single operator list OP to the values on the
341 ;; value stack.
342 (let ((func (pop op))
343 (args nil))
344 (dolist (ty (reverse (cdr op)))
345 (let ((arg (pop valstack)))
346 (cond ((eq (car arg) :invalid)
347 (setf func nil))
348 ((eq (car arg) ty)
349 (push (cdr arg) args))
350 (t
351 (cerror* "Type mismatch: wanted ~A; found ~A"
352 ty (car arg))
353 (setf func nil)))))
354 (if func
355 (multiple-value-bind (type value) (apply func args)
356 (push (cons type value) valstack))
357 (push '(:invalid . nil) valstack))))
358
359 (apply-all (prec)
360 ;; Apply all operators with precedence PREC or higher.
361 (loop
362 (when (or (null opstack) (< (cadar opstack) prec))
363 (return))
364 (apply-op (pop opstack)))))
365
366 (tagbody
367
368 operand
369 ;; Operand state. Push prefix operators, and try to read a
370 ;; primary operand.
371 (case (token-type lexer)
372
373 ;; Aha. A primary. Push it onto the stack, and see if
374 ;; there's an infix operator.
1f1d88f5 375 ((:integer :id :string :char)
abdf50aa
MW
376 (push (cons (token-type lexer)
377 (token-value lexer))
378 valstack)
379 (go operator))
380
381 ;; Look for a Lisp S-expression.
382 (#\?
383 (with-lexer-stream (stream lexer)
384 (let ((value (eval (read stream t))))
385 (push (cons (property-type value) value) valstack)))
386 (go operator))
387
388 ;; Arithmetic unary operators. Push an operator for `+' for
389 ;; the sake of type-checking.
390 (#\+
391 (push (list (lambda (x) (values :integer x))
392 10 :integer)
393 opstack))
394 (#\-
395 (push (list (lambda (x) (values :integer (- x)))
396 10 :integer)
397 opstack))
398
399 ;; The open-paren hack. Push a magic marker which will
400 ;; trigger an error if we hit the end of the expression.
401 ;; Inside the paren, we're still looking for an operand.
402 (#\(
403 (push (list (lambda ()
404 (error "Expected `)' but found ~A"
405 (format-token lexer)))
406 -1)
407 opstack))
408
409 ;; Failed to find anything. Report an error and give up.
410 (t
411 (error "Expected expression but found ~A"
412 (format-token lexer))))
413
414 ;; Assume prefix operators as the default, so go round for more.
415 (next-token lexer)
416 (go operand)
417
418 operator
419 ;; Operator state. Push postfix operators, and try to read an
420 ;; infix operator. It turns out that we're always a token
421 ;; behind here, so catch up.
422 (next-token lexer)
423 (case (token-type lexer)
424
425 ;; Binary operators.
426 (#\+ (apply-all 3)
427 (push (list (lambda (x y) (values :integer (+ x y)))
428 3 :integer :integer)
429 opstack))
430 (#\- (apply-all 3)
431 (push (list (lambda (x y) (values :integer (- x y)))
432 3 :integer :integer)
433 opstack))
434 (#\* (apply-all 5)
435 (push (list (lambda (x y) (values :integer (* x y)))
436 5 :integer :integer)
437 opstack))
438 (#\/ (apply-all 5)
439 (push (list (lambda (x y)
440 (if (zerop y)
441 (progn (cerror* "Division by zero")
442 (values nil :invalid))
443 (values (/ x y) :integer)))
444 5 :integer :integer)
445 opstack))
446
447 ;; The close-paren hack. Finish off the operators pushed
448 ;; since the open-paren. If the operator stack is now empty,
449 ;; this is someone else's paren, so exit. Otherwise pop our
450 ;; magic marker, and continue looking for an operator.
451 (#\) (apply-all 0)
452 (when (null opstack)
453 (go done))
454 (pop opstack)
455 (go operator))
456
457 ;; Nothing useful. Must have hit the end, so leave.
458 (t (go done)))
459
460 ;; Assume we found the binary operator as a default, so snarf a
461 ;; token and head back.
462 (next-token lexer)
463 (go operand)
464
465 done)
466
467 ;; Apply all the pending operators. If there's an unmatched
468 ;; open paren, this will trigger the error message.
469 (apply-all -99)
470
471 ;; If everything worked out, we should have exactly one operand
472 ;; left. This is the one we want.
473 (assert (and (consp valstack)
474 (null (cdr valstack))))
475 (values (cdar valstack) (caar valstack)))
476 (continue ()
77027cca 477 :report "Return an invalid value and continue."
abdf50aa
MW
478 (values nil :invalid)))))
479
77027cca
MW
480;;;--------------------------------------------------------------------------
481;;; Property set parsing.
482
483(defun parse-property (lexer pset)
484 "Parse a single property from LEXER; add it to PSET."
485 (let ((name (require-token lexer :id)))
486 (require-token lexer #\=)
487 (multiple-value-bind (value type) (parse-expression lexer)
488 (unless (eq type :invalid)
489 (add-property pset name value :type type :location lexer)))))
490
abdf50aa
MW
491(defun parse-property-set (lexer)
492 "Parse a property set from LEXER.
493
494 If there wasn't one to parse, return nil; this isn't considered an error,
495 and GET-PROPERTY will perfectly happily report defaults for all requested
496 properties."
497
77027cca
MW
498 (when (require-token lexer #\[ :errorp nil)
499 (let ((pset (make-pset)))
abdf50aa 500 (loop
77027cca 501 (parse-property lexer pset)
abdf50aa
MW
502 (unless (require-token lexer #\, :errorp nil)
503 (return)))
504 (require-token lexer #\])
77027cca 505 pset)))
abdf50aa
MW
506
507;;;--------------------------------------------------------------------------
508;;; Testing cruft.
509
510#+test
77027cca 511(with-input-from-string (raw "[role = before, integer = 42 * (3 - 1)]")
abdf50aa
MW
512 (let* ((in (make-instance 'position-aware-input-stream :stream raw))
513 (lexer (make-instance 'sod-lexer :stream in)))
514 (next-char lexer)
515 (next-token lexer)
516 (multiple-value-call #'values
517 (parse-property-set lexer)
518 (token-type lexer))))
519
520;;;----- That's all, folks --------------------------------------------------