use crate::lang::error::Error;
use crate::lang::eval::Eval;
use crate::value::Value;
use crate::value::span::Span;
use bstr::ByteSlice;
#[derive(Debug)]
pub struct TemplateResult {
pub result: Result<Eval<Value>, Error>,
pub segment: TemplateSegment,
}
impl TemplateResult {
pub fn join(self) -> Vec<u8> {
self.result.unwrap_or_default().result.join()
}
pub fn value(&self) -> Option<&Value> {
self.result.as_ref().map(|r| &r.result).ok()
}
}
pub fn join(vec: Vec<TemplateResult>) -> Result<Eval<Value>, Error> {
let vec = vec
.into_iter()
.map(|s| s.result)
.collect::<Result<Vec<_>, _>>()?;
let vec = vec.into_iter().reduce(|mut a, mut b| {
a.result = Value::from(a.result.into_bytes());
b.result = Value::from(b.result.into_bytes());
let b_result = b.result.with_meta("internal-insert", "append");
a.result.fold(b_result);
a.ops.append(&mut b.ops);
a
});
Ok(vec.unwrap_or_default())
}
#[derive(Debug, Clone, PartialEq)]
pub enum TemplateSegment {
Static(Span),
Dynamic(Span),
Escaper(Span),
Delimiter(Span),
}
impl TemplateSegment {
pub fn span(&self) -> Span {
match self {
TemplateSegment::Static(s) => *s,
TemplateSegment::Dynamic(s) => *s,
TemplateSegment::Escaper(s) => *s,
TemplateSegment::Delimiter(s) => *s,
}
}
}
#[derive(Debug, PartialEq)]
enum TemplateState {
Static,
Dynamic,
Nested(u8, u8),
}
pub fn has_paired_delimiters(data: &[u8]) -> bool {
data.find_byte(b'{')
.and_then(|i| data[i..].find_byte(b'}'))
.is_some()
}
pub fn has_outer_delimiters(data: &[u8]) -> bool {
data.first() == Some(&b'{') && data.last() == Some(&b'}')
}
pub fn split_template_delimiters(data: &[u8]) -> Vec<TemplateSegment> {
let mut state = TemplateState::Static;
let mut split = Vec::new();
let mut start = 0;
let mut depth = 0;
if data.is_empty() {
return Vec::default();
}
let mut iter = data.iter().enumerate();
while let Some((i, b)) = iter.next() {
let delimiter_start = data[i] == b'{';
let delimiter_end = data[i] == b'}';
let delimiter = delimiter_start || delimiter_end;
match state {
TemplateState::Static if delimiter && i == 1 && data[i - 1] == b'\\' => {
split.push(TemplateSegment::Escaper(Span::new(i - 1, i)));
start = i;
iter.next();
continue;
}
TemplateState::Static if delimiter && i > 0 && data[i - 1] == b'\\' => {
split.push(TemplateSegment::Static(Span::new(start, i - 1)));
split.push(TemplateSegment::Escaper(Span::new(i - 1, i)));
start = i;
iter.next();
continue;
}
TemplateState::Static if delimiter_start && i != start => {
split.push(TemplateSegment::Static(Span::new(start, i)));
split.push(TemplateSegment::Delimiter(Span::new(i, i + 1)));
start = i + 1;
state = TemplateState::Dynamic;
}
TemplateState::Static if delimiter_start => {
split.push(TemplateSegment::Delimiter(Span::new(i, i + 1)));
start = i + 1;
state = TemplateState::Dynamic;
}
TemplateState::Dynamic if i > 0 && data[i - 1] == b'\\' => {
continue;
}
TemplateState::Dynamic if delimiter_end => {
split.push(TemplateSegment::Dynamic(Span::new(start, i)));
split.push(TemplateSegment::Delimiter(Span::new(i, i + 1)));
start = i + 1;
state = TemplateState::Static;
}
TemplateState::Dynamic if *b == b'"' => {
state = TemplateState::Nested(b'"', b'"');
}
TemplateState::Dynamic if *b == b'\'' => {
state = TemplateState::Nested(b'\'', b'\'');
}
TemplateState::Dynamic if *b == b'{' && depth == 0 => {
state = TemplateState::Nested(b'{', b'}');
}
TemplateState::Nested(..) if i > 0 && data[i - 1] == b'\\' => {
continue;
}
TemplateState::Nested(l, r) if *b == l && l != r => {
depth += 1;
}
TemplateState::Nested(l, r) if *b == r && l != r && depth > 0 => {
depth -= 1;
}
TemplateState::Nested(_, r) if *b == r => {
state = TemplateState::Dynamic;
}
_ => {}
}
}
if state != TemplateState::Static {
return vec![TemplateSegment::Static(Span::new(0, data.len()))];
}
if start != data.len() {
split.push(TemplateSegment::Static(Span::new(start, data.len())));
}
split
}
#[cfg(test)]
mod test {
use super::*;
use crate::value::span::Span;
#[test]
fn test_static() {
assert_eq!(
template("a"),
vec![TemplateSegment::Static(Span::new(0, 1))]
)
}
#[test]
fn test_empty() {
assert_eq!(template(""), vec![]);
assert_eq!(
template(" "),
vec![TemplateSegment::Static(Span::new(0, 1))]
);
assert_eq!(
template("{}"),
vec![
TemplateSegment::Delimiter(Span::new(0, 1)),
TemplateSegment::Dynamic(Span::new(1, 1)),
TemplateSegment::Delimiter(Span::new(1, 2))
]
);
}
#[test]
fn test_unclosed() {
assert_eq!(
template("{a"),
vec![TemplateSegment::Static(Span::new(0, 2))]
);
assert_eq!(
template("a{a"),
vec![TemplateSegment::Static(Span::new(0, 3))]
);
assert_eq!(
template("{a'}'"),
vec![TemplateSegment::Static(Span::new(0, 5))]
);
assert_eq!(
template("a{a'}'"),
vec![TemplateSegment::Static(Span::new(0, 6))]
);
assert_eq!(
template(r#"{\}"#),
vec![TemplateSegment::Static(Span::new(0, 3))]
);
}
#[test]
fn test_escape_static() {
assert_eq!(
template(r#"\ab"#),
vec![TemplateSegment::Static(Span::new(0, 3))]
);
assert_eq!(
template(r#"a\b"#),
vec![TemplateSegment::Static(Span::new(0, 3))]
);
assert_eq!(
template(r#"ab\"#),
vec![TemplateSegment::Static(Span::new(0, 3))]
);
assert_eq!(
template(r#"\'a'b"#),
vec![TemplateSegment::Static(Span::new(0, 5))]
);
assert_eq!(
template(r#"a\'b'"#),
vec![TemplateSegment::Static(Span::new(0, 5))]
);
assert_eq!(
template(r#"ab\''"#),
vec![TemplateSegment::Static(Span::new(0, 5))]
);
}
#[test]
fn test_escape_dynamic() {
assert_eq!(
template(r#"\{a}"#),
vec![
TemplateSegment::Escaper(Span::new(0, 1)),
TemplateSegment::Static(Span::new(1, 4))
]
);
assert_eq!(
template(r#"{\a}"#),
vec![
TemplateSegment::Delimiter(Span::new(0, 1)),
TemplateSegment::Dynamic(Span::new(1, 3)),
TemplateSegment::Delimiter(Span::new(3, 4))
]
);
assert_eq!(
template(r#"{a\}"#),
vec![TemplateSegment::Static(Span::new(0, 4))]
);
assert_eq!(
template(r#"{a\}}"#),
vec![
TemplateSegment::Delimiter(Span::new(0, 1)),
TemplateSegment::Dynamic(Span::new(1, 4)),
TemplateSegment::Delimiter(Span::new(4, 5))
]
);
assert_eq!(
template(r#"ax{b\'c}d"#),
vec![
TemplateSegment::Static(Span::new(0, 2)),
TemplateSegment::Delimiter(Span::new(2, 3)),
TemplateSegment::Dynamic(Span::new(3, 7)),
TemplateSegment::Delimiter(Span::new(7, 8)),
TemplateSegment::Static(Span::new(8, 9)),
]
);
assert_eq!(
template(r#"a\{b\'c}d"#),
vec![
TemplateSegment::Static(Span::new(0, 1)),
TemplateSegment::Escaper(Span::new(1, 2)),
TemplateSegment::Static(Span::new(2, 9)),
]
);
assert_eq!(
template(r#"\a\{\b\c\}"#),
vec![
TemplateSegment::Static(Span::new(0, 2)),
TemplateSegment::Escaper(Span::new(2, 3)),
TemplateSegment::Static(Span::new(3, 8)),
TemplateSegment::Escaper(Span::new(8, 9)),
TemplateSegment::Static(Span::new(9, 10)),
]
);
}
#[test]
fn test_braces() {
assert_eq!(
template("{{}}"),
vec![
TemplateSegment::Delimiter(Span::new(0, 1)),
TemplateSegment::Dynamic(Span::new(1, 3)),
TemplateSegment::Delimiter(Span::new(3, 4))
]
);
assert_eq!(
template("{{{}}}"),
vec![
TemplateSegment::Delimiter(Span::new(0, 1)),
TemplateSegment::Dynamic(Span::new(1, 5)),
TemplateSegment::Delimiter(Span::new(5, 6))
]
);
}
#[test]
fn test_strings() {
assert_eq!(
template(r#"a{b_"}"_c}d"#),
vec![
TemplateSegment::Static(Span::new(0, 1)),
TemplateSegment::Delimiter(Span::new(1, 2)),
TemplateSegment::Dynamic(Span::new(2, 9)),
TemplateSegment::Delimiter(Span::new(9, 10)),
TemplateSegment::Static(Span::new(10, 11)),
]
);
assert_eq!(
template(r#"a{b_'}'_c}d"#),
vec![
TemplateSegment::Static(Span::new(0, 1)),
TemplateSegment::Delimiter(Span::new(1, 2)),
TemplateSegment::Dynamic(Span::new(2, 9)),
TemplateSegment::Delimiter(Span::new(9, 10)),
TemplateSegment::Static(Span::new(10, 11)),
]
);
assert_eq!(
template(r#"a{b""}""c}d"#),
vec![
TemplateSegment::Static(Span::new(0, 1)),
TemplateSegment::Delimiter(Span::new(1, 2)),
TemplateSegment::Dynamic(Span::new(2, 5)),
TemplateSegment::Delimiter(Span::new(5, 6)),
TemplateSegment::Static(Span::new(6, 11)),
]
);
assert_eq!(
template(r#"a{b''}''c}d"#),
vec![
TemplateSegment::Static(Span::new(0, 1)),
TemplateSegment::Delimiter(Span::new(1, 2)),
TemplateSegment::Dynamic(Span::new(2, 5)),
TemplateSegment::Delimiter(Span::new(5, 6)),
TemplateSegment::Static(Span::new(6, 11)),
]
);
assert_eq!(
template(r#"a{b"}\""c}d"#),
vec![
TemplateSegment::Static(Span::new(0, 1)),
TemplateSegment::Delimiter(Span::new(1, 2)),
TemplateSegment::Dynamic(Span::new(2, 9)),
TemplateSegment::Delimiter(Span::new(9, 10)),
TemplateSegment::Static(Span::new(10, 11)),
]
);
assert_eq!(
template(r#"a{b'}\''c}d"#),
vec![
TemplateSegment::Static(Span::new(0, 1)),
TemplateSegment::Delimiter(Span::new(1, 2)),
TemplateSegment::Dynamic(Span::new(2, 9)),
TemplateSegment::Delimiter(Span::new(9, 10)),
TemplateSegment::Static(Span::new(10, 11)),
]
);
}
#[test]
fn test_misc() {
assert_eq!(
template(r#"a{b "c" d}e{[[{} "g"} "#),
vec![
TemplateSegment::Static(Span::new(0, 1)),
TemplateSegment::Delimiter(Span::new(1, 2)),
TemplateSegment::Dynamic(Span::new(2, 9)),
TemplateSegment::Delimiter(Span::new(9, 10)),
TemplateSegment::Static(Span::new(10, 11)),
TemplateSegment::Delimiter(Span::new(11, 12)),
TemplateSegment::Dynamic(Span::new(12, 20)),
TemplateSegment::Delimiter(Span::new(20, 21)),
TemplateSegment::Static(Span::new(21, 22)),
]
);
}
#[test]
fn test_has_paired_delimiters() {
assert_eq!(has_paired_delimiters(b""), false);
assert_eq!(has_paired_delimiters(b"{"), false);
assert_eq!(has_paired_delimiters(b"}"), false);
assert_eq!(has_paired_delimiters(b"}}"), false);
assert_eq!(has_paired_delimiters(b"{{"), false);
assert_eq!(has_paired_delimiters(b"}{"), false);
assert_eq!(has_paired_delimiters(b"{}"), true);
assert_eq!(has_paired_delimiters(b"{ }"), true);
assert_eq!(has_paired_delimiters(b"{1}"), true);
assert_eq!(has_paired_delimiters(b"}{}{"), true);
assert_eq!(has_paired_delimiters(b"{{}}"), true);
}
#[track_caller]
fn template(s: &str) -> Vec<TemplateSegment> {
super::split_template_delimiters(s.as_bytes())
}
}