1use std::ops::Range;
2
3use crate::{
4 error::ExpressionError,
5 expr::ast::{
6 BinaryOp, BuiltinFn, ExprNode, Expression, ExpressionId, ExpressionReference,
7 ExpressionValue, GlobalVar, PropertyPath, UnaryOp,
8 },
9 node::NodeId,
10};
11
12#[derive(Debug, Clone, PartialEq)]
13enum TokenKind {
14 Identifier(String),
15 Number(f64),
16 String(String),
17 LParen,
18 RParen,
19 Comma,
20 Dot,
21 Plus,
22 Minus,
23 Star,
24 Slash,
25 Percent,
26 Bang,
27 Gt,
28 Lt,
29 Gte,
30 Lte,
31 EqEq,
32 Neq,
33 AndAnd,
34 OrOr,
35 Eof,
36}
37
38#[derive(Debug, Clone, PartialEq)]
39struct Token {
40 kind: TokenKind,
41 span: Range<usize>,
42}
43
44struct Lexer<'a> {
45 source: &'a str,
46 position: usize,
47}
48
49impl<'a> Lexer<'a> {
50 fn new(source: &'a str) -> Self {
51 Self {
52 source,
53 position: 0,
54 }
55 }
56
57 fn next_token(&mut self) -> Result<Token, ExpressionError> {
58 self.skip_whitespace();
59 let start = self.position;
60 let Some(ch) = self.peek_char() else {
61 return Ok(Token {
62 kind: TokenKind::Eof,
63 span: start..start,
64 });
65 };
66
67 let kind = match ch {
68 '(' => {
69 self.bump_char();
70 TokenKind::LParen
71 }
72 ')' => {
73 self.bump_char();
74 TokenKind::RParen
75 }
76 ',' => {
77 self.bump_char();
78 TokenKind::Comma
79 }
80 '.' => {
81 self.bump_char();
82 TokenKind::Dot
83 }
84 '+' => {
85 self.bump_char();
86 TokenKind::Plus
87 }
88 '-' => {
89 self.bump_char();
90 TokenKind::Minus
91 }
92 '*' => {
93 self.bump_char();
94 TokenKind::Star
95 }
96 '/' => {
97 self.bump_char();
98 TokenKind::Slash
99 }
100 '%' => {
101 self.bump_char();
102 TokenKind::Percent
103 }
104 '!' => {
105 self.bump_char();
106 if self.peek_char() == Some('=') {
107 self.bump_char();
108 TokenKind::Neq
109 } else {
110 TokenKind::Bang
111 }
112 }
113 '=' => {
114 self.bump_char();
115 if self.peek_char() == Some('=') {
116 self.bump_char();
117 TokenKind::EqEq
118 } else {
119 return Err(ExpressionError::Parse {
120 path: None,
121 details: "unexpected `=`; use `==` for equality".to_string(),
122 });
123 }
124 }
125 '>' => {
126 self.bump_char();
127 if self.peek_char() == Some('=') {
128 self.bump_char();
129 TokenKind::Gte
130 } else {
131 TokenKind::Gt
132 }
133 }
134 '<' => {
135 self.bump_char();
136 if self.peek_char() == Some('=') {
137 self.bump_char();
138 TokenKind::Lte
139 } else {
140 TokenKind::Lt
141 }
142 }
143 '&' => {
144 self.bump_char();
145 if self.peek_char() == Some('&') {
146 self.bump_char();
147 TokenKind::AndAnd
148 } else {
149 return Err(ExpressionError::Parse {
150 path: None,
151 details: "unexpected `&`; use `&&`".to_string(),
152 });
153 }
154 }
155 '|' => {
156 self.bump_char();
157 if self.peek_char() == Some('|') {
158 self.bump_char();
159 TokenKind::OrOr
160 } else {
161 return Err(ExpressionError::Parse {
162 path: None,
163 details: "unexpected `|`; use `||`".to_string(),
164 });
165 }
166 }
167 '\'' | '"' => TokenKind::String(self.read_string(ch)?),
168 c if c.is_ascii_digit() => TokenKind::Number(self.read_number()?),
169 c if is_ident_start(c) => TokenKind::Identifier(self.read_identifier()),
170 other => {
171 return Err(ExpressionError::Parse {
172 path: None,
173 details: format!("unexpected character `{other}` at byte {start}"),
174 });
175 }
176 };
177
178 Ok(Token {
179 kind,
180 span: start..self.position,
181 })
182 }
183
184 fn skip_whitespace(&mut self) {
185 while matches!(self.peek_char(), Some(ch) if ch.is_whitespace()) {
186 self.bump_char();
187 }
188 }
189
190 fn read_string(&mut self, quote: char) -> Result<String, ExpressionError> {
191 let start = self.position;
192 self.bump_char();
193 let mut output = String::new();
194 loop {
195 let Some(ch) = self.peek_char() else {
196 return Err(ExpressionError::Parse {
197 path: None,
198 details: format!("unterminated string literal starting at byte {start}"),
199 });
200 };
201 self.bump_char();
202 match ch {
203 c if c == quote => return Ok(output),
204 '\\' => {
205 let Some(escaped) = self.peek_char() else {
206 return Err(ExpressionError::Parse {
207 path: None,
208 details: "unterminated escape sequence".to_string(),
209 });
210 };
211 self.bump_char();
212 let decoded = match escaped {
213 '\\' => '\\',
214 '\'' => '\'',
215 '"' => '"',
216 'n' => '\n',
217 't' => '\t',
218 'r' => '\r',
219 other => other,
220 };
221 output.push(decoded);
222 }
223 other => output.push(other),
224 }
225 }
226 }
227
228 fn read_number(&mut self) -> Result<f64, ExpressionError> {
229 let start = self.position;
230 while matches!(self.peek_char(), Some(ch) if ch.is_ascii_digit()) {
231 self.bump_char();
232 }
233 if self.peek_char() == Some('.') {
234 self.bump_char();
235 while matches!(self.peek_char(), Some(ch) if ch.is_ascii_digit()) {
236 self.bump_char();
237 }
238 }
239
240 self.source[start..self.position]
241 .parse::<f64>()
242 .map_err(|error| ExpressionError::Parse {
243 path: None,
244 details: format!("invalid number at byte {start}: {error}"),
245 })
246 }
247
248 fn read_identifier(&mut self) -> String {
249 let start = self.position;
250 self.bump_char();
251 while matches!(self.peek_char(), Some(ch) if is_ident_continue(ch)) {
252 self.bump_char();
253 }
254 self.source[start..self.position].to_string()
255 }
256
257 fn peek_char(&self) -> Option<char> {
258 self.source[self.position..].chars().next()
259 }
260
261 fn bump_char(&mut self) -> Option<char> {
262 let ch = self.peek_char()?;
263 self.position += ch.len_utf8();
264 Some(ch)
265 }
266}
267
268fn is_ident_start(ch: char) -> bool {
269 ch == '_' || ch.is_ascii_alphabetic()
270}
271
272fn is_ident_continue(ch: char) -> bool {
273 is_ident_start(ch) || ch.is_ascii_digit()
274}
275
276pub fn parse_expression(source: &str) -> Result<Expression, ExpressionError> {
277 let mut parser = Parser::new(source);
278 parser.parse()
279}
280
281struct Parser<'a> {
282 source: &'a str,
283 tokens: Vec<Token>,
284 position: usize,
285 references: Vec<ExpressionReference>,
286}
287
288impl<'a> Parser<'a> {
289 fn new(source: &'a str) -> Self {
290 Self {
291 source,
292 tokens: Vec::new(),
293 position: 0,
294 references: Vec::new(),
295 }
296 }
297
298 fn parse(&mut self) -> Result<Expression, ExpressionError> {
299 self.tokenize()?;
300 let ast = self.parse_or()?;
301 self.expect(TokenExpectation::Eof)?;
302 Ok(Expression {
303 id: ExpressionId(0),
304 ast,
305 references: self.references.clone(),
306 source: self.source.to_string(),
307 })
308 }
309
310 fn tokenize(&mut self) -> Result<(), ExpressionError> {
311 let mut lexer = Lexer::new(self.source);
312 loop {
313 let token = lexer.next_token()?;
314 let is_eof = matches!(token.kind, TokenKind::Eof);
315 self.tokens.push(token);
316 if is_eof {
317 break;
318 }
319 }
320 Ok(())
321 }
322
323 fn parse_or(&mut self) -> Result<ExprNode, ExpressionError> {
324 let mut node = self.parse_and()?;
325 while self.match_token(|kind| matches!(kind, TokenKind::OrOr)) {
326 let rhs = self.parse_and()?;
327 node = ExprNode::Binary(Box::new(node), BinaryOp::Or, Box::new(rhs));
328 }
329 Ok(node)
330 }
331
332 fn parse_and(&mut self) -> Result<ExprNode, ExpressionError> {
333 let mut node = self.parse_equality()?;
334 while self.match_token(|kind| matches!(kind, TokenKind::AndAnd)) {
335 let rhs = self.parse_equality()?;
336 node = ExprNode::Binary(Box::new(node), BinaryOp::And, Box::new(rhs));
337 }
338 Ok(node)
339 }
340
341 fn parse_equality(&mut self) -> Result<ExprNode, ExpressionError> {
342 let mut node = self.parse_comparison()?;
343 loop {
344 let op = if self.match_token(|kind| matches!(kind, TokenKind::EqEq)) {
345 Some(BinaryOp::Eq)
346 } else if self.match_token(|kind| matches!(kind, TokenKind::Neq)) {
347 Some(BinaryOp::Neq)
348 } else {
349 None
350 };
351 let Some(op) = op else {
352 break;
353 };
354 let rhs = self.parse_comparison()?;
355 node = ExprNode::Binary(Box::new(node), op, Box::new(rhs));
356 }
357 Ok(node)
358 }
359
360 fn parse_comparison(&mut self) -> Result<ExprNode, ExpressionError> {
361 let mut node = self.parse_term()?;
362 loop {
363 let op = if self.match_token(|kind| matches!(kind, TokenKind::Gte)) {
364 Some(BinaryOp::Gte)
365 } else if self.match_token(|kind| matches!(kind, TokenKind::Lte)) {
366 Some(BinaryOp::Lte)
367 } else if self.match_token(|kind| matches!(kind, TokenKind::Gt)) {
368 Some(BinaryOp::Gt)
369 } else if self.match_token(|kind| matches!(kind, TokenKind::Lt)) {
370 Some(BinaryOp::Lt)
371 } else {
372 None
373 };
374 let Some(op) = op else {
375 break;
376 };
377 let rhs = self.parse_term()?;
378 node = ExprNode::Binary(Box::new(node), op, Box::new(rhs));
379 }
380 Ok(node)
381 }
382
383 fn parse_term(&mut self) -> Result<ExprNode, ExpressionError> {
384 let mut node = self.parse_factor()?;
385 loop {
386 let op = if self.match_token(|kind| matches!(kind, TokenKind::Plus)) {
387 Some(BinaryOp::Add)
388 } else if self.match_token(|kind| matches!(kind, TokenKind::Minus)) {
389 Some(BinaryOp::Sub)
390 } else {
391 None
392 };
393 let Some(op) = op else {
394 break;
395 };
396 let rhs = self.parse_factor()?;
397 node = ExprNode::Binary(Box::new(node), op, Box::new(rhs));
398 }
399 Ok(node)
400 }
401
402 fn parse_factor(&mut self) -> Result<ExprNode, ExpressionError> {
403 let mut node = self.parse_unary()?;
404 loop {
405 let op = if self.match_token(|kind| matches!(kind, TokenKind::Star)) {
406 Some(BinaryOp::Mul)
407 } else if self.match_token(|kind| matches!(kind, TokenKind::Slash)) {
408 Some(BinaryOp::Div)
409 } else if self.match_token(|kind| matches!(kind, TokenKind::Percent)) {
410 Some(BinaryOp::Mod)
411 } else {
412 None
413 };
414 let Some(op) = op else {
415 break;
416 };
417 let rhs = self.parse_unary()?;
418 node = ExprNode::Binary(Box::new(node), op, Box::new(rhs));
419 }
420 Ok(node)
421 }
422
423 fn parse_unary(&mut self) -> Result<ExprNode, ExpressionError> {
424 if self.match_token(|kind| matches!(kind, TokenKind::Bang)) {
425 return Ok(ExprNode::Unary(UnaryOp::Not, Box::new(self.parse_unary()?)));
426 }
427 if self.match_token(|kind| matches!(kind, TokenKind::Minus)) {
428 return Ok(ExprNode::Unary(UnaryOp::Neg, Box::new(self.parse_unary()?)));
429 }
430 self.parse_primary()
431 }
432
433 fn parse_primary(&mut self) -> Result<ExprNode, ExpressionError> {
434 let token = self.peek().cloned().ok_or(ExpressionError::Parse {
435 path: None,
436 details: "unexpected end of expression".to_string(),
437 })?;
438 match token.kind {
439 TokenKind::Number(value) => {
440 self.position += 1;
441 Ok(ExprNode::Literal(ExpressionValue::Number(value)))
442 }
443 TokenKind::String(value) => {
444 self.position += 1;
445 Ok(ExprNode::Literal(ExpressionValue::String(value)))
446 }
447 TokenKind::Identifier(identifier) => {
448 self.position += 1;
449 self.parse_identifier(identifier)
450 }
451 TokenKind::LParen => {
452 self.position += 1;
453 let expr = self.parse_or()?;
454 self.expect(TokenExpectation::RParen)?;
455 Ok(expr)
456 }
457 _ => Err(ExpressionError::Parse {
458 path: None,
459 details: format!("expected expression at byte {}", token.span.start),
460 }),
461 }
462 }
463
464 fn parse_identifier(&mut self, identifier: String) -> Result<ExprNode, ExpressionError> {
465 let mut dotted_segments = vec![identifier.clone()];
466 while self.match_token(|kind| matches!(kind, TokenKind::Dot)) {
467 let token = self.peek().cloned().ok_or(ExpressionError::Parse {
468 path: None,
469 details: "expected identifier after `.`".to_string(),
470 })?;
471 match token.kind {
472 TokenKind::Identifier(next_segment) => {
473 self.position += 1;
474 dotted_segments.push(next_segment);
475 }
476 _ => {
477 return Err(ExpressionError::Parse {
478 path: None,
479 details: "expected identifier after `.`".to_string(),
480 });
481 }
482 }
483 }
484 if dotted_segments.len() > 1 {
485 self.references.push(ExpressionReference::SymbolicPath {
486 segments: dotted_segments.clone(),
487 });
488 return Ok(ExprNode::SymbolicPath(dotted_segments));
489 }
490
491 if identifier == "true" {
492 return Ok(ExprNode::Literal(ExpressionValue::Boolean(true)));
493 }
494 if identifier == "false" {
495 return Ok(ExprNode::Literal(ExpressionValue::Boolean(false)));
496 }
497
498 if self.match_token(|kind| matches!(kind, TokenKind::LParen)) {
499 let mut args = Vec::new();
500 if !self.match_token(|kind| matches!(kind, TokenKind::RParen)) {
501 loop {
502 args.push(self.parse_or()?);
503 if self.match_token(|kind| matches!(kind, TokenKind::Comma)) {
504 continue;
505 }
506 self.expect(TokenExpectation::RParen)?;
507 break;
508 }
509 }
510
511 if identifier == "if" {
512 if args.len() != 3 {
513 return Err(ExpressionError::Parse {
514 path: None,
515 details: "if(cond, then, else) requires exactly 3 arguments".to_string(),
516 });
517 }
518 return Ok(ExprNode::Conditional(
519 Box::new(args[0].clone()),
520 Box::new(args[1].clone()),
521 Box::new(args[2].clone()),
522 ));
523 }
524
525 if identifier == "node" {
526 return self.parse_node_reference(args);
527 }
528
529 let builtin = builtin_for_name(&identifier).ok_or(ExpressionError::Parse {
530 path: None,
531 details: format!("unknown function `{identifier}`"),
532 })?;
533 return Ok(ExprNode::Builtin(builtin, args));
534 }
535
536 Ok(ExprNode::Global(match identifier.as_str() {
537 "frame" => GlobalVar::Frame,
538 "time" => GlobalVar::Time,
539 "fps" => GlobalVar::Fps,
540 "width" => GlobalVar::Width,
541 "height" => GlobalVar::Height,
542 _ => GlobalVar::Custom(identifier),
543 }))
544 }
545
546 fn parse_node_reference(&mut self, args: Vec<ExprNode>) -> Result<ExprNode, ExpressionError> {
547 if args.is_empty() || args.len() > 2 {
548 return Err(ExpressionError::Parse {
549 path: None,
550 details: "node(id) or node(id, property_path) requires 1 or 2 arguments"
551 .to_string(),
552 });
553 }
554
555 let node_id = match &args[0] {
556 ExprNode::Literal(ExpressionValue::Number(value)) => {
557 if *value < 0.0 || value.fract() != 0.0 {
558 return Err(ExpressionError::Parse {
559 path: None,
560 details: "node(id, ..) expects an unsigned integer id".to_string(),
561 });
562 }
563 NodeId(*value as u64)
564 }
565 _ => {
566 return Err(ExpressionError::Parse {
567 path: None,
568 details: "node(id, ..) first argument must be a numeric node id".to_string(),
569 });
570 }
571 };
572
573 if args.len() == 1 {
574 self.references.push(ExpressionReference::Node { node_id });
575 return Ok(ExprNode::Node(node_id));
576 }
577
578 let property_path = match &args[1] {
579 ExprNode::Literal(ExpressionValue::String(value)) => PropertyPath::new(value.clone()),
580 _ => {
581 return Err(ExpressionError::Parse {
582 path: None,
583 details: "node(.., property_path) second argument must be a string".to_string(),
584 });
585 }
586 };
587
588 self.references.push(ExpressionReference::PropertyValue {
589 node_id,
590 property_path: property_path.clone(),
591 });
592
593 Ok(ExprNode::PropertyValue(node_id, property_path))
594 }
595
596 fn match_token(&mut self, predicate: impl Fn(&TokenKind) -> bool) -> bool {
597 match self.peek() {
598 Some(token) if predicate(&token.kind) => {
599 self.position += 1;
600 true
601 }
602 _ => false,
603 }
604 }
605
606 fn expect(&mut self, expected: TokenExpectation) -> Result<(), ExpressionError> {
607 let token = self.peek().ok_or(ExpressionError::Parse {
608 path: None,
609 details: format!("expected {}", expected.describe()),
610 })?;
611
612 let matches = match expected {
613 TokenExpectation::RParen => matches!(token.kind, TokenKind::RParen),
614 TokenExpectation::Eof => matches!(token.kind, TokenKind::Eof),
615 };
616 if matches {
617 self.position += 1;
618 Ok(())
619 } else {
620 Err(ExpressionError::Parse {
621 path: None,
622 details: format!("expected {}", expected.describe()),
623 })
624 }
625 }
626
627 fn peek(&self) -> Option<&Token> {
628 self.tokens.get(self.position)
629 }
630}
631
632enum TokenExpectation {
633 RParen,
634 Eof,
635}
636
637impl TokenExpectation {
638 fn describe(&self) -> &'static str {
639 match self {
640 Self::RParen => "`)`",
641 Self::Eof => "end of input",
642 }
643 }
644}
645
646fn builtin_for_name(name: &str) -> Option<BuiltinFn> {
647 match name {
648 "min" => Some(BuiltinFn::Min),
649 "max" => Some(BuiltinFn::Max),
650 "abs" => Some(BuiltinFn::Abs),
651 "floor" => Some(BuiltinFn::Floor),
652 "ceil" => Some(BuiltinFn::Ceil),
653 "round" => Some(BuiltinFn::Round),
654 "sin" => Some(BuiltinFn::Sin),
655 "cos" => Some(BuiltinFn::Cos),
656 "clamp" => Some(BuiltinFn::Clamp),
657 "lerp" => Some(BuiltinFn::Lerp),
658 "pow" => Some(BuiltinFn::Pow),
659 "mod" => Some(BuiltinFn::Mod),
660 "fract" => Some(BuiltinFn::Fract),
661 "smoothstep" => Some(BuiltinFn::Smoothstep),
662 "linear" => Some(BuiltinFn::Linear),
663 "step" => Some(BuiltinFn::Step),
664 "text_height" => Some(BuiltinFn::TextHeight),
665 "text_width" => Some(BuiltinFn::TextWidth),
666 "uppercase" => Some(BuiltinFn::Uppercase),
667 "lowercase" => Some(BuiltinFn::Lowercase),
668 _ => None,
669 }
670}