]> asedeno.scripts.mit.edu Git - cl-protobufs.git/blobdiff - utilities.lisp
Merge branch 'asdf3'
[cl-protobufs.git] / utilities.lisp
index 8ad1c43cd78574e99ff71ce829be2957d4024246..0cc04959d4a9fffc13cfd95a8bb9a83d536c7e59 100644 (file)
@@ -1,8 +1,8 @@
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;;                                                                  ;;;
-;;; Confidential and proprietary information of ITA Software, Inc.   ;;;
+;;; Free Software published under an MIT-like license. See LICENSE   ;;;
 ;;;                                                                  ;;;
-;;; Copyright (c) 2012 ITA Software, Inc.  All rights reserved.      ;;;
+;;; Copyright (c) 2012 Google, Inc.  All rights reserved.            ;;;
 ;;;                                                                  ;;;
 ;;; Original author: Scott McKay                                     ;;;
 ;;;                                                                  ;;;
 
 ;;; Optimized fixnum arithmetic
 
+(eval-when (:compile-toplevel :load-toplevel :execute)
+
+(defparameter $optimize-default     '(optimize (speed 1) (safety 3) (debug 3))
+  "Compiler optimization settings for safe, debuggable code.")
+(defparameter $optimize-fast-unsafe '(optimize (speed 3) (safety 0) (debug 0))
+  "Compiler optimization settings for fast, unsafe, hard-to-debug code.")
+
+)       ;eval-when
+
+
 (defmacro i+ (&rest fixnums)
   `(the fixnum (+ ,@(loop for n in fixnums collect `(the fixnum ,n)))))
 
@@ -60,7 +70,7 @@
 (define-modify-macro idecf (&optional (delta 1)) i-)
 
 (defmacro ildb (bytespec value)
-  `(ldb ,bytespec (the fixnum ,value)))
+  `(the fixnum (ldb ,bytespec (the fixnum ,value))))
 
 
 ;;; String utilities
        (string-equal string suffix :start1 (i- end (length suffix)) :end1 end)
        suffix))
 
+(defun strcat (&rest strings)
+  "Concatenate a bunch of strings."
+  (declare (dynamic-extent strings))
+  (apply #'concatenate 'string strings))
+
 
 ;; (camel-case "camel-case") => "CamelCase"
 (defun camel-case (string &optional (separators '(#\-)))
+  "Take a hyphen-separated string and turn it into a camel-case string."
   (let ((words (split-string string :separators separators)))
     (format nil "~{~@(~A~)~}" words)))
 
 ;; (camel-case-but-one "camel-case") => "camelCase"
 (defun camel-case-but-one (string &optional (separators '(#\-)))
+  "Take a hyphen-separated string and turn its tail into a camel-case string."
   (let ((words (split-string string :separators separators)))
     (format nil "~(~A~)~{~@(~A~)~}" (car words) (cdr words))))
 
-;; (uncamel-case "CamelCase") => "Camel-Case"
-;; (uncamel-case "TCPConnection") => "Tcp-Connection"
-(defun uncamel-case (string &optional (separator #\-))
-  (format nil (format nil "~~{~~A~~^~C~~}" separator)
-          (cl-ppcre:split "(?<=[a-z])(?=[A-Z])" string)))
+
+;; (uncamel-case "CamelCase") => "CAMEL-CASE"
+;; (uncamel-case "TCPConnection") => "TCP-CONNECTION"
+;; (uncamel-case "NewTCPConnection") => "NEW-TCP-CONNECTION"
+;; (uncamel-case "new_RPC_LispService") => "NEW-RPC-LISP-SERVICE"
+;; (uncamel-case "RPC_LispServiceRequest_get_request") => "RPC-LISP-SERVICE-REQUEST-GET-REQUEST"
+;; (uncamel-case "TCP2Name3") => "TCP2-NAME3"
+(defun uncamel-case (name)
+  "Take a camel-case string and turn it into a hyphen-separated string."
+  ;; We need a whole state machine to get this right
+  (labels ((uncamel (chars state result)
+             (let ((ch (first chars)))
+               (cond ((null chars)
+                      result)
+                     ((upper-case-p ch)
+                      (uncamel (rest chars) 'upper
+                               (case state
+                                 ((upper)
+                                  ;; "TCPConnection" => "TCP-CONNECTION"
+                                  (if (and (second chars) (lower-case-p (second chars)))
+                                    (list* ch #\- result)
+                                    (cons ch result)))
+                                 ((lower digit) (list* ch #\- result))
+                                 (otherwise (cons ch result)))))
+                     ((lower-case-p ch)
+                      (uncamel (rest chars) 'lower
+                               (cons (char-upcase ch) result)))
+                     ((digit-char-p ch)
+                      (uncamel (rest chars) 'digit 
+                               (cons ch result)))
+                     ((or (eql ch #\-) (eql ch #\_))
+                      (uncamel (rest chars) 'dash
+                               (cons #\- result)))
+                     ((eql ch #\.)
+                      (uncamel (rest chars) 'dot
+                               (cons #\. result)))
+                     (t
+                      (error "Invalid name character: ~A" ch))))))
+    (strcat (nreverse (uncamel (concatenate 'list name) nil ())))))
 
 
 (defun split-string (line &key (start 0) (end (length line)) (separators '(#\-)))
          (intern (string-upcase x) (find-package "KEYWORD")))
         (t nil)))
 
+
 ;;; Collectors, etc
 
 (defmacro with-collectors ((&rest collection-descriptions) &body body)
+  "'collection-descriptions' is a list of clauses of the form (coll function).
+   The body can call each 'function' to add a value to 'coll'. 'function'
+   runs in constant time, regardless of the length of the list."
   (let ((let-bindings  ())
         (flet-bindings ())
         (dynamic-extents ())
      ,@body))
 
 (defmacro dovector ((var vector &optional value) &body body)
+  "Like 'dolist', but iterates over the vector 'vector'."
   (with-gensyms (vidx vlen vvec)
     `(let* ((,vvec ,vector)
             (,vlen (length ,vvec)))
              do (progn ,@body)
              finally (return ,value)))))
 
+(defmacro doseq ((var sequence &optional value) &body body)
+  "Iterates over a sequence, using 'dolist' or 'dovector' depending on
+   the type of the sequence. In optimized code, this turns out to be
+   faster than (map () #'f sequence).
+   Note that the body gets expanded twice!"
+  (with-gensyms (vseq)
+    `(let ((,vseq ,sequence))
+       (if (vectorp ,vseq)
+         (dovector (,var ,vseq ,value)
+           ,@body)
+         (dolist (,var ,vseq ,value)
+           ,@body)))))
+
 
 ;;; Functional programming, please
 
 (defun curry (function &rest args)
+  "Returns a function that applies 'function' to 'args', plus any
+   additional arguments given at the call site."
   (if (and args (null (cdr args)))                      ;fast test for length = 1
     (let ((arg (car args)))
       #'(lambda (&rest more-args)
     form))
 
 
+;;; Types
+
 ;; A parameterized list type for repeated fields
 ;; The elements aren't type-checked
 (deftype list-of (type)
-  (if (eq type 'null)
+  (if (eq type 'nil)            ;a list that cannot have any element (element-type nil) is null
     'null
     'list))
 
 ;; The same, but use a (stretchy) vector
 (deftype vector-of (type)
-  (if (eq type 'null)
-    'null
-    '(array *)))
+  (if (eq type 'nil)            ;an array that cannot have any element (element-type nil) is of size 0
+    '(array * (0))
+    '(array * (*))))            ;a 1-dimensional array of any type
 
 ;; This corresponds to the :bytes Protobufs type
-(deftype byte-vector () '(array (unsigned-byte 8)))
+(deftype byte-vector () '(array (unsigned-byte 8) (*)))
 
 (defun make-byte-vector (size)
   (make-array size :element-type '(unsigned-byte 8)))
 (deftype sfixed32 () '(signed-byte 32))
 (deftype sfixed64 () '(signed-byte 64))
 
+;; Type expansion
+(defun type-expand (type)
+  #+allegro (excl:normalize-type type :default type)
+  #+ccl (ccl::type-expand type)
+  #+clisp (ext:type-expand type)
+  #+cmu (kernel:type-expand type)
+  #+lispworks (type:expand-user-type type)
+  #+sbcl (sb-ext:typexpand type)
+  #-(or allegro ccl clisp cmu lispworks sbcl) type)
 
 ;;; Code generation utilities
 
 (defvar *proto-name-separators* '(#\- #\_ #\/ #\space))
 (defvar *camel-case-field-names* nil)
 
+(defun find-proto-package (name)
+  "A very fuzzy definition of 'find-package'."
+  (typecase name
+    ((or string symbol)
+     ;; Try looking under the given name and the all-uppercase name
+     (or (find-package (string name))
+         (find-package (string-upcase (string name)))))
+    (cons
+     ;; If 'name' is a list, it's actually a fully-qualified path
+     (or (find-proto-package (first name))
+         (find-proto-package (format nil "~{~A~^.~}" name))))))
+
 ;; "class-name" -> "ClassName", ("ClassName")
 ;; "outer-class.inner-class" -> "InnerClass", ("OuterClass" "InnerClass")
 (defun class-name->proto (x)
                                           (camel-case (format nil "~A" x) *proto-name-separators*))))
          (nx (car (last xs)))
          (name (remove-if-not #'alphanumericp (camel-case nx *proto-name-separators*))))
-    (values name (append ns (list name)))))
+    (values name (append ns (list name))
+            ;; This might be the name of a package, too
+            (format nil "~{~A~^.~}" (butlast xs)))))
 
 ;; "enum-value" -> "ENUM_VALUE", ("ENUM_VALUE")
 ;; "class-name.enum-value" -> "ENUM_VALUE", ("ClassName" "ENUM_VALUE")
          (name (remove-if-not #'(lambda (x) (or (alphanumericp x) (eql x #\_)))
                               (format nil "~{~A~^_~}"
                                       (split-string nx :separators *proto-name-separators*)))))
-    (values name (append ns (list name)))))
+    (values name (append ns (list name))
+            (format nil "~{~A~^.~}" (butlast xs)))))
 
 ;; "slot-name" -> "slot_name", ("slot_name") or "slotName", ("slotName")
 ;; "class-name.slot-name" -> "Class.slot_name", ("ClassName" "slot_name")
                  (remove-if-not #'(lambda (x) (or (alphanumericp x) (eql x #\_)))
                                 (format nil "~{~A~^_~}"
                                         (split-string nx :separators *proto-name-separators*))))))
-    (values name (append ns (list name)))))
+    (values name (append ns (list name))
+            (format nil "~{~A~^.~}" (butlast xs)))))
 
 
 ;; "ClassName" -> 'class-name
 (defun proto->class-name (x &optional package)
   "Given a Protobufs message or enum type name, returns a Lisp class or type name.
    This resolves Protobufs qualified names as best as it can."
-  (let* ((xs (split-string (substitute #\- #\_ (string-upcase (uncamel-case x)))
+  (let* ((xs (split-string (substitute #\- #\_ (uncamel-case x))
                            :separators '(#\.)))
-         (pkg (and (cdr xs) (find-package (first xs))))
-         (package (or pkg package))
-         (name (format nil "~{~A~^.~}" (if pkg (cdr xs) xs))))
-    (values (if package (intern name package) (make-symbol name)) package xs)))
+         (pkg1 (and (cdr xs) (find-proto-package (first xs))))
+         (pkgn (and (cdr xs) (find-proto-package (butlast xs))))
+         (package (or pkg1 pkgn package))
+         (name (format nil "~{~A~^.~}" (if pkg1 (cdr xs) (if pkgn (last xs) xs)))))
+    (values (if package (intern name package) (make-symbol name)) package xs
+            ;; This might be the name of a package, too
+            (format nil "~{~A~^.~}" (butlast xs)))))
 
 ;; "ENUM_VALUE" -> :enum-value
 ;; "cl-user.ENUM_VALUE" -> :enum-value
 (defun proto->enum-name (x &optional package)
   "Given a Protobufs enum value name, returns a Lisp enum value name.
    This resolves Protobufs qualified names as best as it can."
-  (let* ((xs (split-string (substitute #\- #\_ (string-upcase (uncamel-case x)))
+  (let* ((xs (split-string (substitute #\- #\_ (uncamel-case x))
                            :separators '(#\.)))
-         (pkg (and (cdr xs) (find-package (first xs))))
-         (package (or pkg package))
-         (name (format nil "~{~A~^.~}" (if pkg (cdr xs) xs))))
-    (values (kintern name) package xs)))
+         (pkg1 (and (cdr xs) (find-proto-package (first xs))))
+         (pkgn (and (cdr xs) (find-proto-package (butlast xs))))
+         (package (or pkg1 pkgn package))
+         (name (format nil "~{~A~^.~}" (if pkg1 (cdr xs) (if pkgn (last xs) xs)))))
+    (values (kintern name) package xs
+            (format nil "~{~A~^.~}" (butlast xs)))))
 
 ;; "slot_name" or "slotName" -> 'slot-name
 ;; "cl-user.slot_name" or "cl-user.slotName" -> 'cl-user::slot-name
 (defun proto->slot-name (x &optional package)
   "Given a Protobufs field value name, returns a Lisp slot name.
    This resolves Protobufs qualified names as best as it can."
-  (let* ((xs (split-string (substitute #\- #\_ (string-upcase (uncamel-case x)))
+  (let* ((xs (split-string (substitute #\- #\_ (uncamel-case x))
                            :separators '(#\.)))
-         (pkg (and (cdr xs) (find-package (first xs))))
-         (package (or pkg package))
-         (name (format nil "~{~A~^.~}" (if pkg (cdr xs) xs))))
-    (values (if package (intern name package) (make-symbol name)) package xs)))
+         (pkg1 (and (cdr xs) (find-proto-package (first xs))))
+         (pkgn (and (cdr xs) (find-proto-package (butlast xs))))
+         (package (or pkg1 pkgn package))
+         (name (format nil "~{~A~^.~}" (if pkg1 (cdr xs) (if pkgn (last xs) xs)))))
+    (values (if package (intern name package) (make-symbol name)) package xs
+            (format nil "~{~A~^.~}" (butlast xs)))))
+
 
+;;; Warnings
 
 (define-condition protobufs-warning (warning simple-condition) ())
 
   `(let ((dspec:*redefinition-action* :quiet)) ,@body))
 
 \f
-;;; Floating point utilities
+;;; Portable floating point utilities
 
-#+(or abcl allegro cmu sbcl lispworks)
+#+(or abcl allegro ccl cmu sbcl lispworks)
 (defun single-float-bits (x)
   (declare (type single-float x))
   #+abcl    (system:single-float-bits x)
                 (excl:single-float-to-shorts x)
               (declare (type (unsigned-byte 16) high low))
               (logior (ash high 16) low))
+  #+ccl (ccl::single-float-bits x)
   #+cmu  (kernel:single-float-bits x)
   #+sbcl (sb-kernel:single-float-bits x)
   #+lispworks (lispworks-float:single-float-bits x))
 
-#-(or abcl allegro cmu sbcl lispworks)
+#-(or abcl allegro ccl cmu sbcl lispworks)
 (defun single-float-bits (x)
   (declare (type single-float x))
   (assert (= (float-radix x) 2))
           ((-1) (logior unsigned-result (- (expt 2 31)))))))))
 
 
-#+(or abcl allegro cmu sbcl lispworks)
+#+(or abcl allegro ccl cmu sbcl lispworks)
 (defun double-float-bits (x)
   (declare (type double-float x))
   #+abcl    (values (system:double-float-low-bits x)
                 (excl:double-float-to-shorts x)
               (logior (ash us1 16) us0)
               (logior (ash us3 16) us2))
+  #+ccl  (multiple-value-bind (high low)
+             (ccl::double-float-bits x)
+           (values low high))
   #+cmu  (values (kernel:double-float-low-bits x)
                  (kernel:double-float-high-bits x))
   #+sbcl (values (sb-kernel:double-float-low-bits x)
                 (values (logand #xffffffff bits)
                         (ash bits -32))))
 
-#-(or abcl allegro cmu sbcl lispworks)
+#-(or abcl allegro ccl cmu sbcl lispworks)
 (defun double-float-bits (x)
   (declare (type double-float x))
   (assert (= (float-radix x) 2))
           (values (logand #xffffffff result) (ash result -32)))))))
 
 
-#+(or abcl allegro cmu sbcl lispworks)
+#+(or abcl allegro ccl cmu sbcl lispworks)
 (defun make-single-float (bits)
   (declare (type (signed-byte 32) bits))
   #+abcl    (system:make-single-float bits)
   #+allegro (excl:shorts-to-single-float (ldb (byte 16 16) bits)
                                          (ldb (byte 16 0) bits))
+  #+ccl  (ccl::host-single-float-from-unsigned-byte-32 bits)
   #+cmu  (kernel:make-single-float bits)
   #+sbcl (sb-kernel:make-single-float bits)
   #+lispworks (lispworks-float:make-single-float bits))
 
-#-(or abcl allegro cmu sbcl lispworks)
+#-(or abcl allegro ccl cmu sbcl lispworks)
 (defun make-single-float (bits)
   (declare (type (signed-byte 32) bits))
   (cond
        (* sign (expt 2.0 exponent) mantissa)))))
 
 
-#+(or abcl allegro cmu sbcl lispworks)
+#+(or abcl allegro ccl cmu sbcl lispworks)
 (defun make-double-float (low high)
   (declare (type (unsigned-byte 32) low)
            (type (signed-byte   32) high))
                                          (ldb (byte 16 0) high)
                                          (ldb (byte 16 16) low)
                                          (ldb (byte 16 0) low))
+  #+ccl  (ccl::double-float-from-bits (logand high #xffffffff) low)
   #+cmu  (kernel:make-double-float high low)
   #+sbcl (sb-kernel:make-double-float high low)
   #+lispworks (lispworks-float:make-double-float high low))
 
-#-(or abcl allegro cmu sbcl lispworks)
+#-(or abcl allegro ccl cmu sbcl lispworks)
 (defun make-double-float (low high)
   (declare (type (unsigned-byte 32) low)
            (type (signed-byte   32) high))