;;; -*-lisp-*- ;;; ;;; Top-level parser for module syntax ;;; ;;; (c) 2010 Straylight/Edgeware ;;; ;;;----- Licensing notice --------------------------------------------------- ;;; ;;; This file is part of the Sensible Object Design, an object system for C. ;;; ;;; SOD is free software; you can redistribute it and/or modify ;;; it under the terms of the GNU General Public License as published by ;;; the Free Software Foundation; either version 2 of the License, or ;;; (at your option) any later version. ;;; ;;; SOD is distributed in the hope that it will be useful, ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with SOD; if not, write to the Free Software Foundation, ;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. (in-package #:sod) ;;;-------------------------------------------------------------------------- ;;; Toplevel syntax. ;;; Type names. (define-pluggable-parser module typename (scanner pset) ;; `typename' list[id] `;' (declare (ignore pset)) (with-parser-context (token-scanner-context :scanner scanner) (parse (and "typename" (skip-many () (error () (seq ((id :id)) (if (or (gethash id *module-type-map*) (find-simple-c-type id)) (cerror* "Type `~A' already defined" id) (add-to-module *module* (make-instance 'type-item :name id)))) (skip-until () #\, #\;)) #\,) (must #\;))))) ;;; Fragments. (define-pluggable-parser module code (scanner pset) ;; `code' id `:' item-name [constraints] `{' c-fragment `}' ;; ;; constraints ::= `[' list[constraint] `]' ;; constraint ::= item-name+ ;; item-name ::= id | `(' id+ `)' (declare (ignore pset)) (with-parser-context (token-scanner-context :scanner scanner) (labels ((kw () (parse (seq ((kw :id)) (intern (frob-identifier kw) 'keyword)))) (item () (parse (or (kw) (seq (#\( (names (list (:min 1) (kw))) #\)) names))))) (parse (seq ("code" (reason (must (kw))) (nil (must #\:)) (name (must (item))) (constraints (? (seq (#\[ (constraints (list () (list (:min 1) (error (:ignore-unconsumed t) (item) (skip-until () :id #\( #\, #\]))) #\,)) #\]) constraints))) (fragment (parse-delimited-fragment scanner #\{ #\}))) (when name (add-to-module *module* (make-instance 'code-fragment-item :fragment fragment :constraints constraints :reason reason :name name)))))))) ;;; External files. (export 'read-module) (defun read-module (pathname &key (truename nil truep) location) "Parse the file at PATHNAME as a module, returning it. This is the main entry point for parsing module files. You may well know the file's TRUENAME already (e.g., because `probe-file' dropped it into your lap) so you can avoid repeating the search by providing it. The LOCATION is the thing which wanted the module imported -- usually a `file-location' object, though it might be anything other than `t' which can be printed in the event of circular imports." (setf pathname (merge-pathnames pathname (make-pathname :type "SOD" :case :common))) (unless truep (setf truename (truename pathname))) (define-module (pathname :location location :truename truename) (with-open-file (f-stream pathname :direction :input) (let* ((*readtable* (copy-readtable)) (*package* (find-package '#:sod-user)) (char-scanner (make-instance 'charbuf-scanner :stream f-stream :filename (namestring pathname))) (scanner (make-instance 'sod-token-scanner :char-scanner char-scanner))) (with-default-error-location (scanner) (with-parser-context (token-scanner-context :scanner scanner) (multiple-value-bind (result winp consumedp) (parse (skip-many () (seq ((pset (parse-property-set scanner)) (nil (error () (plug module scanner pset) (skip-until (:keep-end nil) #\; #\})))) (check-unused-properties pset)))) (declare (ignore consumedp)) (unless winp (syntax-error scanner result))))))))) (define-pluggable-parser module test (scanner pset) ;; `demo' string `;' (declare (ignore pset)) (with-parser-context (token-scanner-context :scanner scanner) (parse (seq ("demo" (string (must :string)) (nil (must #\;))) (format t ";; DEMO ~S~%" string))))) (define-pluggable-parser module file (scanner pset) ;; `import' string `;' ;; `load' string `;' (declare (ignore pset)) (flet ((common (name type what thunk) (when name (find-file scanner (merge-pathnames name (make-pathname :type type :case :common)) what thunk)))) (with-parser-context (token-scanner-context :scanner scanner) (parse (or (seq ("import" (name (must :string)) (nil (must #\;))) (common name "SOD" "module" (lambda (path true) (handler-case (let ((module (read-module path :truename true))) (when module (module-import module) (pushnew module (module-dependencies *module*)))) (file-error (error) (cerror* "Error reading module ~S: ~A" path error)) (error (error) (cerror* "Unexpected error reading ~ module ~S: ~A" path error)))))) (seq ("load" (name (must :string)) (nil (must #\;))) (common name "LISP" "Lisp file" (lambda (path true) (handler-case (load true :verbose nil :print nil) (error (error) (cerror* "Error loading Lisp file ~S: ~A" path error))))))))))) ;;; Setting properties. (define-pluggable-parser module set (scanner pset) ;; `set' list[property] `;' (with-parser-context (token-scanner-context :scanner scanner) (parse (and "set" (lisp (let ((module-pset (module-pset *module*))) (when pset (pset-map (lambda (prop) (add-property module-pset (p-name prop) (p-value prop) :type (p-type prop) :location (p-location prop)) (setf (p-seenp prop) t)) pset)) (parse (skip-many (:min (if pset 0 1)) (error (:ignore-unconsumed t) (parse-property scanner module-pset) (skip-until () #\, #\;)) #\,)))) #\;)))) ;;; Lisp escape. (define-pluggable-parser module lisp (scanner pset) ;; `lisp' s-expression `;' (declare (ignore pset)) (with-parser-context (token-scanner-context :scanner scanner) (parse (seq ((sexp (if (and (eql (token-type scanner) :id) (string= (token-value scanner) "lisp")) (let* ((stream (make-scanner-stream scanner)) (sexp (read stream t))) (scanner-step scanner) (values sexp t t)) (values '((:id "lisp")) nil nil))) (nil (must #\;))) (eval sexp))))) ;;;-------------------------------------------------------------------------- ;;; Class declarations. (export 'class-item) (define-pluggable-parser class-item initfrags (scanner class pset) ;; raw-class-item ::= frag-keyword `{' c-fragment `}' ;; frag-keyword ::= `init' | `teardown' (with-parser-context (token-scanner-context :scanner scanner) (parse (seq ((make (or (seq ("init") #'make-sod-class-initfrag) (seq ("teardown") #'make-sod-class-tearfrag))) (frag (parse-delimited-fragment scanner #\{ #\}))) (funcall make class frag pset scanner))))) (define-pluggable-parser class-item initargs (scanner class pset) ;; initarg-item ::= `initarg' declspec+ list[init-declarator] ;; init-declarator ::= declarator [`=' initializer] (with-parser-context (token-scanner-context :scanner scanner) (parse (seq ("initarg" (base-type (parse-c-type scanner)) (nil (skip-many (:min 1) (seq ((declarator (parse-declarator scanner base-type)) (init (? (parse-delimited-fragment scanner #\= (list #\; #\,) :keep-end t)))) (make-sod-user-initarg class (cdr declarator) (car declarator) pset init scanner)) #\,)) (nil (must #\;))))))) (defun synthetic-name () "Return an obviously bogus synthetic not-identifier." (let ((ix *temporary-index*)) (incf *temporary-index*) (make-instance 'temporary-variable :tag (format nil "%%#~A" ix)))) (defun parse-class-body (scanner pset name supers) ;; class-body ::= `{' class-item* `}' ;; ;; class-item ::= property-set raw-class-item (with-parser-context (token-scanner-context :scanner scanner) (when name (make-class-type name)) (let* ((duff (null name)) (synthetic-name (or name (let ((var (synthetic-name))) (unless pset (setf pset (make-property-set))) (unless (pset-get pset "nick") (add-property pset "nick" var :type :id)) var))) (class (make-sod-class synthetic-name (restart-case (mapcar #'find-sod-class (or supers (list "SodObject"))) (continue () (setf duff t) (list (find-sod-class "SodObject")))) pset scanner)) (nick (sod-class-nickname class))) (labels ((must-id () (parse (must :id (progn (setf duff t) (synthetic-name))))) (parse-maybe-dotted-name () ;; maybe-dotted-name ::= [id `.'] id ;; ;; A plain identifier is returned as a string, as usual; a ;; dotted identifier is returned as a cons cell of the two ;; names. (parse (seq ((name-a (must-id)) (name-b (? (seq (#\. (id (must-id))) id)))) (if name-b (cons name-a name-b) name-a)))) (parse-maybe-dotted-declarator (base-type) ;; Parse a declarator or dotted-declarator, i.e., one whose ;; centre is maybe-dotted-name above. (parse-declarator scanner base-type :keywordp t :kernel #'parse-maybe-dotted-name)) (parse-message-item (sub-pset type name) ;; message-item ::= ;; declspec+ declarator -!- (method-body | `;') ;; ;; Don't allow a method-body here if the message takes a ;; varargs list, because we don't have a name for the ;; `va_list' parameter. (let ((message (make-sod-message class name type sub-pset scanner))) (if (varargs-message-p message) (parse #\;) (parse (or #\; (parse-method-item sub-pset type nick name)))))) (parse-method-item (sub-pset type sub-nick name) ;; method-item ::= ;; declspec+ dotted-declarator -!- method-body ;; ;; method-body ::= `{' c-fragment `}' | `extern' `;' (parse (seq ((body (or (seq ("extern" #\;) nil) (parse-delimited-fragment scanner #\{ #\})))) (restart-case (make-sod-method class sub-nick name type body sub-pset scanner) (continue () :report "Continue"))))) (parse-initializer () ;; initializer ::= `=' c-fragment ;; ;; Return a VALUE, ready for passing to a `sod-initializer' ;; constructor. (parse-delimited-fragment scanner #\= '(#\, #\;) :keep-end t)) (parse-slot-item (sub-pset base-type type name) ;; slot-item ::= ;; declspec+ declarator -!- [initializer] ;; [`,' list[init-declarator]] `;' ;; ;; init-declarator ::= declarator [initializer] (flet ((make-it (name type init) (restart-case (progn (make-sod-slot class name type sub-pset scanner) (when init (make-sod-instance-initializer class nick name init sub-pset scanner))) (continue () :report "Continue")))) (parse (and (error () (seq ((init (? (parse-initializer)))) (make-it name type init)) (skip-until () #\, #\;)) (skip-many () (error (:ignore-unconsumed t) (seq (#\, (ds (parse-declarator scanner base-type)) (init (? (parse-initializer)))) (make-it (cdr ds) (car ds) init)) (skip-until () #\, #\;))) (must #\;))))) (parse-initializer-item (sub-pset must-init-p constructor) ;; initializer-item ::= ;; [`class'] -!- list[slot-initializer] `;' ;; ;; slot-initializer ::= id `.' id [initializer] (let ((parse-init (if must-init-p #'parse-initializer (parser () (? (parse-initializer)))))) (parse (and (skip-many () (error (:ignore-unconsumed t) (seq ((name-a :id) #\. (name-b (must-id)) (init (funcall parse-init))) (restart-case (funcall constructor class name-a name-b init sub-pset scanner) (continue () :report "Continue"))) (skip-until () #\, #\;)) #\,) (must #\;))))) (class-item-dispatch (sub-pset base-type type name) ;; Logically part of `parse-raw-class-item', but the ;; indentation was getting crazy. We're currently at ;; ;; raw-class-item ::= ;; declspec+ (declarator | dotted-declarator) -!- ... ;; | other-items ;; ;; If the declarator is dotted then this must be a method ;; definition; otherwise it might be a message or slot. (cond ((not (typep type 'c-function-type)) (when (consp name) (cerror* "Method declarations must have function type") (setf name (cdr name))) (parse-slot-item sub-pset base-type type name)) ((consp name) (parse-method-item sub-pset type (car name) (cdr name))) (t (parse-message-item sub-pset type name)))) (parse-raw-class-item (sub-pset) ;; raw-class-item ::= ;; message-item ;; | method-item ;; | slot-item ;; | initializer-item ;; | initfrag-item ;; ;; Most of the above begin with declspecs and a declarator ;; (which might be dotted). So we parse that here and ;; dispatch based on what we find. (parse (or (plug class-item scanner class sub-pset) (peek (seq ((ds (parse-c-type scanner)) (dc (parse-maybe-dotted-declarator ds)) (nil (commit)) (nil (class-item-dispatch sub-pset ds (car dc) (cdr dc)))))) (and "class" (parse-initializer-item sub-pset t #'make-sod-class-initializer)) (parse-initializer-item sub-pset nil #'make-sod-instance-initializer))))) (parse (seq ((nil (must #\{)) (nil (skip-many () (seq ((sub-pset (parse-property-set scanner)) (nil (parse-raw-class-item sub-pset))) (check-unused-properties sub-pset)))) (nil (must #\}))) (unless (finalize-sod-class class) (setf duff t)) (unless duff (add-to-module *module* class)))))))) (define-pluggable-parser module class (scanner pset) ;; `class' id `:' list[id] class-body ;; `class' id `;' (with-parser-context (token-scanner-context :scanner scanner) (parse (seq ("class" (name (must :id)) (nil (or (seq (#\;) (when name (make-class-type name))) (seq ((supers (must (seq (#\: (ids (list () :id #\,))) ids))) (nil (parse-class-body scanner pset name supers))))))))))) ;;;----- That's all, folks --------------------------------------------------