rusty_handlebars_parser/
expression.rs1use std::{cmp::min, fmt::Display};
27
28use crate::error::{Result, ParseError};
29
30#[derive(Debug, Clone, Copy)]
32pub enum ExpressionType{
33 Comment, HtmlEscaped, Raw, Open, Close, Escaped
35}
36
37#[derive(Debug, Clone, Copy)]
39pub struct Expression<'a>{
40 pub expression_type: ExpressionType,
42 pub prefix: &'a str,
44 pub content: &'a str,
46 pub postfix: &'a str,
48 pub raw: &'a str
50}
51
52#[inline]
54fn nibble(src: &str, start: usize, len: usize) -> Result<usize>{
55 let end = start + len;
56 if end >= src.len(){
57 return Err(ParseError::unclosed(src));
58 }
59 Ok(end)
60}
61
62impl<'a> Expression<'a>{
63 fn close(expression_type: ExpressionType, preffix: &'a str, start: &'a str, end: &'static str) -> Result<Self>{
65 match start.find(end){
66 Some(mut pos) => {
67 if pos == 0{
68 return Err(ParseError { message: format!("empty block near {}", preffix) });
69 }
70 let mut postfix = &start[pos + end.len() ..];
71 if &start[pos - 1 .. pos] == "~"{
72 postfix = postfix.trim_start();
73 pos -= 1;
74 }
75 Ok(Self { expression_type, prefix: preffix, content: &start[.. pos], postfix, raw: &start[.. pos + end.len()] })
76 },
77 None => Err(ParseError::unclosed(preffix))
78 }
79 }
80
81 fn check_comment(preffix: &'a str, start: &'a str) -> Result<Self>{
83 if let Some(pos) = start.find("--"){
84 if pos == 0{
85 return Self::close(ExpressionType::Comment, preffix, &start[2 ..], "--}}");
86 }
87 }
88 Self::close(ExpressionType::Comment, preffix, start, "}}")
89 }
90
91 fn find_closing_escape(open: Expression<'a>) -> Result<Self>{
93 let mut postfix = open.postfix;
94 let mut from: usize = 0;
95 loop{
96 let candidate = postfix.find("{{{{/").ok_or(ParseError::unclosed(&open.raw))?;
97 let start = candidate + 5;
98 let remains = &postfix[start ..];
99 let close = remains.find("}}}}").ok_or(ParseError::unclosed(&open.raw))?;
100 let end = start + close + 4;
101 if &remains[.. close] == open.content{
102 return Ok(Self{
103 expression_type: ExpressionType::Escaped,
104 prefix: open.prefix,
105 content: &open.postfix[.. from + candidate],
106 postfix: &postfix[end ..],
107 raw: open.raw
108 })
109 }
110 from += end;
111 postfix = &postfix[from ..];
112 }
113 }
114
115 pub fn from(src: &'a str) -> Result<Option<Self>>{
117 match src.find("{{"){
118 Some(start) => {
119 let mut second = nibble(src, start, 3)?;
120 if start > 0 && &src[start - 1 .. start] == "\\"{
121 return Ok(Some(Self::close(ExpressionType::Escaped, &src[.. start - 1], &src[second - 1 ..], "}}")?));
122 }
123 let mut prefix = &src[.. start];
124 let mut marker = &src[start + 2 .. second];
125 if marker == "~"{
126 prefix = prefix.trim_end();
127 second = nibble(src, second, 1)?;
128 marker = &src[start + 3 .. second];
129 }
130 Ok(Some(match marker{
131 "{" => {
132 let next = nibble(src, second, 1)?;
133 let char = &src[second .. next];
134 if char == "{"{
135 second = next;
136 let next = nibble(src, second, 1)?;
137 if &src[second .. next] == "~"{
138 second = next;
139 prefix = prefix.trim_end();
140 }
141 return Ok(Some(Self::find_closing_escape(Self::close(ExpressionType::Escaped, prefix, &src[second ..], "}}}}")?)?));
142 }
143 if char == "~"{
144 second = next;
145 prefix = prefix.trim_end();
146 }
147 Self::close(ExpressionType::Raw, prefix, &src[second ..], "}}}")?
148 },
149 "!" => Self::check_comment(prefix, &src[second ..])?,
150 "#" => Self::close(ExpressionType::Open, prefix, &src[second ..], "}}")?,
151 "/" => Self::close(ExpressionType::Close, prefix, &src[second ..], "}}")?,
152 _ => Self::close(ExpressionType::HtmlEscaped, prefix, &src[second - 1 ..], "}}")?
153 }))
154 },
155 None => Ok(None)
156 }
157 }
158
159 pub fn next(&self) -> Result<Option<Self>>{
161 Self::from(self.postfix)
162 }
163
164 pub fn around(&self) -> &str{
166 let len = self.raw.len();
167 if len == 0{
168 return self.raw;
169 }
170 let start = self.prefix.len();
171 let end = start + self.content.len() + 16;
172 return &self.raw[min(len - 1, if start > 16{ start - 16 } else {0}) .. min(self.raw.len(), end)];
173 }
174}
175
176impl<'a> Display for Expression<'a>{
177 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178 f.write_str(self.raw)
179 }
180}