Howdy!
Is there an easy way to convert a structure to scheme code (sexp) that can by restored by evaluating it? I'm looking for something like this:
http://schemecookbook.org/Cookbook/StructuresReadingAndWriting
Thanks in advance!
Afficher les réponses par date
Howdy!
Is there an easy way to convert a structure to scheme code (sexp) that can by restored by evaluating it? I'm looking for something like this:
http://schemecookbook.org/Cookbook/StructuresReadingAndWriting
I can think of two ways to achieve this. The one I prefer is using the built-in serialization feature of Gambit-C. Here's some sample code:
; using the built-in serialization feature
(define (serialize obj) (call-with-output-string '() (lambda (p) (output-port-readtable-set! p (readtable-sharing-allowed?-set (output-port-readtable p) 'serialize)) (write obj p))))
(define (deserialize str) (call-with-input-string str (lambda (p) (input-port-readtable-set! p (readtable-sharing-allowed?-set (input-port-readtable p) 'serialize)) (read p))))
(define-type date id: 836BAA77-46D5-11D9-B4D1-00039301BA52 day month year )
(define-type person id: 83C73CCE-46D5-11D9-9982-00039301BA52 name birthdate )
(define p (make-person "Joe Smith" (make-date 5 12 2004)))
(define s (serialize p))
(display "\nstructure:\n") (pp p)
(display "\nserialized:\n") (pp s)
(display "\ndeserialized:\n") (pp (deserialize s))
; Here's the output generated: ; ;structure: ;#<person #2 ; name: "Joe Smith" ; birthdate: #<date #3 day: 5 month: 12 year: 2004>> ; ;serialized: ;"#structure(#structure(#0=#structure(#0# ##type-6 type 8 #f #(id 1 #f name 5 #f flags 5 #f super 5 #f fields 5 #f cfields 5 #f) #(1 2 3 4 5 6)) ##type-2-83C73CCE-46D5-11D9-9982-00039301BA52 person 8 #f #(name 0 #f birthdate 0 #f) #(1 2)) "Joe Smith" #structure(#structure(#0# ##type-3-836BAA77-46D5-11D9-B4D1-00039301BA52 date 8 #f #(day 0 #f month 0 #f year 0 #f) #(1 2 3)) 5 12 2004))" ; ;deserialized: ;#<person #4 ; name: "Joe Smith" ; birthdate: #<date #5 day: 5 month: 12 year: 2004>>
Notice that the structures are given a "unique id", which allows the system to recover the correct type of a deserialized object (in the sense that the type predicate and accessors will continue to work properly). It also allows these structures to be communicated to a Scheme process (server, client, etc) that knows nothing about this type. That process can store these structures (but not access the content), and then send them back to a process that knows about this type (like the originating process) which can access the content. Another advantage of this method is that you can serialize functions and continuations. I'm not sure you need this for your program but maybe you'll find a use for this in the future.
The main problem with this method is that the serialized representation takes quite a bit of space (because it contains all the information of the type and the supertypes, such as the name of the type, the name of the fields, the attributes of the fields, the unique identifier, etc). I don't think this is a problem for your application because objects are small and over a network there is little difference between sending a 40 byte packet and a 400 byte packet. Moreover, if you are sending a long list of objects the type information will be included only once in the serialized representation.
The second method is closer in spirit to the web link you mention. It consists of an explicit encoding/decoding of the structures into vectors with a special first element that indicates the type of the structure. Here's some sample code:
; implementation of read/write structures
(define-macro (define-read-write-type name . definition) `(begin (define-type ,name ,@definition) (register-constructor ',name ,(string->symbol (string-append "make-" (symbol->string name))))))
(define (constructor-id name) (string->symbol (string-append "<STRUCTURE>" (symbol->string name))))
(define constructor-registry '())
(define (register-constructor name constructor) (set! constructor-registry (cons (cons (constructor-id name) constructor) constructor-registry)))
(define (encode obj) (cond ((##structure? obj) (let ((type (##structure-type obj))) (list->vector (cons (constructor-id (##type-name type)) (map encode (cdr (##vector->list obj))))))) ((pair? obj) (cons (encode (car obj)) (encode (cdr obj)))) ((vector? obj) (list->vector (map encode (vector->list obj)))) ((or (number? obj) (string? obj) (boolean? obj) (null? obj)) obj) (else (error "can't encode this object" obj))))
(define (decode obj) (cond ((pair? obj) (cons (decode (car obj)) (decode (cdr obj)))) ((vector? obj) (cond ((and (>= (vector-length obj) 1) (assq (vector-ref obj 0) constructor-registry)) => (lambda (c) (apply (cdr c) (map decode (cdr (vector->list obj)))))) (else (list->vector (map decode (vector->list obj)))))) ((or (number? obj) (string? obj) (boolean? obj) (null? obj)) obj) (else (error "can't decode this object" obj))))
; a test:
(define-read-write-type date day month year )
(define-read-write-type person name birthdate )
(define p (make-person "Joe Smith" (make-date 5 12 2004)))
(define e (encode p))
(display "\nstructure:\n") (pp p)
(display "\nencoded:\n") (pp e)
(display "\ndecoded:\n") (pp (decode e))
; Here's the output generated: ; ;structure: ;#<person #6 ; name: "Joe Smith" ; birthdate: #<date #7 day: 5 month: 12 year: 2004>> ; ;encoded: ;#(<STRUCTURE>person "Joe Smith" #(<STRUCTURE>date 5 12 2004)) ; ;decoded: ;#<person #8 ; name: "Joe Smith" ; birthdate: #<date #9 day: 5 month: 12 year: 2004>>
Hope this helps.
Marc
I'm looking to embed Gambit into my poker server and client. Gambit will be half of the app. The other half will be RakNet (http:// www.rakkarsoft.com/) for the server and SDL (http://www.libsdl.org) for the client.
The server loop will be getting packets from the network layer and passing them on to Gambit. On the client side it will be more or less the same except there's the SDL GUI event loop.
I understand that I can build the RakNet interface as some sort of an extension object for Gambit and do the main loop in Gambit itself. It does not seem to be applicable to the client, though.
Can I have my app handle the main loop and pass events to Gambit? What is the best way to accomplish this?
I would also like to have a REPL in my code so that I could type LISP commands on the client and have them run on the server. This could be a compile-time option so as not to compromise production installations.
Thanks, Joel