#lang racket/base
(require
srfi/26
(only-in srfi/13 string-null? string-prefix? string-index-right)
racket/contract
racket/match
racket/list
racket/path
racket/port
racket/pretty
"main.rkt"
"google/protobuf/descriptor.rkt"
"google/protobuf/compiler/plugin.rkt"
"extend/protobuf/bigint.rkt")
(define-syntax doto
(syntax-rules ()
[(doto v op0 ops ...)
(doto (op0 v) ops ...)]
[(doto v)
v]))
(define (proto-string->symbol str [prefix #f])
(doto str
(cut regexp-replace* #rx"(^|[a-z])([A-Z])" <>
(λ (_ l u) (if (string-null? l) u (string-append l "-" u))))
string-downcase
(cut regexp-replace* #rx"_" <> "-")
(cut regexp-replace #rx"^is-(.*)" <>
(λ (_ s) (string-append s "?")))
(if prefix
(cut format "~a:~a" prefix <>)
values)
string->symbol))
(provide/contract
[proto-string->symbol
(->* (string?) (any/c)
symbol?)])
(define (register-types! types proto)
(let ([package (cond
[(file-descriptor-proto-package proto #f)
=> (cut string-append "." <>)]
[else
""])])
(for-each
(cut register-enum-type! types package <>)
(file-descriptor-proto-enum-type proto))
(for-each
(cut register-message-types! types package <>)
(file-descriptor-proto-message-type proto))))
(define (register-enum-type! types package proto [prefix #f])
(let* ([name (enum-descriptor-proto-name proto)]
[packaged (string-append package "." name)]
[prefixed (proto-string->symbol name prefix)])
(hash-set! types packaged (cons 'enum prefixed))))
(define (register-message-types! types package proto [prefix #f])
(let* ([name (descriptor-proto-name proto)]
[packaged (string-append package "." name)]
[prefixed (proto-string->symbol name prefix)])
(hash-set! types packaged (cons 'struct prefixed))
(for-each
(cut register-enum-type! types packaged <> prefixed)
(descriptor-proto-enum-type proto))
(for-each
(cut register-message-types! types packaged <> prefixed)
(descriptor-proto-nested-type proto))))
(define (type-ref types package name)
(if (string-prefix? "." name)
(hash-ref types name (cut raise-user-error name "unknown absolute type"))
(let retry ([package package])
(if (string-null? package)
(raise-user-error name "unknown relative type")
(or (hash-ref types (string-append package "." name) #f)
(retry (substring
package
0 (or (string-index-right package #\.) 0))))))))
(provide/contract
[register-types!
(-> (hash/c string? (cons/c symbol? symbol?) #:immutable #f) file-descriptor-proto?
any)]
[register-enum-type!
(->* ((hash/c string? (cons/c symbol? symbol?) #:immutable #f)
(or/c string? #f) enum-descriptor-proto?)
(any/c)
any)]
[register-message-types!
(->* ((hash/c string? (cons/c symbol? symbol?) #:immutable #f)
(or/c string? #f) descriptor-proto?)
(any/c)
any)]
[type-ref
(-> (hash/c string? (cons/c symbol? symbol?) #:immutable 'dont-care)
string? string? (cons/c symbol? symbol?))])
(define (translate-types types proto)
(let ([package (cond
[(file-descriptor-proto-package proto #f)
=> (cut string-append "." <>)]
[else
""])])
(append
(append*
(map
(cut translate-enum-type types package <>)
(file-descriptor-proto-enum-type proto))
(map
(cut translate-message-types types package <>)
(file-descriptor-proto-message-type proto)))
(map
(cut translate-extension types package <>)
(file-descriptor-proto-extension proto)))))
(define (translate-enum-type types package proto [prefix #f])
(let* ([name (enum-descriptor-proto-name proto)]
[prefixed (proto-string->symbol name prefix)])
`(define-enum-type ,prefixed
,(for/list ([val (in-list (enum-descriptor-proto-value proto))])
(list (proto-string->symbol (enum-value-descriptor-proto-name val))
(enum-value-descriptor-proto-number val))))))
(define (translate-message-types types package proto [prefix #f])
(let* ([name (descriptor-proto-name proto)]
[packaged (string-append package "." name)]
[prefixed (proto-string->symbol name prefix)])
(cons
`(define-message-type ,prefixed
,(for/list ([field (in-list (descriptor-proto-field proto))])
(translate-field types packaged field)))
(append
(append*
(map
(cut translate-enum-type types packaged <> prefixed)
(descriptor-proto-enum-type proto))
(map
(cut translate-message-types types packaged <> prefixed)
(descriptor-proto-nested-type proto)))
(map
(cut translate-extension types packaged <>)
(descriptor-proto-extension proto))))))
(define (translate-extension types package proto)
(match-let* ([name (field-descriptor-proto-extendee proto)]
[(cons _ host) (type-ref types package name)])
`(define-message-extension ,host
,(translate-field types package proto))))
(define (translate-field types packaged field)
(let ([name (proto-string->symbol (field-descriptor-proto-name field))]
[type (field-descriptor-proto-type field)]
[options (field-descriptor-proto-options field field-options*)])
(list*
(case (field-descriptor-proto-label field)
[(label-required)
'required]
[(label-optional)
'optional]
[(label-repeated)
(if (field-options-packed options #f)
'packed
'repeated)])
(cond
[(field-descriptor-proto-type-name field #f)
=> (λ (name)
(match-let ([(cons kind name) (type-ref types packaged name)])
(set-field-descriptor-proto-type! field
(case kind
[(enum) 'type-enum]
[(struct) 'type-message]))
(string->symbol (format "~a:~a" kind name))))]
[else
(case type
[(type-int32)
'primitive:int32]
[(type-int64)
'primitive:int64]
[(type-uint32)
'primitive:uint32]
[(type-uint64)
(let ([max-size (field-options-max-size options)])
(if (= max-size 10)
'primitive:uint64
`(primitive:uint* ,(and (positive? max-size) max-size))))]
[(type-sint32)
'primitive:sint32]
[(type-sint64)
(let ([max-size (field-options-max-size options)])
(if (= max-size 10)
'primitive:sint64
`(primitive:sint* ,(and (positive? max-size) max-size))))]
[(type-fixed32)
'primitive:fixed32]
[(type-fixed64)
'primitive:fixed64]
[(type-sfixed32)
'primitive:sfixed32]
[(type-sfixed64)
'primitive:sfixed64]
[(type-bool)
'primitive:bool]
[(type-float)
'primitive:float]
[(type-double)
'primitive:double]
[(type-bytes)
'primitive:bytes]
[(type-string)
'primitive:string]
[else
(raise-user-error
name "unsupported field type: ~e"
type)])])
name
(field-descriptor-proto-number field)
(cond
[(field-descriptor-proto-default-value field #f)
=> (λ (default)
(list
(case type
[(type-int32 type-int64 type-uint32 type-uint64 type-sint32 type-sint64
type-fixed32 type-fixed64 type-sfixed32 type-sfixed64
type-float type-double)
(string->number default)]
[(type-bool)
(not (equal? default "false"))]
[(type-bytes)
(call-with-input-string
(string-append "#\"" default "\"")
read)]
[(type-string)
default]
[(type-enum)
`(quote ,(proto-string->symbol default))]
[else
(raise-user-error
name "unsupported default value of type ~e: ~v"
type default)])))]
[else
null]))))
(provide/contract
[translate-types
(-> (hash/c string? (cons/c symbol? symbol?)) file-descriptor-proto?
list?)]
[translate-enum-type
(->* ((hash/c string? (cons/c symbol? symbol?))
(or/c string? #f) enum-descriptor-proto?)
(any/c)
any/c)]
[translate-message-types
(->* ((hash/c string? (cons/c symbol? symbol?))
(or/c string? #f) descriptor-proto?)
(any/c)
list?)]
[translate-extension
(-> (hash/c string? (cons/c symbol? symbol?))
(or/c string? #f) field-descriptor-proto?
any/c)])
(define (generate-racket req)
(define types
(make-hash))
(define protos
(for/hash ([file (in-list (code-generator-request-proto-file req))])
(register-types! types file)
(values (file-descriptor-proto-name file)
file)))
(with-handlers ([exn:fail:user?
(λ (exn)
(code-generator-response* #:error (exn-message exn)))])
(code-generator-response*
#:file
(for/list ([proto-file (code-generator-request-file-to-generate req)]
#:when #t
[racket-file (in-value (path-replace-suffix proto-file ".rkt"))]
[proto (in-value (hash-ref protos proto-file))])
(code-generator-response:file*
#:name (path->string racket-file)
#:content
(with-output-to-string
(λ ()
(parameterize ([print-as-expression #f])
(display "#lang racket/base")
(newline)
(display ";; Generated using protoc-gen-racket v1.0.0")
(newline)
(pretty-print
`(require
(planet murphy/protobuf:1/syntax)
,@(for/list ([dep (in-list (file-descriptor-proto-dependency proto))])
(path->string
(find-relative-path
(let-values ([(base name dir?) (split-path racket-file)])
(cond
[dir?
racket-file]
[(memq base '(relative #f))
"/"]
[else
(build-path/convention-type 'unix "/" base)]))
(let ([target (path-replace-suffix dep ".rkt")])
(build-path/convention-type 'unix "/" target)))))))
(newline)
(for-each pretty-print (translate-types types proto))
(newline)
(pretty-print '(provide (all-defined-out)))))))))))
(define (main . args)
(serialize (generate-racket (deserialize struct:code-generator-request))))
(provide/contract
[generate-racket
(-> code-generator-request? code-generator-response?)])
(provide
main)