Add initial parsing

Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
Marcel Müller 2026-03-05 08:22:10 +01:00
parent 31e102a4ee
commit f4e8137e17
6 changed files with 1098 additions and 6 deletions

620
Cargo.lock generated Normal file
View file

@ -0,0 +1,620 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "annotate-snippets"
version = "0.12.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74fc7650eedcb2fee505aad48491529e408f0e854c2d9f63eb86c1361b9b3f93"
dependencies = [
"anstyle",
"memchr",
"unicode-width",
]
[[package]]
name = "anstyle"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anyhow"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "console"
version = "0.15.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
dependencies = [
"encode_unicode",
"libc",
"once_cell",
"windows-sys 0.59.0",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "getrandom"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
"wasip3",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "indexmap"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
"serde",
"serde_core",
]
[[package]]
name = "insta"
version = "1.46.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4"
dependencies = [
"console",
"once_cell",
"similar",
"tempfile",
]
[[package]]
name = "itoa"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rustix"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "similar"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0"
dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys 0.61.2",
]
[[package]]
name = "temple"
version = "0.1.0"
dependencies = [
"annotate-snippets",
"displaydoc",
"insta",
"serde",
"serde_json",
"thiserror",
"winnow",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-width"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "wasip2"
version = "1.0.2+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasip3"
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"leb128fmt",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"

View file

@ -4,3 +4,13 @@ version = "0.1.0"
edition = "2024"
[dependencies]
annotate-snippets = "0.12.13"
displaydoc = "0.2.5"
serde = "1.0.228"
serde_json = "1.0.149"
thiserror = "2.0.18"
winnow = { version = "0.7.14", features = ["unstable-recover"] }
[dev-dependencies]
annotate-snippets = { version = "0.12.13", features = ["testing-colors"] }
insta = "1.46.3"

View file

@ -90,6 +90,8 @@
nativeBuildInputs = [
rustfmt'
rustTarget
pkgs.cargo-insta
];
};
}

View file

@ -1,14 +1,99 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
use std::collections::BTreeMap;
use std::collections::HashMap;
use displaydoc::Display;
use serde::Serialize;
use thiserror::Error;
pub mod parser;
#[derive(Debug, Error, Display)]
pub enum TempleError {
/// Could not parse the given template
ParseError {},
}
pub struct Temple {
templates: HashMap<String, Template>,
}
impl Default for Temple {
fn default() -> Self {
Self::new()
}
}
impl Temple {
pub fn new() -> Temple {
Temple {
templates: HashMap::new(),
}
}
pub fn add_template(
&mut self,
name: impl Into<String>,
value: impl AsRef<str>,
) -> Result<(), TempleError> {
Ok(())
}
fn render(&self, arg: &str, ctx: &Context) -> Result<String, TempleError> {
Ok(String::new())
}
}
struct Template {}
pub struct Context {
values: BTreeMap<String, serde_json::Value>,
}
impl Default for Context {
fn default() -> Self {
Context::new()
}
}
impl Context {
pub fn new() -> Context {
Context {
values: BTreeMap::new(),
}
}
pub fn try_insert(
&mut self,
key: impl Into<String>,
value: impl Serialize,
) -> Result<(), serde_json::Error> {
self.values.insert(key.into(), serde_json::to_value(value)?);
Ok(())
}
pub fn insert(&mut self, key: impl Into<String>, value: impl Serialize) {
self.try_insert(key, value)
.expect("inserted value should serialize without error");
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Context;
use crate::Temple;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
fn check_simple_template() {
let mut temp = Temple::new();
temp.add_template("base", "Hello {{= name }}").unwrap();
let mut ctx = Context::new();
ctx.insert("name", "World");
let rendered = temp.render("base", &ctx).unwrap();
insta::assert_snapshot!(rendered, @"")
}
}

367
src/parser/mod.rs Normal file
View file

@ -0,0 +1,367 @@
use std::ops::Range;
use std::sync::Arc;
use annotate_snippets::AnnotationKind;
use annotate_snippets::Level;
use annotate_snippets::Renderer;
use annotate_snippets::Snippet;
use winnow::LocatingSlice;
use winnow::Parser;
use winnow::RecoverableParser;
use winnow::ascii::alpha1;
use winnow::ascii::multispace0;
use winnow::ascii::multispace1;
use winnow::combinator::alt;
use winnow::combinator::cut_err;
use winnow::combinator::eof;
use winnow::combinator::opt;
use winnow::combinator::peek;
use winnow::combinator::repeat_till;
use winnow::combinator::terminated;
use winnow::combinator::trace;
use winnow::error::AddContext;
use winnow::error::FromRecoverableError;
use winnow::error::ModalError;
use winnow::error::ParserError;
use winnow::stream::Location;
use winnow::stream::Recoverable;
use winnow::stream::Stream;
use winnow::token::any;
use winnow::token::rest;
use winnow::token::take_until;
type Input<'input> = Recoverable<LocatingSlice<&'input str>, ParseError>;
type PResult<'input, T> = Result<T, ParseError>;
#[derive(Debug, Clone)]
pub struct SourceSpan {
pub range: Range<usize>,
}
#[derive(Debug)]
pub struct ParseFailure {
source: Arc<str>,
errors: Vec<ParseError>,
}
impl ParseFailure {
fn from_errors(errors: Vec<ParseError>, input: &str) -> ParseFailure {
ParseFailure {
source: Arc::from(input.to_string()),
errors,
}
}
pub fn to_report(&self) -> String {
let mut report = String::new();
for error in &self.errors {
let rep = &[Level::ERROR
.primary_title(
error
.message
.as_deref()
.unwrap_or("An error occurred while parsing"),
)
.element(
Snippet::source(self.source.as_ref()).annotation(
AnnotationKind::Primary
.span(error.span.clone().map(|s| s.range).unwrap_or_else(|| 0..0)),
),
)];
let renderer =
Renderer::styled().decor_style(annotate_snippets::renderer::DecorStyle::Unicode);
report.push_str(&renderer.render(rep));
}
report
}
}
#[derive(Debug, Clone)]
pub struct ParseError {
pub(crate) message: Option<String>,
pub(crate) span: Option<SourceSpan>,
is_fatal: bool,
}
impl ParseError {
fn ctx() -> Self {
ParseError {
message: None,
span: None,
is_fatal: false,
}
}
fn msg(mut self, message: &str) -> Self {
self.message = Some(message.to_string());
self
}
}
impl ModalError for ParseError {
fn cut(mut self) -> Self {
self.is_fatal = true;
self
}
fn backtrack(mut self) -> Self {
self.is_fatal = false;
self
}
}
impl<'input> FromRecoverableError<Input<'input>, ParseError> for ParseError {
fn from_recoverable_error(
token_start: &<Input<'input> as winnow::stream::Stream>::Checkpoint,
_err_start: &<Input<'input> as winnow::stream::Stream>::Checkpoint,
input: &Input<'input>,
mut e: ParseError,
) -> Self {
e.span = e
.span
.or_else(|| Some(span_from_checkpoint(input, token_start)));
e
}
}
impl<'input> AddContext<Input<'input>, ParseError> for ParseError {
fn add_context(
mut self,
_input: &Input<'input>,
_token_start: &<Input<'input> as Stream>::Checkpoint,
context: ParseError,
) -> Self {
self.message = context.message.or(self.message);
self
}
}
fn span_from_checkpoint<I: Stream + Location>(
input: &I,
token_start: &<I as Stream>::Checkpoint,
) -> SourceSpan {
let offset = input.offset_from(token_start);
SourceSpan {
range: (input.current_token_start() - offset)..input.current_token_start(),
}
}
impl<'input> ParserError<Input<'input>> for ParseError {
type Inner = ParseError;
fn from_input(_input: &Input<'input>) -> Self {
ParseError {
message: None,
span: None,
is_fatal: false,
}
}
fn into_inner(self) -> winnow::Result<Self::Inner, Self> {
Ok(self)
}
fn is_backtrack(&self) -> bool {
!self.is_fatal
}
}
#[derive(Debug)]
pub struct ParsedTemplate<'input> {
content: Vec<TemplateChunk<'input>>,
}
#[derive(Debug)]
pub enum TemplateChunk<'input> {
Content(&'input str),
Expression(InterpolateExpression<'input>),
}
#[derive(Debug)]
pub struct InterpolateExpression<'input> {
pub left_delim: &'input str,
pub wants_output: Option<&'input str>,
pub value: Box<TemplateExpression<'input>>,
pub right_delim: &'input str,
}
#[derive(Debug)]
pub struct TemplateExpression<'input> {
pub before_ws: &'input str,
pub expr: TemplateExpr<'input>,
pub after_ws: &'input str,
}
#[derive(Debug)]
pub enum TemplateExpr<'input> {
Variable(&'input str),
}
pub fn parse(input: &str) -> Result<ParsedTemplate<'_>, ParseFailure> {
let (_remaining, val, errors) = parse_chunks.recoverable_parse(LocatingSlice::new(input));
dbg!(&val);
if errors.is_empty()
&& let Some(val) = val
{
Ok(ParsedTemplate { content: val })
} else {
Err(ParseFailure::from_errors(errors, input))
}
}
fn parse_chunks<'input>(input: &mut Input<'input>) -> PResult<'input, Vec<TemplateChunk<'input>>> {
repeat_till(0.., alt((parse_interpolate, parse_content)), eof)
.map(|(v, _)| v)
.parse_next(input)
}
fn parse_content<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateChunk<'input>> {
alt((take_until(1.., "{{"), rest))
.map(TemplateChunk::Content)
.parse_next(input)
}
fn parse_interpolate<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateChunk<'input>> {
let left_delim = "{{".parse_next(input)?;
let (wants_output, value, right_delim) =
cut_err((opt("="), parse_value.map(Box::new), "}}")).parse_next(input)?;
Ok(TemplateChunk::Expression(InterpolateExpression {
left_delim,
wants_output,
value,
right_delim,
}))
}
fn parse_value<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateExpression<'input>> {
let before_ws = multispace0(input)?;
let expr = trace("parse_value", alt((parse_variable,))).parse_next(input)?;
let after_ws = multispace0(input)?;
Ok(TemplateExpression {
before_ws,
expr,
after_ws,
})
}
fn parse_variable<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateExpr<'input>> {
terminated(alpha1, ident_terminator_check)
.map(TemplateExpr::Variable)
.context(ParseError::ctx().msg("valid variables are alpha"))
.resume_after(bad_ident)
.map(|v| v.unwrap_or(TemplateExpr::Variable("BAD_VARIABLE")))
.parse_next(input)
}
fn ident_terminator<'input>(input: &mut Input<'input>) -> PResult<'input, ()> {
alt((eof.void(), "{".void(), "}".void(), multispace1.void())).parse_next(input)
}
fn ident_terminator_check<'input>(input: &mut Input<'input>) -> PResult<'input, ()> {
cut_err(peek(ident_terminator)).parse_next(input)
}
fn bad_ident<'input>(input: &mut Input<'input>) -> PResult<'input, ()> {
repeat_till(1.., any, peek(ident_terminator))
.map(|((), _)| ())
.parse_next(input)
}
#[cfg(test)]
mod tests {
use crate::parser::parse;
#[test]
fn parse_simple() {
let input = "Hello There";
let output = parse(input);
insta::assert_debug_snapshot!(output, @r#"
Ok(
ParsedTemplate {
content: [
Content(
"Hello There",
),
],
},
)
"#);
}
#[test]
fn parse_interpolate() {
let input = "Hello {{ there }}";
let output = parse(input);
insta::assert_debug_snapshot!(output, @r#"
Ok(
ParsedTemplate {
content: [
Content(
"Hello ",
),
Expression(
InterpolateExpression {
left_delim: "{{",
wants_output: None,
value: TemplateExpression {
before_ws: " ",
expr: Variable(
"there",
),
after_ws: " ",
},
right_delim: "}}",
},
),
],
},
)
"#);
}
#[test]
fn parse_interpolate_bad() {
let input = "Hello {{ the2re }}";
let output = parse(input);
insta::assert_debug_snapshot!(output, @r#"
Err(
ParseFailure {
source: "Hello {{ the2re }}",
errors: [
ParseError {
message: Some(
"valid variables are alpha",
),
span: Some(
SourceSpan {
range: 9..15,
},
),
is_fatal: true,
},
],
},
)
"#);
let error = output.unwrap_err();
insta::assert_snapshot!(error.to_report());
}
}

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: error.to_report()
---
error: valid variables are alpha
 ╭▸ 
1 │ Hello {{ the2re }}
╰╴ ━━━━━━