1#![allow(clippy::many_single_char_names)] use std::f64::consts::{E, PI};
11
12use super::{Ease, Frame, Scope};
13
14pub const MAX_LEN: usize = 4096;
16pub const MAX_DEPTH: usize = 64;
19pub const MAX_ARGS: usize = 255;
21
22const PHI: f64 = 1.618_033_988_749_895;
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct ExprError {
28 pub col: usize,
29 pub msg: String,
30}
31
32impl ExprError {
33 fn new(col: usize, msg: impl Into<String>) -> Self {
34 Self {
35 col,
36 msg: msg.into(),
37 }
38 }
39}
40
41type Res<T> = std::result::Result<T, ExprError>;
42
43#[derive(Debug, Clone, PartialEq)]
44enum Tok {
45 Num(f64),
46 Name(String),
47 Sym(&'static str),
49 End,
50}
51
52const SYMS: [&str; 21] = [
53 "<=", ">=", "==", "!=", "&&", "||", "+", "-", "*", "/", "%", "^", "!", "<", ">", "?", ":", "(",
54 ")", ",", "=",
55];
56
57fn lex(src: &str) -> Res<Vec<(Tok, usize)>> {
58 let chars: Vec<char> = src.chars().collect();
59 let mut out = Vec::new();
60 let mut i = 0;
61 while i < chars.len() {
62 let c = chars[i];
63 let col = i + 1;
64 if c.is_whitespace() {
65 i += 1;
66 } else if c.is_ascii_digit()
67 || (c == '.' && chars.get(i + 1).is_some_and(char::is_ascii_digit))
68 {
69 let start = i;
70 while i < chars.len() && chars[i].is_ascii_digit() {
71 i += 1;
72 }
73 if i < chars.len() && chars[i] == '.' {
74 i += 1;
75 while i < chars.len() && chars[i].is_ascii_digit() {
76 i += 1;
77 }
78 }
79 if i < chars.len() && matches!(chars[i], 'e' | 'E') {
80 let mut j = i + 1;
81 if j < chars.len() && matches!(chars[j], '+' | '-') {
82 j += 1;
83 }
84 if j < chars.len() && chars[j].is_ascii_digit() {
85 i = j;
86 while i < chars.len() && chars[i].is_ascii_digit() {
87 i += 1;
88 }
89 } else {
90 return Err(ExprError::new(j + 1, "expected exponent digits"));
91 }
92 }
93 let s: String = chars[start..i].iter().collect();
94 let v = s
95 .parse()
96 .map_err(|_| ExprError::new(col, format!("bad number `{s}`")))?;
97 out.push((Tok::Num(v), col));
98 } else if c.is_alphabetic() || c == '_' {
99 let start = i;
100 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
101 i += 1;
102 }
103 out.push((Tok::Name(chars[start..i].iter().collect()), col));
104 } else {
105 let two: String = chars[i..chars.len().min(i + 2)].iter().collect();
106 let sym = SYMS
107 .iter()
108 .find(|s| two.starts_with(**s))
109 .ok_or_else(|| ExprError::new(col, format!("unexpected `{c}`")))?;
110 if *sym == "=" {
111 return Err(ExprError::new(col, "unexpected `=` (comparison is `==`)"));
112 }
113 i += sym.len();
114 out.push((Tok::Sym(sym), col));
115 }
116 }
117 out.push((Tok::End, chars.len() + 1));
118 Ok(out)
119}
120
121#[derive(Debug, Clone, PartialEq)]
123pub(crate) enum Node {
124 Num(f64),
125 Var(String, usize),
126 Call(String, usize, Vec<Node>),
128}
129
130impl Node {
131 pub(crate) fn names(&self, out: &mut Vec<String>) {
133 match self {
134 Self::Num(_) => {}
135 Self::Var(n, _) => out.push(n.clone()),
136 Self::Call(_, _, a) => {
137 for a in a {
138 a.names(out);
139 }
140 }
141 }
142 }
143}
144
145struct Parser {
146 toks: Vec<(Tok, usize)>,
147 i: usize,
148 depth: usize,
149}
150
151fn binary(t: &Tok) -> Option<(u8, &'static str)> {
153 let Tok::Sym(s) = t else { return None };
154 Some(match *s {
155 "||" => (1, "||"),
156 "&&" => (2, "&&"),
157 "<" | "<=" | ">" | ">=" | "==" | "!=" => (3, s),
158 "+" | "-" => (4, s),
159 "*" | "/" | "%" => (5, s),
160 _ => return None,
161 })
162}
163
164impl Parser {
165 fn peek(&self) -> &Tok {
166 &self.toks[self.i].0
167 }
168
169 fn col(&self) -> usize {
170 self.toks[self.i].1
171 }
172
173 fn next(&mut self) -> (Tok, usize) {
174 let t = self.toks[self.i].clone();
175 if self.i + 1 < self.toks.len() {
176 self.i += 1;
177 }
178 t
179 }
180
181 fn expect(&mut self, s: &'static str) -> Res<()> {
182 if *self.peek() == Tok::Sym(s) {
183 self.next();
184 Ok(())
185 } else {
186 Err(ExprError::new(self.col(), format!("expected \"{s}\"")))
187 }
188 }
189
190 fn enter(&mut self) -> Res<()> {
191 self.depth += 1;
192 if self.depth > MAX_DEPTH {
193 return Err(ExprError::new(
194 self.col(),
195 format!("nested deeper than {MAX_DEPTH} levels"),
196 ));
197 }
198 Ok(())
199 }
200
201 fn expr(&mut self) -> Res<Node> {
202 self.enter()?;
203 let c = self.binary(1)?;
204 let out = if *self.peek() == Tok::Sym("?") {
205 let col = self.col();
206 self.next();
207 let a = self.expr()?;
208 self.expect(":")?;
209 let b = self.expr()?;
210 Node::Call("?:".into(), col, vec![c, a, b])
211 } else {
212 c
213 };
214 self.depth -= 1;
215 Ok(out)
216 }
217
218 fn binary(&mut self, min: u8) -> Res<Node> {
219 let mut lhs = self.unary()?;
220 let mut chain = 0;
222 let out = loop {
223 let Some((p, op)) = binary(self.peek()) else {
224 break Ok(lhs);
225 };
226 if p < min {
227 break Ok(lhs);
228 }
229 if let Err(e) = self.enter() {
230 break Err(e);
231 }
232 chain += 1;
233 let col = self.col();
234 self.next();
235 let rhs = match self.binary(p + 1) {
236 Ok(r) => r,
237 Err(e) => break Err(e),
238 };
239 if p == 3 && binary(self.peek()).is_some_and(|(q, _)| q == 3) {
240 break Err(ExprError::new(
241 self.col(),
242 "comparisons don't chain: use `&&`",
243 ));
244 }
245 lhs = Node::Call(op.into(), col, vec![lhs, rhs]);
246 };
247 self.depth -= chain;
248 out
249 }
250
251 fn unary(&mut self) -> Res<Node> {
252 let col = self.col();
253 let op = match self.peek() {
254 Tok::Sym("-") => "neg",
255 Tok::Sym("+") => "pos",
256 Tok::Sym("!") => "!",
257 _ => return self.pow(),
258 };
259 self.next();
260 self.enter()?;
261 let a = self.unary()?;
262 self.depth -= 1;
263 Ok(if op == "pos" {
264 a
265 } else {
266 Node::Call(op.into(), col, vec![a])
267 })
268 }
269
270 fn pow(&mut self) -> Res<Node> {
271 let base = self.atom()?;
272 if *self.peek() != Tok::Sym("^") {
273 return Ok(base);
274 }
275 let col = self.col();
276 self.next();
277 self.enter()?;
278 let e = self.unary()?;
279 self.depth -= 1;
280 Ok(Node::Call("^".into(), col, vec![base, e]))
281 }
282
283 fn atom(&mut self) -> Res<Node> {
284 let (t, col) = self.next();
285 match t {
286 Tok::Num(v) => Ok(Node::Num(v)),
287 Tok::Name(n) => {
288 if *self.peek() != Tok::Sym("(") {
289 return Ok(Node::Var(n, col));
290 }
291 self.next();
292 self.enter()?;
293 let mut args = Vec::new();
294 if *self.peek() != Tok::Sym(")") {
295 args.push(self.expr()?);
296 while *self.peek() == Tok::Sym(",") {
297 if args.len() == MAX_ARGS {
298 return Err(ExprError::new(
299 col,
300 format!("{n}() takes at most {MAX_ARGS} arguments"),
301 ));
302 }
303 self.next();
304 args.push(self.expr()?);
305 }
306 }
307 self.expect(")")?;
308 self.depth -= 1;
309 Ok(Node::Call(n, col, args))
310 }
311 Tok::Sym("(") => {
312 self.enter()?;
313 let e = self.expr()?;
314 self.expect(")")?;
315 self.depth -= 1;
316 Ok(e)
317 }
318 Tok::End => Err(ExprError::new(col, "unexpected end")),
319 Tok::Sym(s) => Err(ExprError::new(col, format!("unexpected \"{s}\""))),
320 }
321 }
322}
323
324pub(crate) fn parse(src: &str) -> Res<Node> {
326 if src.len() > MAX_LEN {
327 return Err(ExprError::new(1, format!("longer than {MAX_LEN} bytes")));
328 }
329 let mut p = Parser {
330 toks: lex(src)?,
331 i: 0,
332 depth: 0,
333 };
334 let e = p.expr()?;
335 match p.peek() {
336 Tok::End => Ok(e),
337 _ => Err(ExprError::new(p.col(), "expected an operator or the end")),
338 }
339}
340
341#[derive(Debug, Clone, Copy, PartialEq)]
343enum F {
344 Neg,
345 Not,
346 Add,
347 Sub,
348 Mul,
349 Div,
350 Mod,
351 Pow,
352 Lt,
353 Le,
354 Gt,
355 Ge,
356 Eq,
357 Ne,
358 And,
359 Or,
360 Select,
362 Abs,
363 Floor,
364 Ceil,
365 Round,
366 Trunc,
367 Sqrt,
368 Exp,
369 Log,
370 Sin,
371 Cos,
372 Tan,
373 Asin,
374 Acos,
375 Atan,
376 Atan2,
377 Hypot,
378 Sign,
379 Squish,
380 Gauss,
381 Between,
382 Clip,
383 Min,
384 Max,
385 Lerp,
386 Smoothstep,
387 Ease(Ease),
388 Bezier,
389 Random,
391 Noise,
392}
393
394fn b(c: bool) -> f64 {
395 if c { 1.0 } else { 0.0 }
396}
397
398impl F {
399 fn apply(self, a: &[f64]) -> f64 {
400 let x = a.first().copied().unwrap_or(0.0);
401 let y = a.get(1).copied().unwrap_or(0.0);
402 let z = a.get(2).copied().unwrap_or(0.0);
403 match self {
404 Self::Neg => -x,
405 Self::Not => b(x == 0.0),
406 Self::Add => x + y,
407 Self::Sub => x - y,
408 Self::Mul => x * y,
409 Self::Div => x / y,
410 Self::Mod => x - (x / y).floor() * y,
412 Self::Pow => x.powf(y),
413 Self::Lt => b(x < y),
414 Self::Le => b(x <= y),
415 Self::Gt => b(x > y),
416 Self::Ge => b(x >= y),
417 #[allow(clippy::float_cmp)]
418 Self::Eq => b(x == y),
419 #[allow(clippy::float_cmp)]
420 Self::Ne => b(x != y),
421 Self::And => b(x != 0.0 && y != 0.0),
422 Self::Or => b(x != 0.0 || y != 0.0),
423 Self::Select => {
424 if x == 0.0 {
425 z
426 } else {
427 y
428 }
429 }
430 Self::Abs => x.abs(),
431 Self::Floor => x.floor(),
432 Self::Ceil => x.ceil(),
433 Self::Round => x.round(),
434 Self::Trunc => x.trunc(),
435 Self::Sqrt => x.sqrt(),
436 Self::Exp => x.exp(),
437 Self::Log => x.ln(),
438 Self::Sin => x.sin(),
439 Self::Cos => x.cos(),
440 Self::Tan => x.tan(),
441 Self::Asin => x.asin(),
442 Self::Acos => x.acos(),
443 Self::Atan => x.atan(),
444 Self::Atan2 => x.atan2(y),
445 Self::Hypot => x.hypot(y),
446 Self::Sign => {
447 if x > 0.0 {
448 1.0
449 } else if x < 0.0 {
450 -1.0
451 } else {
452 0.0
453 }
454 }
455 Self::Squish => 1.0 / (1.0 + (4.0 * x).exp()),
456 Self::Gauss => (-x * x / 2.0).exp() / (2.0 * PI).sqrt(),
457 Self::Between => b(x >= y && x <= z),
458 Self::Clip => {
459 if x.is_nan() || y.is_nan() || z.is_nan() || y > z {
460 f64::NAN
461 } else {
462 x.clamp(y, z)
463 }
464 }
465 Self::Min => a.iter().copied().fold(f64::INFINITY, f64::min),
466 Self::Max => a.iter().copied().fold(f64::NEG_INFINITY, f64::max),
467 Self::Lerp => x + (y - x) * z,
468 Self::Smoothstep => {
469 let u = ((z - x) / (y - x)).clamp(0.0, 1.0);
470 u * u * (3.0 - 2.0 * u)
471 }
472 Self::Ease(e) => e.at(x),
473 Self::Bezier => Ease::Bezier([x, y, z, a[3]]).at(a[4]),
474 Self::Random => random(x, y),
475 Self::Noise => noise(x, y),
476 }
477 }
478}
479
480fn splitmix64(mut z: u64) -> u64 {
481 z = z.wrapping_add(0x9E37_79B9_7F4A_7C15);
482 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
483 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
484 z ^ (z >> 31)
485}
486
487#[allow(
489 clippy::cast_possible_truncation,
490 clippy::cast_sign_loss,
491 clippy::cast_precision_loss
492)]
493fn hash01(seed: f64, i: f64) -> f64 {
494 let s = splitmix64((seed + 0.0).to_bits());
496 let h = splitmix64(s ^ (i as i64 as u64));
497 (h >> 11) as f64 / (1u64 << 53) as f64
498}
499
500pub fn random(seed: f64, n: f64) -> f64 {
502 hash01(seed, n.round())
503}
504
505pub fn noise(seed: f64, x: f64) -> f64 {
508 let i = x.floor();
509 let u = x - i;
510 let f = u * u * u * (u * (u * 6.0 - 15.0) + 10.0);
511 let (a, b) = (
512 hash01(seed, i) * 2.0 - 1.0,
513 hash01(seed, i + 1.0) * 2.0 - 1.0,
514 );
515 a + (b - a) * f
516}
517
518fn function(name: &str) -> Option<(F, usize, usize)> {
520 let many = MAX_ARGS;
521 Some(match name {
522 "lt" => (F::Lt, 2, 2),
523 "lte" => (F::Le, 2, 2),
524 "gt" => (F::Gt, 2, 2),
525 "gte" => (F::Ge, 2, 2),
526 "eq" => (F::Eq, 2, 2),
527 "not" => (F::Not, 1, 1),
528 "between" => (F::Between, 3, 3),
529 "clip" => (F::Clip, 3, 3),
530 "min" => (F::Min, 2, many),
531 "max" => (F::Max, 2, many),
532 "abs" => (F::Abs, 1, 1),
533 "floor" => (F::Floor, 1, 1),
534 "ceil" => (F::Ceil, 1, 1),
535 "round" => (F::Round, 1, 1),
536 "trunc" => (F::Trunc, 1, 1),
537 "mod" => (F::Mod, 2, 2),
538 "sqrt" => (F::Sqrt, 1, 1),
539 "pow" => (F::Pow, 2, 2),
540 "exp" => (F::Exp, 1, 1),
541 "log" => (F::Log, 1, 1),
542 "sin" => (F::Sin, 1, 1),
543 "cos" => (F::Cos, 1, 1),
544 "tan" => (F::Tan, 1, 1),
545 "asin" => (F::Asin, 1, 1),
546 "acos" => (F::Acos, 1, 1),
547 "atan" => (F::Atan, 1, 1),
548 "atan2" => (F::Atan2, 2, 2),
549 "hypot" => (F::Hypot, 2, 2),
550 "sign" | "sgn" => (F::Sign, 1, 1),
551 "squish" => (F::Squish, 1, 1),
552 "gauss" => (F::Gauss, 1, 1),
553 "lerp" => (F::Lerp, 3, 3),
554 "smoothstep" => (F::Smoothstep, 3, 3),
555 "ease" => (F::Ease(Ease::Ease), 1, 1),
556 "ease_in" => (F::Ease(Ease::In), 1, 1),
557 "ease_out" => (F::Ease(Ease::Out), 1, 1),
558 "ease_in_out" => (F::Ease(Ease::InOut), 1, 1),
559 "back" => (F::Ease(Ease::Back), 1, 1),
560 "bezier" => (F::Bezier, 5, 5),
561 "noise" => (F::Noise, 2, 2),
562 _ => return None,
563 })
564}
565
566pub fn is_builtin_name(name: &str) -> bool {
568 matches!(name, "PI" | "E" | "PHI" | "if" | "ifnot" | "random")
569 || function(name).is_some()
570 || super::Builtin::from_name(name).is_some()
571}
572
573#[derive(Debug, Clone, PartialEq)]
575enum Ir {
576 Num(f64),
577 Slot(u16),
578 Op(F, Vec<Ir>),
579}
580
581#[allow(clippy::too_many_lines)]
582fn resolve(n: &Node, scope: &Scope) -> Res<Ir> {
583 let ir = match n {
584 Node::Num(v) => Ir::Num(*v),
585 Node::Var(name, col) => match name.as_str() {
586 "PI" => Ir::Num(PI),
587 "E" => Ir::Num(E),
588 "PHI" => Ir::Num(PHI),
589 _ => Ir::Slot(
590 scope
591 .slot(name)
592 .ok_or_else(|| ExprError::new(*col, scope.unknown(name)))?,
593 ),
594 },
595 Node::Call(name, col, args) => {
596 let a = args
597 .iter()
598 .map(|a| resolve(a, scope))
599 .collect::<Res<Vec<_>>>()?;
600 let count = |lo: usize, hi: usize| {
601 if a.len() < lo || a.len() > hi {
602 let want = match (lo, hi) {
603 (l, h) if l == h => format!("{l}"),
604 (l, MAX_ARGS) => format!("{l} or more"),
605 (l, h) => format!("{l} or {h}"),
606 };
607 Err(ExprError::new(
608 *col,
609 format!("{name}() takes {want} arguments, not {}", a.len()),
610 ))
611 } else {
612 Ok(())
613 }
614 };
615 let op = match name.as_str() {
616 "neg" => F::Neg,
617 "!" => F::Not,
618 "+" => F::Add,
619 "-" => F::Sub,
620 "*" => F::Mul,
621 "/" => F::Div,
622 "%" => F::Mod,
623 "^" => F::Pow,
624 "<" => F::Lt,
625 "<=" => F::Le,
626 ">" => F::Gt,
627 ">=" => F::Ge,
628 "==" => F::Eq,
629 "!=" => F::Ne,
630 "&&" => F::And,
631 "||" => F::Or,
632 "?:" => F::Select,
633 "if" | "ifnot" => {
634 count(2, 3)?;
635 let mut a = a.into_iter();
636 let (c, x) = (a.next(), a.next());
637 let y = a.next().unwrap_or(Ir::Num(0.0));
638 let (c, x) = (c.unwrap_or(Ir::Num(0.0)), x.unwrap_or(Ir::Num(0.0)));
639 let args = if name == "if" {
640 vec![c, x, y]
641 } else {
642 vec![c, y, x]
643 };
644 return Ok(fold(F::Select, args));
645 }
646 "random" => {
647 count(1, 1)?;
648 let n = scope.slot("n").ok_or_else(|| {
649 ExprError::new(
650 *col,
651 "random() needs the frame index `n`, which isn't known here",
652 )
653 })?;
654 let mut a = a;
655 a.push(Ir::Slot(n));
656 return Ok(Ir::Op(F::Random, a));
657 }
658 _ => {
659 let (f, lo, hi) = function(name).ok_or_else(|| {
660 ExprError::new(*col, format!("unknown function `{name}`"))
661 })?;
662 count(lo, hi)?;
663 f
664 }
665 };
666 fold(op, a)
667 }
668 };
669 Ok(ir)
670}
671
672fn fold(f: F, args: Vec<Ir>) -> Ir {
673 let consts: Option<Vec<f64>> = args
674 .iter()
675 .map(|a| match a {
676 Ir::Num(v) => Some(*v),
677 _ => None,
678 })
679 .collect();
680 match consts {
681 Some(c) => Ir::Num(f.apply(&c)),
682 None => Ir::Op(f, args),
683 }
684}
685
686#[derive(Debug, Clone, Copy, PartialEq)]
687enum Op {
688 Push(f64),
689 Load(u16),
690 Apply(F, u8),
691}
692
693#[derive(Debug, Clone, PartialEq)]
695pub struct Compiled {
696 src: String,
697 ops: Vec<Op>,
698 depth: usize,
699}
700
701fn emit(ir: &Ir, ops: &mut Vec<Op>, sp: &mut usize, max: &mut usize) {
702 match ir {
703 Ir::Num(v) => {
704 ops.push(Op::Push(*v));
705 *sp += 1;
706 }
707 Ir::Slot(s) => {
708 ops.push(Op::Load(*s));
709 *sp += 1;
710 }
711 Ir::Op(f, a) => {
712 for x in a {
713 emit(x, ops, sp, max);
714 }
715 let n = u8::try_from(a.len()).expect("at most MAX_ARGS arguments");
717 ops.push(Op::Apply(*f, n));
718 *sp = *sp + 1 - a.len();
719 }
720 }
721 *max = (*max).max(*sp);
722}
723
724impl Compiled {
725 pub fn new(src: &str, scope: &Scope) -> Res<Self> {
728 let ir = resolve(&parse(src)?, scope)?;
729 if let Ir::Num(v) = ir
730 && !v.is_finite()
731 {
732 return Err(ExprError::new(1, format!("always {v}: not finite")));
733 }
734 let (mut ops, mut sp, mut depth) = (Vec::new(), 0, 0);
735 emit(&ir, &mut ops, &mut sp, &mut depth);
736 Ok(Self {
737 src: src.to_owned(),
738 ops,
739 depth,
740 })
741 }
742
743 pub fn constant(v: f64) -> Self {
745 Self {
746 src: String::new(),
747 ops: vec![Op::Push(v)],
748 depth: 1,
749 }
750 }
751
752 pub fn as_const(&self) -> Option<f64> {
754 match self.ops[..] {
755 [Op::Push(v)] => Some(v),
756 _ => None,
757 }
758 }
759
760 pub fn source(&self) -> &str {
761 &self.src
762 }
763
764 pub fn eval(&self, frame: &Frame) -> f64 {
766 if let [Op::Push(v)] = self.ops[..] {
767 return v;
768 }
769 let mut st: Vec<f64> = Vec::with_capacity(self.depth);
770 for op in &self.ops {
771 match *op {
772 Op::Push(v) => st.push(v),
773 Op::Load(s) => st.push(frame.get(s)),
774 Op::Apply(f, n) => {
775 let at = st.len() - usize::from(n);
776 let v = f.apply(&st[at..]);
777 st.truncate(at);
778 st.push(v);
779 }
780 }
781 }
782 st.pop().unwrap_or(f64::NAN)
783 }
784}