1 ;;; erlext.el --- Encoding and decoding of Erlang external term format
3 ;; Copyleft (]) 2000-2002 Luke Gorrie <luke@bluetail.com>
9 ;; Library for encoding/decoding elisp terms into erlang's external
10 ;; term format. For format details see erts/emulator/internal_doc/ in
11 ;; the Erlang/OTP sources.
13 ;; Supported mappings from/to erlext to elisp:
18 ;; tuple -> (vector ...)
19 ;; pid -> (vector ERL-TAG 'pid node id serial creation)
21 ;; Not mapped/supported yet:
22 ;; ref, port, float, bignum, function, ...
24 ;; ----------------------------------------------------------------------
27 ;; Originally written some time in 2000, borrowing lots of code that I
28 ;; didn't understand from Lennart Staflin's nice elisp CORBA client.
30 ;; May 2001: Added asynchronous networking support for the "shbuf"
31 ;; program that shares emacs buffers on the network via an erlang
34 ;; March 2002: Big cleanup for use in distributed erlang. Removed the
35 ;; old networking code.
37 (eval-when-compile (require 'cl
))
38 (eval-when-compile (load "cl-extra"))
42 (defconst erlext-tag-alist
61 (defconst erlext-max-atom-length
255 "The maximum length of an erlang atom.")
62 (defconst erlext-protocol-version
131)
64 (defconst empty-symbol
(intern "")
65 "The zero-length lisp symbol.")
67 (defconst erl-tag
(make-symbol "TYPE")
68 "Tag placed in the first element of a vector to indicate a non-tuple type.
69 This is an uninterned symbol, which is only eq/eqv/equal/equalp to
72 ;; ------------------------------------------------------------
73 ;; Encoding / decoding interface
74 ;; ------------------------------------------------------------
76 (defun erlext-binary-to-term (string)
77 "Decode and return the elisp representation of `string'."
78 (assert (stringp string
))
79 (let (default-enable-multibyte-characters)
82 (goto-char (point-min))
83 (erlext-read-whole-obj))))
85 (defun erlext-term-to-binary (term)
86 "Encode `term' as erlext and return the result as a string."
87 (let (default-enable-multibyte-characters)
89 (insert erlext-protocol-version
)
90 (erlext-write-obj term
)
93 ;; Tuple datatype: (tuple X Y Z) => [X Y Z]
95 (defun tuple (&rest elems
)
96 (apply #'vector elems
))
98 (defun tuple-to-list (x)
100 (mapcar #'identity x
))
104 (or (zerop (length x
))
105 (not (eq (elt x
0) erl-tag
)))))
107 (defun tuple-arity (tuple)
110 (defmacro tuple-elt
(tuple index
)
111 "Return element INDEX from TUPLE. Index starts from 1."
112 ;; Defined as a macro just so that we get the setf of `elt' for free
113 `(elt ,tuple
(1- ,index
)))
115 ;; ------------------------------------------------------------
117 ;; ------------------------------------------------------------
119 (defun erlext-write-obj (obj)
120 (cond ((listp obj
) ; lists at top since (symbolp '()) => t
121 (erlext-write-list obj
))
123 (erlext-write-string obj
))
125 (erlext-write-atom obj
))
128 (erlext-write-tuple (tuple-to-list obj
))
129 (let* ((list (mapcar #'identity obj
))
134 (apply #'erlext-write-pid elts
))
136 (apply #'erlext-write-port elts
))
138 (apply #'erlext-write-ref elts
))
140 (apply #'erlext-write-new-ref elts
))))))
142 (erlext-write-int obj
))
144 (error "erlext can't marshal %S" obj
))))
146 (defun erlext-write1 (n)
147 (assert (integerp n
))
149 (defun erlext-write2 (n)
150 (assert (integerp n
))
151 (insert (logand (ash n -
8) 255)
153 (defun erlext-write4 (n)
154 (assert (integerp n
))
155 (insert (logand (ash n -
24) 255)
156 (logand (ash n -
16) 255)
157 (logand (ash n -
8) 255)
159 (defun erlext-writen (bytes)
160 (assert (stringp bytes
))
162 (defun erlext-insert4 (n offset
)
165 (goto-char (point-max)))
167 (defun erlext-write-atom (atom)
168 (assert (symbolp atom
))
169 (let* ((string (symbol-name atom
))
170 (len (length string
)))
171 (assert (<= len erlext-max-atom-length
))
172 (erlext-write1 (erlext-get-code 'atom
))
173 (erlext-write2 (length string
))
174 (erlext-writen string
)))
175 (defun erlext-write-int (n)
176 (assert (integerp n
))
177 (cond ((= n
(logand n
255))
178 (erlext-write1 (erlext-get-code 'smallInt
))
180 ;; elisp has small numbers so 32bit on the wire is as far as
181 ;; we need bother supporting
183 (erlext-write1 (erlext-get-code 'int
))
185 (defun erlext-write-list (lst)
189 (progn (erlext-write-list-head (length lst
))
190 (mapc 'erlext-write-obj lst
)
191 (erlext-write-null))))
192 (defun erlext-write-string (str)
193 (assert (stringp str
))
194 (erlext-write1 (erlext-get-code 'string
))
195 (erlext-write2 (length str
))
197 (defun erlext-write-binary (str)
198 (assert (stringp str
))
199 (erlext-write1 (erlext-get-code 'bin
))
200 (erlext-write4 (length str
))
202 (defun erlext-write-null ()
203 (erlext-write1 (erlext-get-code 'null
)))
204 (defun erlext-write-list-head (arity)
206 (erlext-write1 (erlext-get-code 'list
))
207 (erlext-write4 arity
))
208 (defun erlext-write-tuple (elts)
209 (assert (listp elts
))
210 (let ((arity (length elts
)))
212 (progn (erlext-write1 (erlext-get-code 'smallTuple
))
213 (erlext-write1 arity
))
214 (progn (erlext-write1 (erlext-get-code 'largeTuple
))
215 (erlext-write4 arity
))))
216 (mapc 'erlext-write-obj elts
))
217 (defun erlext-write-pid (node id serial creation
)
218 (erlext-write1 (erlext-get-code 'pid
))
219 (erlext-write-obj node
)
221 (erlext-write4 serial
)
222 (erlext-write1 creation
))
223 (defun erlext-write-port (node id creation
)
224 (erlext-write1 (erlext-get-code 'port
))
225 (erlext-write-obj node
)
227 (erlext-write1 creation
))
228 (defun erlext-write-ref (node id creation
)
229 (erlext-write1 (erlext-get-code 'ref
))
230 (erlext-write-obj node
)
232 (erlext-write1 creation
))
233 (defun erlext-write-new-ref (node creation id
)
234 (erlext-write1 (erlext-get-code 'newRef
))
235 (erlext-write2 (/ (length id
) 4))
236 (erlext-write-obj node
)
237 (erlext-write1 creation
)
240 ;; ------------------------------------------------------------
242 ;; ------------------------------------------------------------
245 (if (fboundp 'char-int
)
246 ;; convert character to string
247 (defsubst erlext-read1
()
248 (prog1 (char-int (following-char))
250 (defsubst erlext-read1
()
251 (prog1 (following-char)
254 (defun erlext-read-whole-obj ()
255 (let ((version (erlext-read1)))
256 (assert (= version erlext-protocol-version
))
259 (defun erlext-read-obj ()
260 (let ((tag (erlext-get-tag (erlext-read1))))
262 ((smallInt) (erlext-read1))
263 ((int) (erlext-read4))
264 ((atom) (erlext-read-atom))
265 ((smallTuple) (erlext-read-small-tuple))
266 ((largeTuple) (erlext-read-large-tuple))
267 ((list) (erlext-read-list))
268 ((string) (erlext-read-string))
269 ((bin) (erlext-read-binary))
271 ((pid) (vector erl-tag
273 (erlext-read-obj) ; node
275 (erlext-read4) ; serial
276 (erlext-read1))); creation
277 ((port) (vector erl-tag
279 (erlext-read-obj) ; node
281 (erlext-read1))) ; creation
282 ((ref) (vector erl-tag
284 (erlext-read-obj) ; node
286 (erlext-read1))) ; creation
287 ((newRef) (erlext-read-newref))
288 ((smallBig) (erlext-read-small-bignum))
289 ((largeBig) (erlext-read-large-bignum))
291 (error "Unknown tag: %S" tag
)))))
293 (defun erlext-read (size)
297 ((4) (erlext-read4))))
298 ;; read1 moved above so that it can be inlined
299 (defun erlext-read2 ()
300 (logior (ash (erlext-read1) 8)
302 (defun erlext-read4 ()
303 (logior (ash (erlext-read1) 24)
304 (ash (erlext-read1) 16)
305 (ash (erlext-read1) 8)
307 (defun erlext-readn (n)
308 (assert (integerp n
))
309 (let ((start (point))
311 (prog1 (let ((string (buffer-substring start end
)))
312 (if (featurep 'xemacs
)
314 (string-as-unibyte string
))) ; fixme: should be
315 ; string-make-unibyte?
316 ; Why is it necessary
319 (defun erlext-read-atom ()
320 (let ((length (erlext-read2)))
321 (intern (erlext-readn length
))))
322 (defun erlext-read-small-tuple ()
323 (erlext-read-tuple (erlext-read1)))
324 (defun erlext-read-large-tuple ()
325 (erlext-read-tuple (erlext-read4)))
326 (defun erlext-read-list ()
327 (let ((arity (erlext-read4)))
328 (prog1 (loop for x from
1 to arity
329 collect
(erlext-read-obj))
330 ;; This seems fishy, I find nil's at the end of lists, not
331 ;; included as elements, and no mention of how it works in the
333 (assert (eq (erlext-get-code 'null
) (erlext-read1))))))
334 (defun erlext-read-tuple (arity)
335 (apply #'vector
(loop for x from
1 to arity
336 collect
(erlext-read-obj))))
338 (defun erlext-read-string ()
339 (erlext-readn (erlext-read2)))
341 (defun erlext-read-binary ()
342 (erlext-readn (erlext-read4)))
344 (defun erlext-read-newref ()
345 (let* ((len (erlext-read2))
346 (node (erlext-read-obj))
347 (creation (erlext-read1))
348 (id (erlext-readn (* 4 len
))))
349 (vector erl-tag
'erl-new-ref node creation id
)))
351 ;; We don't actually support bignums. When we get one, we skip over it
352 ;; and return the symbol {SMALL|LARGE}-BIGNUM.
354 (defun erlext-read-small-bignum ()
355 (erlext-read (erlext-read1))
358 (defun erlext-read-large-bignum ()
359 (erlext-read (erlext-read4))
362 ;; ------------------------------------------------------------
364 ;; ------------------------------------------------------------
366 (defun erlext-get-tag (number)
367 (car (rassq number erlext-tag-alist
)))
368 (defun erlext-get-code (tag)
369 (cdr (assq tag erlext-tag-alist
)))
371 ;; ------------------------------------------------------------
373 ;; ------------------------------------------------------------
375 (defvar erlext-test-cases
376 `(1 foo
"bar" [bar baz
] [,erl-tag erl-pid someone
@somehost
0 0 0] (1 foo
())
377 [,erl-tag erl-port someone
@somehost
0 0]
380 (defun erlext-test ()
381 "Test each term in `erlext-test-cases' by encoding it and decoding
382 it and making sure that it's unchanged."
384 (mapc #'erlext-test-case erlext-test-cases
)
385 (message "Smooth sailing"))
387 (defun erlext-test-case (term)
389 (assert (equal term
(erlext-binary-to-term (erlext-term-to-binary term
))))
390 (error (error "test failed for %S: %S" term
(error-message-string x
)))))