src/output-impl.lisp: No need to use `equal' on items rather than names.
[sod] / src / c-types-parse.lisp
index ba6bf6f..15d942a 100644 (file)
@@ -7,7 +7,7 @@
 
 ;;;----- Licensing notice ---------------------------------------------------
 ;;;
 
 ;;;----- Licensing notice ---------------------------------------------------
 ;;;
-;;; This file is part of the Sensble Object Design, an object system for C.
+;;; 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
 ;;;
 ;;; SOD is free software; you can redistribute it and/or modify
 ;;; it under the terms of the GNU General Public License as published by
@@ -65,8 +65,9 @@
 ;; `inline') since it's meaningless to me.
 
 (defclass declspec ()
 ;; `inline') since it's meaningless to me.
 
 (defclass declspec ()
-  ;; This could have been done with DEFSTRUCT just as well, but a DEFCLASS
-  ;; can be tweaked interactively, which is a win at the moment.
+  ;; Despite the fact that it looks pretty trivial, this can't be done with
+  ;; `defstruct' for the simple reason that we add more methods to the
+  ;; accessor functions later.
   ((label :type keyword :initarg :label :reader ds-label)
    (name :type string :initarg :name :reader ds-name)
    (kind :type (member type sign size qualifier)
   ((label :type keyword :initarg :label :reader ds-label)
    (name :type string :initarg :name :reader ds-name)
    (kind :type (member type sign size qualifier)
 
 (defparameter *declspec-map*
   (let ((map (make-hash-table :test #'equal)))
 
 (defparameter *declspec-map*
   (let ((map (make-hash-table :test #'equal)))
-    (dolist (item '((type :void :char :int :float :double)
+    (dolist (item '((type :void :char :int :float :double
+                         (:bool :name "_Bool"))
+                   (complexity (:complex :name "_Complex")
+                               (:imaginary :name "_Imaginary"))
                    ((type :taggedp t) :enum :struct :union)
                    (size :short :long (:long-long :name "long long"))
                    (sign :signed :unsigned)
                    ((type :taggedp t) :enum :struct :union)
                    (size :short :long (:long-long :name "long long"))
                    (sign :signed :unsigned)
                                     :taggedp taggedp)))
              (setf (gethash name map) ds
                    (gethash label map) ds))))))
                                     :taggedp taggedp)))
              (setf (gethash name map) ds
                    (gethash label map) ds))))))
+    (dolist (label '(:complex :imaginary :bool))
+      (setf (gethash (string-downcase label) map) (gethash label map)))
     map)
     map)
-  "Maps symbolic labels and textual names to DECLSPEC instances.")
+  "Maps symbolic labels and textual names to `declspec' instances.")
 
 ;; A collection of declaration specifiers, and how to merge them together.
 
 (defclass declspecs ()
 
 ;; A collection of declaration specifiers, and how to merge them together.
 
 (defclass declspecs ()
-  ;; Despite the fact that it looks pretty trivial, this can't be done with
-  ;; DEFCLASS for the simple reason that we add more methods to the accessor
-  ;; functions later.
+  ;; This could have been done with `defstruct' just as well, but a
+  ;; `defclass' can be tweaked interactively, which is a win at the moment.
   ((type :initform nil :initarg :type :reader ds-type)
   ((type :initform nil :initarg :type :reader ds-type)
+   (complexity :initform nil :initarg :complexity :reader ds-complexity)
    (sign :initform nil :initarg :sign :reader ds-sign)
    (size :initform nil :initarg :size :reader ds-size)
    (qualifier :initform nil :initarg :qualifiers :reader ds-qualifiers))
    (sign :initform nil :initarg :sign :reader ds-sign)
    (size :initform nil :initarg :size :reader ds-size)
    (qualifier :initform nil :initarg :qualifiers :reader ds-qualifiers))
     we'll just have to live with that.
 
     (Why are instances immutable?  Because it's much easier to merge a new
     we'll just have to live with that.
 
     (Why are instances immutable?  Because it's much easier to merge a new
-    specifier into an existing collection, and then check that the resulting
-    thing is valid rather than having to deal with all of the possible
+    specifier into an existing collection and then check that the resulting
+    thing is valid, rather than having to deal with all of the possible
     special cases of what the new thing might be.  And if the merged
     collection isn't good, I must roll back to the previous version.  So I
     don't get to take advantage of a mutable structure.)"))
     special cases of what the new thing might be.  And if the merged
     collection isn't good, I must roll back to the previous version.  So I
     don't get to take advantage of a mutable structure.)"))
 (defmethod ds-kind ((ty c-type)) 'type)
 
 (defparameter *good-declspecs*
 (defmethod ds-kind ((ty c-type)) 'type)
 
 (defparameter *good-declspecs*
-  '(((:int) (:signed :unsigned) (:short :long :long-long))
-    ((:char) (:signed :unsigned) ())
-    ((:double) () (:long))
-    (t () ()))
+  '(((:int) (:signed :unsigned) (:short :long :long-long) ())
+    ((:char) (:signed :unsigned) () ())
+    ((:double) () (:long) (:complex :imaginary))
+    (t () () ()))
   "List of good collections of declaration specifiers.
 
   "List of good collections of declaration specifiers.
 
-   Each item is a list of the form (TYPES SIGNS SIZES).  Each of TYPES, SIGNS
-   and SIZES is either a list of acceptable specifiers of the appropriate
-   kind, or T, which matches any specifier.")
+   Each item is a list of the form (TYPES SIGNS SIZES COMPLEXITIES).  Each of
+   TYPES, SIGNS, SIZES, and COMPLEXITIES, is either a list of acceptable
+   specifiers of the appropriate kind, or T, which matches any specifier.")
 
 (defun good-declspecs-p (specs)
   "Are SPECS a good collection of declaration specifiers?"
 
 (defun good-declspecs-p (specs)
   "Are SPECS a good collection of declaration specifiers?"
-  (let ((speclist (list (ds-type specs) (ds-sign specs) (ds-size specs))))
+  (let ((speclist (list (ds-type specs)
+                       (ds-sign specs)
+                       (ds-size specs)
+                       (ds-complexity specs))))
     (some (lambda (it)
            (every (lambda (spec pat)
                     (or (eq pat t) (null spec)
     (some (lambda (it)
            (every (lambda (spec pat)
                     (or (eq pat t) (null spec)
   (let ((type (ds-type specs))
        (size (ds-size specs))
        (sign (ds-sign specs))
   (let ((type (ds-type specs))
        (size (ds-size specs))
        (sign (ds-sign specs))
+       (cplx (ds-complexity specs))
        (quals (mapcar #'ds-label (ds-qualifiers specs))))
     (cond ((typep type 'c-type)
           (qualify-c-type type quals))
        (quals (mapcar #'ds-label (ds-qualifiers specs))))
     (cond ((typep type 'c-type)
           (qualify-c-type type quals))
-         ((or type size sign)
+         ((or type size sign cplx)
           (when (and sign (eq (ds-label sign) :signed)
                      (eq (ds-label type) :int))
             (setf sign nil))
           (when (and sign (eq (ds-label sign) :signed)
                      (eq (ds-label type) :int))
             (setf sign nil))
                 ((null type)
                  (setf type (gethash :int *declspec-map*))))
           (make-simple-type (format nil "~{~@[~A~^ ~]~}"
                 ((null type)
                  (setf type (gethash :int *declspec-map*))))
           (make-simple-type (format nil "~{~@[~A~^ ~]~}"
-                                    (mapcar #'ds-label
+                                    (mapcar #'ds-name
                                             (remove nil
                                             (remove nil
-                                                    (list sign size type))))
+                                                    (list sign cplx
+                                                          size type))))
                             quals))
          (t
           nil))))
                             quals))
          (t
           nil))))
 
 (defun scan-declspec
     (scanner &key (predicate (constantly t)) (indicator :declspec))
 
 (defun scan-declspec
     (scanner &key (predicate (constantly t)) (indicator :declspec))
-  "Scan a DECLSPEC from SCANNER.
+  "Scan a `declspec' from SCANNER.
 
    If PREDICATE is provided then only succeed if (funcall PREDICATE DECLSPEC)
    is true, where DECLSPEC is the raw declaration specifier or C-type object,
 
    If PREDICATE is provided then only succeed if (funcall PREDICATE DECLSPEC)
    is true, where DECLSPEC is the raw declaration specifier or C-type object,
   ;; Turns out to be easier to do this by hand.
   (let ((ds (and (eq (token-type scanner) :id)
                 (let ((kw (token-value scanner)))
   ;; Turns out to be easier to do this by hand.
   (let ((ds (and (eq (token-type scanner) :id)
                 (let ((kw (token-value scanner)))
-                  (or (gethash kw *module-type-map*)
+                  (or (and (boundp '*module-type-map*)
+                           (gethash kw *module-type-map*))
                       (gethash kw *declspec-map*))))))
     (cond ((or (not ds) (and predicate (not (funcall predicate ds))))
           (values (list indicator) nil nil))
                       (gethash kw *declspec-map*))))))
     (cond ((or (not ds) (and predicate (not (funcall predicate ds))))
           (values (list indicator) nil nil))
-         ((ds-taggedp ds)
+         ((and (typep ds 'declspec) (ds-taggedp ds))
           (scanner-step scanner)
           (if (eq (token-type scanner) :id)
               (let ((ty (make-c-tagged-type (ds-label ds)
           (scanner-step scanner)
           (if (eq (token-type scanner) :id)
               (let ((ty (make-c-tagged-type (ds-label ds)
           (values it t consumedp)
           (values (list :declspec) nil consumedp)))))
 
           (values it t consumedp)
           (values (list :declspec) nil consumedp)))))
 
+(export 'parse-c-type)
 (defun parse-c-type (scanner)
   "Parse a C type from declaration specifiers.
 
 (defun parse-c-type (scanner)
   "Parse a C type from declaration specifiers.
 
 ;;; (funcall FUNC TYPE) returns the derived type.  The result of
 ;;; `parse-declarator' will be of this form.
 
 ;;; (funcall FUNC TYPE) returns the derived type.  The result of
 ;;; `parse-declarator' will be of this form.
 
-(defun parse-declarator (scanner base-type &key abstractp)
-  (with-parser-context (token-scanner-context :scanner scanner)
+(export 'parse-declarator)
+(defun parse-declarator (scanner base-type &key kernel abstractp)
+  "Parse a C declarator, returning a pair (C-TYPE . NAME).
 
 
-    (labels ((qualifiers ()
-              ;; QUALIFIER*
-
-              (parse
-                (seq ((quals (list ()
-                               (scan-declspec
-                                scanner
-                                :indicator :qualifier
-                                :predicate (lambda (ds)
-                                             (and (typep ds 'declspec)
-                                                  (eq (ds-kind ds)
-                                                      'qualifier)))))))
-                  (mapcar #'ds-label quals))))
-
-            (star ()
-              ;; Prefix: `*' QUALIFIERS
-
-              (parse (seq (#\* (quals (qualifiers)))
-                       (preop "*" (state 9)
-                         (cons (lambda (type)
-                                 (funcall (car state)
-                                          (make-pointer-type type quals)))
-                               (cdr state))))))
-
-            (prefix-lparen ()
-              ;; Prefix: `('
-              ;;
-              ;; Opening parentheses are treated as prefix operators by the
-              ;; expression parsing engine.  There's an annoying ambiguity
-              ;; in the syntax if abstract declarators are permitted: a `('
-              ;; might be either the start of a nested subdeclarator or the
-              ;; start of a postfix function argument list.  The two are
-              ;; disambiguated by stating that if the token following the
-              ;; `(' is a `)' or a declaration specifier, then we have a
-              ;; postfix argument list.
-
-              (parse
-                (peek (seq (#\(
-                            (nil (if (and abstractp
-                                          (eq (token-type scanner) :id)
-                                          (let ((id (token-value scanner)))
-                                            (or (gethash id
-                                                         *module-type-map*)
-                                                (gethash id
-                                                         *declspec-map*))))
-                                     (values nil nil nil)
-                                     (values t t nil))))
-                        (lparen #\))))))
-
-            (centre ()
-              ;; ID | empty
-              ;;
-              ;; The centre might be empty or contain an identifier,
-              ;; depending on the setting of ABSTRACTP.
-
-              (parse (or (when (not (eq abstractp t))
-                           (seq ((id :id)) (cons #'identity id)))
-                         (when abstractp
-                           (t (cons #'identity nil))))))
-
-            (argument-list ()
-              ;; [ ARGUMENT [ `,' ARGUMENT ]* ]
-
-              (parse (list ()
-                       (seq ((base-type (parse-c-type scanner))
-                             (dtor (parse-declarator scanner
-                                                     base-type
-                                                     :abstractp :maybe)))
-                         (make-argument (cdr dtor) (car dtor)))
-                       #\,)))
-
-            (postfix-lparen ()
-              ;; Postfix: `(' ARGUMENT-LIST `)'
-
-              (parse (seq (#\( (args (argument-list)) #\))
-                       (postop "()" (state 9)
-                         (cons (lambda (type)
-                                 (funcall (car state)
-                                          (make-function-type type args)))
-                               (cdr state))))))
-
-            (dimension ()
-              ;; `[' C-FRAGMENT ']'
-
-              (parse-delimited-fragment scanner #\[ #\]))
-
-            (lbracket ()
-              ;; Postfix: DIMENSION+
-
-              (parse (seq ((dims (list (:min 1) (dimension))))
-                       (postop "[]" (state 10)
-                         (cons (lambda (type)
-                                 (funcall (car state)
-                                          (make-array-type type dims)))
-                               (cdr state)))))))
-
-      ;; And now we actually do the declarator parsing.
-      (parse (seq ((value (expr (:nestedp nestedp)
-
-                           ;; An actual operand.
-                           (centre)
-
-                           ;; Binary operators.  There aren't any.
-                           nil
-
-                           ;; Prefix operators.
-                           (or (star)
-                               (prefix-lparen))
-
-                           ;; Postfix operators.
-                           (or (postfix-lparen)
-                               (lbracket)
-                               (when nestedp (seq (#\)) (rparen #\))))))))
-              (cons (funcall (car value) base-type) (cdr value)))))))
+   The SCANNER is a token scanner to read from.  The BASE-TYPE is the type
+   extracted from the preceding declaration specifiers, as parsed by
+   `parse-c-type'.
+
+   The result contains both the resulting constructed C-TYPE (with any
+   qualifiers etc. as necessary), and the name from the middle of the
+   declarator.  The name is parsed using the KERNEL parser provided, and
+   defaults to matching a simple identifier `:id'.  This might, e.g., be
+   (? :id) to parse an `abstract declarator' which has optional names.
+
+   There's an annoying ambiguity in the syntax, if an empty KERNEL is
+   permitted.  In this case, you must ensure that ABSTRACTP is true so that
+   the appropriate heuristic can be applied.  As a convenience, if ABSTRACTP
+   is true then `(? :id)' is used as the default KERNEL."
+  (with-parser-context (token-scanner-context :scanner scanner)
+    (let ((kernel-parser (cond (kernel kernel)
+                              (abstractp (parser () (? :id)))
+                              (t (parser () :id)))))
+
+      (labels ((qualifiers ()
+                ;; qualifier*
+
+                (parse
+                  (seq ((quals (list ()
+                                 (scan-declspec
+                                  scanner
+                                  :indicator :qualifier
+                                  :predicate (lambda (ds)
+                                               (and (typep ds 'declspec)
+                                                    (eq (ds-kind ds)
+                                                        'qualifier)))))))
+                    (mapcar #'ds-label quals))))
+
+              (star ()
+                ;; Prefix: `*' qualifiers
+
+                (parse (seq (#\* (quals (qualifiers)))
+                         (preop "*" (state 9)
+                           (cons (lambda (type)
+                                   (funcall (car state)
+                                            (make-pointer-type type quals)))
+                                 (cdr state))))))
+
+              (predict-argument-list-p ()
+                ;; See `prefix-lparen'.  Predict an argument list rather
+                ;; than a nested declarator if (a) abstract declarators are
+                ;; permitted and (b) the next token is a declaration
+                ;; specifier or ellipsis.
+                (let ((type (token-type scanner))
+                      (value (token-value scanner)))
+                  (and abstractp
+                       (or (eq type :ellipsis)
+                           (and (eq type :id)
+                                (or (gethash value *module-type-map*)
+                                    (gethash value *declspec-map*)))))))
+
+              (prefix-lparen ()
+                ;; Prefix: `('
+                ;;
+                ;; Opening parentheses are treated as prefix operators by
+                ;; the expression parsing engine.  There's an annoying
+                ;; ambiguity in the syntax if abstract declarators are
+                ;; permitted: a `(' might be either the start of a nested
+                ;; subdeclarator or the start of a postfix function argument
+                ;; list.  The two are disambiguated by stating that if the
+                ;; token following the `(' is a `)' or a declaration
+                ;; specifier, then we have a postfix argument list.
+                (parse
+                  (peek (seq (#\(
+                              (nil (if (predict-argument-list-p)
+                                       (values nil nil nil)
+                                       (values t t nil))))
+                          (lparen #\))))))
+
+              (kernel ()
+                (parse (seq ((name (funcall kernel-parser)))
+                         (cons #'identity name))))
+
+              (argument-list ()
+                ;; [argument [`,' argument]* [`,' `...']] | `...'
+                ;;
+                ;; The possibility of a trailing `,' `...' means that we
+                ;; can't use the standard `list' parser.  Note that, unlike
+                ;; `real' C, we allow an ellipsis even if there are no
+                ;; explicit arguments.
+
+                (let ((args nil))
+                  (loop
+                    (when (eq (token-type scanner) :ellipsis)
+                      (push :ellipsis args)
+                      (scanner-step scanner)
+                      (return))
+                    (multiple-value-bind (arg winp consumedp)
+                        (parse (seq ((base-type (parse-c-type scanner))
+                                     (dtor (parse-declarator scanner
+                                                             base-type
+                                                             :abstractp t)))
+                                 (make-argument (cdr dtor) (car dtor))))
+                      (unless winp
+                        (if (or consumedp args)
+                            (return-from argument-list (values arg nil t))
+                            (return)))
+                      (push arg args))
+                    (unless (eq (token-type scanner) #\,)
+                      (return))
+                    (scanner-step scanner))
+                  (values (nreverse args) t args)))
+
+              (postfix-lparen ()
+                ;; Postfix: `(' argument-list `)'
+
+                (parse (seq (#\( (args (argument-list)) #\))
+                         (postop "()" (state 10)
+                           (cons (lambda (type)
+                                   (funcall (car state)
+                                            (make-function-type type args)))
+                                 (cdr state))))))
+
+              (dimension ()
+                ;; `[' c-fragment ']'
+
+                (parse (seq ((frag (parse-delimited-fragment
+                                    scanner #\[ #\])))
+                         (c-fragment-text frag))))
+
+              (lbracket ()
+                ;; Postfix: dimension+
+
+                (parse (seq ((dims (list (:min 1) (dimension))))
+                         (postop "[]" (state 10)
+                           (cons (lambda (type)
+                                   (funcall (car state)
+                                            (make-array-type type dims)))
+                                 (cdr state)))))))
+
+       ;; And now we actually do the declarator parsing.
+       (parse (seq ((value (expr (:nestedp nestedp)
+
+                             ;; An actual operand.
+                             (kernel)
+
+                             ;; Binary operators.  There aren't any.
+                             nil
+
+                             ;; Prefix operators.
+                             (or (star)
+                                 (prefix-lparen))
+
+                             ;; Postfix operators.
+                             (or (postfix-lparen)
+                                 (lbracket)
+                                 (when nestedp (seq (#\)) (rparen #\))))))))
+                (cons (funcall (car value) base-type) (cdr value))))))))
 
 ;;;----- That's all, folks --------------------------------------------------
 
 ;;;----- That's all, folks --------------------------------------------------