1use crate::Val;
2use alloc::string::String;
3use alloc::string::ToString;
4use alloc::vec::Vec;
5use core::str::Chars;
6
7pub type ID = String;
8
9#[derive(Debug, Default, Copy, Clone, PartialEq)]
10pub enum TextEffect {
11 #[default]
13 None,
14 Wavy,
16 Shaky,
18 Rainbow,
20 Color(u8),
22}
23
24#[derive(Debug, Clone, PartialEq)]
25pub enum Tag {
26 Br,
28 Pg,
30 Eff(TextEffect),
32 End,
34 Say(Expr),
36 DrwT(ID),
38 DrwS(ID),
40 DrwI(ID),
42 Pal(ID),
44 Ava(ID),
46 Exit(ID, u8, u8),
48 Set(String, Expr),
50 Unknown(String, String),
52}
53
54#[derive(Debug, Clone, PartialEq)]
55pub enum Expr {
56 SimpleExpr(SimpleExpr),
57 BinOp(BinOp, SimpleExpr, SimpleExpr),
58}
59
60#[derive(Debug, Copy, Clone, PartialEq, Eq)]
61pub enum BinOp {
62 Mul,
63 Div,
64 Add,
65 Sub,
66 Lt,
67 Gt,
68 Lte,
69 Gte,
70 Eq,
71}
72
73#[derive(Debug, Clone, PartialEq)]
74pub enum SimpleExpr {
75 Var(String),
76 Item(String),
77 Val(Val),
78}
79
80#[derive(Debug, Clone, PartialEq)]
81pub enum Token {
82 OpenTag(Tag),
83 CloseTag(Tag),
84 Word(String),
85}
86
87pub struct Tokenizer<'a> {
88 buffer: Chars<'a>,
89 stash: Option<char>,
90}
91
92impl<'a> Tokenizer<'a> {
93 pub fn new(text: &'a str) -> Self {
94 Self {
95 buffer: text.chars(),
96 stash: None,
97 }
98 }
99}
100
101impl<'a> Iterator for Tokenizer<'a> {
102 type Item = Token;
103
104 fn next(&mut self) -> Option<Self::Item> {
105 let mut word = String::new();
106 let mut found_letter = false;
107 let mut open_tags: u8 = 0;
108 loop {
109 let ch = if let Some(stash) = self.stash.take() {
110 stash
111 } else if let Some(ch) = self.buffer.next() {
112 ch
113 } else {
114 break;
115 };
116 word.push(ch);
117 match ch {
118 '\n' => return Some(Token::OpenTag(Tag::Br)),
119 '{' => {
120 if open_tags == 0 && found_letter {
121 self.stash = Some('{');
122 word.pop();
123 break;
124 }
125 open_tags += 1;
126 }
127 '}' => {
128 if open_tags != 0 {
129 open_tags -= 1;
130 if open_tags == 0 {
131 return Some(parse_tag(&word));
132 } else {
133 found_letter = true
134 }
135 }
136 }
137 '\t' | '\x0C' | '\r' | ' ' => {
138 if open_tags == 0 && found_letter {
139 break;
140 }
141 }
142 _ => found_letter = true,
143 }
144 }
145 if word.is_empty() {
146 return None;
147 }
148 Some(Token::Word(word))
149 }
150}
151
152fn parse_tag(word: &str) -> Token {
153 let word = &word[..word.len() - 1]; let mut word = &word[1..]; word = word.trim_ascii();
156 let is_closing = word.starts_with('/');
157 if is_closing {
158 word = &word[1..];
159 word = word.trim_ascii();
160 }
161 let tag = parse_tag_value(word);
162 if is_closing {
163 Token::CloseTag(tag)
164 } else {
165 Token::OpenTag(tag)
166 }
167}
168
169fn parse_tag_value(word: &str) -> Tag {
170 let (name, args) = word.split_once(' ').unwrap_or((word, ""));
171 let args = args.trim_ascii();
172 if args.is_empty() {
173 parse_tag_without_args(name)
174 } else {
175 parse_tag_with_args(name, args)
176 }
177}
178
179fn parse_tag_with_args(name: &str, args: &str) -> Tag {
180 if args.starts_with('=') {
181 return parse_assign(name, args);
182 }
183 match name {
184 "clr" => match args {
185 "0" => Tag::Eff(TextEffect::Color(1)),
186 "1" => Tag::Eff(TextEffect::Color(2)),
187 "2" => Tag::Eff(TextEffect::Color(3)),
188 _ => Tag::Eff(TextEffect::Color(1)),
189 },
190 "say" | "print" => Tag::Say(parse_expr(args)),
191 "drwt" | "printTile" => Tag::DrwT(unquote(args).to_string()),
192 "drws" | "printSprite" => Tag::DrwS(unquote(args).to_string()),
193 "drwi" | "printItem" => Tag::DrwI(unquote(args).to_string()),
194 "ava" => Tag::Ava(unquote(args).to_string()),
195 "pal" => Tag::Pal(unquote(args).to_string()),
196 "exit" => {
197 let (room, x, y) = parse_exit_args(args);
198 let room = room.to_string();
199 Tag::Exit(room, x, y)
200 }
201 _ => Tag::Unknown(name.to_string(), args.to_string()),
202 }
203}
204
205fn parse_tag_without_args(name: &str) -> Tag {
206 match name {
207 "br" => Tag::Br,
208 "pg" => Tag::Pg,
209 "clr1" => Tag::Eff(TextEffect::Color(1)),
210 "clr2" => Tag::Eff(TextEffect::Color(2)),
211 "clr3" => Tag::Eff(TextEffect::Color(3)),
212 "wvy" => Tag::Eff(TextEffect::Wavy),
213 "shk" => Tag::Eff(TextEffect::Shaky),
214 "rbw" => Tag::Eff(TextEffect::Rainbow),
215 "end" => Tag::End,
216 _ => Tag::Unknown(name.to_string(), "".to_string()),
217 }
218}
219
220fn parse_assign(name: &str, args: &str) -> Tag {
221 let args = &args[1..];
222 let expr = parse_expr(args);
223 Tag::Set(name.to_string(), expr)
224}
225
226fn parse_expr(args: &str) -> Expr {
227 let args = args.trim_ascii();
228 let args = args.replace("item ", "item@");
229 let parts: Vec<_> = args.split_ascii_whitespace().collect();
230 if let Some(part) = unwrap_vec_1(&parts) {
231 Expr::SimpleExpr(parse_simple_expr(part))
232 } else if let Some((left, op, right)) = unwrap_vec_3(&parts) {
233 let left = parse_simple_expr(left);
234 let right = parse_simple_expr(right);
235 let op = op.trim_ascii();
236 let op = match op {
237 "*" => BinOp::Mul,
238 "/" => BinOp::Div,
239 "+" => BinOp::Add,
240 "-" => BinOp::Sub,
241 "<" => BinOp::Lt,
242 ">" => BinOp::Gt,
243 "<=" => BinOp::Lte,
244 ">=" => BinOp::Gte,
245 "==" => BinOp::Eq,
246 _ => {
247 let val = Val::S(args.to_string());
248 return Expr::SimpleExpr(SimpleExpr::Val(val));
249 }
250 };
251 Expr::BinOp(op, left, right)
252 } else {
253 let val = Val::S(args.to_string());
254 Expr::SimpleExpr(SimpleExpr::Val(val))
255 }
256}
257
258fn parse_simple_expr(part: &str) -> SimpleExpr {
259 let part = part.trim_ascii();
260 if let Some(name) = part.strip_prefix("{item@") {
261 let name = name.strip_suffix('}').unwrap_or(name);
262 let name = name.trim_ascii();
263 let name = unquote(name);
264 return SimpleExpr::Item(name.to_string());
265 }
266 if part == "true" {
267 return SimpleExpr::Val(Val::I(1));
268 }
269 if part == "false" {
270 return SimpleExpr::Val(Val::I(0));
271 }
272 if let Ok(i) = part.parse::<i16>() {
273 return SimpleExpr::Val(Val::I(i));
274 }
275 if let Ok(f) = part.parse::<f32>() {
276 return SimpleExpr::Val(Val::F(f));
277 }
278 if part.starts_with('"') {
279 return SimpleExpr::Val(Val::S(unquote(part).to_string()));
280 }
281 if is_var(part) {
282 return SimpleExpr::Var(part.to_string());
283 }
284 SimpleExpr::Val(Val::S(part.to_string()))
285}
286
287fn is_var(part: &str) -> bool {
288 let mut first = true;
289 for ch in part.chars() {
290 if !first && ch.is_ascii_digit() {
291 return false;
292 }
293 first = false;
294 if ch.is_ascii_alphanumeric() {
295 continue;
296 }
297 if ch == '_' {
298 continue;
299 }
300 return false;
301 }
302 true
303}
304
305fn parse_exit_args(args: &str) -> (&str, u8, u8) {
306 let args = unquote(args);
307 let (room, args) = args.split_once(',').unwrap_or((args, "0,0"));
308 let room = unquote(room);
309 let (x, y) = args.split_once(',').unwrap_or(("0", "0"));
310 let x = x.trim_ascii();
311 let y = y.trim_ascii();
312 let x: u8 = x.parse().unwrap_or_default();
313 let y: u8 = y.parse().unwrap_or_default();
314 (room, x, y)
315}
316
317fn unquote(v: &str) -> &str {
318 let n_quotes = v.chars().filter(|ch| *ch == '"').count();
319 if n_quotes != 2 {
320 return v;
321 }
322 if v.starts_with('"') && v.ends_with('"') {
323 let v = &v[1..];
324 &v[..v.len() - 1]
325 } else {
326 v
327 }
328}
329
330fn unwrap_vec_1<'a>(items: &[&'a str]) -> Option<&'a str> {
331 if items.len() != 1 {
332 return None;
333 }
334 Some(items[0])
335}
336
337fn unwrap_vec_3<'a>(items: &[&'a str]) -> Option<(&'a str, &'a str, &'a str)> {
338 if items.len() != 3 {
339 return None;
340 }
341 Some((items[0], items[1], items[2]))
342}