implemented #define and #undef.

Pascal J. Bourguignon [2015-06-24 07:05]
implemented #define and #undef.
Filename
languages/cpp/cpp.lisp
languages/cpp/tests/Makefile
languages/cpp/tests/concat.h
languages/cpp/tests/define.h
languages/cpp/tests/empty-macro.c
languages/cpp/tests/priority.h
diff --git a/languages/cpp/cpp.lisp b/languages/cpp/cpp.lisp
index 67ab034..2a14803 100644
--- a/languages/cpp/cpp.lisp
+++ b/languages/cpp/cpp.lisp
@@ -35,13 +35,22 @@
 (defpackage "COM.INFORMATIMAGO.COMMON-LISP.LANGUAGES.CPP"
   (:use "COMMON-LISP"
         "COM.INFORMATIMAGO.COMMON-LISP.CESARUM.STREAM"
-        "COM.INFORMATIMAGO.COMMON-LISP.CESARUM.STRING")
-
+        "COM.INFORMATIMAGO.COMMON-LISP.CESARUM.STRING"
+        "COM.INFORMATIMAGO.COMMON-LISP.CESARUM.UTILITY")
+  (:shadow "IMPORT" "INCLUDE")
+  (:shadowing-import-from "COM.INFORMATIMAGO.COMMON-LISP.CESARUM.STRING"
+                          "STRING-DESIGNATOR")
+  (:shadowing-import-from "COM.INFORMATIMAGO.COMMON-LISP.CESARUM.STREAM"
+                          "COPY-STREAM")
   (:export "TOKEN" "TOKEN-LINE" "TOKEN-COLUMN" "TOKEN-FILE"
            "TOKEN-TEXT" "IDENTIFIER-TOKEN" "NUMBER-TOKEN" "PUNCTUATION-TOKEN"
            "OTHER-TOKEN"

-           "READ-CPP-TOKENS"))
+           "READ-CPP-TOKENS"
+           "ENVIRONMENT-MACRO-DEFINITION"
+   "ENVIRONMENT-MACRO-DEFINEDP"
+   "ENVIRONMENT-MACRO-UNDEFINE"
+           "PROCESS"))

 (in-package "COM.INFORMATIMAGO.COMMON-LISP.LANGUAGES.CPP")

@@ -99,13 +108,7 @@
           :do (setf (aref text j) (aref text i))
               (incf j)
               (incf i)
-        :finally (setf (line-text line) (if (array-has-fill-pointer-p text)
-                                            (progn
-                                              (setf (fill-pointer text) j)
-                                              text)
-                                            (let ((result (make-array j :element-type 'character :fill-pointer j :adjustable t)))
-                                              (replace result text)
-                                              result))))))
+        :finally (setf (line-text line) (subseq text 0 j)))))
   line)


@@ -208,7 +211,8 @@
                     (incf i)
                     (if (< i (length text))
                         (incf i)
-                        (error "~A:~A: backslash in string literal at the end of the line" file lino)))
+                        (progn (cerror "Continue" "~A:~A: backslash in string literal at the end of the line" file lino)
+                               (setf state :top))))
                    ((#\")
                     (incf i)
                     (setf state :top))
@@ -219,16 +223,19 @@
                     (incf i)
                     (if (< i (length text))
                         (incf i)
-                        (error "~A:~A: backslash in character literal at the end of the line" file lino)))
+                        (progn (cerror "Continue" "~A:~A: backslash in character literal at the end of the line" file lino)
+                               (setf state :top))))
                    ((#\')
                     (incf i)
                     (setf state :top))
                    (otherwise (incf i))))))
         :finally (return (case state
                            (:in-string
-                            (error "~A:~A: unterminated string literal at the end of the line" file lino))
+                            (cerror "Continue" "~A:~A: unterminated string literal at the end of the line" file lino)
+                            (values (concatenate-chunks (nreverse chunks)) :top))
                            (:in-character
-                            (error "~A:~A: unterminated character literal at the end of the line" file lino))
+                            (cerror "Continue" "~A:~A: unterminated character literal at the end of the line" file lino)
+                            (values (concatenate-chunks (nreverse chunks)) :top))
                            (:top
                             (values (concatenate-chunks (nreverse (if (< start (length text))
                                                                       (cons (subseq text start) chunks)
@@ -252,7 +259,8 @@
                    :do (multiple-value-setq (new-line new-state)
                          (remove-comments-in-line new-line (pop lines) new-state single-line-comments)))
                  (when (eql new-state :in-multiline-comment)
-                   (error "~A:~A: end of file before end of multiline comment" file lino))
+                   (cerror "Continue" "~A:~A: end of file before end of multiline comment" file lino)
+                   (setf new-state :top))
                  (setf state new-state)
                  new-line))))

@@ -270,7 +278,8 @@
        (defclass ,class-name   (token) ())
        (defmethod print-object ((self ,class-name) stream)
          (print-unreadable-object (self stream :identity nil :type t)
-           (format stream "~S" (token-text self)))
+           (format stream "~A:~A:~A: ~S"
+                   (token-file self) (token-line self) (token-column self) (token-text self)))
          self)
        (defun ,(intern (concatenate 'string (string 'make-) (string name))) (text column line file)
          (make-instance ',class-name :text text :column column :line line :file file)))))
@@ -366,7 +375,10 @@ RETURN: the token text; the end position."
 (defun scan-delimited-literal (line start)
   (loop
     :with text := (line-text line)
-    :with terminator = (aref text start)
+    :with terminator = (ecase (aref text start)
+                         (#\" #\")
+                         (#\' #\')
+                         (#\< #\>))
     :with end := (1+ start)
     :while (< end (length text))
     :do (let ((ch (aref text end)))
@@ -375,11 +387,11 @@ RETURN: the token text; the end position."
              (incf end)
              (loop-finish))
             ((char= #\\ ch)
-             (unless (< (1+ end) (length text))
-               (error "~A:~A: unterminated ~:[string~;character~] literal ending with incomplete escape"
+             (if (< (1+ end) (length text))
+                 (incf end 2)
+                 (cerror "Continue" "~A:~A: unterminated ~:[string~;character~] literal ending with incomplete escape"
                        (line-file line) (line-lino line)
-                       (char= terminator #\')))
-             (incf end 2))
+                       (char= terminator #\'))))
             (t
              (incf end))))
     :finally (return (values (subseq text start end) end))))
@@ -391,7 +403,7 @@ RETURN: the token text; the end position."
   (let* ((text (line-text line))
          (ch   (aref text start)))
     (flet ((greedy2 (alternatives)
-             (if (and (<= (1+ start) (length text))
+             (if (and (< (1+ start) (length text))
                       (find (aref text (1+ start)) alternatives))
                  (values (subseq text start (+ 2 start)) (+ 2 start))
                  (values (subseq text start (1+ start) ) (1+ start))))
@@ -400,7 +412,7 @@ RETURN: the token text; the end position."
                ((and (<= (+ (length token) start) (length text))
                      (string= text token :start1 start :end1 (+ (length token) start)))
                 (values (subseq text start (+ (length token) start)) (+ (length token) start)))
-               ((and (<= (1+ start) (length text))
+               ((and (< (1+ start) (length text))
                      (find (aref text (1+ start)) alternatives))
                 (values (subseq text start (+ 2 start)) (+ 2 start)))
                (t
@@ -420,8 +432,9 @@ RETURN: the token text; the end position."
         ((#\>)                  (greedy3 "=>"   ">>="))
         ((#\%)                  (greedy3 "=>:"  "%:%:"))
         (otherwise
-         (error "~A:~A: invalid punctuation: ~S"
-                (line-file line) (line-lino line) ch))))))
+         (cerror "Continue" "~A:~A: invalid punctuation: ~S"
+                 (line-file line) (line-lino line) ch)
+         (values "?" (1+ start)))))))


 (defun punctuatorp (ch)
@@ -436,42 +449,59 @@ RETURN: the token text; the end position."
                                 "_"
                                 "_$")))
       (loop
+        :with header = 1
         :do (setf start (skip-spaces text start))
         :while (< start (length text))
         :collect (let ((ch (aref text start)))
-                    (cond
-                      ((or (find ch first-identifier)
-                           (alpha-char-p ch)
-                           (and accept-unicode-escapes
-                                (char= #\\ ch)
-                                (< (1+ start) (length text))
-                                (char-equal #\u (aref text (1+ start)))))
-                       (multiple-value-bind (token end) (scan-identifier line start first-identifier
-                                                                         :accept-unicode-escapes accept-unicode-escapes)
-                         (prog1 (make-identifier token start lino file)
-                           (setf start end))))
-                      ((or (and (char= ch #\.)
-                                (< (1+ start) (length text))
-                                (digit-char-p (aref text (1+ start))))
-                           (digit-char-p ch))
-                       (multiple-value-bind (token end) (scan-number line start)
-                         (prog1 (make-number token start lino file)
-                           (setf start end))))
-                      ((char= #\" ch)
-                       (multiple-value-bind (token end) (scan-delimited-literal line start)
-                         (prog1 (make-string-literal token start lino file)
-                           (setf start end))))
-                      ((char= #\' ch)
-                       (multiple-value-bind (token end) (scan-delimited-literal line start)
-                         (prog1 (make-character-literal token start lino file)
-                           (setf start end))))
-                      ((punctuatorp ch)
-                       (multiple-value-bind (token end) (scan-punctuation line start)
-                         (prog1 (make-punctuation token start lino file)
-                           (setf start end))))
-                      (t ;; others
-                       (prog1 (make-other (subseq text start (1+ start)) start lino file)
-                         (incf start)))))))))
+                   (cond
+                     ((or (find ch first-identifier)
+                          (alpha-char-p ch)
+                          (and accept-unicode-escapes
+                               (char= #\\ ch)
+                               (< (1+ start) (length text))
+                               (char-equal #\u (aref text (1+ start)))))
+                      (multiple-value-bind (token end) (scan-identifier line start first-identifier
+                                                                        :accept-unicode-escapes accept-unicode-escapes)
+                        (if (and (eql 2 header) (or (string= "include" token)
+                                                    (string= "import" token)))
+                            (setf header t)
+                            (setf header nil))
+                        (prog1 (make-identifier token start lino file)
+                          (setf start end))))
+                     ((or (and (char= ch #\.)
+                               (< (1+ start) (length text))
+                               (digit-char-p (aref text (1+ start))))
+                          (digit-char-p ch))
+                      (multiple-value-bind (token end) (scan-number line start)
+                        (setf header nil)
+                        (prog1 (make-number token start lino file)
+                          (setf start end))))
+                     ((char= #\" ch)
+                      (multiple-value-bind (token end) (scan-delimited-literal line start)
+                        (setf header nil)
+                        (prog1 (make-string-literal token start lino file)
+                          (setf start end))))
+                     ((char= #\' ch)
+                      (multiple-value-bind (token end) (scan-delimited-literal line start)
+                        (setf header nil)
+                        (prog1 (make-character-literal token start lino file)
+                          (setf start end))))
+                     ((and (eq header t) (char= #\< ch))
+                      (multiple-value-bind (token end) (scan-delimited-literal line start)
+                        (setf header nil)
+                        (prog1 (make-string-literal token start lino file)
+                          (setf start end))))
+                     ((punctuatorp ch)
+                      (multiple-value-bind (token end) (scan-punctuation line start)
+                        (if (and (eql 1 header) (string= "#" token))
+                            (setf header 2)
+                            (setf header nil))
+                        (prog1 (make-punctuation token start lino file)
+                          (setf start end))))
+                     (t ;; others
+                      (setf header nil)
+                      (prog1 (make-other (subseq text start (1+ start)) start lino file)
+                        (incf start)))))))))



@@ -520,8 +550,7 @@ RETURN: the token text; the end position."
                           (single-line-comments t)
                           (accept-unicode-escapes nil)
                           (dollar-is-punctuation nil))
-  (let ((lines (number-lines (stream-to-string-list character-stream)
-                             file-name)))
+  (let ((lines (number-lines (stream-to-string-list character-stream) file-name)))
     (when substitute-trigraphs
       (dolist (line lines)
         (substitute-trigraphs line :warn-on-trigraph warn-on-trigraph)))
@@ -529,31 +558,298 @@ RETURN: the token text; the end position."
               (tokenize-line line
                              :accept-unicode-escapes accept-unicode-escapes
                              :dollar-is-punctuation dollar-is-punctuation))
-            (remove-comments (merge-continued-lines lines :warn-spaces-in-continued-lines warn-spaces-in-continued-lines)
+            (remove-comments (merge-continued-lines lines
+                                                    :warn-spaces-in-continued-lines
+                                                    warn-spaces-in-continued-lines)
                              :single-line-comments single-line-comments))))


+(defun sharpp (token)
+  (and (typep token 'punctuation-token)
+       (or (string= "#"  (token-text token)))))
+
+(defun sharpsharpp (token)
+  (and (typep token 'punctuation-token)
+       (or (string= "##"  (token-text token)))))
+
+(defun openp (token)
+  (and (typep token 'punctuation-token)
+       (or (string= "("  (token-text token)))))
+
+(defun closep (token)
+  (and (typep token 'punctuation-token)
+       (or (string= ")"  (token-text token)))))
+
+(defun commap (token)
+  (and (typep token 'punctuation-token)
+       (or (string= ","  (token-text token)))))
+
+(defun ellipsisp (token)
+  (and (typep token 'punctuation-token)
+       (or (string= "..."  (token-text token)))))
+
+
+(defun identifierp (token)
+  (typep token 'identifier-token))
+
+
+(defgeneric environment-macro-definedp (environment macro-name))
+(defgeneric environment-macro-undefine (environment macro-name))
+(defgeneric environment-macro-definition (environment macro-name))
+(defgeneric (setf environment-macro-definition) (definition environment macro-name))

-(let ((file "tests/trigraphs.c"))
-  (with-open-file (in file)
-    (read-cpp-tokens in
-                     :file-name file
-                     :substitute-trigraphs t
-                     :warn-on-trigraph nil)))
+(defmethod environment-macro-definedp ((environment hash-table) (macro-name string))
+  (assert (eq 'equal (hash-table-test environment)))
+  (nth-value 1 (gethash macro-name environment)))
+
+(defmethod environment-macro-undefine ((environment hash-table) (macro-name string))
+  (assert (eq 'equal (hash-table-test environment)))
+  (remhash macro-name environment))
+
+(defmethod environment-macro-definition ((environment hash-table) (macro-name string))
+  (assert (eq 'equal (hash-table-test environment)))
+  (gethash macro-name environment))
+
+(defmethod (setf environment-macro-definition) (definition (environment hash-table) (macro-name string))
+  (assert (eq 'equal (hash-table-test environment)))
+  (setf (gethash macro-name environment) definition))
+
+
+
+(defun parse-stringifies (line parameters)
+  (loop
+    :while line
+    :if (sharpp (first line))
+      :collect (let* ((sharp (pop line))
+                      (file  (token-file sharp))
+                      (lino  (token-line sharp)))
+                 (let ((parameter (pop line)))
+                   (if (or (null parameter)
+                           (not (member (token-text parameter) parameters
+                                        :key (function token-text)
+                                        :test (function string=))))
+                       (progn
+                         (cerror "Continue" "~A:~A: '#' is not followed by a macro parameter" file lino)
+                         (if (null parameter)
+                             sharp
+                             parameter))
+                       `(:stringify ,parameter))))
+    :else
+      :collect (pop line)))
+
+(defun parse-concatenates (line)
+  (if (sharpsharpp (first line))
+      (progn
+        (cerror "Continue" "~A:~A: '##' cannot appear at either end of a macro expansion"
+                (token-file (first line)) (token-line (first line)))
+        line)
+      (loop
+        :with result = ()
+        :while line
+        :do (let ((curr (pop line)))
+              (if (sharpsharpp (first line))
+                  (let ((file (token-file (first line)))
+                        (lino (token-line (first line))))
+                    (push (loop
+                            :with concat = (list curr)
+                            :while (sharpsharpp (first line))
+                            :do (pop line)
+                                (unless line
+                                  (cerror "Continue" "~A:~A: '##' cannot appear at either end of a macro expansion" file lino))
+                                (unless (sharpsharpp (first line))
+                                  (push (pop line) concat))
+                            :finally (return `(:concatenate ,@(nreverse concat)))) result))
+                  (push curr result)))
+        :finally (return (nreverse result)))))
+
+(defun parse-function-macro-definition-body (line parameters)
+  (when line
+    (parse-concatenates (parse-stringifies line parameters))))
+
+(defun parse-object-macro-definition-body (line)
+  (when line
+    (parse-concatenates line)))
+
+
+(defun parse-macro-definition (line)
+  (cond
+    ((null line)
+     '())
+    ((openp (first line))
+     (let ((file (token-file (first line)))
+           (lino (token-line (first line))))
+       (pop line)
+       (let ((parameters (loop
+                           :collect (let ((parameter (pop line)))
+                                      (cond
+                                        ((identifierp parameter)
+                                         (if (and line (ellipsisp (first line)))
+                                             (progn
+                                               (pop line)
+                                               (unless (and line (closep (first line)))
+                                                 (cerror "Continue" "~A:~A: ellipsis should be the last macro parameter"
+                                                        (token-file parameter) (token-line parameter)))
+                                               (list ':ellipsis parameter))
+                                             (progn
+                                               (unless (and line (or (commap (first line)) (closep (first line))))
+                                                 (cerror "Continue" "~A:~A: Missing a comma after parameter ~A"
+                                                        (token-file parameter) (token-line parameter) (token-text parameter)))
+                                               parameter)))
+                                        ((ellipsisp parameter)
+                                         (unless (and line (closep (first line)))
+                                           (cerror "Continue" "~A:~A: ellipsis should be the last macro parameter"
+                                                  (token-file parameter) (token-line parameter)))
+                                         '(:ellipsis))
+                                        (t
+                                         (cerror "Continue" "~A:~A: Expected a macro parameter name, not ~S"
+                                                 (token-file parameter) (token-line parameter) (token-text parameter))
+                                         parameter)))
+                           :while (and line (commap (first line)))
+                           :do (pop line)
+                           :finally (if (and line (closep (first line)))
+                                        (pop line)
+                                        (cerror "Continue" "~A:~A: Expected a closing parentheses after the parameter list" file lino)))))
+         (list :function parameters (parse-function-macro-definition-body line parameters)))))
+    (t
+     (list :object (parse-object-macro-definition-body line)))))
+
+
+(defun define (line environment)
+  (let ((file (token-file (first line)))
+        (lino (token-file (first line))))
+    (pop line) (pop line)
+    (if line
+        (let ((name (pop line)))
+          (if (identifierp name)
+              (let ((old-definition (environment-macro-definition environment (token-text name)))
+                    (new-definition (parse-macro-definition line)))
+                (when (environment-macro-definedp environment (token-text name))
+                  (unless (equal old-definition new-definition)
+                    (warn "~A:~A: Redefiniting the macro ~A with a different definition"
+                          (line-file name) (line-lino name) (token-text name))))
+                (setf (environment-macro-definition environment (token-text name)) new-definition))
+              (cerror "Continue" "~A:~A: Expected an identifier as macro name after #define, not ~S"
+                      (line-file line) (line-lino line) (token-text name))))
+        (cerror "Continue" "~A:~A: Missing macro name after #define" file lino))))
+
+(defun undef (line environment)
+  (let ((file (token-file (first line)))
+        (lino (token-file (first line))))
+    (pop line) (pop line)
+    (if line
+        (progn
+          (let ((name (pop line)))
+            (if (identifierp name)
+                (environment-macro-undefine environment-macro-undefine (token-text name))
+                (cerror "Continue" "~A:~A: Expected an identifier as macro name after #undef, not ~S"
+                        (line-file name) (line-lino name) (token-text name))))
+          (when line
+            (cerror "Continue" "~A:~A: Didn't expect anything after the macro name after #undef, not ~S"
+                    (line-file (first line)) (line-lino (first line)) (token-text (first line)))))
+        (cerror "Continue" "~A:~A: Missing macro name after #undef" file lino))))
+
+
+(defun include (line include-level environment)
+  )
+(defun import (line include-level environment)
+  )
+(defun ifdef (line lines if-level environment)
+  )
+(defun ifndef (line lines if-level environment)
+  )
+(defun cpp-if (line lines if-level environment)
+  )
+(defun cpp-line (line lines if-level environment)
+  )
+(defun pragma (line environment)
+  )
+(defun cpp-error (line environment)
+  )
+(defun cpp-warning (line environment)
+  )
+
+(defun cpp-macro-expand)
+(defun process (tokenized-lines environment &key (if-level 0) (include-level 0))
+  "
+TOKENIZED-LINES: a list of list of tokens (one sublist per input line).
+ENVIRONMENT:     an object with the ENVIRONMENT-MACRO-DEFINITION accessor,
+                 where the macros, keyed by their name (string), are stored.
+RETURN:          the C-pre-processed source in form of list of list of tokens
+                 (one sublist per output line).
+"
+  (loop
+    :with output = '()
+    :while tokenized-lines
+    :do (let ((line (pop tokenized-lines)))
+          (if (and (sharpp (first line))
+                   (identifierp (second line)))
+              (scase (token-text (second line))
+                (("define")  (define line environment))
+                (("undef")   (undef  line environment))
+                (("include") (nreconc (include line include-level environment) output))
+                (("import")  (nreconc (import  line include-level environment) output))
+                (("ifdef")   (setf tokenized-lines (ifdef  line tokenized-lines if-level environment)))
+                (("ifndef")  (setf tokenized-lines (ifndef line tokenized-lines if-level environment)))
+                (("if")      (setf tokenized-lines (cpp-if line tokenized-lines if-level environment)))
+                (("elif" "else" "endif"))
+                (("line")    (setf tokenized-lines (cpp-line line tokenized-lines environment)))
+                (("pragma")  (pragma      line environment))
+                (("error")   (cpp-error   line environment))
+                (("warning") (cpp-warning line environment))
+                (("ident" "sccs"))
+                (otherwise (cerror "Continue" "~A:~A: invalid directive ~A"
+                                   (line-file line) (line-lino line) (token-text (second line)))))
+              ;; (multiple-value-setq (tokenized-lines output) (cpp-macro-expand line tokenized-lines output environment))
+              (push line output)))
+    :finally (return (nreverse output))))
+
+
+
+#-(and) (progn
+
+          (let ((file "tests/define.h"))
+            (with-open-file (in file)
+              (let ((environment (make-hash-table :test 'equal)))
+                (process (read-cpp-tokens in
+                                          :file-name file
+                                          :substitute-trigraphs t
+                                          :warn-on-trigraph nil)
+                         environment)
+                (print-hashtable environment))))
+
+
+
+
+          (let ((file "tests/trigraphs.c"))
+            (with-open-file (in file)
+              (read-cpp-tokens in
+                               :file-name file
+                               :substitute-trigraphs t
+                               :warn-on-trigraph nil)))
+
+          (let ((file "tests/comment.c"))
+            (with-open-file (in file)
+              (read-cpp-tokens in
+                               :file-name file
+                               :substitute-trigraphs t
+                               :warn-on-trigraph nil)))
+
+          (let ((file "tests/test.c"))
+            (with-open-file (in file)
+              (read-cpp-tokens in
+                               :file-name file
+                               :substitute-trigraphs t
+                               :warn-on-trigraph nil)))
+
+          (let ((file #P"~/src/macosx/emacs-24.5/src/lisp.c"))
+            (with-open-file (in file)
+              (read-cpp-tokens in
+                               :file-name file
+                               :substitute-trigraphs t
+                               :warn-on-trigraph nil)))
+          )

-(let ((file "tests/comment.c"))
-  (with-open-file (in file)
-    (read-cpp-tokens in
-                     :file-name file
-                     :substitute-trigraphs t
-                     :warn-on-trigraph nil)))

-(let ((file "tests/test.c"))
-  (with-open-file (in file)
-    (read-cpp-tokens in
-                     :file-name file
-                     :substitute-trigraphs t
-                     :warn-on-trigraph nil)))



@@ -702,13 +998,16 @@ RETURN: the token text; the end position."
                  '("..." 12)))
   :success)

-(test/number-lines)
-(test/substitute-trigraphs)
-(test/merge-continued-lines)
-(test/remove-comments-in-line)
-(test/remove-comments)
-(test/scan-identifier)
-(test/scan-number)
-(test/scan-punctuation)
+(defun test/all ()
+  (test/number-lines)
+  (test/substitute-trigraphs)
+  (test/merge-continued-lines)
+  (test/remove-comments-in-line)
+  (test/remove-comments)
+  (test/scan-identifier)
+  (test/scan-number)
+  (test/scan-punctuation))
+
+(test/all)

 ;;;; THE END ;;;;
diff --git a/languages/cpp/tests/Makefile b/languages/cpp/tests/Makefile
new file mode 100644
index 0000000..9e00d0d
--- /dev/null
+++ b/languages/cpp/tests/Makefile
@@ -0,0 +1,8 @@
+all:
+empty-macro:
+	gcc -E -o - empty-macro.c
+priority:
+	gcc -E -o - priority.h
+concat:
+	gcc -E -o - concat.h
+
diff --git a/languages/cpp/tests/concat.h b/languages/cpp/tests/concat.h
new file mode 100644
index 0000000..e138fae
--- /dev/null
+++ b/languages/cpp/tests/concat.h
@@ -0,0 +1,9 @@
+#define DOUBLE(X,Y) X ## ## Y
+#define DANGLING(X) X ##
+
+#define STRINGY_DANGLING(X) # ## X
+#define STRINGY_TOKEN(X) # ahah ## X
+#define STRINGIFY_OBJECT # object ## entity
+
+double(hello,kitty) DOUBLE(HELLO,KITTY)
+stringify_object STRINGIFY_OBJECT
diff --git a/languages/cpp/tests/define.h b/languages/cpp/tests/define.h
new file mode 100644
index 0000000..206bb3a
--- /dev/null
+++ b/languages/cpp/tests/define.h
@@ -0,0 +1,5 @@
+#define EMPTY
+#define VOO 42
+#define FOO(X) ((X)+42)
+#define SOO(X) #X
+#define COO(X,Y) X##Y
diff --git a/languages/cpp/tests/empty-macro.c b/languages/cpp/tests/empty-macro.c
new file mode 100644
index 0000000..959e297
--- /dev/null
+++ b/languages/cpp/tests/empty-macro.c
@@ -0,0 +1,7 @@
+#define EMPTY
+
+#ifdef EMPTY
+int empty_ifdef;
+#endif
+
+EMPTY
diff --git a/languages/cpp/tests/priority.h b/languages/cpp/tests/priority.h
new file mode 100644
index 0000000..b97bb8a
--- /dev/null
+++ b/languages/cpp/tests/priority.h
@@ -0,0 +1,8 @@
+#define C(X,Y) X ## Y
+#define S(X) SS(X)
+#define SS(X) # X
+#define M(X,Y) S(C(X,Y))
+
+char* a=M(HELLO,KITTY);
+
+
ViewGit