Initial work on the parser for the Hornbeam grammar
This commit is contained in:
commit
0e3b3ea96d
|
@ -0,0 +1 @@
|
||||||
|
/.idea
|
|
@ -0,0 +1,386 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console"
|
||||||
|
version = "0.15.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60"
|
||||||
|
dependencies = [
|
||||||
|
"encode_unicode",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"crypto-common",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encode_unicode"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hornbeam"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"hornbeam_grammar",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hornbeam_grammar"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"insta",
|
||||||
|
"lazy_static",
|
||||||
|
"pest",
|
||||||
|
"pest_consume",
|
||||||
|
"pest_derive",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hornbeam_interpreter"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hornbeam_ir"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hornbeam_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "insta"
|
||||||
|
version = "1.28.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fea5b3894afe466b4bcf0388630fc15e11938a6074af0cd637c825ba2ec8a099"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"lazy_static",
|
||||||
|
"linked-hash-map",
|
||||||
|
"serde",
|
||||||
|
"similar",
|
||||||
|
"yaml-rust",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.139"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linked-hash-map"
|
||||||
|
version = "0.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.17.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest"
|
||||||
|
version = "2.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror",
|
||||||
|
"ucd-trie",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_consume"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "79447402d15d18e7142e14c72f2e63fa3d155be1bc5b70b3ccbb610ac55f536b"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_consume_macros",
|
||||||
|
"pest_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_consume_macros"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d8630a7a899cb344ec1c16ba0a6b24240029af34bdc0a21f84e411d7f793f29"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_derive"
|
||||||
|
version = "2.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2ac3922aac69a40733080f53c1ce7f91dcf57e1a5f6c52f421fadec7fbdc4b69"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_generator",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_generator"
|
||||||
|
version = "2.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d06646e185566b5961b4058dd107e0a7f56e77c3f484549fb119867773c0f202"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_meta",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_meta"
|
||||||
|
version = "2.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6f60b2ba541577e2a0c307c8f39d1439108120eb7903adeb6497fa880c59616"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"pest",
|
||||||
|
"sha2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.51"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.152"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.152"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "similar"
|
||||||
|
version = "2.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ucd-trie"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.42.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yaml-rust"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||||
|
dependencies = [
|
||||||
|
"linked-hash-map",
|
||||||
|
]
|
|
@ -0,0 +1,17 @@
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"hornbeam_grammar",
|
||||||
|
"hornbeam_ir",
|
||||||
|
"hornbeam_interpreter",
|
||||||
|
"hornbeam_macros",
|
||||||
|
"hornbeam"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Enable optimisation for testing helpers
|
||||||
|
[profile.dev.package.insta]
|
||||||
|
opt-level = 3
|
||||||
|
|
||||||
|
[profile.dev.package.similar]
|
||||||
|
opt-level = 3
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Hornbeam Template Engine
|
||||||
|
|
||||||
|
WIP. Will be: a lightweight and nimble HTML templating engine, designed for component reuse and ease of use with HTMX, whilst not harming iterative development time.
|
||||||
|
|
||||||
|
## Crates
|
||||||
|
|
||||||
|
* `hornbeam` — usual point of entry to the engine, useful in applications.
|
||||||
|
* `hornbeam_grammar` — grammar definition for Hornbeam templates.
|
||||||
|
* `hornbeam_ir` — intermediate representation for Hornbeam templates.
|
||||||
|
* `hornbeam_interpreter` — interpreter for Hornbeam templates, using `bevy_reflect` and being hot-reloadable. Useful in debug builds.
|
||||||
|
* `hornbeam_macros` — macros for compile-time template compilation to Rust. Useful in release builds.
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
TODO. Currently All Rights Reserved.
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "hornbeam"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
hornbeam_grammar = { version = "0.1.0", path = "../hornbeam_grammar" }
|
|
@ -0,0 +1,14 @@
|
||||||
|
pub fn add(left: usize, right: usize) -> usize {
|
||||||
|
left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let result = add(2, 2);
|
||||||
|
assert_eq!(result, 4);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "hornbeam_grammar"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pest = "2.5.5"
|
||||||
|
pest_derive = "2.5.5"
|
||||||
|
pest_consume = "1.1.3"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
serde = { version = "1.0.152", features = ["derive"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
insta = { version = "1.28.0", features = ["yaml"] }
|
|
@ -0,0 +1,142 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||||
|
pub struct Template<'a> {
|
||||||
|
pub blocks: Vec<Block<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||||
|
pub enum Block<'a> {
|
||||||
|
HtmlElement(HtmlElement<'a>),
|
||||||
|
ComponentElement(ComponentElement<'a>),
|
||||||
|
IfBlock(IfBlock<'a>),
|
||||||
|
Text(StringExpr<'a>),
|
||||||
|
DefineExpandSlot(DefineExpandSlot<'a>),
|
||||||
|
DefineFragment(DefineFragment<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||||
|
pub struct HtmlElement<'a> {
|
||||||
|
pub name: Cow<'a, str>,
|
||||||
|
pub children: Vec<Block<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||||
|
pub struct ComponentElement<'a> {
|
||||||
|
pub name: Cow<'a, str>,
|
||||||
|
pub slots: BTreeMap<Cow<'a, str>, Vec<Block<'a>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||||
|
pub struct IfBlock<'a> {
|
||||||
|
pub condition: Expression<'a>,
|
||||||
|
pub blocks: Vec<Block<'a>>,
|
||||||
|
pub else_blocks: Vec<Block<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||||
|
pub struct DefineExpandSlot<'a> {
|
||||||
|
pub name: Cow<'a, str>,
|
||||||
|
pub optional: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||||
|
pub struct DefineFragment<'a> {
|
||||||
|
pub name: Cow<'a, str>,
|
||||||
|
pub blocks: Vec<Block<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||||
|
pub struct StringExpr<'a> {
|
||||||
|
pub pieces: Vec<StringPiece<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||||
|
pub enum StringPiece<'a> {
|
||||||
|
Literal(Cow<'a, str>),
|
||||||
|
Interpolation(Expression<'a>),
|
||||||
|
Localise {
|
||||||
|
trans_key: Cow<'a, str>,
|
||||||
|
parameters: BTreeMap<Cow<'a, str>, Expression<'a>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||||
|
pub enum Expression<'a> {
|
||||||
|
// Arithmetic Operators
|
||||||
|
Add {
|
||||||
|
left: Box<Expression<'a>>,
|
||||||
|
right: Box<Expression<'a>>,
|
||||||
|
},
|
||||||
|
Sub {
|
||||||
|
left: Box<Expression<'a>>,
|
||||||
|
right: Box<Expression<'a>>,
|
||||||
|
},
|
||||||
|
Mul {
|
||||||
|
left: Box<Expression<'a>>,
|
||||||
|
right: Box<Expression<'a>>,
|
||||||
|
},
|
||||||
|
Div {
|
||||||
|
left: Box<Expression<'a>>,
|
||||||
|
right: Box<Expression<'a>>,
|
||||||
|
},
|
||||||
|
Negate {
|
||||||
|
sub: Box<Expression<'a>>,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Boolean Operators
|
||||||
|
BAnd {
|
||||||
|
left: Box<Expression<'a>>,
|
||||||
|
right: Box<Expression<'a>>,
|
||||||
|
},
|
||||||
|
BOr {
|
||||||
|
left: Box<Expression<'a>>,
|
||||||
|
right: Box<Expression<'a>>,
|
||||||
|
},
|
||||||
|
BNot {
|
||||||
|
sub: Box<Expression<'a>>,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Comparators
|
||||||
|
Equals {
|
||||||
|
left: Box<Expression<'a>>,
|
||||||
|
right: Box<Expression<'a>>,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Other Operators
|
||||||
|
ListAdd {
|
||||||
|
left: Box<Expression<'a>>,
|
||||||
|
right: Box<Expression<'a>>,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Literals
|
||||||
|
List {
|
||||||
|
elements: Vec<Expression<'a>>,
|
||||||
|
},
|
||||||
|
IntLiteral {
|
||||||
|
val: i64,
|
||||||
|
},
|
||||||
|
StringExpr(StringExpr<'a>),
|
||||||
|
|
||||||
|
// Relatives
|
||||||
|
FieldLookup {
|
||||||
|
obj: Box<Expression<'a>>,
|
||||||
|
ident: Cow<'a, str>,
|
||||||
|
},
|
||||||
|
MethodCall {
|
||||||
|
obj: Box<Expression<'a>>,
|
||||||
|
ident: Cow<'a, str>,
|
||||||
|
args: Vec<Expression<'a>>,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Other Primaries
|
||||||
|
Variable {
|
||||||
|
name: Cow<'a, str>,
|
||||||
|
},
|
||||||
|
FunctionCall {
|
||||||
|
name: Cow<'a, str>,
|
||||||
|
args: Vec<Expression<'a>>,
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
TestComponent
|
||||||
|
:main
|
||||||
|
div
|
||||||
|
''
|
||||||
|
This isn't half bad... ${1 + 1}
|
||||||
|
''
|
||||||
|
if 1 + 1 = 2
|
||||||
|
div
|
||||||
|
span
|
||||||
|
''
|
||||||
|
woohoo
|
||||||
|
''
|
||||||
|
:header
|
||||||
|
''
|
||||||
|
Example 01!
|
||||||
|
''
|
|
@ -0,0 +1,173 @@
|
||||||
|
//
|
||||||
|
Hornbeam = { SOI ~ wsnl* ~ BlockContent* ~ ws* ~ EOI }
|
||||||
|
|
||||||
|
NewBlock = _{
|
||||||
|
PEEK_ALL ~ PUSH(" "+ | "\t"+) ~ BlockContent ~
|
||||||
|
(PEEK_ALL ~ BlockContent)* ~ DROP
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockContent = _{
|
||||||
|
Element |
|
||||||
|
IfBlock |
|
||||||
|
Text |
|
||||||
|
DefineExpandSlot |
|
||||||
|
ForBlock |
|
||||||
|
DefineFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
Element = {
|
||||||
|
ElementName ~ lineEnd ~ (NewBlock | NewSlotBlock)?
|
||||||
|
}
|
||||||
|
|
||||||
|
Text = {
|
||||||
|
String ~ lineEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
IfBlock = {
|
||||||
|
"if" ~ ws+ ~ IfCondition ~ lineEnd ~ NewBlock ~
|
||||||
|
ElseBlock?
|
||||||
|
}
|
||||||
|
|
||||||
|
ElseBlock = {
|
||||||
|
"else" ~ (ws+ ~ IfBlock | lineEnd ~ NewBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
IfCondition = {
|
||||||
|
Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
ForBlock = {
|
||||||
|
"for" ~ ws+ ~ Binding ~ ws+ ~ "in" ~ ws+ ~ Expr ~ lineEnd ~ NewBlock ~
|
||||||
|
EmptyForBlock?
|
||||||
|
}
|
||||||
|
// Optional part of a for block: triggered if there were no items to show.
|
||||||
|
EmptyForBlock = {
|
||||||
|
"empty" ~ ws+ ~ lineEnd ~ NewBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
DefineExpandSlot = {
|
||||||
|
SlotOptional? ~ "slot" ~ ws+ ~ ":" ~ Identifier ~ lineEnd
|
||||||
|
}
|
||||||
|
SlotOptional = { "optional" ~ ws+ }
|
||||||
|
|
||||||
|
DefineFragment = {
|
||||||
|
"fragment" ~ ws+ ~ Identifier ~ lineEnd ~ NewBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
NewSlotBlock = _{
|
||||||
|
PEEK_ALL ~ PUSH(" "+ | "\t"+) ~ SupplySlot ~
|
||||||
|
(PEEK_ALL ~ SupplySlot)* ~ DROP
|
||||||
|
}
|
||||||
|
SupplySlot = {
|
||||||
|
":" ~ Identifier ~ lineEnd ~ NewBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementName = { componentName | htmlBuiltin }
|
||||||
|
|
||||||
|
// PascalCase
|
||||||
|
componentName = _{ ASCII_ALPHA_UPPER ~ ASCII_ALPHANUMERIC+ }
|
||||||
|
|
||||||
|
// Built-in tag names
|
||||||
|
htmlBuiltin = _{ "html" | "head" | "body" | "x" | "div" | "span" }
|
||||||
|
|
||||||
|
// We don't actually want to consume the EOI because then we'll get an annoying extra node in our parse...
|
||||||
|
nlOrEoi = _{ NEWLINE | &EOI }
|
||||||
|
comment = _{ "//" ~ (!NEWLINE ~ ANY)* ~ nlOrEoi }
|
||||||
|
comment_nonl = _{ "//" ~ (!NEWLINE ~ ANY)* ~ &nlOrEoi }
|
||||||
|
|
||||||
|
// I don't like the implicit WHITESPACE thing in Pest, it doesn't work well,
|
||||||
|
// atomicity of rules (@) propagates inwards and it's all very messy.
|
||||||
|
// Instead, define our own `ws` and use it how we like.
|
||||||
|
ws = _{ " " | "\t" | comment }
|
||||||
|
wsnl = _{ " " | "\t" | NEWLINE | comment }
|
||||||
|
wsnc = _{ " " | "\t" }
|
||||||
|
ws_nocnl = _{ " " | "\t" | comment_nonl }
|
||||||
|
|
||||||
|
// Line ending, with allowed blank lines.
|
||||||
|
lineEnd = _{ ws_nocnl* ~ nlOrEoi ~ (!EOI ~ ws* ~ nlOrEoi)* }
|
||||||
|
|
||||||
|
SingleStringContent = { (!("'" | "\\" | "$") ~ ANY)+ }
|
||||||
|
singleString = _{ !("''") ~ "'" ~ (SingleStringContent | SEscape | SInterpol)* ~ "'" }
|
||||||
|
|
||||||
|
DoubleStringContent = { (!("\"" | "\\" | "$") ~ ANY)+ }
|
||||||
|
doubleString = _{ "\"" ~ (DoubleStringContent | SEscape | SInterpol)* ~ "\"" }
|
||||||
|
|
||||||
|
blockStringStart = _{ "''" ~ NEWLINE ~ PEEK_ALL }
|
||||||
|
blockStringEnd = _{ NEWLINE ~ (" " | "\t")* ~ "''" }
|
||||||
|
BlockStringContent = { (!(NEWLINE | blockStringEnd | "\\" | "$" | "@") ~ ANY)+ }
|
||||||
|
// This rule becomes just \n later on, so it effectively strips leading indentation!
|
||||||
|
BlockStringNewline = { !blockStringEnd ~ NEWLINE ~ PEEK_ALL }
|
||||||
|
blockString = _{ blockStringStart ~ (BlockStringContent | SEscape | SInterpol | ParameterisedLocalisation | BlockStringNewline)* ~ blockStringEnd }
|
||||||
|
String = { blockString | singleString | doubleString | ParameterisedLocalisation }
|
||||||
|
|
||||||
|
SEscape = { "\\" ~ ("\\" | "'" | "\"" | "$" | "@") }
|
||||||
|
SInterpol = { "${" ~ ws* ~ Expr ~ ws* ~ "}" }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// We'll use a Pratt parser rather than encode operator precedence in PEG.
|
||||||
|
|
||||||
|
// wshack: needed to prevent the final newline after a comment being devoured
|
||||||
|
// when it's needed for an explicit line change, but there's no Expr after the comment.
|
||||||
|
wshack = _{ (wsnc | (comment ~ &(ws* ~ Expr)))* }
|
||||||
|
|
||||||
|
Expr = { prefix* ~ ws* ~ Term ~ ws* ~ postfix* ~ (ws* ~ infix ~ ws* ~ prefix* ~ ws* ~ Term ~ wshack ~ postfix*)* }
|
||||||
|
|
||||||
|
// INFIX
|
||||||
|
infix = _{ add | sub | mul | div | pow | modulo | listAdd | equals | band | bor }
|
||||||
|
add = { "+" }
|
||||||
|
sub = { "-" }
|
||||||
|
mul = { "*" }
|
||||||
|
div = { "/" }
|
||||||
|
pow = { "^" }
|
||||||
|
modulo = { "%" }
|
||||||
|
listAdd = { "++" }
|
||||||
|
equals = { "==" }
|
||||||
|
|
||||||
|
// BUG: these should have forced whitespace at the start!
|
||||||
|
// (lookbehind wouldn't be the worst feature in the world for a parser grammar!)
|
||||||
|
band = { "and" ~ ws+ }
|
||||||
|
bor = { "or" ~ ws+ }
|
||||||
|
|
||||||
|
// PREFIX
|
||||||
|
prefix = _{ negation | bnot }
|
||||||
|
negation = { "-" } // Negation
|
||||||
|
bnot = { "not" ~ ws+ }
|
||||||
|
|
||||||
|
// POSTFIX
|
||||||
|
postfix = _{ unwrap | MethodCall | FieldLookup | Indexing }
|
||||||
|
unwrap = { "?" } // Not sure I'm convinced about this one, but we can think about it.
|
||||||
|
// Note that functions aren't first-class; we don't allow you to 'call' an arbitrary term.
|
||||||
|
// This is probably for the best since we might have multiple backends for the templating.
|
||||||
|
MethodCall = { "." ~ ws* ~ Identifier ~ "(" ~ commaSeparatedExprs ~ ")" }
|
||||||
|
FieldLookup = { "." ~ ws* ~ Identifier }
|
||||||
|
Indexing = { "[" ~ ws* ~ Expr ~ ws* ~ "]" }
|
||||||
|
|
||||||
|
Term = _{ (IntLiteral | bracketedTerm | FunctionCall | ListLiteral | MapLiteral | String | Variable) }
|
||||||
|
|
||||||
|
bracketedTerm = _{ "(" ~ Expr ~ ")" }
|
||||||
|
|
||||||
|
|
||||||
|
IntLiteral = @{ (ASCII_NONZERO_DIGIT ~ ASCII_DIGIT+ | ASCII_DIGIT) }
|
||||||
|
Identifier = { (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }
|
||||||
|
commaSeparatedExprs = _{ wsnl* ~ (Expr ~ wsnl* ~ ("," ~ wsnl* ~ Expr ~ wsnl*)* ~ ("," ~ wsnl*)?)? }
|
||||||
|
FunctionCall = { Identifier ~ "(" ~ commaSeparatedExprs ~ ")" }
|
||||||
|
Variable = { "$" ~ Identifier }
|
||||||
|
|
||||||
|
ListLiteral = { "[" ~ commaSeparatedExprs ~ "]" }
|
||||||
|
|
||||||
|
KVPair = { Identifier ~ wsnl* ~ "=" ~ wsnl* ~ Expr }
|
||||||
|
commaSeparatedKVPairs = _{ wsnl* ~ (KVPair ~ wsnl* ~ ("," ~ wsnl* ~ KVPair ~ wsnl*)* ~ ("," ~ wsnl*)?)? }
|
||||||
|
MapLiteral = { "{" ~ commaSeparatedKVPairs ~ "}" }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// As in a let binding or for binding.
|
||||||
|
// More options in the future, but for now you just get one identifier and that's it!
|
||||||
|
Binding = {
|
||||||
|
Identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalisationIdentifier = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_" | "-")+ }
|
||||||
|
ParameterisedLocalisation = { "@" ~ LocalisationIdentifier ~ (!"{" | MapLiteral) }
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod ast;
|
||||||
|
mod parser;
|
|
@ -0,0 +1,434 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use crate::ast::{
|
||||||
|
Block, ComponentElement, DefineExpandSlot, DefineFragment, Expression, HtmlElement, IfBlock,
|
||||||
|
StringExpr, StringPiece, Template,
|
||||||
|
};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use pest::error::ErrorVariant;
|
||||||
|
use pest::pratt_parser::{Assoc, Op, PrattParser};
|
||||||
|
use pest::Span;
|
||||||
|
use pest_consume::{match_nodes, Error as PCError, Parser};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
type PCResult<T> = Result<T, PCError<Rule>>;
|
||||||
|
type Node<'i> = pest_consume::Node<'i, Rule, ()>;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[grammar = "hornbeam.pest"]
|
||||||
|
struct HornbeamParser;
|
||||||
|
|
||||||
|
fn error<R: Copy + Debug + Hash + Ord>(msg: &str, span: Span) -> PCError<R> {
|
||||||
|
PCError::new_from_span(
|
||||||
|
ErrorVariant::CustomError {
|
||||||
|
message: msg.to_owned(),
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref PRATT_PARSER: PrattParser<Rule> = PrattParser::new()
|
||||||
|
.op(Op::infix(Rule::band, Assoc::Left) | Op::infix(Rule::bor, Assoc::Left))
|
||||||
|
.op(Op::infix(Rule::equals, Assoc::Left))
|
||||||
|
.op(Op::infix(Rule::add, Assoc::Left) | Op::infix(Rule::sub, Assoc::Left))
|
||||||
|
.op(Op::infix(Rule::mul, Assoc::Left) | Op::infix(Rule::div, Assoc::Left))
|
||||||
|
.op(Op::infix(Rule::pow, Assoc::Right))
|
||||||
|
.op(Op::postfix(Rule::unwrap)
|
||||||
|
| Op::postfix(Rule::FieldLookup)
|
||||||
|
| Op::postfix(Rule::MethodCall))
|
||||||
|
.op(Op::prefix(Rule::negation));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pest_consume::parser]
|
||||||
|
impl HornbeamParser {
|
||||||
|
fn Hornbeam(input: Node) -> PCResult<Template> {
|
||||||
|
let blocks = HornbeamParser::helper_blocks(input.into_children())?;
|
||||||
|
Ok(Template { blocks })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Element(input: Node) -> PCResult<Block> {
|
||||||
|
Ok(match_nodes!(input.into_children();
|
||||||
|
[ElementName(name)] => {
|
||||||
|
if name.chars().next().unwrap().is_ascii_lowercase() {
|
||||||
|
Block::HtmlElement(HtmlElement {
|
||||||
|
name: name,
|
||||||
|
children: Vec::new()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Block::ComponentElement(ComponentElement {
|
||||||
|
name: name,
|
||||||
|
slots: BTreeMap::new()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[ElementName(name), SupplySlot(mut supply_slots)..] => {
|
||||||
|
if name.chars().next().unwrap().is_ascii_lowercase() {
|
||||||
|
let slot_node = supply_slots.next().unwrap();
|
||||||
|
|
||||||
|
return Err(error("You can't supply slots to HTML elements.", slot_node.2));
|
||||||
|
} else {
|
||||||
|
let mut slots = BTreeMap::new();
|
||||||
|
for (slot_name, slot_content_blocks, _slot_span) in supply_slots {
|
||||||
|
slots.insert(Cow::from(slot_name), slot_content_blocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
Block::ComponentElement(ComponentElement {
|
||||||
|
name: name,
|
||||||
|
slots,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[ElementName(name), blocks..] => {
|
||||||
|
if name.chars().next().unwrap().is_ascii_lowercase() {
|
||||||
|
Block::HtmlElement(HtmlElement {
|
||||||
|
name: name,
|
||||||
|
children: HornbeamParser::helper_blocks(blocks)?
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let mut slots = BTreeMap::new();
|
||||||
|
slots.insert(Cow::from("main"), HornbeamParser::helper_blocks(blocks)?);
|
||||||
|
Block::ComponentElement(ComponentElement {
|
||||||
|
name: name,
|
||||||
|
slots,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn DefineFragment(input: Node) -> PCResult<Block> {
|
||||||
|
Ok(match_nodes!(input.into_children();
|
||||||
|
[Identifier(name), blocks..] => {
|
||||||
|
Block::DefineFragment(DefineFragment {
|
||||||
|
name, blocks: HornbeamParser::helper_blocks(blocks)?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn DefineExpandSlot(input: Node) -> PCResult<Block> {
|
||||||
|
let (optional, name) = match_nodes!(input.into_children();
|
||||||
|
[SlotOptional(_), Identifier(name)] => {
|
||||||
|
(true, name)
|
||||||
|
},
|
||||||
|
[Identifier(name)] => {
|
||||||
|
(false, name)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Ok(Block::DefineExpandSlot(DefineExpandSlot { name, optional }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn SlotOptional(_input: Node) -> PCResult<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ElementName(input: Node) -> PCResult<Cow<str>> {
|
||||||
|
Ok(Cow::from(input.as_str()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn SupplySlot(input: Node) -> PCResult<(Cow<str>, Vec<Block>, Span)> {
|
||||||
|
let in_span = input.as_span();
|
||||||
|
match_nodes!(input.into_children();
|
||||||
|
[Identifier(ident), blocks..] => {
|
||||||
|
let blocks = HornbeamParser::helper_blocks(blocks)?;
|
||||||
|
Ok((ident, blocks, in_span))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Identifier(input: Node) -> PCResult<Cow<str>> {
|
||||||
|
Ok(Cow::from(input.as_str()))
|
||||||
|
}
|
||||||
|
fn LocalisationIdentifier(input: Node) -> PCResult<Cow<str>> {
|
||||||
|
Ok(Cow::from(input.as_str()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Text(input: Node) -> PCResult<Block> {
|
||||||
|
Ok(Block::Text(HornbeamParser::String(
|
||||||
|
input.into_children().single()?,
|
||||||
|
)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn String(input: Node) -> PCResult<StringExpr> {
|
||||||
|
let mut pieces = Vec::new();
|
||||||
|
for node in input.into_children() {
|
||||||
|
match node.as_rule() {
|
||||||
|
Rule::SEscape => pieces.push(StringPiece::Literal(HornbeamParser::SEscape(node)?)),
|
||||||
|
Rule::SInterpol => pieces.push(StringPiece::Interpolation(HornbeamParser::Expr(
|
||||||
|
node.into_children().single()?,
|
||||||
|
)?)),
|
||||||
|
Rule::SingleStringContent
|
||||||
|
| Rule::DoubleStringContent
|
||||||
|
| Rule::BlockStringContent => {
|
||||||
|
pieces.push(StringPiece::Literal(Cow::from(node.as_str())));
|
||||||
|
}
|
||||||
|
Rule::BlockStringNewline => {
|
||||||
|
pieces.push(StringPiece::Literal(Cow::from("\n")));
|
||||||
|
}
|
||||||
|
Rule::ParameterisedLocalisation => {
|
||||||
|
let (trans_key, parameters) = match_nodes!(node.into_children();
|
||||||
|
[LocalisationIdentifier(name), MapLiteral(map_literal)] => (name, map_literal),
|
||||||
|
[LocalisationIdentifier(name)] => (name, BTreeMap::new()),
|
||||||
|
);
|
||||||
|
pieces.push(StringPiece::Localise {
|
||||||
|
trans_key,
|
||||||
|
parameters,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
other => unimplemented!("Unknown string piece {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(StringExpr { pieces })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn KVPair(input: Node) -> PCResult<(Cow<str>, Expression)> {
|
||||||
|
Ok(match_nodes!(input.into_children();
|
||||||
|
[Identifier(key), Expr(value)] => (key, value),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn MapLiteral(input: Node) -> PCResult<BTreeMap<Cow<str>, Expression>> {
|
||||||
|
Ok(match_nodes!(input.into_children();
|
||||||
|
[KVPair(kv_pair)..] => kv_pair.collect()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn SEscape(input: Node) -> PCResult<Cow<str>> {
|
||||||
|
let esc = input.as_str();
|
||||||
|
Ok(match esc {
|
||||||
|
"\\\\" | "\\'" | "\\\"" | "\\$" => Cow::from(&esc[1..2]),
|
||||||
|
other => unimplemented!("Unimplemented escape sequence {other:?}! This is a bug."),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Variable(input: Node) -> PCResult<Expression> {
|
||||||
|
let name = Cow::from(input.into_children().single()?.as_str());
|
||||||
|
Ok(Expression::Variable { name })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Expr(input: Node) -> PCResult<Expression> {
|
||||||
|
PRATT_PARSER
|
||||||
|
.map_primary(|primary| Ok(match primary.as_rule() {
|
||||||
|
Rule::IntLiteral => Expression::IntLiteral { val: primary.as_str().parse().map_err(|e| error(&format!("can't parse int: {e:?}"), primary.as_span()))? },
|
||||||
|
Rule::String => Expression::StringExpr(HornbeamParser::String(Node::new(primary))?),
|
||||||
|
Rule::Variable => HornbeamParser::Variable(Node::new(primary))?,
|
||||||
|
Rule::FunctionCall => HornbeamParser::FunctionCall(Node::new(primary))?,
|
||||||
|
other => unimplemented!("unimp primary {other:?}!"),
|
||||||
|
}))
|
||||||
|
.map_prefix(|op, rhs| Ok(match op.as_rule() {
|
||||||
|
Rule::negation => Expression::Negate { sub: Box::new(rhs?) },
|
||||||
|
other => unimplemented!("unimp prefix {other:?}!"),
|
||||||
|
}))
|
||||||
|
.map_infix(|lhs, op, rhs| Ok(match op.as_rule() {
|
||||||
|
Rule::add => Expression::Add { left: Box::new(lhs?), right: Box::new(rhs?) },
|
||||||
|
Rule::sub => Expression::Sub { left: Box::new(lhs?), right: Box::new(rhs?) },
|
||||||
|
Rule::mul => Expression::Mul { left: Box::new(lhs?), right: Box::new(rhs?) },
|
||||||
|
Rule::div => Expression::Div { left: Box::new(lhs?), right: Box::new(rhs?) },
|
||||||
|
Rule::equals => Expression::Equals { left: Box::new(lhs?), right: Box::new(rhs?) },
|
||||||
|
Rule::bor => Expression::BOr { left: Box::new(lhs?), right: Box::new(rhs?) },
|
||||||
|
Rule::band => Expression::BAnd { left: Box::new(lhs?), right: Box::new(rhs?) },
|
||||||
|
other => unimplemented!("unimp infix {other:?}!"),
|
||||||
|
}))
|
||||||
|
.map_postfix(|lhs, op| Ok(match op.as_rule() {
|
||||||
|
Rule::unwrap => unimplemented!("unimp unwrap"),
|
||||||
|
Rule::FieldLookup => {
|
||||||
|
let ident = Cow::from(Node::new(op).into_children().single()?.as_str());
|
||||||
|
Expression::FieldLookup { obj: Box::new(lhs?), ident }
|
||||||
|
},
|
||||||
|
Rule::MethodCall => {
|
||||||
|
match_nodes!(Node::new(op).into_children();
|
||||||
|
[Identifier(ident), Expr(args)..] => {
|
||||||
|
Expression::MethodCall { obj: Box::new(lhs?), ident, args: args.collect() }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
other => unimplemented!("unimp postfix {other:?}!"),
|
||||||
|
}))
|
||||||
|
.parse(input.into_children().into_pairs())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn FunctionCall(input: Node) -> PCResult<Expression> {
|
||||||
|
Ok(match_nodes!(input.into_children();
|
||||||
|
[Identifier(name), Expr(args)..] => {
|
||||||
|
Expression::FunctionCall { name, args: args.collect() }
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn IfCondition(input: Node) -> PCResult<Expression> {
|
||||||
|
Self::Expr(input.into_children().single()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn IfBlock(input: Node) -> PCResult<Block> {
|
||||||
|
let (cond, blocks, else_blocks) = match_nodes!(input.into_children();
|
||||||
|
[IfCondition(cond), blocks.., ElseBlock(else_blocks)] => {
|
||||||
|
(cond, blocks, else_blocks)
|
||||||
|
},
|
||||||
|
[IfCondition(cond), blocks..] => {
|
||||||
|
(cond, blocks, Vec::new())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Block::IfBlock(IfBlock {
|
||||||
|
condition: cond,
|
||||||
|
blocks: HornbeamParser::helper_blocks(blocks)?,
|
||||||
|
else_blocks,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ElseBlock(input: Node) -> PCResult<Vec<Block>> {
|
||||||
|
HornbeamParser::helper_blocks(input.into_children())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)] // for some reason, the existence of this function is required.
|
||||||
|
fn EOI(_input: Node) -> PCResult<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HornbeamParser {
|
||||||
|
fn helper_blocks<'a>(input: impl Iterator<Item = Node<'a>>) -> PCResult<Vec<Block<'a>>> {
|
||||||
|
let mut result = Vec::with_capacity(input.size_hint().0);
|
||||||
|
for child in input {
|
||||||
|
if let Some(block) = HornbeamParser::helper_block(child)? {
|
||||||
|
result.push(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn helper_block<'a>(input: Node) -> PCResult<Option<Block>> {
|
||||||
|
Ok(match input.as_rule() {
|
||||||
|
Rule::Element => Some(HornbeamParser::Element(input)?),
|
||||||
|
Rule::Text => Some(HornbeamParser::Text(input)?),
|
||||||
|
Rule::IfBlock => Some(HornbeamParser::IfBlock(input)?),
|
||||||
|
Rule::DefineFragment => Some(HornbeamParser::DefineFragment(input)?),
|
||||||
|
Rule::DefineExpandSlot => Some(HornbeamParser::DefineExpandSlot(input)?),
|
||||||
|
Rule::EOI => None,
|
||||||
|
other => unimplemented!("unimp rule {other:?}"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_template(input: &str) -> PCResult<Template> {
|
||||||
|
let res = HornbeamParser::parse(Rule::Hornbeam, input)?;
|
||||||
|
let hornbeam = res.single()?;
|
||||||
|
HornbeamParser::Hornbeam(hornbeam)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use insta::{assert_debug_snapshot, assert_yaml_snapshot};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_parses_correct() {
|
||||||
|
assert_yaml_snapshot!(parse_template(
|
||||||
|
r#"
|
||||||
|
// This is a simple Hornbeam template that just shows a <div>
|
||||||
|
div
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn supply_slots_to_components_only() {
|
||||||
|
assert_debug_snapshot!(parse_template(
|
||||||
|
r#"
|
||||||
|
div
|
||||||
|
:someslot
|
||||||
|
"Oops!"
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_yaml_snapshot!(parse_template(
|
||||||
|
r#"
|
||||||
|
MyComponent
|
||||||
|
:someslot
|
||||||
|
"That's better!"
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_interpolations_nested() {
|
||||||
|
assert_yaml_snapshot!(parse_template(
|
||||||
|
r#"
|
||||||
|
MyComponent
|
||||||
|
:someslot
|
||||||
|
''
|
||||||
|
${"abc" + "def${ 1 + 1 }"}
|
||||||
|
Not too bad now.
|
||||||
|
''
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn if_blocks() {
|
||||||
|
assert_yaml_snapshot!(parse_template(
|
||||||
|
r#"
|
||||||
|
if 10 / 2 == 5 or $point.x == 42 // div for a div?
|
||||||
|
div
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.unwrap());
|
||||||
|
|
||||||
|
assert_yaml_snapshot!(parse_template(
|
||||||
|
r#"
|
||||||
|
if 1 + 1 == 2
|
||||||
|
div
|
||||||
|
"Phew, safe!"
|
||||||
|
else if 1 + 2 - 1 == 3 // not too far off, I suppose
|
||||||
|
Warning
|
||||||
|
"Not quite, but fairly close. What kind of world is this?"
|
||||||
|
else // peculiar.
|
||||||
|
"Not even close, eh?"
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fragments_and_slots() {
|
||||||
|
assert_yaml_snapshot!(parse_template(
|
||||||
|
r#"
|
||||||
|
div
|
||||||
|
fragment BobbyDazzler
|
||||||
|
span
|
||||||
|
"Wow!"
|
||||||
|
fragment MainPart
|
||||||
|
slot :main
|
||||||
|
div
|
||||||
|
optional slot :footer
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn localisation() {
|
||||||
|
assert_yaml_snapshot!(parse_template(
|
||||||
|
r#"
|
||||||
|
div
|
||||||
|
span
|
||||||
|
@header-wow
|
||||||
|
MainBody
|
||||||
|
''
|
||||||
|
@body-welcome
|
||||||
|
@body-msg{count = $messages.len()}
|
||||||
|
''
|
||||||
|
Footer
|
||||||
|
@footer-copyright{year = today().year}
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.unwrap());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
source: hornbeam_grammar/src/parser.rs
|
||||||
|
expression: "parse_template(r#\"\ndiv\n fragment BobbyDazzler\n span\n \"Wow!\"\n fragment MainPart\n slot :main\n div\n optional slot :footer\n \"#).unwrap()"
|
||||||
|
---
|
||||||
|
blocks:
|
||||||
|
- HtmlElement:
|
||||||
|
name: div
|
||||||
|
children:
|
||||||
|
- DefineFragment:
|
||||||
|
name: BobbyDazzler
|
||||||
|
blocks:
|
||||||
|
- HtmlElement:
|
||||||
|
name: span
|
||||||
|
children:
|
||||||
|
- Text:
|
||||||
|
pieces:
|
||||||
|
- Literal: Wow!
|
||||||
|
- DefineFragment:
|
||||||
|
name: MainPart
|
||||||
|
blocks:
|
||||||
|
- DefineExpandSlot:
|
||||||
|
name: main
|
||||||
|
optional: false
|
||||||
|
- HtmlElement:
|
||||||
|
name: div
|
||||||
|
children:
|
||||||
|
- DefineExpandSlot:
|
||||||
|
name: footer
|
||||||
|
optional: true
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
---
|
||||||
|
source: hornbeam_grammar/src/parser.rs
|
||||||
|
expression: "parse_template(r#\"\nif 1 + 1 == 2\n div\n \"Phew, safe!\"\nelse if 1 + 2 - 1 == 3 // not too far off, I suppose\n Warning\n \"Not quite, but fairly close. What kind of world is this?\"\nelse // peculiar.\n \"Not even close, eh?\"\n \"#).unwrap()"
|
||||||
|
---
|
||||||
|
blocks:
|
||||||
|
- IfBlock:
|
||||||
|
condition:
|
||||||
|
Equals:
|
||||||
|
left:
|
||||||
|
Add:
|
||||||
|
left:
|
||||||
|
IntLiteral:
|
||||||
|
val: 1
|
||||||
|
right:
|
||||||
|
IntLiteral:
|
||||||
|
val: 1
|
||||||
|
right:
|
||||||
|
IntLiteral:
|
||||||
|
val: 2
|
||||||
|
blocks:
|
||||||
|
- HtmlElement:
|
||||||
|
name: div
|
||||||
|
children:
|
||||||
|
- Text:
|
||||||
|
pieces:
|
||||||
|
- Literal: "Phew, safe!"
|
||||||
|
else_blocks:
|
||||||
|
- IfBlock:
|
||||||
|
condition:
|
||||||
|
Equals:
|
||||||
|
left:
|
||||||
|
Sub:
|
||||||
|
left:
|
||||||
|
Add:
|
||||||
|
left:
|
||||||
|
IntLiteral:
|
||||||
|
val: 1
|
||||||
|
right:
|
||||||
|
IntLiteral:
|
||||||
|
val: 2
|
||||||
|
right:
|
||||||
|
IntLiteral:
|
||||||
|
val: 1
|
||||||
|
right:
|
||||||
|
IntLiteral:
|
||||||
|
val: 3
|
||||||
|
blocks:
|
||||||
|
- ComponentElement:
|
||||||
|
name: Warning
|
||||||
|
slots:
|
||||||
|
main:
|
||||||
|
- Text:
|
||||||
|
pieces:
|
||||||
|
- Literal: "Not quite, but fairly close. What kind of world is this?"
|
||||||
|
else_blocks:
|
||||||
|
- Text:
|
||||||
|
pieces:
|
||||||
|
- Literal: "Not even close, eh?"
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
---
|
||||||
|
source: hornbeam_grammar/src/parser.rs
|
||||||
|
expression: "parse_template(r#\"\nif 10 / 2 == 5 or $point.x == 42 // div for a div?\n div\n \"#).unwrap()"
|
||||||
|
---
|
||||||
|
blocks:
|
||||||
|
- IfBlock:
|
||||||
|
condition:
|
||||||
|
BOr:
|
||||||
|
left:
|
||||||
|
Equals:
|
||||||
|
left:
|
||||||
|
Div:
|
||||||
|
left:
|
||||||
|
IntLiteral:
|
||||||
|
val: 10
|
||||||
|
right:
|
||||||
|
IntLiteral:
|
||||||
|
val: 2
|
||||||
|
right:
|
||||||
|
IntLiteral:
|
||||||
|
val: 5
|
||||||
|
right:
|
||||||
|
Equals:
|
||||||
|
left:
|
||||||
|
FieldLookup:
|
||||||
|
obj:
|
||||||
|
Variable:
|
||||||
|
name: point
|
||||||
|
ident: x
|
||||||
|
right:
|
||||||
|
IntLiteral:
|
||||||
|
val: 42
|
||||||
|
blocks:
|
||||||
|
- HtmlElement:
|
||||||
|
name: div
|
||||||
|
children: []
|
||||||
|
else_blocks: []
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
---
|
||||||
|
source: hornbeam_grammar/src/parser.rs
|
||||||
|
expression: "parse_template(r#\"\ndiv\n span\n @header-wow\n MainBody\n ''\n @body-welcome\n @body-msg{count = $messages.len()}\n ''\n Footer\n @footer-copyright{year = today().year}\n \"#).unwrap()"
|
||||||
|
---
|
||||||
|
blocks:
|
||||||
|
- HtmlElement:
|
||||||
|
name: div
|
||||||
|
children:
|
||||||
|
- HtmlElement:
|
||||||
|
name: span
|
||||||
|
children:
|
||||||
|
- Text:
|
||||||
|
pieces:
|
||||||
|
- Localise:
|
||||||
|
trans_key: header-wow
|
||||||
|
parameters: {}
|
||||||
|
- ComponentElement:
|
||||||
|
name: MainBody
|
||||||
|
slots:
|
||||||
|
main:
|
||||||
|
- Text:
|
||||||
|
pieces:
|
||||||
|
- Localise:
|
||||||
|
trans_key: body-welcome
|
||||||
|
parameters: {}
|
||||||
|
- Literal: "\n"
|
||||||
|
- Localise:
|
||||||
|
trans_key: body-msg
|
||||||
|
parameters:
|
||||||
|
count:
|
||||||
|
MethodCall:
|
||||||
|
obj:
|
||||||
|
Variable:
|
||||||
|
name: messages
|
||||||
|
ident: len
|
||||||
|
args: []
|
||||||
|
- ComponentElement:
|
||||||
|
name: Footer
|
||||||
|
slots:
|
||||||
|
main:
|
||||||
|
- Text:
|
||||||
|
pieces:
|
||||||
|
- Localise:
|
||||||
|
trans_key: footer-copyright
|
||||||
|
parameters:
|
||||||
|
year:
|
||||||
|
FieldLookup:
|
||||||
|
obj:
|
||||||
|
FunctionCall:
|
||||||
|
name: today
|
||||||
|
args: []
|
||||||
|
ident: year
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
source: hornbeam_grammar/src/parser.rs
|
||||||
|
expression: "parse_template(r#\"\n// This is a simple Hornbeam template that just shows a <div>\ndiv\n \"#).unwrap()"
|
||||||
|
---
|
||||||
|
blocks:
|
||||||
|
- HtmlElement:
|
||||||
|
name: div
|
||||||
|
children: []
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
---
|
||||||
|
source: hornbeam_grammar/src/parser.rs
|
||||||
|
expression: "parse_template(r#\"\nMyComponent\n :someslot\n ''\n ${\"abc\" + \"def${ 1 + 1 }\"}\n Not too bad now.\n ''\n \"#).unwrap()"
|
||||||
|
---
|
||||||
|
blocks:
|
||||||
|
- ComponentElement:
|
||||||
|
name: MyComponent
|
||||||
|
slots:
|
||||||
|
someslot:
|
||||||
|
- Text:
|
||||||
|
pieces:
|
||||||
|
- Interpolation:
|
||||||
|
Add:
|
||||||
|
left:
|
||||||
|
StringExpr:
|
||||||
|
pieces:
|
||||||
|
- Literal: abc
|
||||||
|
right:
|
||||||
|
StringExpr:
|
||||||
|
pieces:
|
||||||
|
- Literal: def
|
||||||
|
- Interpolation:
|
||||||
|
Add:
|
||||||
|
left:
|
||||||
|
IntLiteral:
|
||||||
|
val: 1
|
||||||
|
right:
|
||||||
|
IntLiteral:
|
||||||
|
val: 1
|
||||||
|
- Literal: "\n"
|
||||||
|
- Literal: Not too bad now.
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
source: hornbeam_grammar/src/parser.rs
|
||||||
|
expression: "parse_template(r#\"\nMyComponent\n :someslot\n \"That's better!\"\n \"#).unwrap()"
|
||||||
|
---
|
||||||
|
blocks:
|
||||||
|
- ComponentElement:
|
||||||
|
name: MyComponent
|
||||||
|
slots:
|
||||||
|
someslot:
|
||||||
|
- Text:
|
||||||
|
pieces:
|
||||||
|
- Literal: "That's better!"
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
---
|
||||||
|
source: hornbeam_grammar/src/parser.rs
|
||||||
|
expression: "parse_template(r#\"\ndiv\n :someslot\n \"Oops!\"\n \"#)"
|
||||||
|
---
|
||||||
|
Err(
|
||||||
|
Error {
|
||||||
|
variant: CustomError {
|
||||||
|
message: "You can't supply slots to HTML elements.",
|
||||||
|
},
|
||||||
|
location: Span(
|
||||||
|
(
|
||||||
|
9,
|
||||||
|
43,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
line_col: Span(
|
||||||
|
(
|
||||||
|
3,
|
||||||
|
5,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
5,
|
||||||
|
9,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
path: None,
|
||||||
|
line: " :someslot",
|
||||||
|
continued_line: Some(
|
||||||
|
" ",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "hornbeam_interpreter"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
|
@ -0,0 +1,14 @@
|
||||||
|
pub fn add(left: usize, right: usize) -> usize {
|
||||||
|
left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let result = add(2, 2);
|
||||||
|
assert_eq!(result, 4);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "hornbeam_ir"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
|
@ -0,0 +1,14 @@
|
||||||
|
pub fn add(left: usize, right: usize) -> usize {
|
||||||
|
left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let result = add(2, 2);
|
||||||
|
assert_eq!(result, 4);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "hornbeam_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
|
@ -0,0 +1,14 @@
|
||||||
|
pub fn add(left: usize, right: usize) -> usize {
|
||||||
|
left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let result = add(2, 2);
|
||||||
|
assert_eq!(result, 4);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
|
||||||
|
let
|
||||||
|
# We may need some packages from nixpkgs-unstable
|
||||||
|
#unstable = import <nixpkgs-unstable> {};
|
||||||
|
|
||||||
|
rust-toolchain = pkgs.symlinkJoin {
|
||||||
|
name = "rust-toolchain";
|
||||||
|
paths = [pkgs.rustc pkgs.cargo pkgs.rustfmt pkgs.rustPlatform.rustcSrc];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
|
||||||
|
pkgs.mkShell {
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
rust-toolchain
|
||||||
|
|
||||||
|
pkgs.pkg-config
|
||||||
|
|
||||||
|
#pkgs.libclang # ??
|
||||||
|
];
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
pkgs.openssl
|
||||||
|
];
|
||||||
|
|
||||||
|
# LIBCLANG_PATH="${pkgs.llvmPackages_latest.libclang.lib}/lib";
|
||||||
|
|
||||||
|
# Cargo culted:
|
||||||
|
# Add to rustc search path
|
||||||
|
# RUSTFLAGS = (builtins.map (a: ''-L ${a}/lib'') [
|
||||||
|
# ]);
|
||||||
|
# Add to bindgen search path
|
||||||
|
BINDGEN_EXTRA_CLANG_ARGS =
|
||||||
|
# Includes with normal include path
|
||||||
|
(builtins.map (a: ''-I"${a}/include"'') [
|
||||||
|
# pkgs.glibc.dev
|
||||||
|
])
|
||||||
|
# Includes with special directory paths
|
||||||
|
++ [
|
||||||
|
# ''-I"${pkgs.llvmPackages_latest.libclang.lib}/lib/clang/${pkgs.llvmPackages_latest.libclang.version}/include"''
|
||||||
|
#''-I"${pkgs.glib.dev}/include/glib-2.0"''
|
||||||
|
#''-I${pkgs.glib.out}/lib/glib-2.0/include/''
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue