Skip to content
This repository was archived by the owner on Jan 17, 2021. It is now read-only.

Commit 406b56b

Browse files
committedMay 28, 2019
Rebirth
1 parent 1fc1f15 commit 406b56b

28 files changed

+362
-50296
lines changed
 

‎.gitignore

+4-88
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,4 @@
1-
*.exe
2-
3-
# Created by https://www.gitignore.io/api/node,ocaml
4-
5-
### Node ###
6-
# Logs
7-
logs
8-
*.log
9-
npm-debug.log*
10-
yarn-debug.log*
11-
yarn-error.log*
12-
13-
# Runtime data
14-
pids
15-
*.pid
16-
*.seed
17-
*.pid.lock
18-
19-
# Directory for instrumented libs generated by jscoverage/JSCover
20-
lib-cov
21-
22-
# Coverage directory used by tools like istanbul
23-
coverage
24-
25-
# nyc test coverage
26-
.nyc_output
27-
28-
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
29-
.grunt
30-
31-
# Bower dependency directory (https://bower.io/)
32-
bower_components
33-
34-
# node-waf configuration
35-
.lock-wscript
36-
37-
# Compiled binary addons (http://nodejs.org/api/addons.html)
38-
build/Release
39-
40-
# Dependency directories
41-
node_modules/
42-
jspm_packages/
43-
44-
# Typescript v1 declaration files
45-
typings/
46-
47-
# Optional npm cache directory
48-
.npm
49-
50-
# Optional eslint cache
51-
.eslintcache
52-
53-
# Optional REPL history
54-
.node_repl_history
55-
56-
# Output of 'npm pack'
57-
*.tgz
58-
59-
# Yarn Integrity file
60-
.yarn-integrity
61-
62-
# dotenv environment variables file
63-
.env
64-
65-
66-
### OCaml ###
67-
*.annot
68-
*.cmo
69-
*.cma
70-
*.cmi
71-
*.a
72-
*.o
73-
*.cmx
74-
*.cmxs
75-
*.cmxa
76-
77-
# ocamlbuild working directory
78-
_build/
79-
80-
# ocamlbuild targets
81-
*.byte
82-
*.native
83-
84-
# oasis generated files
85-
setup.data
86-
setup.log
87-
88-
# End of https://www.gitignore.io/api/node,ocaml
1+
_build
2+
_opam
3+
*.install
4+
*.merlin

‎.gitmodules

-15
This file was deleted.

‎.merlin

-2
This file was deleted.

‎.npmignore

-10
This file was deleted.

‎LICENSE

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2019 — Present CHEN Xian-an <xianan.chen@gmail.com>
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

‎Makefile

-8
This file was deleted.

‎README.md

+33-20
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,51 @@
11
# ppx_bsx
22

3-
OCaml JSX for [ReasonReact](https://github.com/reasonml/reason-react/).
3+
OCaml JSX for ReasonReact.
44

55
## Install
66

7-
- `yarn add -D ppx_bsx` or `npm i --save-dev ppx_bsx`
8-
- add `"ppx-flags": ["./node_modules/ppx_bsx/bin/ppx_bsx.exe"]` to `bsconfig.json`
7+
`ppx_bsx` depends on `ppx_lib`, which means that `ppx_bsx` doesn't support `bs-platform` 5.x, so first step is configuring your project to `"bs-platform": "^6.0.1"`.
98

10-
👉 <https://github.com/cxa/ppx_bsx_example>
9+
Install `ppx_bsx` with `opam` or `esy`, and add `ppx_bsx` executable to `bs-config.json`:
1110

12-
## Usage
11+
```json
12+
{
13+
"ppx-flags": [
14+
"./_opam/bin/ppx_bsx",
15+
"./node_modules/bs-platform/lib/bsppx.exe -bs-jsx 3"
16+
]
17+
}
18+
```
19+
20+
Replace `./_opam/bin/ppx_bsx` to actual `ppx_bsx` installed path.
21+
22+
## Basic Usage
1323

1424
This is how it feel:
1525

1626
```ocaml
1727
[%bsx "
18-
<Container>
19-
<h1>Nice example</h1>
20-
<nav className="(styles "sidebar")">
21-
<Router.Route path='/' component="sidebar" />
22-
</nav>
23-
<div className="(styles "content")">
24-
<Router.Switch>"(mk_switches link_groups)"</Router.Switch>
25-
</div>
26-
</Container>
28+
<Container>
29+
<h1>Nice example</h1>
30+
<nav className="(styles "sidebar")">
31+
This is sidebar
32+
</nav>
33+
<div className="(styles "content")">
34+
"{j|这是主内容|j}"
35+
</div>
36+
</Container>
2737
"]
2838
```
2939

30-
### Simple rules
40+
### Simple Rules
41+
- Break `[%bsx ""]` into
42+
```ocaml
43+
[%bsx "
3144
45+
"]
46+
```
47+
and ignore the first and last quotation marks.
3248
- When you need OCaml expression, wrap it with double quotation marks, otherwise
3349
- For string literal value, just use single quotation marks
34-
- For singe text node, you don't need to wrap it to `ReasonReact.stringToElement`, (surprisedly) `<div>Hello, World</div>` is accepted
35-
36-
### Bonus
37-
38-
For non-ascii string, you can simply use string literal like `{|中文|}`, `ppx_bsx` will convert to `{j|中文|j}` automatically.
50+
- For single text node, you don't need to wrap it to `ReasonReact.string`, (surprisedly) `<span>Hello, World</span>` or `<span>"{j|你好,世界|j}"</span>` is accepted
51+
- `{|你好|}` will be transformed to `{j|你好|j}` automatically

‎bin/ppx_bsx

-17
This file was deleted.

‎bspack/README.md

-11
This file was deleted.

‎bspack/markup

-1
This file was deleted.

‎bspack/ocaml-migrate-parsetree

-1
This file was deleted.

‎bspack/ocaml-re

-1
This file was deleted.

‎bspack/pack.sh

-22
This file was deleted.

‎bspack/uchar

-1
This file was deleted.

‎bspack/uutf

-1
This file was deleted.

‎dune-project

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
(lang dune 1.9)
2+
(name ppx_bsx)

‎package.json

-30
This file was deleted.

‎postinstall.sh

-5
This file was deleted.

‎ppx_bsx.ml

-49,764
This file was deleted.

‎ppx_bsx.opam

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
opam-version: "2.0"
2+
3+
name: "ppx_bsx"
4+
version: "2.0.0"
5+
synopsis: "ReasonReact JSX for OCaml"
6+
description: """
7+
ReasonReact JSX v3 for OCaml, ReasonReact 0.7+ and BuckleScript 6.0+ required
8+
"""
9+
maintainer: "CHEN Xian-an <xianan.chen@gmail.com>"
10+
authors: "CHEN Xian-an <xianan.chen@gmail.com>"
11+
tags: [ "BuckleScript" "ReasonReact" "React" "JSX" ]
12+
license: "MIT"
13+
homepage: "https://github.com/cxa/ppx_bsx"
14+
dev-repo: "git+https://github.com/cxa/ppx_bsx.git"
15+
bug-reports: "https://github.com/cxa/ppx_bsx/issues"
16+
doc: "https://github.com/cxa/ppx_bsx"
17+
build: [
18+
[ "dune" "subst" ] {pinned}
19+
[ "dune" "build" "-p" name "-j" jobs ]
20+
]
21+
depends: [
22+
"ocaml" { = "4.06.1" }
23+
"markup"
24+
"ppxlib"
25+
"dune" {build}
26+
]
27+
url {
28+
src: "https://github.com/cxa/ppx_bsx/archive/2.0.0.tar.gz"
29+
}

‎src/dune

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
(library
2+
(name ppx_bsx_lib)
3+
(public_name ppx_bsx.lib)
4+
(synopsis "ReasonReact JSX in OCaml")
5+
(modules ppx_bsx_lib)
6+
(kind ppx_rewriter)
7+
(libraries str markup ppxlib)
8+
(preprocess (pps ppxlib.metaquot)))
9+
10+
(executable
11+
(name ppx_bsx)
12+
(public_name ppx_bsx)
13+
(modules ppx_bsx)
14+
(libraries ppx_bsx.lib))

‎src/ppx_bsx.ml

+2-276
Original file line numberDiff line numberDiff line change
@@ -1,276 +1,2 @@
1-
module STR = Str (* Avoid shadowed by Ast_helper.Str *)
2-
3-
open Migrate_parsetree
4-
open Ast_404
5-
open Ast_mapper
6-
open Ast_helper
7-
open Asttypes
8-
open Parsetree
9-
open Location
10-
open Longident
11-
12-
module ExprMap = Map.Make(String)
13-
14-
type collector =
15-
{ html_frags: string list;
16-
exprs: expression ExprMap.t;
17-
placeholder_index: int;
18-
}
19-
20-
type tag = string
21-
type prop_name = string
22-
type props = (arg_label * expression) list
23-
type dom_repr =
24-
| Empty
25-
| Text of Location.t * expression list
26-
| Element of Location.t * tag * props * dom_repr list
27-
28-
let expr_placeholder_prefix = "__b_s_x__"
29-
30-
let fragment_placeholder = "ppx_bsx_fragment"
31-
32-
let re_id = Re.compile Re.(seq [ str expr_placeholder_prefix; rep digit ])
33-
34-
let collect e =
35-
let rec loop col e =
36-
match e.pexp_desc with
37-
| Pexp_apply ({ pexp_desc = Pexp_constant Pconst_string (str, None) }, al) ->
38-
let expr_list = snd @@ List.split al in
39-
let c = List.fold_left loop col expr_list in
40-
let html_frags = str :: (List.rev c.html_frags) in
41-
{ c with html_frags }
42-
| Pexp_constant Pconst_string (str, None) ->
43-
{ col with html_frags = str :: col.html_frags }
44-
| _ ->
45-
let html =
46-
Printf.sprintf "%s%i" expr_placeholder_prefix col.placeholder_index
47-
in
48-
let html_frags = html :: col.html_frags in
49-
let exprs = ExprMap.add html e col.exprs in
50-
let placeholder_index = col.placeholder_index + 1 in
51-
{ html_frags; exprs; placeholder_index }
52-
in
53-
loop { html_frags = []; exprs = ExprMap.empty; placeholder_index = 0 } e
54-
55-
type text_expr_type =
56-
| Pure_text of expression
57-
| Ocaml_expr of expression
58-
59-
let rec text_to_exprs loc expr_map str =
60-
let convert_to_j_if_neccessary e = match e.pexp_desc with
61-
| Pexp_constant Pconst_string (str, Some "") ->
62-
Exp.constant ~loc:e.pexp_loc (Pconst_string (str, Some "j"))
63-
| _ -> e
64-
in
65-
let to_expr s g =
66-
let slen = String.length s in
67-
let (i,j) = Re.Group.offset g 0 in
68-
let key = String.sub s i (j-i) in
69-
let e =
70-
try
71-
ExprMap.find key expr_map
72-
with _ ->
73-
let err =
74-
Location.error
75-
~loc "Wrong OCaml expression, you may missed the parentheses."
76-
in
77-
raise (Location.Error err)
78-
in
79-
let oe = [Ocaml_expr (convert_to_j_if_neccessary e)] in
80-
let isWhole = i = 0 && j = slen in
81-
if isWhole then oe else begin
82-
let es = ref oe in
83-
if i > 0 then
84-
es := text_to_exprs loc expr_map (String.sub s 0 i) @ !es
85-
else ();
86-
if j < slen then
87-
es := !es @ text_to_exprs loc expr_map (String.sub s j (slen-j))
88-
else ();
89-
!es
90-
end
91-
in
92-
match Re.exec_opt re_id str with
93-
| None ->
94-
begin
95-
match String.trim str with
96-
| "" -> [ ]
97-
| _ as s ->
98-
let nl_to_sp = STR.(global_replace (regexp "\n") " " s) in
99-
[ Pure_text (Exp.constant (Pconst_string (nl_to_sp, None))) ]
100-
end
101-
| Some g -> to_expr str g
102-
103-
let handle_text loc expr_map xs =
104-
let str =
105-
xs
106-
|> List.map (fun x ->
107-
STR.(split (regexp "\n+") x)
108-
|> List.map String.trim
109-
|> String.concat "\n"
110-
|> String.trim
111-
)
112-
|> String.concat ""
113-
in
114-
match str with
115-
| "" -> Empty
116-
| _ ->
117-
let exprs = text_to_exprs loc expr_map str in
118-
let to_react_el e =
119-
let loc = e.pexp_loc in
120-
let rrste =
121-
Exp.ident ~loc { loc; txt = Ldot (Lident "ReasonReact", "string")}
122-
in
123-
Exp.apply rrste [ (Nolabel, e)]
124-
in
125-
let fold acc item = match item with
126-
| Pure_text e -> to_react_el e :: acc
127-
| Ocaml_expr e ->
128-
match e.pexp_desc with
129-
| Pexp_constant Pconst_string (_, Some "j") -> to_react_el e :: acc
130-
| _ -> e :: acc
131-
in
132-
Text (loc, (exprs |> List.fold_left fold [] |> List.rev))
133-
134-
let tidy_attr =
135-
function
136-
| "class" -> "className"
137-
| "for" -> "htmlFor"
138-
| "type" -> "type_"
139-
| "to" -> "to_"
140-
| "open" -> "open_"
141-
| "begin" -> "begin_"
142-
| "end" -> "end_"
143-
| "in" -> "in_"
144-
| _ as origin -> origin
145-
146-
let handle_element loc expr_map (_, name) attrs children =
147-
let attrs_map ((_,n), v) =
148-
let n = tidy_attr n in
149-
match v with
150-
| "" -> [ (Labelled n, Exp.ident { loc; txt = Lident n }) ]
151-
| _ ->
152-
let v_exprs = text_to_exprs loc expr_map v in
153-
let to_e item = match item with Pure_text e -> e | Ocaml_expr e -> e in
154-
match (List.length v_exprs) with
155-
| 0 -> []
156-
| 1 -> [ (Labelled n, to_e (List.hd v_exprs)) ]
157-
| _ ->
158-
let str_cat acc item =
159-
let e = to_e item in
160-
let loc = e.pexp_loc in
161-
Exp.apply
162-
~loc
163-
(Exp.ident ~loc { loc; txt = Lident "^"})
164-
([ (Nolabel, acc); (Nolabel, e) ])
165-
in
166-
[ ( Labelled n
167-
, List.tl v_exprs
168-
|> List.fold_left str_cat (List.hd v_exprs |> to_e )) ]
169-
in
170-
Element (loc, name, attrs |> List.map attrs_map |> List.concat, children)
171-
172-
let is_titlecase str =
173-
let fstc = String.get str 0 in
174-
Char.uppercase fstc = fstc
175-
176-
let handle_titlecase loc tag_name props children_expr =
177-
let create_comp =
178-
Exp.ident
179-
~loc { loc; txt = Ldot (Lident "ReasonReact", "element")}
180-
in
181-
let is_key_or_ref (label, _) = match label with
182-
| Labelled l -> l = "key" || l = "ref"
183-
| _ -> false
184-
in
185-
let (kr_props, mk_props) = List.partition is_key_or_ref props in
186-
let modules = (STR.split (STR.regexp "\\.") tag_name) @ [ "make" ] in
187-
let mk_ldot acc m = Ldot (acc, m) in
188-
let ident =
189-
List.tl modules
190-
|> List.fold_left mk_ldot (Lident (List.hd modules)) in
191-
let make = Exp.ident ~loc { loc; txt = ident } in
192-
let comp =
193-
Exp.apply make (mk_props @ [ (Nolabel, Exp.array children_expr) ])
194-
in
195-
Exp.apply create_comp (kr_props @ [ (Nolabel, comp) ])
196-
197-
let handle_lowercase loc tag_name props children_expr =
198-
let create_dom_el =
199-
Exp.ident ~ loc { loc; txt = Ldot (Lident "ReactDOMRe", "createElement")}
200-
in
201-
let tag_name_expr =
202-
if tag_name = fragment_placeholder then
203-
( Nolabel
204-
, Exp.ident ~ loc { loc; txt = Ldot (Lident "ReasonReact", "fragment")}
205-
)
206-
else
207-
(Nolabel, Exp.constant (Pconst_string (tag_name, None))) in
208-
let args = match List.length props with
209-
| 0 -> [ tag_name_expr; (Nolabel, Exp.array children_expr) ]
210-
| _ ->
211-
let create_props =
212-
Exp.ident ~loc { loc; txt = Ldot (Lident "ReactDOMRe", "props")}
213-
in
214-
let unit_ = (Nolabel, Exp.construct ~loc {loc; txt = Lident "()"} None) in
215-
let props_expr = Exp.apply create_props (props @ [ unit_ ]) in
216-
[ tag_name_expr; (Labelled "props", props_expr)
217-
; (Nolabel, Exp.array children_expr) ]
218-
in
219-
Exp.apply create_dom_el args
220-
221-
let expr mapper e =
222-
match e.pexp_desc with
223-
| Pexp_extension ({ txt = "bsx" }, PStr [{pstr_desc = Pstr_eval (e, _)}]) ->
224-
let open Markup in
225-
let c = collect e in
226-
let html = String.concat "" c.html_frags |> String.trim in
227-
let rec dom_to_expr = function
228-
| Element (loc, tag_name, props, children) ->
229-
let fold acc item = match item with
230-
| Element _ -> acc @ [ dom_to_expr item ]
231-
| Text (loc, els) -> acc @ els
232-
| _ -> acc
233-
in
234-
let handler =
235-
if is_titlecase tag_name then handle_titlecase
236-
else handle_lowercase
237-
in
238-
handler loc tag_name props (children |> List.fold_left fold [])
239-
| _ -> default_mapper.expr mapper e
240-
in
241-
let report location (error: Markup.Error.t) =
242-
match error with
243-
| `Bad_token _ -> () (* ignore this error because our attrs missing quotations*)
244-
| _ ->
245-
let open Lexing in
246-
let loc = e.pexp_loc in
247-
let location =
248-
fst location + loc.loc_start.pos_lnum
249-
, snd location + loc.loc_start.pos_cnum
250-
in
251-
let errstr = Error.to_string ~location error in
252-
raise (Location.Error (Location.error ~loc errstr))
253-
in
254-
begin match
255-
html
256-
|> STR.global_replace
257-
(STR.regexp "<>") ("<" ^ fragment_placeholder ^ ">")
258-
|> STR.global_replace
259-
(STR.regexp "</>") ("</" ^ fragment_placeholder ^ ">")
260-
|> string
261-
|> parse_xml ~report
262-
|> signals
263-
|> tree
264-
~text: (handle_text e.pexp_loc c.exprs)
265-
~element: (handle_element e.pexp_loc c.exprs)
266-
with
267-
| Some de -> dom_to_expr de
268-
| None -> default_mapper.expr mapper e
269-
end
270-
| _ -> default_mapper.expr mapper e
271-
272-
let mapper _ =
273-
let module To_current = Convert(OCaml_404)(OCaml_current) in
274-
To_current.copy_mapper {default_mapper with expr}
275-
276-
let () = Compiler_libs.Ast_mapper.register "bsx" mapper
1+
let () =
2+
Ppxlib.Driver.run_as_ppx_rewriter ()

‎src/ppx_bsx_lib.ml

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
open Ppxlib
2+
3+
module ExprsMap = Map.Make(String)
4+
5+
type html_frags =
6+
{ strs: string list
7+
; exprs: expression ExprsMap.t
8+
}
9+
10+
let name = "ppx_bsx"
11+
12+
let expr_placeholder_prefix = "__b_s_x__placeholder__"
13+
14+
let fragment_placeholder = "ppx_bsx_fragment"
15+
16+
let tidy_attr_name =
17+
function
18+
| "class" -> "className"
19+
| "for" -> "htmlFor"
20+
| "type" -> "type_"
21+
| "to" -> "to_"
22+
| "open" -> "open_"
23+
| "begin" -> "begin_"
24+
| "end" -> "end_"
25+
| "in" -> "in_"
26+
| _ as origin -> origin
27+
28+
let is_titlecase str =
29+
let fstc = String.get str 0 in
30+
Char.uppercase_ascii fstc = fstc
31+
32+
let placeholderize_fragment html =
33+
Str.(
34+
html
35+
|> global_replace (regexp "<>") ("<" ^ fragment_placeholder ^ ">")
36+
|> global_replace (regexp "</>") ("</" ^ fragment_placeholder ^ ">")
37+
)
38+
39+
let collect_html first_html_frag expr_list =
40+
expr_list
41+
|> List.fold_left (fun (frags, placeholder_index) (_, expr) ->
42+
match expr.pexp_desc with
43+
| Pexp_constant (Pconst_string (str, None)) ->
44+
({ frags with strs = str :: frags.strs }, placeholder_index)
45+
| _ ->
46+
let attr = Printf.sprintf "%s%i" expr_placeholder_prefix placeholder_index in
47+
({ strs = attr :: frags.strs
48+
; exprs = ExprsMap.add attr expr frags.exprs
49+
}, placeholder_index + 1)
50+
) ({ strs = [first_html_frag]; exprs = ExprsMap.empty }, 0)
51+
|> fst
52+
53+
let text_to_expr loc txts exprs =
54+
let txt = String.concat "" txts in
55+
Ast_builder.Default.(
56+
match exprs |> ExprsMap.find_opt txt with
57+
| Some expr ->
58+
begin match expr.pexp_desc with
59+
| Pexp_constant (Pconst_string (str, Some "")) -> (* transform {|w|} to {j|w|j} *)
60+
pexp_constant ~loc (Pconst_string (str, Some "j"))
61+
| _ -> expr
62+
end
63+
| None -> estring ~loc txt
64+
)
65+
66+
let handle_markup_text loc exprs txts =
67+
let expr = text_to_expr loc txts exprs in
68+
match expr.pexp_desc with
69+
| Pexp_constant _ ->
70+
let args = [(Nolabel, expr)] in
71+
Ast_builder.Default.pexp_apply ~loc [%expr React.string] args
72+
| _ -> expr
73+
74+
let add_jsx_attr loc e =
75+
{e with pexp_attributes = [({txt = "JSX"; loc}, PStr [])] }
76+
77+
let handle_markup_element loc exprs (_nsuri, lname) attrs children =
78+
let open Ast_builder.Default in
79+
match lname with
80+
| n when n = fragment_placeholder ->
81+
elist ~loc children |> add_jsx_attr loc
82+
| _ ->
83+
let is_titlecase = is_titlecase lname in
84+
let fname =
85+
if is_titlecase
86+
then Printf.sprintf "%s.createElement" lname
87+
else lname
88+
in
89+
let f = [%expr [%e evar ~loc fname]] in
90+
let labled_args =
91+
attrs
92+
|> List.map (fun ((_uri, name), v) ->
93+
let exp =
94+
if String.length v = 0
95+
then evar ~loc name
96+
else text_to_expr loc [v] exprs
97+
in
98+
((Labelled (tidy_attr_name name)), exp)
99+
)
100+
in
101+
let args =
102+
(Nolabel, eunit ~loc)
103+
:: (Labelled "children", elist ~loc children)
104+
:: List.rev labled_args
105+
|> List.rev
106+
in
107+
pexp_apply ~loc f args |> add_jsx_attr loc
108+
109+
let report loc mloc error =
110+
Markup.(
111+
match error with
112+
| `Bad_token _ -> () (* ignore this error because our attrs missing quotations*)
113+
| _ ->
114+
let location =
115+
fst mloc + loc.loc_start.pos_lnum
116+
, snd mloc + loc.loc_start.pos_cnum
117+
in
118+
let errstr = Error.to_string ~location error in
119+
Location.raise_errorf ~loc "%s" errstr
120+
)
121+
122+
let mk_html str_list =
123+
str_list
124+
|> List.rev
125+
|> String.concat ""
126+
|> placeholderize_fragment
127+
128+
let markupize loc html_frags =
129+
Markup.(
130+
mk_html html_frags.strs
131+
|> string
132+
|> parse_xml ~report:(report loc)
133+
|> signals
134+
|> trim
135+
|> normalize_text
136+
|> tree
137+
~text:(handle_markup_text loc html_frags.exprs)
138+
~element:(handle_markup_element loc html_frags.exprs)
139+
)
140+
141+
let expand ~loc ~path:_ expr =
142+
let first_html_frag, expr_list =
143+
match expr.pexp_desc with
144+
| Pexp_constant (Pconst_string (str, None)) ->
145+
str, []
146+
| Pexp_apply ({ pexp_desc = Pexp_constant (Pconst_string (str, None)); _ }, exprs) ->
147+
str, exprs
148+
| _ -> Location.raise_errorf ~loc "Wrong JSX format"
149+
in
150+
let html_frags = collect_html first_html_frag expr_list in
151+
match markupize loc html_frags with
152+
| Some expr -> expr
153+
| None -> Location.raise_errorf ~loc "Wrong JSX format"
154+
155+
let ext =
156+
Extension.declare
157+
"bsx"
158+
Extension.Context.expression
159+
Ast_pattern.(single_expr_payload __)
160+
expand
161+
162+
let () =
163+
Driver.register_transformation name ~extensions:[ext]

‎test/dune

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
(executable
2+
(name pp)
3+
(modules pp)
4+
(libraries ppx_bsx.lib ppxlib))
5+
6+
(rule
7+
(targets test.actual.ml)
8+
(deps (:pp pp.exe) (:input test.ml))
9+
(action (run ./%{pp} --impl %{input} -o %{targets})))
10+
11+
(alias
12+
(name runtest)
13+
(action (diff test.expected.ml test.actual.ml)))
14+
15+
(test
16+
(name test)
17+
(modules test)
18+
(preprocess (pps ppx_bsx.lib)))

‎test/pp.ml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Ppxlib.Driver.standalone ()

‎test/sample.ml

-23
This file was deleted.

‎test/test.expected.ml

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
type el =
2+
| S of string
3+
| L of el list
4+
module React = struct let string s = S s end
5+
module Foo =
6+
struct
7+
let createElement ?(a= "") ?(b= "") ?(children= []) () =
8+
L ([S a; S b] |> (List.append children))
9+
end
10+
let div ?(a= "") ?(b= "") ?(children= []) () =
11+
L ([S a; S b] |> (List.append children))
12+
let span ?(a= "") ?(b= "") ?(children= []) () =
13+
L ([S a; S b] |> (List.append children))
14+
let _ =
15+
let a = "foo" in
16+
let b = "bar" in
17+
let txt = React.string "baz" in
18+
let name = "bba" in
19+
(([((Foo.createElement ~a ~b
20+
~children:[((div ~a:{j|数据|j}
21+
~children:[((span ~a ~children:[txt] ())
22+
[@JSX ]);
23+
((span
24+
~children:[React.string
25+
{j|你好世界|j}] ())
26+
[@JSX ]);
27+
((span
28+
~children:[React.string
29+
{j|你好世界|j}] ())
30+
[@JSX ]);
31+
((span
32+
~children:[React.string "Hello World"]
33+
())
34+
[@JSX ])] ())
35+
[@JSX ]);
36+
((span ~children:[React.string @@ ("Hello, " ^ name)] ())
37+
[@JSX ])] ())
38+
[@JSX ]);
39+
((span ~children:[] ())
40+
[@JSX ])])[@JSX ])

‎test/test.ml

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
type el = S of string | L of el list
2+
3+
module React = struct
4+
let string s =
5+
S s
6+
end
7+
8+
module Foo = struct
9+
let createElement ?(a="") ?(b="") ?(children=[]) () =
10+
L ([S a; S b] |> List.append children)
11+
end
12+
13+
let div ?(a="") ?(b="") ?(children=[]) () =
14+
L ([S a; S b] |> List.append children)
15+
16+
let span ?(a="") ?(b="") ?(children=[]) () =
17+
L ([S a; S b] |> List.append children)
18+
19+
let _ =
20+
let a = "foo" in
21+
let b = "bar" in
22+
let txt = React.string "baz" in
23+
let name = "bba" in
24+
[%bsx "
25+
<>
26+
<Foo a="a" b="b">
27+
<div a="{|数据|}">
28+
<span a>"txt"</span>
29+
<span>"{j|你好世界|j}"</span>
30+
<span>"{|你好世界|}"</span>
31+
<span>Hello World</span>
32+
</div>
33+
<span>"(React.string @@ "Hello, " ^ name)"</span>
34+
</Foo>
35+
<span />
36+
</>
37+
"]

0 commit comments

Comments
 (0)
This repository has been archived.