-
Notifications
You must be signed in to change notification settings - Fork 16
/
urx.rkt
127 lines (106 loc) · 4.78 KB
/
urx.rkt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#lang at-exp racket
;;;
;;; URX
;;;
; This implements urx expressions.
; What jsx expressions are to JavaScript, urx expressions are to Urlang.
; JSX expressions are an extension to JavaScript that makes it easy
; to generate React elements (representing dom elements) at runtime.
; This file (for now) contains both urx expressions.
(require urlang urlang/html
; (except-in xml make-element entity element comment cdata attribute? attribute)
syntax/parse (for-syntax syntax/parse))
(require (for-syntax scribble/html/xml racket/match))
;;; JSX URX (AT-expression) S-EXPR Urlang
;;; <div>Foo</div> @div{Foo} (div "foo") (React.createElement "div" null (array "Foo"))
;;; <div foo="bar">Foo</div> @div[foo: "bar"]{Foo} (div foo: bar "foo") (React.createElement "div" (object [foo "bar"]) (array "Foo"))
;;; <Input /> @Input[] (Input) (React.createElement "Input" null)
;; Note that jsx/urx expressions are allowed in the body too.
;; @list[foo: "foo" bar: "bar"]{baz @42 qux}
;; (foo: "foo" bar: "bar" "baz " 42 " qux")
;; An easy way to see that an urx-expressions reads as:
(define-syntax (urx-test stx)
(syntax-parse stx
[(_ urx-expr)
(syntax/loc stx
'urx-expr)]))
;; (urx-test @div[foo: "bar" Foo])
;; '(div foo: "bar" Foo)
;; The library scribble/html/xml provides a few tools that will help us.
(require scribble/html/xml
)
;; First The attribute names are represented as symbols that end with a colon.
;; > (attribute? 'foo:)
;; foo
;; > (attribute? 'foo)
;; #f
;; To make things clearer, we will use `attribute->symbol` when we need
;; to remove a colon.
(define attribute->symbol attribute?)
;; We will need to split a form into the attributes and the body.
;; The functions `attributes+body` and `split-attributes+body` will help
;; us with this.
;; > (attributes+body '(foo: "bar" Foo))
;; '((foo . "bar"))
;; '(Foo)
;; > (split-attributes+body '(foo: "bar" Foo))
;; '(foo: "bar")
;; '(Foo)
(require racket/syntax)
(define-urlang-macro urx
(λ (stx)
; attributes+body : syntax-list -> ...
(define attribute->symbol attribute?)
(define (attributes+body xs)
(let loop ([xs xs] [as '()])
(define a (and (pair? xs)
(identifier? (car xs))
(attribute->symbol (syntax-e (car xs)))))
(define a-id (and a (datum->syntax (car xs) a (car xs))))
; if a is #f we have seen the last attribute,
; otherwise a is the attribute name (without colon).
(cond [(not a) (values (reverse as) xs)]
[(null? (cdr xs)) (error 'attributes+body
"missing attribute value for `~s:'" a)]
[else (loop (cddr xs)
(cons (cons a-id (cadr xs)) as))])))
(define (convert urx-expr)
; (displayln (list 'urx-convert urx-expr))
(syntax-parse urx-expr
#:datum-literals (ur)
[s:string #'s]
[n:number #'n]
[i:id #'i]
[b:boolean #'b]
[(ur more) #'more] ; "unquote" for urx expressions (called mustaches in jsx)
[(tag:id (~optional (~seq #:spread props:id) #:defaults ([props #f]))
atts+body ...)
(define ps (attribute props))
(define-values (atts body)
(attributes+body (syntax->list #'(atts+body ...))))
(define tag-str (symbol->string (syntax-e #'tag)))
(with-syntax* ([tag (if (char-lower-case? (string-ref tag-str 0))
tag-str
#'tag)]
[((id . val) ...) atts]
[(val ...) (map convert (syntax->list #'(val ...)))]
[(content ...) (map convert body)])
(with-syntax
([atts-expr
(cond [(and (zero? (length atts)) (not ps)) #'null]
[(and (zero? (length atts)) ps) ps]
[(not ps) #'(object [id val] ...)]
[else
(with-syntax ([props ps])
; TODO change assign here to something that works ...
#'(Object.assign (object) (object [id val] ...) props))])])
(match body
['() (syntax/loc urx-expr
(React.createElement tag atts-expr))]
[_ (syntax/loc urx-expr
(React.createElement tag atts-expr content ...))])))]))
(syntax-parse stx
[(_urx urx-expr)
(define out (convert #'urx-expr))
; (displayln (syntax->datum out))
out])))