├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── jsx_macro ├── Cargo.toml └── src │ ├── lib.rs │ └── parsers │ ├── many_custom.rs │ ├── match_dom_element.rs │ ├── match_group.rs │ ├── match_string.rs │ ├── mod.rs │ ├── types.rs │ └── util.rs ├── jsx_macro_tests ├── Cargo.toml ├── failing-tests │ ├── extra-chars.rs │ ├── extra-chars2.rs │ ├── interpolated-string.rs │ └── non-matching-tag.rs └── src │ └── lib.rs └── jsx_types ├── Cargo.toml └── src ├── bare.rs ├── diff.rs ├── events.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | target -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "aho-corasick" 3 | version = "0.6.8" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "atty" 11 | version = "0.2.11" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | dependencies = [ 14 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 15 | "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 16 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 17 | ] 18 | 19 | [[package]] 20 | name = "backtrace" 21 | version = "0.3.9" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | dependencies = [ 24 | "backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", 25 | "cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 26 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 27 | "rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 28 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 29 | ] 30 | 31 | [[package]] 32 | name = "backtrace-sys" 33 | version = "0.1.24" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | dependencies = [ 36 | "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", 37 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 38 | ] 39 | 40 | [[package]] 41 | name = "cc" 42 | version = "1.0.25" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | 45 | [[package]] 46 | name = "cfg-if" 47 | version = "0.1.4" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | 50 | [[package]] 51 | name = "compiletest_rs" 52 | version = "0.3.11" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | dependencies = [ 55 | "diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 56 | "filetime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 57 | "getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", 58 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 59 | "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 60 | "miow 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 61 | "serde 1.0.69 (registry+https://github.com/rust-lang/crates.io-index)", 62 | "serde_derive 1.0.69 (registry+https://github.com/rust-lang/crates.io-index)", 63 | "serde_json 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", 64 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 65 | ] 66 | 67 | [[package]] 68 | name = "diff" 69 | version = "0.1.11" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | 72 | [[package]] 73 | name = "dtoa" 74 | version = "0.4.3" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | 77 | [[package]] 78 | name = "env_logger" 79 | version = "0.5.13" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | dependencies = [ 82 | "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 83 | "humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 85 | "regex 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 86 | "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 87 | ] 88 | 89 | [[package]] 90 | name = "failure" 91 | version = "0.1.2" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | dependencies = [ 94 | "backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 95 | "failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 96 | ] 97 | 98 | [[package]] 99 | name = "failure_derive" 100 | version = "0.1.2" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | dependencies = [ 103 | "proc-macro2 0.4.18 (registry+https://github.com/rust-lang/crates.io-index)", 104 | "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 105 | "syn 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)", 106 | "synstructure 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 107 | ] 108 | 109 | [[package]] 110 | name = "filetime" 111 | version = "0.2.1" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | dependencies = [ 114 | "cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 115 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 116 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 117 | ] 118 | 119 | [[package]] 120 | name = "getopts" 121 | version = "0.2.18" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | dependencies = [ 124 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 125 | ] 126 | 127 | [[package]] 128 | name = "heck" 129 | version = "0.3.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | dependencies = [ 132 | "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 133 | ] 134 | 135 | [[package]] 136 | name = "humantime" 137 | version = "1.1.1" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | dependencies = [ 140 | "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 141 | ] 142 | 143 | [[package]] 144 | name = "itoa" 145 | version = "0.4.2" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | 148 | [[package]] 149 | name = "js-sys" 150 | version = "0.3.1" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | dependencies = [ 153 | "wasm-bindgen 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", 154 | ] 155 | 156 | [[package]] 157 | name = "jsx_macro" 158 | version = "0.1.0" 159 | dependencies = [ 160 | "jsx_types 0.1.0", 161 | "nom 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 162 | "proc-macro2 0.4.18", 163 | "quote 0.6.8", 164 | ] 165 | 166 | [[package]] 167 | name = "jsx_macro_tests" 168 | version = "0.1.0" 169 | dependencies = [ 170 | "compiletest_rs 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", 171 | "jsx_macro 0.1.0", 172 | "jsx_types 0.1.0", 173 | ] 174 | 175 | [[package]] 176 | name = "jsx_types" 177 | version = "0.1.0" 178 | dependencies = [ 179 | "serde 1.0.69 (registry+https://github.com/rust-lang/crates.io-index)", 180 | "serde_derive 1.0.69 (registry+https://github.com/rust-lang/crates.io-index)", 181 | "serde_json 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", 182 | "take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 183 | "wasm-bindgen 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", 184 | "web-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 185 | ] 186 | 187 | [[package]] 188 | name = "lazy_static" 189 | version = "1.1.0" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | dependencies = [ 192 | "version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 193 | ] 194 | 195 | [[package]] 196 | name = "libc" 197 | version = "0.2.42" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | 200 | [[package]] 201 | name = "log" 202 | version = "0.4.3" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | dependencies = [ 205 | "cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 206 | ] 207 | 208 | [[package]] 209 | name = "memchr" 210 | version = "2.0.1" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | dependencies = [ 213 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 214 | ] 215 | 216 | [[package]] 217 | name = "miow" 218 | version = "0.3.1" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | dependencies = [ 221 | "socket2 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 222 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 223 | ] 224 | 225 | [[package]] 226 | name = "nom" 227 | version = "4.0.0" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | dependencies = [ 230 | "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 231 | ] 232 | 233 | [[package]] 234 | name = "proc-macro2" 235 | version = "0.4.18" 236 | dependencies = [ 237 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 238 | ] 239 | 240 | [[package]] 241 | name = "proc-macro2" 242 | version = "0.4.18" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | dependencies = [ 245 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 246 | ] 247 | 248 | [[package]] 249 | name = "quick-error" 250 | version = "1.2.2" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | 253 | [[package]] 254 | name = "quote" 255 | version = "0.6.8" 256 | dependencies = [ 257 | "proc-macro2 0.4.18", 258 | ] 259 | 260 | [[package]] 261 | name = "quote" 262 | version = "0.6.8" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | dependencies = [ 265 | "proc-macro2 0.4.18 (registry+https://github.com/rust-lang/crates.io-index)", 266 | ] 267 | 268 | [[package]] 269 | name = "redox_syscall" 270 | version = "0.1.40" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | 273 | [[package]] 274 | name = "redox_termios" 275 | version = "0.1.1" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | dependencies = [ 278 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 279 | ] 280 | 281 | [[package]] 282 | name = "regex" 283 | version = "1.0.3" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | dependencies = [ 286 | "aho-corasick 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 287 | "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 288 | "regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 289 | "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 290 | "utf8-ranges 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 291 | ] 292 | 293 | [[package]] 294 | name = "regex-syntax" 295 | version = "0.6.2" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | dependencies = [ 298 | "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 299 | ] 300 | 301 | [[package]] 302 | name = "rustc-demangle" 303 | version = "0.1.9" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | 306 | [[package]] 307 | name = "serde" 308 | version = "1.0.69" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | 311 | [[package]] 312 | name = "serde_derive" 313 | version = "1.0.69" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | dependencies = [ 316 | "proc-macro2 0.4.18 (registry+https://github.com/rust-lang/crates.io-index)", 317 | "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 318 | "syn 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)", 319 | ] 320 | 321 | [[package]] 322 | name = "serde_json" 323 | version = "1.0.22" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | dependencies = [ 326 | "dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 327 | "itoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 328 | "serde 1.0.69 (registry+https://github.com/rust-lang/crates.io-index)", 329 | ] 330 | 331 | [[package]] 332 | name = "socket2" 333 | version = "0.3.7" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | dependencies = [ 336 | "cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 337 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 338 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 339 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 340 | ] 341 | 342 | [[package]] 343 | name = "sourcefile" 344 | version = "0.1.4" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | 347 | [[package]] 348 | name = "syn" 349 | version = "0.14.4" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | dependencies = [ 352 | "proc-macro2 0.4.18 (registry+https://github.com/rust-lang/crates.io-index)", 353 | "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 354 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 355 | ] 356 | 357 | [[package]] 358 | name = "syn" 359 | version = "0.15.1" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | dependencies = [ 362 | "proc-macro2 0.4.18 (registry+https://github.com/rust-lang/crates.io-index)", 363 | "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 364 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 365 | ] 366 | 367 | [[package]] 368 | name = "synstructure" 369 | version = "0.9.0" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | dependencies = [ 372 | "proc-macro2 0.4.18 (registry+https://github.com/rust-lang/crates.io-index)", 373 | "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 374 | "syn 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)", 375 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 376 | ] 377 | 378 | [[package]] 379 | name = "take_mut" 380 | version = "0.2.2" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | 383 | [[package]] 384 | name = "termcolor" 385 | version = "1.0.4" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | dependencies = [ 388 | "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 389 | ] 390 | 391 | [[package]] 392 | name = "termion" 393 | version = "1.5.1" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | dependencies = [ 396 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 397 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 398 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 399 | ] 400 | 401 | [[package]] 402 | name = "thread_local" 403 | version = "0.3.6" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | dependencies = [ 406 | "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 407 | ] 408 | 409 | [[package]] 410 | name = "ucd-util" 411 | version = "0.1.1" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | 414 | [[package]] 415 | name = "unicode-segmentation" 416 | version = "1.2.1" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | 419 | [[package]] 420 | name = "unicode-width" 421 | version = "0.1.5" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | 424 | [[package]] 425 | name = "unicode-xid" 426 | version = "0.1.0" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | 429 | [[package]] 430 | name = "utf8-ranges" 431 | version = "1.0.1" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | 434 | [[package]] 435 | name = "version_check" 436 | version = "0.1.4" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | 439 | [[package]] 440 | name = "wasm-bindgen" 441 | version = "0.2.24" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | dependencies = [ 444 | "wasm-bindgen-macro 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", 445 | ] 446 | 447 | [[package]] 448 | name = "wasm-bindgen-backend" 449 | version = "0.2.24" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | dependencies = [ 452 | "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 453 | "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 454 | "proc-macro2 0.4.18 (registry+https://github.com/rust-lang/crates.io-index)", 455 | "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 456 | "serde_json 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", 457 | "syn 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", 458 | "wasm-bindgen-shared 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", 459 | ] 460 | 461 | [[package]] 462 | name = "wasm-bindgen-macro" 463 | version = "0.2.24" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | dependencies = [ 466 | "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 467 | "wasm-bindgen-macro-support 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", 468 | ] 469 | 470 | [[package]] 471 | name = "wasm-bindgen-macro-support" 472 | version = "0.2.24" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | dependencies = [ 475 | "proc-macro2 0.4.18 (registry+https://github.com/rust-lang/crates.io-index)", 476 | "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 477 | "syn 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", 478 | "wasm-bindgen-backend 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", 479 | "wasm-bindgen-shared 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", 480 | ] 481 | 482 | [[package]] 483 | name = "wasm-bindgen-shared" 484 | version = "0.2.24" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | dependencies = [ 487 | "serde 1.0.69 (registry+https://github.com/rust-lang/crates.io-index)", 488 | "serde_derive 1.0.69 (registry+https://github.com/rust-lang/crates.io-index)", 489 | ] 490 | 491 | [[package]] 492 | name = "wasm-bindgen-webidl" 493 | version = "0.2.18" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | dependencies = [ 496 | "failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 497 | "failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 498 | "heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 499 | "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 500 | "proc-macro2 0.4.18 (registry+https://github.com/rust-lang/crates.io-index)", 501 | "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", 502 | "syn 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", 503 | "wasm-bindgen-backend 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", 504 | "weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 505 | ] 506 | 507 | [[package]] 508 | name = "web-sys" 509 | version = "0.3.1" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | dependencies = [ 512 | "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", 513 | "failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 514 | "js-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 515 | "sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 516 | "wasm-bindgen 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", 517 | "wasm-bindgen-webidl 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", 518 | ] 519 | 520 | [[package]] 521 | name = "weedle" 522 | version = "0.8.0" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | dependencies = [ 525 | "nom 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 526 | ] 527 | 528 | [[package]] 529 | name = "winapi" 530 | version = "0.3.5" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | dependencies = [ 533 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 534 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 535 | ] 536 | 537 | [[package]] 538 | name = "winapi-i686-pc-windows-gnu" 539 | version = "0.4.0" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | 542 | [[package]] 543 | name = "winapi-util" 544 | version = "0.1.1" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | dependencies = [ 547 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 548 | ] 549 | 550 | [[package]] 551 | name = "winapi-x86_64-pc-windows-gnu" 552 | version = "0.4.0" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | 555 | [[package]] 556 | name = "wincolor" 557 | version = "1.0.1" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | dependencies = [ 560 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 561 | "winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 562 | ] 563 | 564 | [metadata] 565 | "checksum aho-corasick 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "68f56c7353e5a9547cbd76ed90f7bb5ffc3ba09d4ea9bd1d8c06c8b1142eeb5a" 566 | "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" 567 | "checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a" 568 | "checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0" 569 | "checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" 570 | "checksum cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efe5c877e17a9c717a0bf3613b2709f723202c4e4675cc8f12926ded29bcb17e" 571 | "checksum compiletest_rs 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "04cea0fe8b8aaca8143af607ad69076866c9f08b83c4b7faca0e993e5486831b" 572 | "checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a" 573 | "checksum dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d301140eb411af13d3115f9a562c85cc6b541ade9dfa314132244aaee7489dd" 574 | "checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38" 575 | "checksum failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7efb22686e4a466b1ec1a15c2898f91fa9cb340452496dca654032de20ff95b9" 576 | "checksum failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "946d0e98a50d9831f5d589038d2ca7f8f455b1c21028c0db0e84116a12696426" 577 | "checksum filetime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "da4b9849e77b13195302c174324b5ba73eec9b236b24c221a61000daefb95c5f" 578 | "checksum getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797" 579 | "checksum heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea04fa3ead4e05e51a7c806fc07271fdbde4e246a6c6d1efd52e72230b771b82" 580 | "checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e" 581 | "checksum itoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5adb58558dcd1d786b5f0bd15f3226ee23486e24b7b58304b60f64dc68e62606" 582 | "checksum js-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dbd301ea79322dac569ad37566c93588edaf6adc09e130af89a5248a570e972b" 583 | "checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" 584 | "checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1" 585 | "checksum log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "61bd98ae7f7b754bc53dca7d44b604f733c6bba044ea6f41bc8d89272d8161d2" 586 | "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" 587 | "checksum miow 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9224c91f82b3c47cf53dcf78dfaa20d6888fbcc5d272d5f2fcdf8a697f3c987d" 588 | "checksum nom 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "898696750eb5c3ce5eb5afbfbe46e7f7c4e1936e19d3e97be4b7937da7b6d114" 589 | "checksum proc-macro2 0.4.18 (registry+https://github.com/rust-lang/crates.io-index)" = "afa4d377067cc02eb5e0b491d3f7cfbe145ad4da778535bfb13c444413dd35b9" 590 | "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" 591 | "checksum quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dd636425967c33af890042c483632d33fa7a18f19ad1d7ea72e8998c6ef8dea5" 592 | "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" 593 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 594 | "checksum regex 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3d8c9f33201f46669484bacc312b00e7541bed6aaf296dffe2bb4e0ac6b8ce2a" 595 | "checksum regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "747ba3b235651f6e2f67dfa8bcdcd073ddb7c243cb21c442fc12395dfcac212d" 596 | "checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" 597 | "checksum serde 1.0.69 (registry+https://github.com/rust-lang/crates.io-index)" = "210e5a3b159c566d7527e9b22e44be73f2e0fcc330bb78fef4dbccb56d2e74c8" 598 | "checksum serde_derive 1.0.69 (registry+https://github.com/rust-lang/crates.io-index)" = "dd724d68017ae3a7e63600ee4b2fdb3cad2158ffd1821d44aff4580f63e2b593" 599 | "checksum serde_json 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)" = "84b8035cabe9b35878adec8ac5fe03d5f6bc97ff6edd7ccb96b44c1276ba390e" 600 | "checksum socket2 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "962a516af4d3a7c272cb3a1d50a8cc4e5b41802e4ad54cfb7bee8ba61d37d703" 601 | "checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" 602 | "checksum syn 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2beff8ebc3658f07512a413866875adddd20f4fd47b2a4e6c9da65cd281baaea" 603 | "checksum syn 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "85fb2f7f9b7a4c8df2c913a852de570efdb40f0d2edd39c8245ad573f5c7fbcc" 604 | "checksum synstructure 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb9b7550d063ea184027c9b8c20ac167cd36d3e06b3a40bceb9d746dc1a7b7" 605 | "checksum take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" 606 | "checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" 607 | "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" 608 | "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" 609 | "checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d" 610 | "checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" 611 | "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" 612 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 613 | "checksum utf8-ranges 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd70f467df6810094968e2fce0ee1bd0e87157aceb026a8c083bcf5e25b9efe4" 614 | "checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051" 615 | "checksum wasm-bindgen 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)" = "992ee9aa1502b4143fa07287b55653e046ecc7722172b31a254d33f6b91b1b71" 616 | "checksum wasm-bindgen-backend 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)" = "310eb3f8ffc7703da939a7969d25991302ea10b5a9374dfc07cf88771f5c8e1a" 617 | "checksum wasm-bindgen-macro 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)" = "e080ee1bb1e217f0dcb7b184e2abfce9bccbe59d28c26e0138a6d081a0bee042" 618 | "checksum wasm-bindgen-macro-support 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)" = "e234cae6d3c0cbf8cceb42e768c064b23d9dfa0c3ee886c41fcaf15441de0ac7" 619 | "checksum wasm-bindgen-shared 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)" = "3fb964cac1f7d5c8e92ee28336ef453cae403cb8e7249fff8b3b202bbe818ae2" 620 | "checksum wasm-bindgen-webidl 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "24ae8fa66e9c50471836affcd2f195f2d55b02299264e24e359a81d16562fd2f" 621 | "checksum web-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "148f6b9f27ffcbbbbb4c63dc8c790127e32fd07bd90382ca3ce8a30fb1ce9935" 622 | "checksum weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "26a4c67f132386d965390b8a734d5d10adbcd30eb5cc74bd9229af8b83f10044" 623 | "checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd" 624 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 625 | "checksum winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "afc5508759c5bf4285e61feb862b6083c8480aec864fa17a81fdec6f69b461ab" 626 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 627 | "checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" 628 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "jsx_macro", 5 | "jsx_types", 6 | "jsx_macro_tests", 7 | # "proc-macro2", 8 | # "quote", 9 | ] 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `jsx_compiler` 2 | 3 | > A procedural macro for compiling jsx into a `DomElement` type 4 | 5 | ## Example 6 | 7 | ```rs 8 | #![feature(proc_macro, proc_macro_non_items)] 9 | 10 | extern crate jsx_types; 11 | extern crate jsx_macro; 12 | 13 | let on_click: Box = Box::new(|_| println!("foo!")); 14 | let subtitle = "This is a subtitle"; 15 | let dom = jsx!(
16 |

This title is clickable

17 |
{ subtitle }
18 |
); 19 | ``` 20 | 21 | ## Crate Organization 22 | 23 | ### jsx_macro 24 | 25 | * This is the main crate, which exports the jsx! and jsx_verbose! macros. 26 | 27 | ### jsx_types 28 | 29 | * This macro exports all of the used types: `HtmlToken`, `DomElement`, `EventName`, `Event`, `EventHandler`, `Attributes` and `EventHandlers`. 30 | 31 | ### jsx_macro_tests 32 | 33 | * Tests the `jsx_macro` crate. 34 | 35 | ## TODO 36 | 37 | * Add RustDoc docs 38 | * `DomElement.event_handlers` is not done correctly. For example, an `OnClick` handlers and an `OnMouseOver` handlers should receive different events. Thus, `DomElement` should have separate, optional fields, e.g.: 39 | 40 | ``` 41 | pub type DomElement { 42 | // ... other fields 43 | on_click: Option ()>>, 44 | on_mouseover: Option ()>>, 45 | } 46 | ``` 47 | 48 | * There should be also be a builder-pattern constructor for `DomElement`. 49 | * Integrate with a wasm full-stack app. 50 | * Other methods: `to_inner_html` and the like. 51 | * Spacing on multiline idents and such 52 | 53 | ## Presentation 54 | 55 | Follow along with the [presentation](https://docs.google.com/presentation/d/11KK06J-p-Q2XLg1VW7GK02rSCn3z-pvfKf59WMxNirA/edit?usp=sharing) and with the [video](https://youtu.be/sorD8vpKHHU). The code that is current at the time of the presentation was tag v7, but you should have no trouble following along with master (as of July 8th). 56 | -------------------------------------------------------------------------------- /jsx_macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsx_macro" 3 | version = "0.1.0" 4 | authors = ["Robert "] 5 | 6 | [dependencies] 7 | jsx_types = { path = "../jsx_types" } 8 | proc-macro2 = { path = "../proc-macro2" } 9 | quote = { path = "../quote" } 10 | # quote = "0.6.8" 11 | # proc-macro2 = { version = "0.4.6", features = ["nightly"] } 12 | nom = "4.0.0" 13 | 14 | [lib] 15 | proc-macro = true 16 | -------------------------------------------------------------------------------- /jsx_macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro_span, proc_macro_raw_ident)] 2 | 3 | extern crate jsx_types; 4 | extern crate proc_macro; 5 | extern crate proc_macro2; 6 | #[macro_use] 7 | extern crate nom; 8 | #[macro_use] 9 | extern crate quote; 10 | 11 | mod parsers; 12 | 13 | #[proc_macro] 14 | pub fn jsx(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 15 | let input_2: proc_macro2::TokenStream = input.into(); 16 | let vec_of_trees: Vec = input_2.into_iter().collect(); 17 | 18 | let parsed = parsers::match_jsx(&vec_of_trees); 19 | let unwrapped = parsed.unwrap(); 20 | let remaining = unwrapped.0; 21 | 22 | if remaining.len() > 0 { 23 | panic!("the jsx! macro had left over characters. Make sure you only pass one html node."); 24 | } 25 | 26 | unwrapped.1.into() 27 | } 28 | 29 | #[proc_macro] 30 | pub fn jsx_verbose(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 31 | println!("\nInput = {:?}\n", input); 32 | let input_2: proc_macro2::TokenStream = input.into(); 33 | let vec_of_trees: Vec = input_2.into_iter().collect(); 34 | 35 | let parsed = parsers::match_jsx(&vec_of_trees); 36 | println!("Output = {:?}\n", parsed); 37 | let unwrapped = parsed.unwrap(); 38 | println!("Output2 = {}\n", unwrapped.1); 39 | let remaining = unwrapped.0; 40 | println!("remaining = {:?}\n", remaining); 41 | 42 | if remaining.len() > 0 { 43 | panic!("the jsx! macro had left over characters. Make sure you only pass one html node."); 44 | } 45 | 46 | unwrapped.1.into() 47 | } 48 | -------------------------------------------------------------------------------- /jsx_macro/src/parsers/many_custom.rs: -------------------------------------------------------------------------------- 1 | macro_rules! many_0_custom( 2 | ($i:expr, $submac:ident!( $($args:tt)* )) => ( 3 | { 4 | use ::nom::lib::std::result::Result::*; 5 | // use ::nom::{Err,AtEof}; 6 | use ::nom::Err; 7 | 8 | // ret is Ok|Err 9 | let ret; 10 | let mut vec_of_responses = ::nom::lib::std::vec::Vec::new(); 11 | let mut input = $i.clone(); 12 | 13 | loop { 14 | let input_ = input.clone(); 15 | match $submac!(input_, $($args)*) { 16 | Ok((i, o)) => { 17 | // i is remaining 18 | // o is matched 19 | 20 | // N.B. I don't know if this is actually solves the infinite loops... 21 | 22 | // loop trip must always consume (otherwise infinite loops) 23 | if i.len() == 0 || i.len() == input.len() { 24 | vec_of_responses.push(o); 25 | ret = Ok((input, vec_of_responses)); 26 | break; 27 | } 28 | // if i == input { 29 | // if i.at_eof() { 30 | // ret = Ok((input, res)); 31 | // } else { 32 | // ret = Err(Err::Error(error_position!(input, ::nom::ErrorKind::Many0))); 33 | // } 34 | // break; 35 | // } 36 | vec_of_responses.push(o); 37 | 38 | input = i; 39 | }, 40 | Err(Err::Error(_)) => { 41 | ret = Ok((input, vec_of_responses)); 42 | break; 43 | }, 44 | Err(e) => { 45 | ret = Err(e); 46 | break; 47 | }, 48 | } 49 | } 50 | 51 | ret 52 | } 53 | ); 54 | ($i:expr, $f:expr) => ( 55 | many_0_custom!($i, call!($f)); 56 | ); 57 | ); 58 | 59 | /// `many1!(I -> IResult) => I -> IResult>` 60 | /// Applies the parser 1 or more times and returns the list of results in a Vec 61 | /// 62 | /// the embedded parser may return Incomplete 63 | /// 64 | /// ``` 65 | /// # #[macro_use] extern crate nom; 66 | /// # use nom::Err; 67 | /// # use nom::ErrorKind; 68 | /// # fn main() { 69 | /// named!(multi<&[u8], Vec<&[u8]> >, many1!( tag!( "abcd" ) ) ); 70 | /// 71 | /// let a = b"abcdabcdefgh"; 72 | /// let b = b"azerty"; 73 | /// 74 | /// let res = vec![&b"abcd"[..], &b"abcd"[..]]; 75 | /// assert_eq!(multi(&a[..]),Ok((&b"efgh"[..], res))); 76 | /// assert_eq!(multi(&b[..]), Err(Err::Error(error_position!(&b[..], ErrorKind::Many1)))); 77 | /// # } 78 | /// ``` 79 | macro_rules! many_1_custom( 80 | ($i:expr, $submac:ident!( $($args:tt)* )) => ( 81 | { 82 | use ::nom::lib::std::result::Result::*; 83 | use ::nom::Err; 84 | 85 | use ::nom::InputLength; 86 | let i_ = $i.clone(); 87 | match $submac!(i_, $($args)*) { 88 | Err(Err::Error(_)) => Err(Err::Error( 89 | error_position!(i_, ::nom::ErrorKind::Many1) 90 | )), 91 | Err(Err::Failure(_)) => Err(Err::Failure( 92 | error_position!(i_, ::nom::ErrorKind::Many1) 93 | )), 94 | Err(i) => Err(i), 95 | Ok((i1,o1)) => { 96 | let mut res = ::nom::lib::std::vec::Vec::with_capacity(4); 97 | res.push(o1); 98 | let mut input = i1; 99 | let mut error = ::nom::lib::std::option::Option::None; 100 | loop { 101 | let input_ = input.clone(); 102 | match $submac!(input_, $($args)*) { 103 | Err(Err::Error(_)) => { 104 | break; 105 | }, 106 | Err(e) => { 107 | error = ::nom::lib::std::option::Option::Some(e); 108 | break; 109 | }, 110 | Ok((i, o)) => { 111 | if i.input_len() == input.input_len() { 112 | break; 113 | } 114 | res.push(o); 115 | input = i; 116 | } 117 | } 118 | } 119 | 120 | match error { 121 | ::nom::lib::std::option::Option::Some(e) => Err(e), 122 | ::nom::lib::std::option::Option::None => Ok((input, res)) 123 | } 124 | } 125 | } 126 | } 127 | ); 128 | ($i:expr, $f:expr) => ( 129 | many_1_custom!($i, call!($f)); 130 | ); 131 | ); 132 | -------------------------------------------------------------------------------- /jsx_macro/src/parsers/match_dom_element.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Spacing, Delimiter, TokenStream}; 2 | use super::types::*; 3 | use super::util::{match_punct, match_ident, match_group, match_literal}; 4 | use super::match_string::match_string; 5 | use super::match_group::match_bracketed_group_to_tokens; 6 | 7 | type AttributeOrEventHandler = (String, Option); 8 | 9 | macro_rules! match_event { 10 | ($key:ident, $val:ident, $attr_opt:ident, $event_opt:ident, $handler_name:ident, $handler_name_string:expr) => { 11 | if $key == $handler_name_string { 12 | return ($attr_opt, Some(quote!{ 13 | #$event_opt 14 | event_handlers.$handler_name = Some(#$val); 15 | })); 16 | } 17 | }; 18 | } 19 | 20 | fn generate_dom_element_tokens( 21 | node_type: String, 22 | attributes_or_event_handlers: Vec, 23 | children: Vec 24 | ) -> TokenStream { 25 | let (attribute_assignment, event_handler_assignment) = attributes_or_event_handlers 26 | .into_iter() 27 | .fold( 28 | (None, None), 29 | |(attr_opt, event_opt): (Option, Option), (key, val)| { 30 | // --Clipboard 31 | match_event!(key, val, attr_opt, event_opt, on_copy, "on_copy"); 32 | match_event!(key, val, attr_opt, event_opt, on_cut, "on_cut"); 33 | match_event!(key, val, attr_opt, event_opt, on_paste, "on_paste"); 34 | // --Composition 35 | // onCompositionEnd 36 | // onCompositionStart 37 | // onCompositionUpdate 38 | // --Keyboard 39 | match_event!(key, val, attr_opt, event_opt, on_key_down, "on_key_down"); 40 | match_event!(key, val, attr_opt, event_opt, on_key_press, "on_key_press"); 41 | match_event!(key, val, attr_opt, event_opt, on_key_up, "on_key_up"); 42 | // --Focus 43 | match_event!(key, val, attr_opt, event_opt, on_focus, "on_focus"); 44 | match_event!(key, val, attr_opt, event_opt, on_blur, "on_blur"); 45 | // --Form 46 | match_event!(key, val, attr_opt, event_opt, on_change, "on_change"); 47 | match_event!(key, val, attr_opt, event_opt, on_input, "on_input"); 48 | match_event!(key, val, attr_opt, event_opt, on_invalid, "on_invalid"); 49 | match_event!(key, val, attr_opt, event_opt, on_submit, "on_submit"); 50 | // --Mouse 51 | match_event!(key, val, attr_opt, event_opt, on_click, "on_click"); 52 | match_event!(key, val, attr_opt, event_opt, on_context_menu, "on_context_menu"); 53 | match_event!(key, val, attr_opt, event_opt, on_dbl_click, "on_dbl_click"); 54 | 55 | match_event!(key, val, attr_opt, event_opt, on_drag, "on_drag"); 56 | match_event!(key, val, attr_opt, event_opt, on_drag_end, "on_drag_end"); 57 | match_event!(key, val, attr_opt, event_opt, on_drag_enter, "on_drag_enter"); 58 | match_event!(key, val, attr_opt, event_opt, on_drag_exit, "on_drag_exit"); 59 | match_event!(key, val, attr_opt, event_opt, on_drag_leave, "on_drag_leave"); 60 | match_event!(key, val, attr_opt, event_opt, on_drag_over, "on_drag_over"); 61 | match_event!(key, val, attr_opt, event_opt, on_drag_start, "on_drag_start"); 62 | match_event!(key, val, attr_opt, event_opt, on_drop, "on_drop"); 63 | 64 | match_event!(key, val, attr_opt, event_opt, on_mouse_down, "on_mouse_down"); 65 | match_event!(key, val, attr_opt, event_opt, on_mouse_enter, "on_mouse_enter"); 66 | match_event!(key, val, attr_opt, event_opt, on_mouse_leave, "on_mouse_leave"); 67 | match_event!(key, val, attr_opt, event_opt, on_mouse_move, "on_mouse_move"); 68 | match_event!(key, val, attr_opt, event_opt, on_mouse_over, "on_mouse_over"); 69 | match_event!(key, val, attr_opt, event_opt, on_mouse_out, "on_mouse_out"); 70 | match_event!(key, val, attr_opt, event_opt, on_mouse_up, "on_mouse_up"); 71 | // --Pointer 72 | match_event!(key, val, attr_opt, event_opt, on_pointer_down, "on_pointer_down"); 73 | match_event!(key, val, attr_opt, event_opt, on_pointer_move, "on_pointer_move"); 74 | match_event!(key, val, attr_opt, event_opt, on_pointer_up, "on_pointer_up"); 75 | match_event!(key, val, attr_opt, event_opt, on_pointer_cancel, "on_pointer_cancel"); 76 | match_event!(key, val, attr_opt, event_opt, on_got_pointer_capture, "on_got_pointer_capture"); 77 | match_event!(key, val, attr_opt, event_opt, on_lost_pointer_capture, "on_lost_pointer_capture"); 78 | match_event!(key, val, attr_opt, event_opt, on_pointer_enter, "on_pointer_enter"); 79 | match_event!(key, val, attr_opt, event_opt, on_pointer_leave, "on_pointer_leave"); 80 | match_event!(key, val, attr_opt, event_opt, on_pointer_over, "on_pointer_over"); 81 | match_event!(key, val, attr_opt, event_opt, on_pointer_out, "on_pointer_out"); 82 | // --Selection 83 | match_event!(key, val, attr_opt, event_opt, on_select, "on_select"); 84 | // --Touch 85 | match_event!(key, val, attr_opt, event_opt, on_touch_cancel, "on_touch_cancel"); 86 | match_event!(key, val, attr_opt, event_opt, on_touch_end, "on_touch_end"); 87 | match_event!(key, val, attr_opt, event_opt, on_touch_move, "on_touch_move"); 88 | match_event!(key, val, attr_opt, event_opt, on_touch_start, "on_touch_start"); 89 | // --Scroll 90 | match_event!(key, val, attr_opt, event_opt, on_scroll, "on_scroll"); 91 | // --Wheel 92 | // onWheel 93 | // --Media 94 | // onAbort 95 | // onCanPlay 96 | // onCanPlayThrough 97 | // onDurationChange 98 | // onEmptied 99 | // onEncrypted 100 | // onEnded 101 | // onError 102 | // onLoadedData 103 | // onLoadedMetadata 104 | // onLoadStart 105 | // onPause 106 | // onPlay 107 | // onPlaying 108 | // onProgress 109 | // onRateChange 110 | // onSeeked 111 | // onSeeking 112 | // onStalled 113 | // onSuspend 114 | // onTimeUpdate 115 | // onVolumeChange 116 | // onWaiting 117 | // --Image 118 | match_event!(key, val, attr_opt, event_opt, on_load, "on_load"); 119 | match_event!(key, val, attr_opt, event_opt, on_error, "on_error"); 120 | // --Animation 121 | match_event!(key, val, attr_opt, event_opt, on_animation_start, "on_animation_start"); 122 | match_event!(key, val, attr_opt, event_opt, on_animation_end, "on_animation_end"); 123 | match_event!(key, val, attr_opt, event_opt, on_animation_iteration, "on_animation_iteration"); 124 | // --Transition 125 | match_event!(key, val, attr_opt, event_opt, on_transition_end, "on_transition_end"); 126 | // --Other 127 | match_event!(key, val, attr_opt, event_opt, on_toggle, "on_toggle"); 128 | 129 | // We did not match an event; thus, it must be a regular attribute. 130 | // The value must implement Into 131 | 132 | // N.B. #val expands to an empty string (or no tokens) if val == None 133 | if val.is_some() { 134 | ( 135 | Some(quote!{ 136 | #attr_opt 137 | attr_map.insert(#key.into(), Some(#val.into())); 138 | }), 139 | event_opt 140 | ) 141 | } else { 142 | ( 143 | Some(quote!{ 144 | #attr_opt 145 | attr_map.insert(#key.into(), None); 146 | }), 147 | event_opt 148 | ) 149 | } 150 | } 151 | ); 152 | 153 | let attribute_assignment = attribute_assignment 154 | .map(|token_stream| { 155 | quote!{{ 156 | let mut attr_map = ::std::collections::HashMap::new(); 157 | #token_stream 158 | attr_map 159 | }} 160 | }) 161 | .unwrap_or_else(|| quote!{ ::std::collections::HashMap::new() }); 162 | 163 | let event_handler_assignment = event_handler_assignment 164 | .map(|token_stream| { 165 | quote!{{ 166 | let mut event_handlers = ::jsx_types::events::EventHandlers::default(); 167 | #token_stream 168 | event_handlers 169 | }} 170 | }) 171 | .unwrap_or_else(|| quote!{ ::jsx_types::events::EventHandlers::default() }); 172 | 173 | let children_vec = quote!{{ 174 | let vec: Vec<::jsx_types::WrappedVector<::jsx_types::HtmlToken>> = vec![#(#children.into()),*]; 175 | vec.into_iter().flat_map(|o| o.0).collect() 176 | }}; 177 | 178 | // N.B. jsx_types is in scope from mod.rs 179 | (quote!{{ 180 | jsx_types::HtmlToken::DomElement( 181 | jsx_types::DomElement { 182 | node_type: #node_type.into(), 183 | attributes: #attribute_assignment, 184 | children: #children_vec, 185 | event_handlers: #event_handler_assignment, 186 | } 187 | ) 188 | }}).into() 189 | } 190 | 191 | named!( 192 | match_attribute , 193 | alt!( 194 | map!( 195 | tuple!( 196 | apply!(match_ident, None, false), 197 | apply!(match_punct, Some('='), None, vec![]), 198 | alt!( 199 | map!( 200 | apply!(match_group, Some(Delimiter::Brace)), 201 | |group| group.into() 202 | ) 203 | | map!( 204 | call!(match_literal), 205 | |literal| literal.into() 206 | ) 207 | ) 208 | ), 209 | |(attr_name, _, lit_or_group)| (attr_name, Some(lit_or_group)) 210 | ) 211 | | map!( 212 | apply!(match_ident, None, false), 213 | |s| (s, None) 214 | ) 215 | ) 216 | ); 217 | 218 | // TODO match_self_closing_tag and match_opening_tag are very similar. 219 | named!( 220 | match_self_closing_tag , 221 | map!( 222 | delimited!( 223 | apply!(match_punct, Some('<'), Some(Spacing::Alone), vec![]), 224 | tuple!( 225 | apply!(match_ident, None, false), 226 | many_0_custom!(match_attribute) 227 | ), 228 | tuple!( 229 | apply!(match_punct, Some('/'), Some(Spacing::Joint), vec![]), 230 | apply!(match_punct, Some('>'), None, vec![]) 231 | ) 232 | ), 233 | |s| { 234 | generate_dom_element_tokens(s.0, s.1, vec![]) 235 | } 236 | ) 237 | ); 238 | 239 | named!( 240 | match_opening_tag )>, 241 | delimited!( 242 | apply!(match_punct, Some('<'), Some(Spacing::Alone), vec![]), 243 | tuple!( 244 | apply!(match_ident, None, false), 245 | many_0_custom!(match_attribute) 246 | ), 247 | apply!(match_punct, Some('>'), None, vec![]) 248 | ) 249 | ); 250 | 251 | named!( 252 | match_closing_tag , 253 | delimited!( 254 | tuple!( 255 | apply!(match_punct, Some('<'), Some(Spacing::Joint), vec![]), 256 | apply!(match_punct, Some('/'), None, vec![]) 257 | ), 258 | apply!(match_ident, None, false), 259 | apply!(match_punct, Some('>'), None, vec![]) 260 | ) 261 | ); 262 | 263 | named!( 264 | match_tag , 265 | map!( 266 | tuple!( 267 | match_opening_tag, 268 | many_0_custom!(match_html_token), 269 | match_closing_tag 270 | ), 271 | |s| { 272 | // TODO can we assert this in a way that feels native to nom? 273 | let opening_tag = s.0; 274 | assert_eq!(opening_tag.0, s.2); 275 | let children = s.1; 276 | generate_dom_element_tokens(opening_tag.0, opening_tag.1, children) 277 | } 278 | ) 279 | ); 280 | 281 | named!( 282 | pub match_dom_element , 283 | alt!( 284 | match_self_closing_tag 285 | | match_tag 286 | ) 287 | ); 288 | 289 | named!( 290 | pub match_html_token , 291 | alt!( 292 | match_dom_element 293 | | match_string 294 | | match_bracketed_group_to_tokens 295 | ) 296 | ); 297 | -------------------------------------------------------------------------------- /jsx_macro/src/parsers/match_group.rs: -------------------------------------------------------------------------------- 1 | use super::types::*; 2 | use proc_macro2::Delimiter; 3 | use super::util::match_group; 4 | 5 | named!( 6 | pub match_bracketed_group_to_tokens , 7 | map!( 8 | apply!(match_group, Some(Delimiter::Brace)), 9 | |group| group.stream() 10 | ) 11 | ); 12 | -------------------------------------------------------------------------------- /jsx_macro/src/parsers/match_string.rs: -------------------------------------------------------------------------------- 1 | use super::types::*; 2 | use proc_macro2::Literal; 3 | use super::util::{match_punct, match_ident, match_literal_as_string}; 4 | 5 | named!( 6 | pub match_string , 7 | map!( 8 | many_1_custom!( 9 | alt!( 10 | apply!(match_ident, None, true) 11 | | map!( 12 | apply!(match_punct, None, None, vec!['<']), 13 | |c| c.to_string() 14 | ) 15 | | call!(match_literal_as_string) 16 | ) 17 | ), 18 | |vec_of_strings| { 19 | let concatenated_str: String = vec_of_strings.join(""); 20 | let lit = Literal::string(&concatenated_str); 21 | quote!{{ 22 | extern crate jsx_types; 23 | jsx_types::HtmlToken::Text(#lit.into()) 24 | }} 25 | } 26 | ) 27 | ); 28 | -------------------------------------------------------------------------------- /jsx_macro/src/parsers/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod many_custom; 3 | mod types; 4 | mod match_group; 5 | mod match_dom_element; 6 | mod match_string; 7 | mod util; 8 | 9 | use self::match_dom_element::match_html_token; 10 | use self::types::*; 11 | 12 | named!( 13 | pub match_jsx , 14 | // N.B. this complete! macro does not do what I expect it to, 15 | // so I manually check in the calling code. 16 | map!( 17 | complete!(match_html_token), 18 | |token_stream| { 19 | quote!({ 20 | // N.B. explicitly cast the returned value as a HtmlToken 21 | // because otherwise jsx!({ foo }) is valid and can be of any type. 22 | extern crate jsx_types; 23 | let mut casted: jsx_types::HtmlToken = { #token_stream.into() }; 24 | casted.merge_string_tokens(); 25 | casted 26 | }) 27 | } 28 | ) 29 | ); 30 | -------------------------------------------------------------------------------- /jsx_macro/src/parsers/types.rs: -------------------------------------------------------------------------------- 1 | use nom::IResult; 2 | pub use proc_macro2::{TokenTree, TokenStream, Literal, Group}; 3 | use quote::ToTokens; 4 | 5 | pub type TokenTreeSlice<'a> = &'a [TokenTree]; 6 | pub type JsxIResult<'a, T> = IResult, T>; 7 | 8 | #[derive(Clone, Debug)] 9 | pub enum LiteralOrGroup { 10 | Literal(Literal), 11 | Group(Group), 12 | } 13 | 14 | impl From for LiteralOrGroup { 15 | fn from(literal: Literal) -> Self { 16 | LiteralOrGroup::Literal(literal) 17 | } 18 | } 19 | 20 | impl From for LiteralOrGroup { 21 | fn from(group: Group) -> Self { 22 | LiteralOrGroup::Group(group) 23 | } 24 | } 25 | 26 | impl ToTokens for LiteralOrGroup { 27 | fn to_tokens(&self, tokens: &mut TokenStream) { 28 | match self { 29 | LiteralOrGroup::Literal(literal) => literal.to_tokens(tokens), 30 | LiteralOrGroup::Group(ref group) => { 31 | group.to_tokens(tokens) 32 | }, 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /jsx_macro/src/parsers/util.rs: -------------------------------------------------------------------------------- 1 | use super::types::*; 2 | use proc_macro2::{Spacing, Delimiter, Group, Literal}; 3 | use nom; 4 | use std::iter; 5 | 6 | pub type StringResult<'a> = JsxIResult<'a, String>; 7 | 8 | pub fn match_punct( 9 | input: TokenTreeSlice, 10 | c_opt: Option, 11 | spacing_opt: Option, 12 | excluded_chars: Vec 13 | ) -> StringResult { 14 | let get_err = || Err(nom::Err::Error(error_position!(input, nom::ErrorKind::Custom(42)))); 15 | let filler_spaces = get_filler_spaces(input, true); 16 | 17 | match input.split_first() { 18 | Some((first, rest)) => { 19 | match first { 20 | TokenTree::Punct(ref punct) => { 21 | 22 | let wrong_char = c_opt.map(|c| punct.as_char() != c).unwrap_or(false); 23 | let wrong_spacing = spacing_opt.map(|spacing| punct.spacing() != spacing).unwrap_or(false); 24 | let contains_excluded_char = excluded_chars.contains(&punct.as_char()); 25 | 26 | if wrong_char || wrong_spacing || contains_excluded_char { 27 | get_err() 28 | } else { 29 | Ok((rest, format!("{}{}", punct.as_char(), filler_spaces))) 30 | } 31 | }, 32 | _ => get_err(), 33 | } 34 | }, 35 | None => get_err(), 36 | } 37 | } 38 | 39 | 40 | pub fn match_ident(input: TokenTreeSlice, sym_opt: Option, include_filler: bool) -> StringResult { 41 | let get_err = || Err(nom::Err::Error(error_position!(input, nom::ErrorKind::Custom(42)))); 42 | 43 | let filler_spaces = get_filler_spaces(input, include_filler); 44 | match input.split_first() { 45 | Some((first, rest)) => { 46 | match first { 47 | TokenTree::Ident(ref ident) => { 48 | let get_success = || Ok((rest, format!("{}{}", ident, filler_spaces))); 49 | match sym_opt { 50 | Some(s) => { 51 | if s == format!("{}", ident) { 52 | get_success() 53 | } else { 54 | get_err() 55 | } 56 | }, 57 | None => get_success() 58 | } 59 | }, 60 | _ => get_err(), 61 | } 62 | }, 63 | None => get_err(), 64 | } 65 | } 66 | 67 | pub type GroupResult<'a> = JsxIResult<'a, Group>; 68 | 69 | pub fn match_group(input: TokenTreeSlice, delimiter_opt: Option) -> GroupResult { 70 | let get_err = || Err(nom::Err::Error(error_position!(input, nom::ErrorKind::Custom(42)))); 71 | 72 | match input.split_first() { 73 | Some((first, rest)) => { 74 | match first { 75 | TokenTree::Group(ref group) => { 76 | let get_success = || Ok((rest, group.clone())); 77 | match delimiter_opt { 78 | Some(delimiter) => { 79 | if group.delimiter() == delimiter { 80 | get_success() 81 | } else { 82 | get_err() 83 | } 84 | }, 85 | None => get_success() 86 | } 87 | }, 88 | _ => get_err(), 89 | } 90 | }, 91 | None => get_err(), 92 | } 93 | } 94 | 95 | pub fn match_literal(input: TokenTreeSlice) -> JsxIResult { 96 | let get_err = || Err(nom::Err::Error(error_position!(input, nom::ErrorKind::Custom(42)))); 97 | 98 | match input.split_first() { 99 | Some((first, rest)) => { 100 | match first { 101 | TokenTree::Literal(literal) => Ok(( 102 | rest, 103 | literal.clone(), 104 | )), 105 | _ => get_err(), 106 | } 107 | }, 108 | None => get_err(), 109 | } 110 | } 111 | 112 | pub fn match_literal_as_string(input: TokenTreeSlice) -> JsxIResult { 113 | let filler_spaces = get_filler_spaces(input, true); 114 | 115 | match_literal(input) 116 | .map(|(rest, lit)| (rest, format!("{}{}", lit.to_string(), filler_spaces))) 117 | } 118 | 119 | pub fn get_filler_spaces(input: TokenTreeSlice, do_it: bool) -> String { 120 | // LOL but seriously, would rather have this logic here than strewn about 121 | if !do_it { 122 | return "".into(); 123 | } 124 | 125 | 126 | 127 | let first_opt = input.get(0).map(|i| { 128 | // let foo: () = i.span(); 129 | i.span().end() 130 | }); 131 | let second_opt = input.get(1).map(|i| i.span().start()); 132 | match (first_opt, second_opt) { 133 | (Some(first), Some(second)) => { 134 | if first.line != second.line { 135 | "".into() 136 | } else { 137 | iter::repeat(" ").take(second.column - first.column).collect::() 138 | } 139 | }, 140 | _ => "".into() 141 | } 142 | } -------------------------------------------------------------------------------- /jsx_macro_tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsx_macro_tests" 3 | version = "0.1.0" 4 | authors = ["Robert "] 5 | 6 | [dependencies] 7 | jsx_macro = { path = "../jsx_macro" } 8 | jsx_types = { path = "../jsx_types" } 9 | compiletest_rs = "*" 10 | -------------------------------------------------------------------------------- /jsx_macro_tests/failing-tests/extra-chars.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro_non_items)] 2 | 3 | #[macro_use] 4 | extern crate jsx_macro; 5 | use jsx_macro::jsx; 6 | 7 | fn main() { 8 | let _ = jsx!(
fofo); //~ ERROR proc macro panicked 9 | } 10 | -------------------------------------------------------------------------------- /jsx_macro_tests/failing-tests/extra-chars2.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro_non_items)] 2 | 3 | #[macro_use] 4 | extern crate jsx_macro; 5 | use jsx_macro::jsx; 6 | 7 | fn main() { 8 | let _ = jsx!(
); //~ ERROR proc macro panicked 9 | } 10 | -------------------------------------------------------------------------------- /jsx_macro_tests/failing-tests/interpolated-string.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro_non_items)] 2 | 3 | #[macro_use] 4 | extern crate jsx_macro; 5 | use jsx_macro::jsx; 6 | 7 | fn main() { 8 | let bar = "bar"; 9 | let _ = jsx!(foo {bar}); //~ ERROR proc macro panicked 10 | } 11 | -------------------------------------------------------------------------------- /jsx_macro_tests/failing-tests/non-matching-tag.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro_non_items)] 2 | 3 | #[macro_use] 4 | extern crate jsx_macro; 5 | use jsx_macro::jsx; 6 | 7 | fn main() { 8 | let _ = jsx!(
); //~ ERROR proc macro panicked 9 | } 10 | -------------------------------------------------------------------------------- /jsx_macro_tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro_non_items)] 2 | 3 | extern crate compiletest_rs as compiletest; 4 | 5 | extern crate jsx_macro; 6 | extern crate jsx_types; 7 | 8 | #[allow(unused_imports)] 9 | use jsx_macro::{jsx, jsx_verbose}; 10 | 11 | use jsx_types::{*, events::*}; 12 | use std::collections::{HashMap, HashSet}; 13 | use std::collections::hash_map::Keys; 14 | use std::path::PathBuf; 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use super::*; 19 | 20 | fn get_bare_div<'a>() -> HtmlToken<'a> { 21 | HtmlToken::DomElement(DomElement { 22 | node_type: "div".into(), 23 | children: vec![], 24 | attributes: HashMap::new(), 25 | event_handlers: EventHandlers::new(), 26 | }) 27 | } 28 | 29 | #[derive(PartialEq, Debug, Clone)] 30 | enum ComparableHtmlToken { 31 | Text(String), 32 | DomElement(ComparableDomElement), 33 | } 34 | 35 | #[derive(Debug, PartialEq, Clone)] 36 | struct ComparableDomElement { 37 | pub node_type: String, 38 | pub children: Vec, 39 | pub attributes: jsx_types::Attributes, 40 | } 41 | 42 | impl ComparableDomElement { 43 | fn from_dom_element(d: &jsx_types::DomElement) -> Self { 44 | ComparableDomElement { 45 | node_type: d.node_type.clone(), 46 | children: d.children.iter().map(|c| { 47 | match c { 48 | jsx_types::HtmlToken::Text(t) => ComparableHtmlToken::Text(t.to_string()), 49 | jsx_types::HtmlToken::DomElement(d) => ComparableHtmlToken::DomElement( 50 | ComparableDomElement::from_dom_element(&d) 51 | ), 52 | } 53 | }).collect(), 54 | attributes: d.attributes.clone(), 55 | } 56 | } 57 | } 58 | 59 | // type KeyType<'a> = Keys<'a, jsx_types::events::EventName, Box>>; 60 | // fn compare_event_handler_keys(k1: KeyType, k2: KeyType) -> bool { 61 | // let l1 = k1.len(); 62 | // let s1 = k1.fold(HashSet::with_capacity(l1), |mut set, key| { 63 | // set.insert(key); 64 | // set 65 | // }); 66 | // let l2 = k2.len(); 67 | // let s2 = k2.fold(HashSet::with_capacity(l2), |mut set, key| { 68 | // set.insert(key); 69 | // set 70 | // }); 71 | // s1 == s2 72 | // } 73 | 74 | fn equal_enough(t1: &HtmlToken, t2: &HtmlToken) -> bool { 75 | let print_error = || println!("equal_enough returned false.\nleft={:?}\nright={:?}", t1, t2); 76 | match (&t1, &t2) { 77 | (HtmlToken::Text(s1), HtmlToken::Text(s2)) => s1 == s2, 78 | (HtmlToken::DomElement(d1), HtmlToken::DomElement(d2)) => { 79 | // N.B. this doesn't check children's event handlers... 80 | 81 | // let event_handlers_equal = compare_event_handler_keys(d1.event_handlers.keys(), d2.event_handlers.keys()); 82 | // true 83 | 84 | // if !event_handlers_equal { 85 | // print_error(); 86 | // return false; 87 | // } 88 | 89 | let w1: ComparableDomElement = ComparableDomElement::from_dom_element(&d1); 90 | let w2: ComparableDomElement = ComparableDomElement::from_dom_element(&d2); 91 | 92 | let result = w1 == w2; 93 | 94 | if !result { print_error(); } 95 | result 96 | }, 97 | _ => { print_error(); false }, 98 | } 99 | } 100 | 101 | #[test] 102 | fn basic_test() { 103 | let dom = jsx!(
); 104 | assert!(equal_enough(&dom, &get_bare_div())); 105 | } 106 | 107 | #[test] 108 | fn attribute_test() { 109 | let qux = "qux"; 110 | let dom = jsx!(
); 111 | assert!(equal_enough(&dom, &HtmlToken::DomElement(DomElement { 112 | node_type: "div".into(), 113 | children: vec![], 114 | attributes: { 115 | let mut map = HashMap::new(); 116 | map.insert("foo".into(), "bar".into()); 117 | map.insert("baz".into(), qux.into()); 118 | map 119 | }, 120 | event_handlers: EventHandlers::new(), 121 | }))); 122 | } 123 | 124 | #[test] 125 | fn child_component_dom_element_test() { 126 | let dom = jsx!(

); 127 | assert!(equal_enough(&dom, &HtmlToken::DomElement(DomElement { 128 | node_type: "div".into(), 129 | children: vec![ 130 | HtmlToken::DomElement(DomElement { 131 | node_type: "h1".into(), 132 | children: vec![], 133 | attributes: HashMap::new(), 134 | event_handlers: EventHandlers::new(), 135 | }), 136 | ], 137 | attributes: HashMap::new(), 138 | event_handlers: EventHandlers::new(), 139 | }))); 140 | } 141 | 142 | #[test] 143 | fn child_component_string_test() { 144 | let dom = jsx!(
foo bar
); 145 | assert!(equal_enough(&dom, &HtmlToken::DomElement(DomElement { 146 | node_type: "div".into(), 147 | children: vec![ 148 | HtmlToken::Text("foo bar".into()), 149 | ], 150 | attributes: HashMap::new(), 151 | event_handlers: EventHandlers::new(), 152 | }))); 153 | } 154 | 155 | #[test] 156 | fn child_component_interpolated_test() { 157 | let inner_dom = jsx!(); 158 | let inner_dom_copy = jsx!(); 159 | let dom = jsx!(
{ inner_dom }
); 160 | assert!(equal_enough(&dom, &HtmlToken::DomElement(DomElement { 161 | node_type: "div".into(), 162 | children: vec![ 163 | inner_dom_copy, 164 | ], 165 | attributes: HashMap::new(), 166 | event_handlers: EventHandlers::new(), 167 | }))); 168 | } 169 | 170 | #[test] 171 | fn child_component_interpolated_string_test() { 172 | // N.B. we are cloning because foo is moved in the jsx! call. 173 | // Is this necessary? 174 | let foo: String = "foo".into(); 175 | let foo2 = foo.clone(); 176 | let dom = jsx!(
{foo}
); 177 | assert!(equal_enough(&dom, &HtmlToken::DomElement(DomElement { 178 | node_type: "div".into(), 179 | children: vec![ 180 | HtmlToken::Text(foo2.into()), 181 | ], 182 | attributes: HashMap::new(), 183 | event_handlers: EventHandlers::new(), 184 | }))); 185 | } 186 | 187 | #[test] 188 | fn child_component_interpolated_str_test() { 189 | // N.B. we are cloning because foo is moved in the jsx! call. 190 | // Is this necessary? 191 | let foo: &str = "foo"; 192 | let foo2 = foo.clone(); 193 | let dom = jsx!(
{foo}
); 194 | assert!(equal_enough(&dom, &HtmlToken::DomElement(DomElement { 195 | node_type: "div".into(), 196 | children: vec![ 197 | HtmlToken::Text(foo2.into()), 198 | ], 199 | attributes: HashMap::new(), 200 | event_handlers: EventHandlers::new(), 201 | }))); 202 | } 203 | 204 | #[test] 205 | fn non_self_closing_component() { 206 | let dom = jsx!(
); 207 | assert!(equal_enough(&dom, &get_bare_div())); 208 | } 209 | 210 | #[test] 211 | fn strings_are_valid_jsx() { 212 | let dom = jsx!(foo); 213 | assert!(equal_enough(&dom, &HtmlToken::Text("foo".into()))); 214 | } 215 | 216 | #[test] 217 | fn multiple_strings_are_valid_jsx() { 218 | let dom = jsx!(foo bar); 219 | assert!(equal_enough(&dom, &HtmlToken::Text("foo bar".into()))); 220 | } 221 | 222 | #[test] 223 | fn many_spaces_are_valid_jsx() { 224 | let dom = jsx!(foo bar baz qux); 225 | assert!(equal_enough(&dom, &HtmlToken::Text("foo bar baz qux".into()))); 226 | } 227 | 228 | #[test] 229 | fn multi_line_spaces_work_correctly() { 230 | // N.B. this is weird behavior, and should be changed, but for now, 231 | // let's document it. 232 | let dom = jsx!(
foo 233 | bar 234 |
); 235 | assert!(equal_enough(&dom, &HtmlToken::DomElement(DomElement { 236 | node_type: "div".into(), 237 | children: vec![HtmlToken::Text("foobar".into())], 238 | attributes: HashMap::new(), 239 | event_handlers: EventHandlers::new(), 240 | }))); 241 | } 242 | 243 | #[test] 244 | fn multiple_strings_are_valid_jsx_2() { 245 | let dom = jsx!(foo bar "baz" 'q' ux); 246 | // N.B. we include the quotes, which is ... correct? 247 | // TODO characters should probably not have single quotes around it. 248 | // because that's how we'd include parentheses, backslash, <, etc. 249 | assert!(equal_enough(&dom, &HtmlToken::Text("foo bar \"baz\" \'q\' ux".into()))); 250 | } 251 | 252 | #[test] 253 | fn random_characters_allowed_in_strings() { 254 | // N.B. obviously < is not allowed 255 | // Also, neither is a backslash, apparently. 256 | let dom = jsx!(+ / * ^ #@&); 257 | assert!(equal_enough(&dom, &HtmlToken::Text("+ / * ^ #@&".into()))); 258 | } 259 | 260 | #[test] 261 | fn interpolated_strings_by_themselves_are_valid_jsx() { 262 | let bar = "bar"; 263 | let dom = jsx!({ bar }); 264 | assert!(equal_enough(&dom, &HtmlToken::Text(bar.into()))); 265 | } 266 | 267 | // #[test] 268 | // fn event_handlers_work() { 269 | // let on_click = get_event_handler(); 270 | // let on_click2 = get_event_handler(); 271 | // let dom = jsx!(
); 272 | // assert!(equal_enough(&dom, &HtmlToken::DomElement(DomElement { 273 | // node_type: "div".into(), 274 | // children: vec![], 275 | // attributes: HashMap::new(), 276 | // event_handlers: EventHandlers::new(), 277 | // }))); 278 | // } 279 | 280 | // #[test] 281 | // fn event_handlers_are_more_complicated() { 282 | // let on_click = get_event_handler(); 283 | // let on_click2 = get_event_handler(); 284 | // let on_mouse_over = get_event_handler(); 285 | // let on_mouse_over2 = get_event_handler(); 286 | // let on_mouse_out = get_event_handler(); 287 | // let on_mouse_out2 = get_event_handler(); 288 | // let dom = jsx!(
289 | //

290 | //

); 291 | 292 | // assert!(equal_enough( 293 | // &dom, 294 | // &HtmlToken::DomElement(DomElement { 295 | // node_type: "div".into(), 296 | // children: vec![ 297 | // HtmlToken::DomElement(DomElement { 298 | // node_type: "h1".into(), 299 | // children: vec![], 300 | // attributes: HashMap::new(), 301 | // event_handlers: EventHandlers::new(), 302 | // }) 303 | // ], 304 | // attributes: HashMap::new(), 305 | // event_handlers: EventHandlers::new(), 306 | // }) 307 | // )) 308 | // } 309 | 310 | #[test] 311 | fn failing_test() { 312 | let mut config = compiletest::Config::default(); 313 | config.mode = "compile-fail".parse().unwrap(); 314 | config.src_base = PathBuf::from("failing-tests"); 315 | config.link_deps(); 316 | config.clean_rmeta(); 317 | compiletest::run_tests(&config); 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /jsx_types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsx_types" 3 | version = "0.1.0" 4 | authors = ["Robert "] 5 | 6 | [dependencies] 7 | # enum_derive = "0.1.7" 8 | # custom_derive = "0.1.7" 9 | wasm-bindgen = { version = "0.2.24", features = ["nightly"] } 10 | serde = "1.0.62" 11 | serde_json = "1.0.18" 12 | serde_derive = "1.0.62" 13 | take_mut = "0.2.2" 14 | web-sys = { version = "0.3.1", features = [ 15 | "Document", 16 | "DocumentFragment", 17 | "Element", 18 | "Window", 19 | "HtmlElement", 20 | "HtmlTemplateElement", 21 | "Event", 22 | "UiEvent", 23 | "ClipboardEvent", 24 | "KeyboardEvent", 25 | "InputEvent", 26 | "FocusEvent", 27 | "MouseEvent", 28 | "PointerEvent", 29 | "TouchEvent", 30 | "ScrollAreaEvent", 31 | "AnimationEvent", 32 | "TransitionEvent", 33 | "EventTarget", 34 | "Node", 35 | "NodeList", 36 | "console" 37 | ] } 38 | -------------------------------------------------------------------------------- /jsx_types/src/bare.rs: -------------------------------------------------------------------------------- 1 | use super::{Attributes, AsInnerHtml}; 2 | use super::diff::{ 3 | Diff, 4 | DiffItem, 5 | DiffOperation, 6 | Path, 7 | ReplaceOperation, 8 | InsertOperation, 9 | DeleteOperation, 10 | UpdateAttributesOperation, 11 | }; 12 | 13 | #[derive(Clone)] 14 | pub struct BareDomElement { 15 | pub node_type: String, 16 | pub children: Vec, 17 | pub attributes: Attributes, 18 | } 19 | 20 | #[derive(Clone, Debug)] 21 | pub enum BareHtmlToken { 22 | Text(String), 23 | DomElement(BareDomElement), 24 | } 25 | 26 | impl BareHtmlToken { 27 | /** 28 | * Diffing algorithm 29 | * 30 | * - If it's a string, compare strings 31 | * - If the node_type and attributes are the same, keep it, and: 32 | * - for each existing child 33 | * - If it has the same node_type 34 | * - keep it, and repeat 35 | * - If it has a different node_type or attributes, add it 36 | * - for each additional new child, add it 37 | * 38 | * - Thus

to

39 | * should see that div is the same, see that h1 is the same, 40 | * see that "h2" !== "h3", and create a diff operation for 41 | * that. 42 | * 43 | * - In all cases, "other" is the old rendered stuff that we 44 | * want to get rid of efficiently. 45 | */ 46 | pub fn get_diff_with(&self, other: &Self) -> Diff { 47 | // N.B. diff has to be the reverse order of what we would naturally generate. 48 | // I'm not sure if this is the best place to call reverse, but it works. 49 | // 50 | // The reasoning is that if the diff is: 51 | // path - operation 52 | // [0] - delete or insert 53 | // [1] - any diff operation 54 | // 55 | // if we delete/insert the 0th item, [1] will refer to the wrong item (or no 56 | // item.) But in reverse order, it *should* always work. (Does it?) 57 | let mut diff = self.get_path_diff_with(other, vec![0]); 58 | diff.reverse(); 59 | diff 60 | } 61 | 62 | fn get_path_diff_with(&self, other: &Self, path: Path) -> Diff { 63 | match (self, other) { 64 | (BareHtmlToken::Text(self_text), BareHtmlToken::Text(other_text)) => { 65 | BareHtmlToken::get_diff_from_strings(self_text, other_text, path) 66 | .into_iter() 67 | .collect::() 68 | }, 69 | (BareHtmlToken::DomElement(self_dom), BareHtmlToken::DomElement(other_dom)) => { 70 | BareHtmlToken::get_diff_from_dom_elements(self_dom, other_dom, path) 71 | }, 72 | _ => { 73 | vec![self.get_replace_self_diff_item(path)] 74 | }, 75 | } 76 | } 77 | 78 | fn get_diff_from_strings(self_str: &str, other_str: &str, path: Path) -> Option { 79 | if self_str == other_str { 80 | None 81 | } else { 82 | Some(get_replace_diff_item(self_str.to_string(), path)) 83 | } 84 | } 85 | 86 | fn get_diff_from_dom_elements( 87 | self_dom: &BareDomElement, 88 | other_dom: &BareDomElement, 89 | path: Path 90 | ) -> Diff { 91 | if self_dom.node_type != other_dom.node_type { 92 | vec![get_replace_diff_item(self_dom.as_inner_html(), path)] 93 | } else { 94 | let mut diff: Diff = vec![]; 95 | if self_dom.attributes != other_dom.attributes { 96 | diff.push( 97 | get_update_attributes_diff_item(self_dom.attributes.clone(), path.clone()) 98 | ); 99 | } 100 | let self_children = &self_dom.children; 101 | let other_children = &other_dom.children; 102 | diff.append(&mut self_children.iter() 103 | .zip(0..(self_children.len())) 104 | .flat_map(|(&ref self_html_token, i)| { 105 | let mut new_path = path.clone(); 106 | new_path.push(i); 107 | 108 | match other_children.get(i) { 109 | Some(other_html_token) => { 110 | self_html_token.get_path_diff_with(other_html_token, new_path) 111 | }, 112 | None => { 113 | vec![self_html_token.get_insert_self_diff_item(new_path)] 114 | } 115 | } 116 | }) 117 | .chain( 118 | (self_children.len()..other_children.len()) 119 | .map(|i| { 120 | let mut new_path = path.clone(); 121 | new_path.push(i); 122 | get_delete_diff_item(new_path) 123 | }) 124 | ) 125 | .collect::()); 126 | diff 127 | } 128 | } 129 | 130 | // N.B. a little weird that this is defined here, and not also on BareDomElement 131 | fn get_replace_self_diff_item(&self, path: Path) -> DiffItem { 132 | get_replace_diff_item(self.as_inner_html(), path) 133 | } 134 | 135 | fn get_insert_self_diff_item(&self, path: Path) -> DiffItem { 136 | get_insert_diff_item(self.as_inner_html(), path) 137 | } 138 | 139 | } 140 | 141 | fn get_replace_diff_item(new_inner_html: String, path: Path) -> DiffItem { 142 | ( 143 | path, 144 | DiffOperation::Replace( 145 | ReplaceOperation { 146 | new_inner_html, 147 | } 148 | ) 149 | ) 150 | } 151 | 152 | fn get_insert_diff_item(new_inner_html: String, path: Path) -> DiffItem { 153 | ( 154 | path, 155 | DiffOperation::Insert( 156 | InsertOperation { 157 | new_inner_html, 158 | } 159 | ) 160 | ) 161 | } 162 | 163 | fn get_delete_diff_item(path: Path) -> DiffItem { 164 | ( 165 | path, 166 | DiffOperation::Delete(DeleteOperation {}) 167 | ) 168 | } 169 | 170 | fn get_update_attributes_diff_item(new_attributes: Attributes, path: Path) -> DiffItem { 171 | ( 172 | path, 173 | DiffOperation::UpdateAttributes(UpdateAttributesOperation { 174 | new_attributes, 175 | }) 176 | ) 177 | } 178 | 179 | impl AsInnerHtml for BareHtmlToken { 180 | fn as_inner_html(&self) -> String { 181 | match &self { 182 | BareHtmlToken::Text(s) => s.to_string(), 183 | BareHtmlToken::DomElement(d) => d.as_inner_html(), 184 | } 185 | } 186 | } 187 | 188 | impl AsInnerHtml for BareDomElement { 189 | fn as_inner_html(&self) -> String { 190 | let attr_str: String = self.attributes 191 | .iter() 192 | .map(|(key, val)| 193 | val.clone().map(|v| format!("{}=\"{}\"", key, v)) 194 | .unwrap_or_else(|| key.to_string()) 195 | ) 196 | .collect::>() 197 | .join(" "); 198 | 199 | match self.children.len() { 200 | 0 => { 201 | format!("<{} {} />", self.node_type, attr_str) 202 | }, 203 | _ => { 204 | format!( 205 | "<{} {}>{}", 206 | self.node_type, 207 | attr_str, 208 | self.children.iter().map(|c| c.as_inner_html()).collect::>().join(""), 209 | self.node_type 210 | ) 211 | } 212 | } 213 | } 214 | } 215 | 216 | impl ::std::fmt::Debug for BareDomElement { 217 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 218 | write!( 219 | f, 220 | "DomElement {{ node_type: {}, children: {:?} }}", 221 | self.node_type, 222 | self.children 223 | ) 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /jsx_types/src/diff.rs: -------------------------------------------------------------------------------- 1 | use super::Attributes; 2 | 3 | type NewInnerHtml = String; 4 | 5 | pub type Path = Vec; 6 | 7 | #[derive(Serialize, Debug)] 8 | pub struct ReplaceOperation { 9 | pub new_inner_html: NewInnerHtml, 10 | } 11 | 12 | #[derive(Serialize, Debug)] 13 | pub struct InsertOperation { 14 | pub new_inner_html: NewInnerHtml, 15 | } 16 | 17 | #[derive(Serialize, Debug)] 18 | pub struct DeleteOperation {} 19 | 20 | #[derive(Serialize, Debug)] 21 | pub struct UpdateAttributesOperation { 22 | pub new_attributes: Attributes, 23 | } 24 | 25 | #[derive(Serialize, Debug)] 26 | pub enum DiffOperation { 27 | Replace(ReplaceOperation), 28 | Insert(InsertOperation), 29 | Delete(DeleteOperation), 30 | UpdateAttributes(UpdateAttributesOperation), 31 | } 32 | 33 | pub type DiffItem = (Path, DiffOperation); 34 | pub type Diff = Vec; 35 | -------------------------------------------------------------------------------- /jsx_types/src/events.rs: -------------------------------------------------------------------------------- 1 | pub type EventHandler<'a, T> = 'a + FnMut(&T) -> (); 2 | use web_sys::{ 3 | ClipboardEvent, 4 | KeyboardEvent, 5 | UiEvent, 6 | FocusEvent, 7 | MouseEvent, 8 | PointerEvent, 9 | TouchEvent, 10 | ScrollAreaEvent, 11 | InputEvent, 12 | AnimationEvent, 13 | TransitionEvent, 14 | }; 15 | 16 | pub type UiEventHandler<'a> = EventHandler<'a, UiEvent>; 17 | pub type ClipboardEventHandler<'a> = EventHandler<'a, ClipboardEvent>; 18 | pub type KeyboardEventHandler<'a> = EventHandler<'a, KeyboardEvent>; 19 | pub type InputEventHandler<'a> = EventHandler<'a, InputEvent>; 20 | pub type FocusEventHandler<'a> = EventHandler<'a, FocusEvent>; 21 | pub type MouseEventHandler<'a> = EventHandler<'a, MouseEvent>; 22 | pub type PointerEventHandler<'a> = EventHandler<'a, PointerEvent>; 23 | pub type TouchEventHandler<'a> = EventHandler<'a, TouchEvent>; 24 | pub type ScrollAreaEventHandler<'a> = EventHandler<'a, ScrollAreaEvent>; 25 | pub type AnimationEventHandler<'a> = EventHandler<'a, AnimationEvent>; 26 | pub type TransitionEventHandler<'a> = EventHandler<'a, TransitionEvent>; 27 | 28 | #[derive(Default)] 29 | pub struct EventHandlers<'a> { 30 | // --Clipboard 31 | pub on_copy: Option>>, 32 | pub on_cut: Option>>, 33 | pub on_paste: Option>>, 34 | // --Composition 35 | // onCompositionEnd 36 | // onCompositionStart 37 | // onCompositionUpdate 38 | // --Keyboard 39 | pub on_key_down: Option>>, 40 | pub on_key_press: Option>>, 41 | pub on_key_up: Option>>, 42 | // --Focus 43 | pub on_focus: Option>>, 44 | pub on_blur: Option>>, 45 | // --Form 46 | pub on_change: Option>>, 47 | pub on_input: Option>>, 48 | pub on_invalid: Option>>, 49 | pub on_submit: Option>>, 50 | // --Mouse 51 | pub on_click: Option>>, 52 | pub on_context_menu: Option>>, 53 | pub on_dbl_click: Option>>, 54 | 55 | pub on_drag: Option>>, 56 | pub on_drag_end: Option>>, 57 | pub on_drag_enter: Option>>, 58 | pub on_drag_exit: Option>>, 59 | pub on_drag_leave: Option>>, 60 | pub on_drag_over: Option>>, 61 | pub on_drag_start: Option>>, 62 | pub on_drop: Option>>, 63 | 64 | pub on_mouse_down: Option>>, 65 | pub on_mouse_enter: Option>>, 66 | pub on_mouse_leave: Option>>, 67 | pub on_mouse_move: Option>>, 68 | pub on_mouse_over: Option>>, 69 | pub on_mouse_out: Option>>, 70 | pub on_mouse_up: Option>>, 71 | // --Pointer 72 | pub on_pointer_down: Option>>, 73 | pub on_pointer_move: Option>>, 74 | pub on_pointer_up: Option>>, 75 | pub on_pointer_cancel: Option>>, 76 | pub on_got_pointer_capture: Option>>, 77 | pub on_lost_pointer_capture: Option>>, 78 | pub on_pointer_enter: Option>>, 79 | pub on_pointer_leave: Option>>, 80 | pub on_pointer_over: Option>>, 81 | pub on_pointer_out: Option>>, 82 | // --Selection 83 | pub on_select: Option>>, 84 | // --Touch 85 | pub on_touch_cancel: Option>>, 86 | pub on_touch_end: Option>>, 87 | pub on_touch_move: Option>>, 88 | pub on_touch_start: Option>>, 89 | pub on_scroll: Option>>, 90 | // --Wheel 91 | // onWheel 92 | // --Media 93 | // onAbort 94 | // onCanPlay 95 | // onCanPlayThrough 96 | // onDurationChange 97 | // onEmptied 98 | // onEncrypted 99 | // onEnded 100 | // onError 101 | // onLoadedData 102 | // onLoadedMetadata 103 | // onLoadStart 104 | // onPause 105 | // onPlay 106 | // onPlaying 107 | // onProgress 108 | // onRateChange 109 | // onSeeked 110 | // onSeeking 111 | // onStalled 112 | // onSuspend 113 | // onTimeUpdate 114 | // onVolumeChange 115 | // onWaiting 116 | // --Image 117 | pub on_load: Option>>, 118 | pub on_error: Option>>, 119 | // --Animation 120 | pub on_animation_start: Option>>, 121 | pub on_animation_end: Option>>, 122 | pub on_animation_iteration: Option>>, 123 | // --Transition 124 | pub on_transition_end: Option>>, 125 | // --Other 126 | pub on_toggle: Option>>, 127 | } 128 | -------------------------------------------------------------------------------- /jsx_types/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(nll)] 2 | 3 | extern crate wasm_bindgen; 4 | #[macro_use] 5 | extern crate serde_derive; 6 | extern crate take_mut; 7 | extern crate web_sys; 8 | 9 | use std::collections::HashMap; 10 | use std::convert::From; 11 | use std::fmt; 12 | pub mod events; 13 | pub mod bare; 14 | pub mod diff; 15 | 16 | use events::*; 17 | 18 | pub struct WrappedVector(pub Vec); 19 | 20 | #[derive(Debug)] 21 | pub enum HtmlToken<'a> { 22 | Text(String), 23 | DomElement(DomElement<'a>), 24 | } 25 | 26 | impl<'a> HtmlToken<'a> { 27 | pub fn as_bare_token(&self) -> bare::BareHtmlToken { 28 | match self { 29 | HtmlToken::Text(t) => bare::BareHtmlToken::Text(t.clone()), 30 | HtmlToken::DomElement(d) => bare::BareHtmlToken::DomElement(d.as_bare_dom_element()), 31 | } 32 | } 33 | 34 | pub fn merge_string_tokens(&mut self) { 35 | match self { 36 | HtmlToken::Text(_) => {}, 37 | HtmlToken::DomElement(ref mut d) => { 38 | for child in d.children.iter_mut() { 39 | child.merge_string_tokens() 40 | } 41 | take_mut::take(&mut d.children, |children| { 42 | consume_and_merge(children) 43 | }) 44 | } 45 | } 46 | } 47 | } 48 | 49 | fn consume_and_merge(children: Vec) -> Vec { 50 | children.into_iter() 51 | .fold(vec![], |mut accum, child| { 52 | match child { 53 | HtmlToken::DomElement(_) => { 54 | accum.push(child); 55 | accum 56 | }, 57 | HtmlToken::Text(ref current_text) => { 58 | let last_opt = accum.pop(); 59 | match last_opt { 60 | Some(HtmlToken::Text(ref last_text)) => { 61 | accum.push(HtmlToken::Text(format!("{} {}", last_text, current_text))); 62 | accum 63 | }, 64 | Some(x) => { 65 | accum.push(x); 66 | accum.push(child); 67 | accum 68 | }, 69 | None => { 70 | accum.push(child); 71 | accum 72 | } 73 | } 74 | }, 75 | } 76 | }) 77 | } 78 | 79 | impl<'a> AsInnerHtml for HtmlToken<'a> { 80 | fn as_inner_html(&self) -> String { 81 | match &self { 82 | HtmlToken::Text(s) => s.to_string(), 83 | HtmlToken::DomElement(d) => d.as_inner_html(), 84 | } 85 | } 86 | } 87 | 88 | pub struct DomElement<'a> { 89 | pub node_type: String, 90 | pub children: Vec>, 91 | pub attributes: Attributes, 92 | pub event_handlers: EventHandlers<'a>, 93 | } 94 | 95 | impl<'a> DomElement<'a> { 96 | pub fn as_bare_dom_element(&self) -> bare::BareDomElement { 97 | bare::BareDomElement { 98 | node_type: self.node_type.clone(), 99 | children: self.children.iter().map(|c| c.as_bare_token()).collect::>(), 100 | attributes: self.attributes.clone(), 101 | } 102 | } 103 | } 104 | 105 | impl<'a> AsInnerHtml for DomElement<'a> { 106 | fn as_inner_html(&self) -> String { 107 | let attr_str: String = self.attributes 108 | .iter() 109 | .map(|(key, val)| 110 | // TODO figure out why we're cloning here! 111 | val.clone().map(|v| format!("{}=\"{}\"", key, v)) 112 | .unwrap_or_else(|| key.to_string()) 113 | ) 114 | .collect::>() 115 | .join(" "); 116 | 117 | match self.children.len() { 118 | 0 => { 119 | format!("<{} {} />", self.node_type, attr_str) 120 | }, 121 | _ => { 122 | format!( 123 | "<{} {}>{}", 124 | self.node_type, 125 | attr_str, 126 | self.children.iter().map(|c| c.as_inner_html()).collect::>().join(""), 127 | self.node_type 128 | ) 129 | } 130 | } 131 | } 132 | } 133 | 134 | impl<'a> fmt::Debug for DomElement<'a> { 135 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 136 | write!( 137 | f, 138 | "DomElement {{ node_type: {}, children: {:?} }}", 139 | self.node_type, 140 | self.children 141 | ) 142 | } 143 | } 144 | 145 | pub type Attributes = HashMap>; 146 | 147 | impl<'a> From> for WrappedVector> { 148 | fn from(t: HtmlToken<'a>) -> Self { 149 | WrappedVector(vec![t]) 150 | } 151 | } 152 | 153 | impl<'a, T> From>> for WrappedVector> where T: Into> { 154 | fn from(opt: Option>) -> Self { 155 | let vec = opt.into_iter().flat_map(|v| v.into_iter().map(|item| item.into())).collect(); 156 | WrappedVector(vec) 157 | } 158 | } 159 | 160 | impl<'a, T> From> for HtmlToken<'a> where T: Into> { 161 | fn from(opt: Option) -> Self { 162 | match opt { 163 | Some(t) => t.into(), 164 | None => HtmlToken::Text("".to_string()), 165 | } 166 | } 167 | } 168 | 169 | impl<'a, T> From> for WrappedVector> where T: Into> { 170 | fn from(opt: Option) -> Self { 171 | WrappedVector(opt.into_iter().map(|t| t.into()).collect()) 172 | } 173 | } 174 | 175 | impl<'a> From for HtmlToken<'a> { 176 | fn from(s: String) -> Self { 177 | HtmlToken::Text(s) 178 | } 179 | } 180 | 181 | impl<'a> From for WrappedVector> { 182 | fn from(s: String) -> Self { 183 | WrappedVector(vec![s.into()]) 184 | } 185 | } 186 | 187 | impl<'a, T> From> for WrappedVector> where T: Into> { 188 | fn from(s: Vec) -> Self { 189 | let vec = s.into_iter().map(|item| item.into()).collect(); 190 | WrappedVector(vec) 191 | } 192 | } 193 | 194 | impl<'a, 'b> From<&'b str> for HtmlToken<'a> { 195 | fn from(s: &str) -> Self { 196 | HtmlToken::Text(s.to_string()) 197 | } 198 | } 199 | 200 | impl<'a, 'b> From<&'b str> for WrappedVector> { 201 | fn from(s: &str) -> Self { 202 | WrappedVector(vec![s.into()]) 203 | } 204 | } 205 | 206 | impl<'a> From for HtmlToken<'a> { 207 | fn from(i: i32) -> Self { 208 | HtmlToken::Text(i.to_string()) 209 | } 210 | } 211 | 212 | impl<'a> From for WrappedVector> { 213 | fn from(i: i32) -> Self { 214 | WrappedVector(vec![i.into()]) 215 | } 216 | } 217 | 218 | impl<'a> From for HtmlToken<'a> { 219 | fn from(u: u32) -> Self { 220 | HtmlToken::Text(u.to_string()) 221 | } 222 | } 223 | 224 | impl<'a> From for WrappedVector> { 225 | fn from(u: u32) -> Self { 226 | WrappedVector(vec![u.into()]) 227 | } 228 | } 229 | 230 | impl<'a> From for HtmlToken<'a> { 231 | fn from(u: usize) -> Self { 232 | HtmlToken::Text(u.to_string()) 233 | } 234 | } 235 | 236 | impl<'a> From for WrappedVector> { 237 | fn from(u: usize) -> Self { 238 | WrappedVector(vec![u.into()]) 239 | } 240 | } 241 | 242 | impl<'a> From for HtmlToken<'a> { 243 | fn from(c: char) -> Self { 244 | HtmlToken::Text(c.to_string()) 245 | } 246 | } 247 | 248 | impl<'a> From for WrappedVector> { 249 | fn from(c: char) -> Self { 250 | WrappedVector(vec![c.into()]) 251 | } 252 | } 253 | 254 | // TODO make a macro or implement more 255 | 256 | // TODO make a TopLevelComponent trait/type alias 257 | pub trait Component<'a, T> { 258 | fn render(&'a mut self, T) -> HtmlToken<'a>; 259 | } 260 | 261 | pub trait StatelessComponent<'a, T> { 262 | // N.B. maybe we should rename render to render_stateless 263 | fn render(T) -> HtmlToken<'a>; 264 | } 265 | 266 | pub trait AsInnerHtml { 267 | fn as_inner_html(&self) -> String; 268 | } --------------------------------------------------------------------------------