1use fixed32::Fp;
7use seq_map::SeqMap;
8
9#[derive(Clone, Debug, PartialEq)]
10pub enum Node {
11 Text(String),
12 Element(Element),
13}
14
15#[derive(Clone, Debug, Eq, PartialEq)]
16pub enum ArgValue {
17 Int(i64),
18 Fixed(Fp),
19 Bool(bool),
20 Hex(String),
21 Keyword(String),
22 Str(String),
23}
24
25#[derive(Clone, Debug, PartialEq)]
26pub struct Element {
27 pub name: String,
28 pub args: SeqMap<String, ArgValue>,
29 pub children: Vec<Node>,
30}
31
32#[derive(Debug)]
33pub struct ParseError {
34 pub message: String,
35 pub line: usize,
36 pub column: usize,
37}
38
39pub struct Parser<'a> {
40 input: &'a [u8],
41 pos: usize,
42 line: usize,
43 col: usize,
44 pub errors: Vec<ParseError>,
45}
46
47impl<'a> Parser<'a> {
48 pub fn new(input: &'a str) -> Self {
49 Self {
50 input: input.as_bytes(),
51 pos: 0,
52 line: 1,
53 col: 1,
54 errors: Vec::new(),
55 }
56 }
57
58 fn peek(&self) -> Option<u8> {
59 self.input.get(self.pos).copied()
60 }
61
62 fn next(&mut self) -> Option<u8> {
63 let b = self.peek()?;
64 self.pos += 1;
65 if b == b'\n' {
66 self.line += 1;
67 self.col = 1;
68 } else {
69 self.col += 1;
70 }
71 Some(b)
72 }
73
74 fn eof(&self) -> bool {
75 self.pos >= self.input.len()
76 }
77
78 fn starts_with(&self, pat: &[u8]) -> bool {
79 self.input
80 .get(self.pos..)
81 .map_or(false, |r| r.starts_with(pat))
82 }
83
84 fn consume_ws(&mut self) {
85 while let Some(b) = self.peek() {
86 if b.is_ascii_whitespace() {
87 self.next();
88 } else {
89 break;
90 }
91 }
92 }
93
94 pub fn parse(&mut self) -> Vec<Node> {
95 self.parse_nodes(None)
96 }
97
98 fn parse_nodes(&mut self, stop_tag: Option<&str>) -> Vec<Node> {
99 let mut nodes = Vec::new();
100 while !self.eof() {
101 if let Some(tag) = stop_tag {
102 if self.starts_with(format!("[/{tag}]").as_bytes()) {
103 break;
104 }
105 }
106 if self.starts_with(b"[/") {
107 let (l, c) = (self.line, self.col);
108 self.errors.push(ParseError {
109 message: "Unexpected closing tag".into(),
110 line: l,
111 column: c,
112 });
113 while let Some(b) = self.next() {
114 if b == b']' {
115 break;
116 }
117 }
118 continue;
119 }
120
121 if self.peek() == Some(b'[') {
122 nodes.push(self.parse_element());
123 } else {
124 let txt = self.parse_text();
125 if !txt.is_empty() {
126 nodes.push(Node::Text(txt));
127 }
128 }
129 }
130 nodes
131 }
132
133 fn parse_text(&mut self) -> String {
134 let mut out = String::new();
135 while let Some(b) = self.peek() {
136 if b == b'[' {
138 break;
139 }
140
141 if b == b'\\' {
143 if let Some(next) = self.input.get(self.pos + 1) {
144 if *next == b'[' || *next == b']' {
145 self.next();
146 let escaped = self.next().unwrap();
147 out.push(escaped as char);
148 continue;
149 }
150 }
151 }
152
153 out.push(self.next().unwrap() as char);
154 }
155 out
156 }
157
158 fn parse_element(&mut self) -> Node {
159 let (l, c) = (self.line, self.col);
160 self.next(); self.consume_ws();
164 let name_start = self.pos;
165 while let Some(b) = self.peek() {
166 if b.is_ascii_alphanumeric() {
167 self.next();
168 } else {
169 break;
170 }
171 }
172 let name = String::from_utf8_lossy(&self.input[name_start..self.pos]).to_string();
173 if name.is_empty() {
174 self.errors.push(ParseError {
175 message: "Missing tag name".into(),
176 line: l,
177 column: c,
178 });
179 }
180
181 let mut args = SeqMap::new();
183 loop {
184 self.consume_ws();
185 match self.peek() {
186 Some(b'=') | Some(b']') | None => break,
187 Some(_) => {
188 let val = self.parse_arg_value();
189 self.consume_ws();
190 if self.peek() == Some(b'=') {
191 if let ArgValue::Keyword(k) = val.clone() {
192 self.next();
193 self.consume_ws();
194 let v2 = self.parse_arg_value();
195 let _ = args.insert(k, v2);
196 } else {
197 self.errors.push(ParseError {
198 message: "Invalid arg name".into(),
199 line: l,
200 column: c,
201 });
202 }
203 } else {
204 let _ = args.insert("value".into(), val);
205 }
206 }
207 }
208 }
209 if self.next() != Some(b']') {
210 self.errors.push(ParseError {
211 message: format!("Unterminated [{}] tag", name),
212 line: l,
213 column: c,
214 });
215 }
216
217 let children = self.parse_nodes(Some(&name));
219
220 if self.starts_with(format!("[/{name}]").as_bytes()) {
222 for _ in 0..(name.len() + 3) {
223 self.next();
224 }
225 } else {
226 self.errors.push(ParseError {
227 message: format!("Missing closing tag [/{name}]"),
228 line: self.line,
229 column: self.col,
230 });
231 }
232
233 Node::Element(Element {
234 name,
235 args,
236 children,
237 })
238 }
239
240 fn parse_arg_value(&mut self) -> ArgValue {
241 self.consume_ws();
242
243 if self.peek() == Some(b'"') {
245 self.next();
246 let start = self.pos;
247 while let Some(b) = self.peek() {
248 if b == b'"' {
249 break;
250 }
251 self.next();
252 }
253 let s = String::from_utf8_lossy(&self.input[start..self.pos]).to_string();
254 self.next();
255 return ArgValue::Str(s);
256 }
257
258 if self.peek() == Some(b'#') {
260 let start = self.pos;
261 self.next();
262 while let Some(b) = self.peek() {
263 if (b as char).is_ascii_hexdigit() {
264 self.next();
265 } else {
266 break;
267 }
268 }
269 return ArgValue::Hex(
270 String::from_utf8_lossy(&self.input[start..self.pos]).to_string(),
271 );
272 }
273
274 let start = self.pos;
276 while let Some(b) = self.peek() {
277 if b.is_ascii_whitespace() || b == b']' || b == b'=' {
278 break;
279 }
280 self.next();
281 }
282 let tok = String::from_utf8_lossy(&self.input[start..self.pos]).to_string();
283 if tok == "true" {
284 ArgValue::Bool(true)
285 } else if tok == "false" {
286 ArgValue::Bool(false)
287 } else if let Ok(i) = tok.parse::<i64>() {
288 ArgValue::Int(i)
289 } else if let Ok(f) = tok.parse::<f32>() {
290 let a: Fp = f.into();
291 ArgValue::Fixed(a)
292 } else {
293 ArgValue::Keyword(tok)
294 }
295 }
296}
297
298pub fn parse(input: &str) -> Vec<Node> {
299 Parser::new(input).parse()
300}