git.fiddlerwoaroof.com
php-parser.lisp
85d5b584
 (defpackage :php.parser
   (:use :cl :smug :serapeum :alexandria))
 
 (in-package :php.parser)
 
 '(:token (<token-type> *value))
 
 (defmacro if-token (token &body body)
   (once-only (token)
     `(if (and (consp ,token)
 	      (eq (car ,token)
 		  :token))
 	 (progn ,@body)
 	 (error (format nil "~s is not a token" ,token)))))
 
 (defun get-token-type (token)
   (if-token token
     (cadr token)))
 
 (defun get-token-args (token)
   (if-token token
     (cddr token)))
 
 (defmethod input-empty-p ((input null))
   t)
 
 (defmethod input-empty-p ((input cons))
   nil)
 
 (defmethod input-first ((input cons))
   (car input))
 
 (defmethod input-rest ((input cons))
   (cdr input))
 
 (defun .match-lexical-element (keyword)
   (.bind (.item)
 	 (lambda (element)
 	   (if (eq keyword
 		   (car element))
 	       (.identity element)
 	       (.fail)))))
 
 (defun .match-token (&optional type)
   (.let* ((matched-token (.match-lexical-element :token)))
     (if type
 	(if (eq type (get-token-type matched-token))
 	    (.identity matched-token)
 	    (.fail))
 	(.identity matched-token))))
 
 (defun .match-keyword (&optional name)
   (.let* ((tok (.match-token :keyword)))
     (if name
 	(if (equal name (get-token-args tok))
 	    (.identity tok)
 	    (.fail))
 	(.identity tok))))
 
 (defun .match-whitespace ()
   (.match-lexical-element :whitespace))
 
 (defun .match-operator (&optional op)
   (.let* ((matched-op (.or (.match-token :operator)
 			   (.match-token :punctuator)
 			   (.match-token :paired-punctuator))))
     (if (or (not op) 
 	    (equal (get-token-args matched-op)
 		   op))
 	(.identity matched-op)
 	(.fail))))
 
 (defun .match-punctuator (&optional sym)
   (.or (.match-unpaired-punctuator sym)
        (.match-paired-punctuator sym)))
 
 (defun .match-unpaired-punctuator (&optional sym)
   (.let* ((punct (.match-token :punctuator)))
     (if (or (not sym)
 	    (equal (get-token-args punct)
 		   sym))
 	(.identity punct)
 	(.fail))))
 
 (defun .match-paired-punctuator (&optional sym)
   (.let* ((punct (.match-token :paired-punctuator)))
     (if (or (not sym)
 	    (equal (get-token-args punct)
 		   sym))
 	(.identity punct)
 	(.fail))))
 
 (defun .expression ()
   (.item))
 
 (defun .parenthesized-expression ()
   (.prog2 (.match-paired-punctuator "(")
 	  (.expression)
 	  (.match-paired-punctuator ")")))
 
 (defun .match-variable ()
   (.match-token :variable))
 
 (defun qualified-name-arguments (qualified-name-token)
   (apply #'values (coerce (get-token-args qualified-name-token)
 			  'list)))
 
 (defun qualified-name-namespace (qualified-name-token)
   (nth-value 0 (qualified-name-arguments qualified-name-token)))
 
 (defun qualified-name-name (qualified-name-token)
   (nth-value 1 (qualified-name-arguments qualified-name-token)))
 
 (defun .match-qualified-name (&key (namespace nil namespace-p) name)
   (.let* ((matched-name (.match-token :qualified-name)))
     (if (or namespace-p name)
 	(multiple-value-bind (uw-namespace uw-name) (qualified-name-arguments matched-name)
 	  (cond ((and namespace-p (not (equal namespace uw-namespace)))
 		 (.fail))
 		((and name (not (equal name uw-name)))
 		 (.fail))
 		(t (.identity matched-name))))
 	(.identity matched-name))))
 
 (defun .match-name (&optional name)
   (.match-qualified-name :namespace nil :name name))
 
 (defun .match-literal ()
   (.match-token :literal))
 
 
 
 ;;; Expressions
 
 (defun .function-definition-header ()
   (.progn (.match-keyword "function")))
 
 (defun .primary-expression ()
   (php.lexer::tag-result :expression
     (.or (.match-variable)
 	 (.match-qualified-name)
 	 (.match-literal)
 	 (.constant-expression)
 	 ;(.intrinsic)
 	 ;(.anonymous-function-creation-expression)
 	 ;(.parenthesized-expression)
 	 )))
 
 (defun .scalar-type ()
   (.or (.match-name "bool")
        (.match-name "float")
        (.match-name "int")
        (.match-name "string")))
 
 ; Add scalar-type / qualified-name
 (defun .type-declaration ()
   (.or (.match-keyword "array")
        (.match-keyword "callable")
        (.scalar-type)
        (.match-qualified-name)))
 
 (defun .parameter-declaration ()
   (php.lexer::tag-result :parameter-declaration
     (.let* ((declared-type (.optional (.type-declaration)))
 	    (reference-marker (.optional (.match-operator "&")))
 	    (variable-declaration (.match-variable))
 	    (default-value (.optional
 			    (.progn (.match-operator "=")
 				    (.constant-expression)))))
       (.identity (list variable-declaration
 		       :type declared-type
 		       :is-reference (and reference-marker t)
 		       :default-value default-value)))))
 
 (defun .variadic-parameter ()
   (php.lexer::tag-result :variadic-parameter
     (.let* ((declared-type (.optional (.type-declaration)))
 	    (reference-marker (.optional (.match-operator "&")))
 	    (variable-declaration (.progn (.match-operator "...")
 					  (.match-variable))))
       (.identity (list variable-declaration
 		       :type declared-type
 		       :is-reference (and reference-marker t))))))
 
 (defun .simple-parameter-declaration-list ()
   (.or (.let* ((parameters (.first
 			    (.map 'list (.prog1 (.parameter-declaration)
 						(.match-unpaired-punctuator ","))
 				  :at-least 0)))
 	       (last-parameter (.parameter-declaration)))
 	 (.identity (append parameters
 			    (list last-parameter))))
        (.let* ((res (.parameter-declaration)))
 	 (.identity (list res)))))
 
 (defun .variadic-declaration-list ()
   (.let* ((normal-params (.optional (.simple-parameter-declaration-list)))
 	  (variadic-param (.progn (.match-unpaired-punctuator ",")
 				  (.variadic-parameter))))
     (.identity (append normal-params
 		       (list variadic-param)))))
 
 (defun .parameter-declaration-list ()
   (.or (.simple-parameter-declaration-list)
        (.variadic-declaration-list)))
 
 (defun .return-type ()
   (.progn (.match-operator ":")
 	  (.or (.match-qualified-name :name "void")
 	       (.type-declaration))))
 
 (defun .function-definition-header ()
   (php.lexer::tag-result :function-header
     (.progn (.match-keyword "function")
 	    (.let* ((return-by-reference (.optional (.match-operator "&")))
 		    (function-name (.match-name))
 		    (parameters (.optional (.prog2 (.match-paired-punctuator "(")
 						   (.parameter-declaration-list)
 						   (.match-paired-punctuator ")"))))
 		    (return-type (.optional (.return-type))))
 	      (.identity
 	       (list :function
 		     :name function-name
 		     :parameters parameters
 		     :return-type return-type
 		     :return-by-reference (not (null return-by-reference))))))))
 
 (defun .function-definition ()
   (php.lexer::tag-result :function-definition
     (.let* ((header (.function-definition-header))
 	    (body (.compound-statement)))
       (.identity
        (list header body)))))
 
 (defun .array-element-initializer ()
   (flet ((.element-key () (.expression))
 	 (.element-value () (.expression)))
     (.or (.let* ((key (.prog1 (.element-key)
 			      (.match-operator "=>")))
 		 (is-ref (.optional (.match-operator "&")))
 		 (value (.element-value)))
 	   (.identity (list key value :is-reference (and is-ref t))))
 	 (.let* ((is-ref (.optional (.match-operator "&")))
 		 (value (.element-value)))
 	   (.identity (list value :is-reference (and is-ref t)))))))
 
 (defun .array-initializer-list ()
   (.let* ((first (.array-element-initializer))
 	  (rest (.map 'list (.progn (.match-punctuator)
 				    (.array-element-initializer))
 		      :at-least 0)))
     (.identity (cons first rest))))
 
 (defun .array-initializer ()
   (.optional (.array-initializer-list)))
 
 (defun .array-creation-expression ()
   (php.lexer::tag-result :array
     (.let* ((opening (.or (.progn (.match-keyword "array")
 				  (.match-paired-punctuator "("))
 			  (.match-paired-punctuator "[")))
 	    (list (.array-initializer))
 	    (closing  (.match-paired-punctuator
 		       (if (string= "(" (get-token-args opening))
 			   ")"
 			   "]"))))
       closing
       (.identity list))))
 
 (defun .constant-expression ()
   (.or (.array-creation-expression)
        (.expression)))
 
 (defparameter *fun-tion*
   "function fib($x) {
   array_merge([1,2,3], [2,3,4]);
   if ($x == 0) return 1;
   if ($x == 1) return 1;
   if ($x >  1) return fib($x-1) + fib($x-2);
 }")
 
 (defparameter *function-tokens*
   (parse (php.lexer::.input-file)
 	 *fun-tion*))
 
 (defparameter *uut*
   (parse (php.lexer::.input-file)
 	 "callable &$foo = array_merge([1,2,3], [4,5,6]);"))
 
 
 (defvar *xml-output* (make-synonym-stream '*standard-output*))
 (flet ((escape-cdata-end (string)
 	 (string-replace-all "]]>" string "]]>]]&gt;<![CDATA[")))
   (defgeneric to-xml (tag)
     (:method ((tag cons))
       (format *xml-output* "~&<~(~a~)>~{~a~}</~(~a~)>~%"
 	      (car tag)
 	      (mapcar #'(lambda (x)
 			  (with-output-to-string (*xml-output*)
 			    (to-xml x)))
 		      (cdr tag))
 	      (car tag)))
     (:method ((tag integer))
       (format *xml-output* "<![CDATA[~a]]>" tag))
 
     (:method :around ((tag string))
 	     (format *xml-output* "<![CDATA[~a]]>"
 		     (call-next-method)))
     (:method ((tag string))
       (escape-cdata-end tag))))
 
 (defmacro flip-values (form)
   `(multiple-value-bind (a b) ,form
      (values b a)))