]> asedeno.scripts.mit.edu Git - cl-protobufs.git/blob - define-proto.lisp
0ad110f47cb7384d7f9536b7c844df50f34b86e0
[cl-protobufs.git] / define-proto.lisp
1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2 ;;;                                                                  ;;;
3 ;;; Confidential and proprietary information of ITA Software, Inc.   ;;;
4 ;;;                                                                  ;;;
5 ;;; Copyright (c) 2012 ITA Software, Inc.  All rights reserved.      ;;;
6 ;;;                                                                  ;;;
7 ;;; Original author: Scott McKay                                     ;;;
8 ;;;                                                                  ;;;
9 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
10
11 (in-package "PROTO-IMPL")
12
13
14 ;;; Protocol buffer defining macros
15
16 ;; Define a schema named 'name', corresponding to a .proto file of that name
17 (defmacro define-proto (name (&key proto-name syntax package import options)
18                         &body messages &environment env)
19   "Define a schema named 'name', corresponding to a .proto file of that name.
20    'proto-name' can be used to override the defaultly generated name.
21    'syntax' and 'package' are as in .proto files.
22    'imports' is a list of pathname strings to be imported.
23    'options' is a property list, i.e., (\"key1\" \"val1\" \"key2\" \"val2\" ...).
24    The body consists of 'define-enum', 'define-message' or 'define-service' forms."
25   (with-collectors ((enums collect-enum)
26                     (msgs  collect-msg)
27                     (svcs  collect-svc)
28                     (forms collect-form))
29     (dolist (msg messages)
30       (assert (and (listp msg)
31                    (member (car msg) '(define-enum define-message define-service))) ()
32               "The body of ~S must be one of ~{~S~^ or ~}"
33               'define-proto '(define-enum define-message define-service))
34       ;; The macro-expander will return a form that consists
35       ;; of 'progn' followed by a symbol naming what we've expanded
36       ;; (define-enum, define-message, define-service), followed by
37       ;; by a (optional) Lisp defining form (deftype, defclass),
38       ;; followed by a form that creates the model object
39       (destructuring-bind (&optional progn type model definers)
40           (macroexpand-1 msg env)
41         (assert (eq progn 'progn) ()
42                 "The macroexpansion for ~S failed" msg)
43         (map () #'collect-form definers)
44         (ecase type
45           ((define-enum)
46            (collect-enum model))
47           ((define-message)
48            (collect-msg model))
49           ((define-service)
50            (collect-svc model)))))
51     ;;--- This should warn if the old one isn't upgradable to the new one
52     (let ((vname (fintern "*~A*" name))
53           (pname (or proto-name (proto-class-name name)))
54           (cname name)
55           (options (loop for (key val) on options by #'cddr
56                          collect `(make-instance 'protobuf-option
57                                     :name ,key
58                                     :value ,val))))
59       `(progn
60          ,@forms
61          (defvar ,vname nil)
62          (let ((protobuf (make-instance 'protobuf
63                            :name     ',pname
64                            :class    ',cname
65                            :package  ,(if (stringp package) package (string-downcase (string package)))
66                            :imports  ',(if (listp import) import (list import))
67                            :syntax   ,syntax
68                            :options  (list ,@options)
69                            :enums    (list ,@enums)
70                            :messages (list ,@msgs)
71                            :services (list ,@svcs))))
72            (setq ,vname protobuf)
73            (setf (gethash ',pname *all-protobufs*) protobuf)
74            (setf (gethash ',cname *all-protobufs*) protobuf)
75            protobuf)))))
76
77 ;; Define an enum type named 'name' and a Lisp 'deftype'
78 (defmacro define-enum (name (&key proto-name conc-name) &body values)
79   "Define an enum type named 'name' and a Lisp 'deftype'.
80   'proto-name' can be used to override the defaultly generated name.
81    The body consists of the enum values in the form (name &key index)."
82   (with-collectors ((vals  collect-val)
83                     (evals collect-eval)
84                     (forms collect-form))
85     (let ((index 0))
86       (dolist (val values)
87         (let* ((idx  (if (listp val) (second val) (incf index)))
88                (name (if (listp val) (first val)  val))
89                (val-name  (kintern (if conc-name (format nil "~A~A" conc-name name) (symbol-name name))))
90                (enum-name (if conc-name (format nil "~A~A" conc-name name) (symbol-name name))))
91           (collect-val val-name)
92           (collect-eval `(make-instance 'protobuf-enum-value
93                            :name  ,(proto-enum-name enum-name)
94                            :index ,idx
95                            :value ,val-name)))))
96     (collect-form `(deftype ,name () '(member ,@vals)))
97     `(progn
98        define-enum
99        (make-instance 'protobuf-enum
100          :name   ,(or proto-name (proto-class-name name))
101          :class  ',name
102          :values (list ,@evals))
103        ,forms)))
104
105 ;; Define a message named 'name' and a Lisp 'defclass'
106 (defmacro define-message (name (&key proto-name conc-name) &body fields &environment env)
107   "Define a message named 'name' and a Lisp 'defclass'.
108    'proto-name' can be used to override the defaultly generated name.
109    The body consists of fields, or 'define-enum' or 'define-message' forms.
110    Fields take the form (name &key type default index)."
111   (with-collectors ((enums collect-enum)
112                     (msgs  collect-msg)
113                     (flds  collect-field)
114                     (slots collect-slot)
115                     (forms collect-form))
116     (let ((index 0))
117       (dolist (fld fields)
118         (case (car fld)
119           ((define-enum define-message define-extension)
120            (destructuring-bind (&optional progn type model definers)
121                (macroexpand-1 fld env)
122              (assert (eq progn 'progn) ()
123                      "The macroexpansion for ~S failed" fld)
124              (map () #'collect-form definers)
125              (ecase type
126                ((define-enum)
127                 (collect-enum model))
128                ((define-message)
129                 (collect-msg model))
130                ((define-extension)
131                 (collect-msg model)))))
132           (otherwise
133            (destructuring-bind (slot &key type default) fld
134              (let* ((idx  (if (listp slot) (second slot) (incf index)))
135                     (slot (if (listp slot) (first slot) slot))
136                     (accessor (intern (if conc-name (format nil "~A~A" conc-name slot) (symbol-name slot))
137                                       (symbol-package slot))))
138                (multiple-value-bind (ptype pclass)
139                    (clos-type-to-protobuf-type type)
140                  (collect-slot `(,slot :type ,type
141                                        :accessor ,accessor
142                                        :initarg ,(kintern (symbol-name slot))
143                                        ,@(and default (list :initform default))))
144                  (collect-field `(make-instance 'protobuf-field
145                                    :name  ,(proto-field-name slot)
146                                    :type  ,ptype
147                                    :class ',pclass
148                                    :required ,(clos-type-to-protobuf-required type)
149                                    :index ,idx
150                                    :value ',slot
151                                    :default ,(and default (format nil "~A" default))
152                                    :packed  ,(packed-type-p pclass))))))))))
153     (collect-form `(defclass ,name () (,@slots)))
154     `(progn
155        define-message
156        (make-instance 'protobuf-message
157          :name  ,(or proto-name (proto-class-name name))
158          :class ',name
159          :conc-name ,(and conc-name (string conc-name))
160          :enums    (list ,@enums)
161          :messages (list ,@msgs)
162          :fields   (list ,@flds))
163        ,forms)))
164
165 (defmacro define-extension (from to)
166   "Define an extension range within a message.
167    The \"body\" is the start and end of the range, both inclusive."
168   `(progn
169      define-extension
170      (make-instance 'protobuf-extension
171        :from ,from
172        :to   ,to)
173      ()))
174
175 ;; Define a service named 'name' and a Lisp 'defun'
176 (defmacro define-service (name (&key proto-name) &body rpc-specs)
177   "Define a service named 'name' and a Lisp 'defun'.
178    'proto-name' can be used to override the defaultly generated name.
179    The body consists of a set of RPC specs of the form (name input-type output-type)."
180   (with-collectors ((rpcs collect-rpc))
181     (dolist (rpc rpc-specs)
182       (destructuring-bind (name input-type output-type &key options) rpc
183         (let ((options (loop for (key val) on options by #'cddr
184                              collect `(make-instance 'protobuf-option
185                                         :name ,key
186                                         :value ,val))))
187           (collect-rpc `(make-instance 'protobuf-rpc
188                           :name ,(proto-class-name name)
189                           :class ',name
190                           :input-type  ,(and input-type  (proto-class-name input-type))
191                           :output-type ,(and output-type (proto-class-name output-type))
192                           :options (list ,@options))))))
193     `(progn
194        define-service
195        (make-instance 'protobuf-service
196          :name ,(or proto-name (proto-class-name name))
197          :class ',name
198          :rpcs (list ,@rpcs))
199        ())))                                            ;---*** DEFINE LISP STUB HERE