3 ;;; Protocol for lexical analysis
5 ;;; (c) 2009 Straylight/Edgeware
8 ;;;----- Licensing notice ---------------------------------------------------
10 ;;; This file is part of the Sensible Object Design, an object system for C.
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.
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.
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.
28 ;;;--------------------------------------------------------------------------
31 (export 'sod-token-scanner)
32 (defclass sod-token-scanner (token-scanner)
33 ((char-scanner :initarg :char-scanner :reader token-scanner-char-scanner))
35 "A token scanner for SOD input files.
37 Not a lot here, apart from a character scanner to read from and the
38 standard token scanner infrastructure."))
40 ;;;--------------------------------------------------------------------------
41 ;;; Indicators and error messages.
43 (defvar *indicator-map* (make-hash-table)
44 "Hash table mapping indicator objects to human-readable descriptions.")
46 (export 'define-indicator)
47 (defun define-indicator (indicator description)
48 "Associate an INDICATOR with its textual DESCRIPTION.
50 Updates the the `*indicator-map*'."
51 (setf (gethash indicator *indicator-map*) description)
54 (export 'syntax-error)
55 (defun syntax-error (scanner expected &key (continuep t))
56 "Signal a (maybe) continuable syntax error."
57 (labels ((show-token (type value)
59 (format nil "~/sod::show-char/" type)
61 (:id (format nil "<identifier~@[ `~A'~]>" value))
62 (:int "<integer-literal>")
63 (:string "<string-literal>")
64 (:char "<character-literal>")
65 (:eof "<end-of-file>")
67 (t (format nil "<? ~S~@[ ~S~]>" type value)))))
68 (show-expected (thing)
69 (acond ((gethash thing *indicator-map*) it)
70 ((atom thing) (show-token thing nil))
72 (format nil "`~A'" (cadr thing)))
73 (t (format nil "<? ~S>" thing)))))
74 (funcall (if continuep #'cerror* #'error)
76 expected ~{~#[<bug>~;~A~;~A or ~A~:;~A, ~]~} ~
78 (mapcar #'show-expected expected)
79 (show-token (token-type scanner) (token-value scanner)))))
82 (defun lexer-error (char-scanner expected consumedp)
83 "Signal a continuable lexical error."
84 (cerror* "Lexical error: ~
85 expected ~{~#[<bug>~;~A~;~A or ~A~;:~A, ~]~} ~
86 but found ~/sod::show-char/~
90 (character (format nil "~/sod::show-char/" exp))
91 (string (format nil "`~A'" exp))
92 ((cons (eql :digit) *) (format nil "<radix-~A digit>"
94 ((eql :eof) "<end-of-file>")
95 ((eql :any) "<character>")
96 (t (format nil "<? ~S>" exp))))
98 (and (not (scanner-at-eof-p char-scanner))
99 (scanner-current-char char-scanner))
100 (and consumedp (file-location char-scanner))))
103 (defparse skip-until (:context (context token-scanner-context)
104 (&key (keep-end nil keep-end-p))
106 "Discard tokens until we find one listed in TOKEN-TYPES.
108 If KEEP-END is true then retain the found token for later; otherwise
109 discard it. KEEP-END defaults to true if multiple TOKEN-TYPES are given;
110 otherwise false. If end-of-file is encountered then the indicator list is
111 simply the list of TOKEN-TYPES; otherwise the result is `nil'."
112 `(skip-until ,(parser-scanner context)
114 :keep-end ,(if keep-end-p keep-end
115 (> (length token-types) 1))))
118 (defparse error (:context (context token-scanner-context)
119 (&key ignore-unconsumed)
120 sub &optional (recover t))
121 "Try to parse SUB; if it fails then report an error, and parse RECOVER.
123 This is the main way to recover from errors and continue parsing. Even
124 then, it's not especially brilliant.
126 If the SUB parser succeeds then just propagate its result: it's like we
127 were never here. Otherwise, try to recover in a sensible way so we can
128 continue parsing. The details of this recovery are subject to change, but
129 the final action is generally to invoke the RECOVER parser and return its
132 If IGNORE-UNCONSUMED evaluates non-nil, then just propagate a failure of
133 SUB if it didn't consume input. (This makes it suitable for use where the
134 parser containing `error' might be optional.)"
135 `(parse-error-recover ,(parser-scanner context)
138 :ignore-unconsumed ,ignore-unconsumed))
140 ;;;--------------------------------------------------------------------------
141 ;;; Lexical analysis utilities.
143 (export 'scan-comment)
144 (defun scan-comment (char-scanner)
145 "Scan a comment (either `/* ... */' or `// ...') from CHAR-SCANNER.
147 The result isn't interesting."
148 (with-parser-context (character-scanner-context :scanner char-scanner)
151 (and (skip-many () (not #\*))
152 (label "*/" (skip-many (:min 1) #\*)))
156 (skip-many () (not #\newline))
159 ;;;----- That's all, folks --------------------------------------------------