1use std::collections::HashMap;
15use std::f64::consts::PI;
16
17use crate::ast::*;
18use crate::geom::{Bbox, Point};
19use crate::ir::*;
20use crate::token::{self, Corner, Dir, EnvVar, Func1, Func2, LineType, Prim};
21
22#[derive(Debug, Clone, PartialEq)]
24pub struct EvalError {
25 pub msg: String,
26}
27
28impl std::fmt::Display for EvalError {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 write!(f, "{}", self.msg)
31 }
32}
33
34type ER<T> = Result<T, EvalError>;
35
36fn err<T>(msg: impl Into<String>) -> ER<T> {
37 Err(EvalError { msg: msg.into() })
38}
39
40pub fn eval(pic: &Picture) -> ER<Drawing> {
42 let mut st = State::new();
43 st.eval_stmts(&pic.stmts)?;
44 Ok(Drawing {
45 shapes: st.shapes,
46 bbox: st.bbox,
47 anims: st.anims,
48 })
49}
50
51#[derive(Clone)]
54struct EnvVars {
55 v: HashMap<u8, f64>,
56}
57
58fn ev_key(e: EnvVar) -> u8 {
59 e as u8
60}
61
62impl EnvVars {
63 fn new() -> Self {
64 use EnvVar::*;
65 let defaults = [
66 (Arcrad, 0.25),
67 (Arrowht, 0.1),
68 (Arrowwid, 0.05),
69 (Boxht, 0.5),
70 (Boxrad, 0.0),
71 (Boxwid, 0.75),
72 (Circlerad, 0.25),
73 (Dashwid, 0.05),
74 (Ellipseht, 0.5),
75 (Ellipsewid, 0.75),
76 (Lineht, 0.5),
77 (Linewid, 0.5),
78 (Moveht, 0.5),
79 (Movewid, 0.5),
80 (Textht, 0.0),
81 (Textoffset, 0.05),
82 (Textwid, 0.0),
83 (Arrowhead, 2.0),
84 (Fillval, 0.5),
85 (Linethick, -1.0),
86 (Maxpsht, 11.0),
87 (Maxpswid, 8.5),
88 (Scale, 1.0),
89 ];
90 let mut v = HashMap::new();
91 for (e, d) in defaults {
92 v.insert(ev_key(e), d);
93 }
94 EnvVars { v }
95 }
96 fn get(&self, e: EnvVar) -> f64 {
97 *self.v.get(&ev_key(e)).unwrap_or(&0.0)
98 }
99 fn set(&mut self, e: EnvVar, val: f64) {
100 self.v.insert(ev_key(e), val);
101 }
102}
103
104#[derive(Clone, Copy, PartialEq, Eq)]
107enum PKind {
108 Box,
109 Circle,
110 Ellipse,
111 Line,
112 Move,
113 Spline,
114 Arc,
115 Block,
116 Text,
117}
118
119#[derive(Clone)]
120struct Placed {
121 kind: PKind,
122 center: Point,
123 bbox: Bbox,
124 start: Point,
125 end: Point,
126 thick: f64,
127 shape: Option<usize>,
129}
130
131impl Placed {
132 fn corner(&self, c: Corner) -> Point {
133 let (lo, hi) = (self.bbox.min, self.bbox.max);
134 let mid = self.center;
135 match c {
136 Corner::N => Point::new(mid.x, hi.y),
137 Corner::S => Point::new(mid.x, lo.y),
138 Corner::E => Point::new(hi.x, mid.y),
139 Corner::W => Point::new(lo.x, mid.y),
140 Corner::Ne => Point::new(hi.x, hi.y),
141 Corner::Se => Point::new(hi.x, lo.y),
142 Corner::Nw => Point::new(lo.x, hi.y),
143 Corner::Sw => Point::new(lo.x, lo.y),
144 Corner::Center => mid,
145 Corner::Start => self.start,
146 Corner::End => self.end,
147 }
148 }
149}
150
151struct State {
154 pos: Point,
155 dir: Dir,
156 vars: HashMap<String, f64>,
157 env: EnvVars,
158 shapes: Vec<Shape>,
159 placed: Vec<Placed>,
160 labels: HashMap<String, usize>,
161 bbox: Bbox,
162 anims: Vec<Anim>,
164 anim_cursor: f64,
165 anim_end: HashMap<usize, f64>,
166}
167
168const DEFAULT_ANIM_DUR: f64 = 0.6;
169
170fn dir_unit(d: Dir) -> Point {
171 match d {
172 Dir::Right => Point::new(1.0, 0.0),
173 Dir::Left => Point::new(-1.0, 0.0),
174 Dir::Up => Point::new(0.0, 1.0),
175 Dir::Down => Point::new(0.0, -1.0),
176 }
177}
178fn horizontal(d: Dir) -> bool {
179 matches!(d, Dir::Right | Dir::Left)
180}
181
182impl State {
183 fn new() -> Self {
184 State {
185 pos: Point::ZERO,
186 dir: Dir::Right,
187 vars: HashMap::new(),
188 env: EnvVars::new(),
189 shapes: Vec::new(),
190 placed: Vec::new(),
191 labels: HashMap::new(),
192 bbox: Bbox::new(),
193 anims: Vec::new(),
194 anim_cursor: 0.0,
195 anim_end: HashMap::new(),
196 }
197 }
198
199 fn eval_stmts(&mut self, stmts: &[Stmt]) -> ER<()> {
200 for s in stmts {
201 self.eval_stmt(s)?;
202 }
203 Ok(())
204 }
205
206 fn eval_stmt(&mut self, s: &Stmt) -> ER<()> {
207 match s {
208 Stmt::Direction(d) => {
209 self.dir = *d;
210 }
211 Stmt::Assign(list) => {
212 for a in list {
213 self.eval_assignment(a)?;
214 }
215 }
216 Stmt::Place { label, pos } => {
217 let p = self.eval_pos(pos)?;
218 let mut bb = Bbox::new();
219 bb.add(p);
220 let idx = self.placed.len();
221 self.placed.push(Placed {
222 kind: PKind::Text,
223 center: p,
224 bbox: bb,
225 start: p,
226 end: p,
227 thick: 0.0,
228 shape: None,
229 });
230 self.labels.insert(label.name.clone(), idx);
231 }
232 Stmt::Group(stmts) => {
233 let (pos, dir) = (self.pos, self.dir);
234 self.eval_stmts(stmts)?;
235 self.pos = pos;
236 self.dir = dir;
237 }
238 Stmt::Object { label, object } => {
239 let idx = self.eval_object(object)?;
240 if let Some(l) = label {
241 self.labels.insert(l.name.clone(), idx);
242 }
243 }
244 Stmt::Animate(a) => self.eval_animate(a)?,
245 Stmt::If {
246 cond,
247 then_body,
248 else_body,
249 } => {
250 if self.eval_expr(cond)? != 0.0 {
251 self.eval_stmts(then_body)?;
252 } else if let Some(e) = else_body {
253 self.eval_stmts(e)?;
254 }
255 }
256 Stmt::For {
257 var,
258 from,
259 to,
260 by,
261 mult,
262 body,
263 } => {
264 let from = self.eval_expr(from)?;
265 let to = self.eval_expr(to)?;
266 let by = self.eval_expr(by)?;
267 let mut v = from;
268 let mut iters = 0u64;
269 const EPS: f64 = 1e-9;
270 loop {
271 let cont = if *mult {
272 if by >= 1.0 {
273 v <= to + EPS
274 } else {
275 v >= to - EPS
276 }
277 } else if by >= 0.0 {
278 v <= to + EPS
279 } else {
280 v >= to - EPS
281 };
282 if !cont {
283 break;
284 }
285 self.vars.insert(var.clone(), v);
286 self.eval_stmts(body)?;
287 let prev = v;
288 v = if *mult { v * by } else { v + by };
289 if (v - prev).abs() < f64::EPSILON {
290 break; }
292 iters += 1;
293 if iters > 1_000_000 {
294 return err("for loop exceeded 1,000,000 iterations");
295 }
296 }
297 }
298 Stmt::Print(item) => match item {
299 PrintItem::Expr(e) => {
300 self.eval_expr(e)?;
301 }
302 PrintItem::Str(_) => {}
303 },
304 Stmt::Reset(list) => {
305 if list.is_empty() {
306 self.env = EnvVars::new();
307 } else {
308 let d = EnvVars::new();
309 for e in list {
310 self.env.set(*e, d.get(*e));
311 }
312 }
313 }
314 }
315 Ok(())
316 }
317
318 fn eval_animate(&mut self, a: &Animate) -> ER<()> {
319 let idx = self.place_index(&a.target)?;
320 let shape = self.placed[idx].shape.ok_or_else(|| EvalError {
321 msg: "cannot animate a point (no drawn shape)".into(),
322 })?;
323 let dur = match &a.duration {
324 Some(e) => self.eval_expr(e)?,
325 None => DEFAULT_ANIM_DUR,
326 };
327 let mut start = match &a.timing {
328 Timing::Sequential => self.anim_cursor,
329 Timing::At(e) => self.eval_expr(e)?,
330 Timing::After(p) => {
331 let i = self.place_index(p)?;
332 let sh = self.placed[i].shape.ok_or_else(|| EvalError {
333 msg: "`after` target has no animation".into(),
334 })?;
335 *self.anim_end.get(&sh).unwrap_or(&0.0)
336 }
337 };
338 if let Some(d) = &a.delay {
339 start += self.eval_expr(d)?;
340 }
341 let end = start + dur;
342 self.anim_cursor = end;
343 self.anim_end.insert(shape, end);
344 self.anims.push(Anim {
345 shape,
346 effect: stringexpr_lit(&a.effect),
347 start,
348 duration: dur,
349 });
350 Ok(())
351 }
352
353 fn eval_assignment(&mut self, a: &Assignment) -> ER<()> {
354 let rhs = self.eval_expr(&a.value)?;
355 match &a.target {
356 AssignTarget::Var(name, _sub) => {
357 let cur = *self.vars.get(name).unwrap_or(&0.0);
358 let val = apply_op(a.op, cur, rhs);
359 self.vars.insert(name.clone(), val);
360 }
361 AssignTarget::Env(e) => {
362 let cur = self.env.get(*e);
363 self.env.set(*e, apply_op(a.op, cur, rhs));
364 }
365 }
366 Ok(())
367 }
368
369 fn eval_object(&mut self, obj: &Object) -> ER<usize> {
372 match &obj.kind {
373 ObjectKind::Primitive(p) => match p {
374 Prim::Box | Prim::Circle | Prim::Ellipse => self.closed(*p, obj),
375 Prim::Line | Prim::Arrow | Prim::Move | Prim::Spline => self.open(*p, obj),
376 Prim::Arc => self.arc(obj),
377 },
378 ObjectKind::Text => self.text_obj(obj),
379 ObjectKind::Block(stmts) => self.block(stmts, obj),
380 ObjectKind::Empty => self.block(&[], obj),
381 }
382 }
383
384 fn closed(&mut self, p: Prim, obj: &Object) -> ER<usize> {
385 let style = self.style_of(obj)?;
386 let text = self.text_of(obj)?;
387 let dir = self.dir_of(obj);
388 let scale = self.scale_of(obj)?;
389
390 let (mut w, mut h, mut rad);
392 match p {
393 Prim::Circle => {
394 let r = self
395 .dim(obj, DimKind::Rad)?
396 .unwrap_or(self.env.get(EnvVar::Circlerad));
397 let r = self.dim(obj, DimKind::Diam)?.map(|d| d / 2.0).unwrap_or(r);
398 w = 2.0 * r;
399 h = 2.0 * r;
400 rad = r;
401 }
402 Prim::Ellipse => {
403 w = self
404 .dim(obj, DimKind::Wid)?
405 .unwrap_or(self.env.get(EnvVar::Ellipsewid));
406 h = self
407 .dim(obj, DimKind::Ht)?
408 .unwrap_or(self.env.get(EnvVar::Ellipseht));
409 rad = 0.0;
410 }
411 _ => {
412 w = self
414 .dim(obj, DimKind::Wid)?
415 .unwrap_or(self.env.get(EnvVar::Boxwid));
416 h = self
417 .dim(obj, DimKind::Ht)?
418 .unwrap_or(self.env.get(EnvVar::Boxht));
419 rad = self
420 .dim(obj, DimKind::Rad)?
421 .unwrap_or(self.env.get(EnvVar::Boxrad));
422 }
423 }
424 w *= scale;
425 h *= scale;
426 rad *= scale;
427
428 let extent = if horizontal(dir) { w } else { h };
429 let center = self.place_center(obj, dir, extent, w, h)?;
430
431 let mut bb = Bbox::new();
432 bb.add(center - Point::new(w / 2.0, h / 2.0));
433 bb.add(center + Point::new(w / 2.0, h / 2.0));
434
435 let shape = match p {
436 Prim::Circle => Shape::Circle {
437 c: center,
438 r: rad,
439 style,
440 text,
441 },
442 Prim::Ellipse => Shape::Ellipse {
443 c: center,
444 w,
445 h,
446 style,
447 text,
448 },
449 _ => Shape::Box {
450 c: center,
451 w,
452 h,
453 rad,
454 style,
455 text,
456 },
457 };
458 self.shapes.push(shape);
459 self.bbox.union(&bb);
460
461 let half = dir_unit(dir) * (extent / 2.0);
462 let start = center - half;
463 let end = center + half;
464 self.pos = end;
465 self.dir = dir;
466 let kind = match p {
467 Prim::Circle => PKind::Circle,
468 Prim::Ellipse => PKind::Ellipse,
469 _ => PKind::Box,
470 };
471 let sh = self.shapes.len() - 1;
472 Ok(self.record(kind, center, bb, start, end, 0.0, Some(sh)))
473 }
474
475 fn open(&mut self, p: Prim, obj: &Object) -> ER<usize> {
476 let style = self.style_of(obj)?;
477 let text = self.text_of(obj)?;
478 let is_move = matches!(p, Prim::Move);
479 let (deflen_h, deflen_v) = if is_move {
480 (self.env.get(EnvVar::Movewid), self.env.get(EnvVar::Moveht))
481 } else {
482 (self.env.get(EnvVar::Linewid), self.env.get(EnvVar::Lineht))
483 };
484
485 let start = self.from_of(obj)?.unwrap_or(self.pos);
487 let mut pts = vec![start];
488 let mut pend = Point::ZERO;
489 let mut any = false;
490 let mut last_dir = self.dir;
491
492 for a in &obj.attrs {
493 match a {
494 Attr::Direction(d, opt) => {
495 let dist = match opt {
496 Some(e) => self.eval_expr(e)?,
497 None => {
498 if horizontal(*d) {
499 deflen_h
500 } else {
501 deflen_v
502 }
503 }
504 };
505 pend = pend + dir_unit(*d) * dist;
506 last_dir = *d;
507 any = true;
508 }
509 Attr::Then => {
510 let np = *pts.last().unwrap() + pend;
511 pts.push(np);
512 pend = Point::ZERO;
513 }
514 Attr::To(pos) => {
515 if pend != Point::ZERO {
516 let np = *pts.last().unwrap() + pend;
517 pts.push(np);
518 pend = Point::ZERO;
519 }
520 pts.push(self.eval_pos(pos)?);
521 any = true;
522 }
523 Attr::By(pos) => {
524 let d = self.eval_pos(pos)?;
525 pend = pend + (d - Point::ZERO);
526 any = true;
527 }
528 _ => {}
529 }
530 }
531 if pend != Point::ZERO {
532 let np = *pts.last().unwrap() + pend;
533 pts.push(np);
534 }
535 if pts.len() == 1 && !any {
536 let dist = if horizontal(self.dir) {
538 deflen_h
539 } else {
540 deflen_v
541 };
542 pts.push(start + dir_unit(self.dir) * dist);
543 last_dir = self.dir;
544 }
545
546 let arrows = self.arrows_of(obj, matches!(p, Prim::Arrow));
548
549 let mut bb = Bbox::new();
550 for pt in &pts {
551 bb.add(*pt);
552 }
553 self.bbox.union(&bb);
554
555 let end = *pts.last().unwrap();
556 let center = (pts[0] + end) * 0.5;
557 let kind = match p {
558 Prim::Spline => PKind::Spline,
559 Prim::Move => PKind::Move,
560 _ => PKind::Line,
561 };
562 let mut style = style;
563 if is_move {
564 style.invis = true;
565 }
566 let shape = if matches!(p, Prim::Spline) {
567 Shape::Spline {
568 pts: pts.clone(),
569 arrows,
570 style,
571 text,
572 }
573 } else {
574 Shape::Path {
575 pts: pts.clone(),
576 arrows,
577 style,
578 text,
579 }
580 };
581 self.shapes.push(shape);
582
583 self.pos = end;
584 self.dir = last_dir;
585 let sh = self.shapes.len() - 1;
586 Ok(self.record(kind, center, bb, pts[0], end, 0.0, Some(sh)))
587 }
588
589 fn arc(&mut self, obj: &Object) -> ER<usize> {
590 let style = self.style_of(obj)?;
591 let text = self.text_of(obj)?;
592 let start = self.from_of(obj)?.unwrap_or(self.pos);
593 let r = self
594 .dim(obj, DimKind::Rad)?
595 .unwrap_or(self.env.get(EnvVar::Arcrad));
596 let cw = obj.attrs.iter().any(|a| matches!(a, Attr::Cw));
597 let din = dir_unit(self.dir);
598 let normal = if cw {
600 Point::new(din.y, -din.x)
601 } else {
602 Point::new(-din.y, din.x)
603 };
604 let center = start + normal * r;
605 let a0 = (start - center).y.atan2((start - center).x);
606 let sweep = if cw { -PI / 2.0 } else { PI / 2.0 };
607 let a1 = a0 + sweep;
608 let end = center + Point::new(a1.cos(), a1.sin()) * r;
609
610 let arrows = self.arrows_of(obj, false);
611
612 let mut bb = Bbox::new();
613 bb.add(start);
614 bb.add(end);
615 for k in 0..=8 {
616 let t = a0 + sweep * (k as f64 / 8.0);
617 bb.add(center + Point::new(t.cos(), t.sin()) * r);
618 }
619 self.bbox.union(&bb);
620
621 self.shapes.push(Shape::Arc {
622 c: center,
623 r,
624 a0,
625 a1,
626 cw,
627 arrows,
628 style,
629 text,
630 });
631
632 let new = din.rotate(sweep);
634 self.dir = nearest_dir(new);
635 self.pos = end;
636 let sh = self.shapes.len() - 1;
637 Ok(self.record(PKind::Arc, center, bb, start, end, 0.0, Some(sh)))
638 }
639
640 fn text_obj(&mut self, obj: &Object) -> ER<usize> {
641 let text = self.text_of(obj)?;
642 let at = self.at_of(obj)?.unwrap_or(self.pos);
643 let mut bb = Bbox::new();
644 bb.add(at);
645 self.bbox.union(&bb);
646 self.shapes.push(Shape::Text { at, text });
647 let sh = self.shapes.len() - 1;
648 Ok(self.record(PKind::Text, at, bb, at, at, 0.0, Some(sh)))
649 }
650
651 fn block(&mut self, stmts: &[Stmt], obj: &Object) -> ER<usize> {
652 let mut sub = State::new();
654 sub.env = self.env.clone();
655 sub.vars = self.vars.clone();
656 sub.eval_stmts(stmts)?;
657
658 let sub_bb = if sub.bbox.is_empty() {
659 let mut b = Bbox::new();
660 b.add(Point::ZERO);
661 b
662 } else {
663 sub.bbox
664 };
665 let local_center = (sub_bb.min + sub_bb.max) * 0.5;
666 let w = sub_bb.width();
667 let h = sub_bb.height();
668
669 let dir = self.dir_of(obj);
670 let extent = if horizontal(dir) { w } else { h };
671 let target = self.place_center(obj, dir, extent, w, h)?;
672 let shift = target - local_center;
673
674 let first_shape = self.shapes.len();
675 for mut sh in sub.shapes.into_iter() {
676 translate_shape(&mut sh, shift);
677 self.shapes.push(sh);
678 }
679 let shape = if self.shapes.len() > first_shape {
680 Some(first_shape)
681 } else {
682 None
683 };
684 let mut bb = Bbox::new();
685 bb.add(sub_bb.min + shift);
686 bb.add(sub_bb.max + shift);
687 self.bbox.union(&bb);
688
689 let half = dir_unit(dir) * (extent / 2.0);
690 let start = target - half;
691 let end = target + half;
692 self.pos = end;
693 self.dir = dir;
694 Ok(self.record(PKind::Block, target, bb, start, end, 0.0, shape))
695 }
696
697 #[allow(clippy::too_many_arguments)]
698 fn record(
699 &mut self,
700 kind: PKind,
701 center: Point,
702 bbox: Bbox,
703 start: Point,
704 end: Point,
705 thick: f64,
706 shape: Option<usize>,
707 ) -> usize {
708 let idx = self.placed.len();
709 self.placed.push(Placed {
710 kind,
711 center,
712 bbox,
713 start,
714 end,
715 thick,
716 shape,
717 });
718 idx
719 }
720
721 fn dim(&mut self, obj: &Object, kind: DimKind) -> ER<Option<f64>> {
724 for a in &obj.attrs {
725 if let Attr::Dim(k, e) = a {
726 if *k == kind {
727 return Ok(Some(self.eval_expr(e)?));
728 }
729 }
730 }
731 Ok(None)
732 }
733
734 fn scale_of(&mut self, obj: &Object) -> ER<f64> {
735 Ok(self.dim(obj, DimKind::Scaled)?.unwrap_or(1.0))
736 }
737
738 fn dir_of(&self, obj: &Object) -> Dir {
739 obj.attrs
740 .iter()
741 .rev()
742 .find_map(|a| match a {
743 Attr::Direction(d, _) => Some(*d),
744 _ => None,
745 })
746 .unwrap_or(self.dir)
747 }
748
749 fn from_of(&mut self, obj: &Object) -> ER<Option<Point>> {
750 for a in &obj.attrs {
751 if let Attr::From(pos) = a {
752 return Ok(Some(self.eval_pos(pos)?));
753 }
754 }
755 Ok(None)
756 }
757
758 fn at_of(&mut self, obj: &Object) -> ER<Option<Point>> {
759 for a in &obj.attrs {
760 if let Attr::At(pos) = a {
761 return Ok(Some(self.eval_pos(pos)?));
762 }
763 }
764 Ok(None)
765 }
766
767 fn place_center(&mut self, obj: &Object, dir: Dir, extent: f64, w: f64, h: f64) -> ER<Point> {
769 if let Some(at) = self.at_of(obj)? {
770 return Ok(at);
771 }
772 for a in &obj.attrs {
773 if let Attr::With { anchor, at } = a {
774 let ap = self.eval_pos(at)?;
775 let off = match anchor {
776 WithAnchor::Corner(c) => corner_offset(*c, w, h),
777 WithAnchor::Pair(x, y) => Point::new(self.eval_expr(x)?, self.eval_expr(y)?),
778 WithAnchor::Plain => Point::ZERO,
779 };
780 return Ok(ap - off);
781 }
782 }
783 Ok(self.pos + dir_unit(dir) * (extent / 2.0))
784 }
785
786 fn arrows_of(&self, obj: &Object, default_end: bool) -> Arrowheads {
787 let mut found = None;
788 for a in &obj.attrs {
789 if let Attr::Arrowhead(h, _) = a {
790 found = Some(match h {
791 token::Arrow::Left => Arrowheads::Start,
792 token::Arrow::Right => Arrowheads::End,
793 token::Arrow::Double => Arrowheads::Both,
794 });
795 }
796 }
797 found.unwrap_or(if default_end {
798 Arrowheads::End
799 } else {
800 Arrowheads::None
801 })
802 }
803
804 fn style_of(&mut self, obj: &Object) -> ER<Style> {
805 let mut s = Style::default();
806 for a in &obj.attrs {
807 match a {
808 Attr::LineStyle(lt, _) => match lt {
809 LineType::Solid => s.dash = Dash::Solid,
810 LineType::Dashed => s.dash = Dash::Dashed,
811 LineType::Dotted => s.dash = Dash::Dotted,
812 LineType::Invis => s.invis = true,
813 },
814 Attr::Fill(opt) => {
815 let g = match opt {
816 Some(e) => self.eval_expr(e)?,
817 None => self.env.get(EnvVar::Fillval),
818 };
819 s.fill = Some(Fill::Gray(g));
820 }
821 Attr::Color(kind, se) => {
822 let name = stringexpr_lit(se);
823 match kind {
824 token::Color::Outlined => s.stroke = Some(name),
825 token::Color::Colored => {
826 s.stroke = Some(name.clone());
827 s.fill = Some(Fill::Color(name));
828 }
829 token::Color::Shaded => s.fill = Some(Fill::Color(name)),
830 }
831 }
832 Attr::Dim(DimKind::Thick, e) => s.thick = Some(self.eval_expr(e)?),
833 _ => {}
834 }
835 }
836 Ok(s)
837 }
838
839 fn text_of(&mut self, obj: &Object) -> ER<Vec<TextLine>> {
840 let mut lines = Vec::new();
841 let mut halign = 0i8;
842 let mut valign = 0i8;
843 for a in &obj.attrs {
844 match a {
845 Attr::TextPos(tp) => match tp {
846 token::TextPos::Ljust => halign = -1,
847 token::TextPos::Rjust => halign = 1,
848 token::TextPos::Center => {
849 halign = 0;
850 valign = 0;
851 }
852 token::TextPos::Above => valign = 1,
853 token::TextPos::Below => valign = -1,
854 },
855 Attr::Text(se) => {
856 let s = self.eval_stringexpr(se)?;
857 lines.push(TextLine { s, halign, valign });
858 }
859 _ => {}
860 }
861 }
862 Ok(lines)
863 }
864
865 fn eval_pos(&mut self, pos: &Position) -> ER<Point> {
868 match pos {
869 Position::Pair(x, y) => Ok(Point::new(self.eval_expr(x)?, self.eval_expr(y)?)),
870 Position::Place(loc, shifts) => {
871 let mut p = self.eval_loc(loc)?;
872 for s in shifts {
873 let d = self.eval_loc(&s.loc)?;
874 p = match s.sign {
875 Sign::Plus => p + d,
876 Sign::Minus => p - d,
877 };
878 }
879 Ok(p)
880 }
881 Position::Between { frac, a, b, .. } => {
882 let f = self.eval_expr(frac)?;
883 let pa = self.eval_pos(a)?;
884 let pb = self.eval_pos(b)?;
885 Ok(pa.lerp(pb, f))
886 }
887 }
888 }
889
890 fn eval_loc(&mut self, loc: &Location) -> ER<Point> {
891 match loc {
892 Location::Place(p) => self.place_point(p),
893 Location::Paren(pos) => self.eval_pos(pos),
894 Location::ParenPair(p1, p2) => {
895 Ok(Point::new(self.eval_pos(p1)?.x, self.eval_pos(p2)?.y))
896 }
897 }
898 }
899
900 fn place_point(&mut self, p: &Place) -> ER<Point> {
901 match p {
902 Place::Here => Ok(self.pos),
903 Place::Name { name, .. } => {
904 let idx = self.label_index(name)?;
905 Ok(self.placed[idx].center)
906 }
907 Place::Nth { count, obj } => {
908 let idx = self.nth_index(count, obj)?;
909 Ok(self.placed[idx].center)
910 }
911 Place::Corner(inner, c) => {
912 let idx = self.place_index(inner)?;
913 Ok(self.placed[idx].corner(*c))
914 }
915 Place::CornerOf(c, inner) => {
916 let idx = self.place_index(inner)?;
917 Ok(self.placed[idx].corner(*c))
918 }
919 Place::Member(_, _) => err("block sub-labels (B.A) are not supported yet"),
920 }
921 }
922
923 fn place_index(&mut self, p: &Place) -> ER<usize> {
924 match p {
925 Place::Name { name, .. } => self.label_index(name),
926 Place::Nth { count, obj } => self.nth_index(count, obj),
927 Place::Corner(inner, _) | Place::CornerOf(_, inner) => self.place_index(inner),
928 Place::Here => err("`Here` is a point, not an object"),
929 Place::Member(_, _) => err("block sub-labels (B.A) are not supported yet"),
930 }
931 }
932
933 fn label_index(&self, name: &str) -> ER<usize> {
934 self.labels.get(name).copied().ok_or_else(|| EvalError {
935 msg: format!("unknown label `{name}`"),
936 })
937 }
938
939 fn nth_index(&mut self, count: &Nth, obj: &PrimObj) -> ER<usize> {
940 let want = primobj_kind(obj);
941 let matches: Vec<usize> = self
942 .placed
943 .iter()
944 .enumerate()
945 .filter(|(_, pl)| pl.kind == want)
946 .map(|(i, _)| i)
947 .collect();
948 if matches.is_empty() {
949 return err(format!("no {:?} object to reference", want_name(want)));
950 }
951 let idx = match count {
952 Nth::Last => *matches.last().unwrap(),
953 Nth::Count(e, from_last) => {
954 let n = self.eval_expr(e)?.round() as i64;
955 if n < 1 {
956 return err("ordinal must be >= 1");
957 }
958 let k = (n - 1) as usize;
959 if *from_last {
960 if k >= matches.len() {
961 return err("ordinal out of range");
962 }
963 matches[matches.len() - 1 - k]
964 } else {
965 if k >= matches.len() {
966 return err("ordinal out of range");
967 }
968 matches[k]
969 }
970 }
971 };
972 Ok(idx)
973 }
974
975 fn eval_stringexpr(&mut self, se: &StringExpr) -> ER<String> {
978 Ok(match se {
979 StringExpr::Lit(s) => s.clone(),
980 StringExpr::Concat(a, b) => {
981 format!("{}{}", self.eval_stringexpr(a)?, self.eval_stringexpr(b)?)
982 }
983 StringExpr::Arg(n) => format!("${n}"), StringExpr::Sprintf(fmt, args) => {
985 let f = self.eval_stringexpr(fmt)?;
986 let mut vals = Vec::with_capacity(args.len());
987 for e in args {
988 vals.push(self.eval_expr(e)?);
989 }
990 sprintf_fmt(&f, &vals)
991 }
992 })
993 }
994
995 fn eval_expr(&mut self, e: &Expr) -> ER<f64> {
998 Ok(match e {
999 Expr::Num(v) => *v,
1000 Expr::Var(name) => *self.vars.get(name).unwrap_or(&0.0),
1001 Expr::Env(v) => self.env.get(*v),
1002 Expr::Unary(op, a) => {
1003 let x = self.eval_expr(a)?;
1004 match op {
1005 UnOp::Neg => -x,
1006 UnOp::Pos => x,
1007 UnOp::Not => bool_f(x == 0.0),
1008 }
1009 }
1010 Expr::Bin(op, a, b) => {
1011 let x = self.eval_expr(a)?;
1012 let y = self.eval_expr(b)?;
1013 match op {
1014 BinOp::Add => x + y,
1015 BinOp::Sub => x - y,
1016 BinOp::Mul => x * y,
1017 BinOp::Div => x / y,
1018 BinOp::Mod => x % y,
1019 BinOp::Pow => x.powf(y),
1020 BinOp::Eq => bool_f(x == y),
1021 BinOp::Ne => bool_f(x != y),
1022 BinOp::Lt => bool_f(x < y),
1023 BinOp::Le => bool_f(x <= y),
1024 BinOp::Gt => bool_f(x > y),
1025 BinOp::Ge => bool_f(x >= y),
1026 BinOp::And => bool_f(x != 0.0 && y != 0.0),
1027 BinOp::Or => bool_f(x != 0.0 || y != 0.0),
1028 }
1029 }
1030 Expr::Func1(f, a) => {
1031 let x = self.eval_expr(a)?;
1032 match f {
1033 Func1::Abs => x.abs(),
1034 Func1::Acos => x.acos(),
1035 Func1::Asin => x.asin(),
1036 Func1::Cos => x.cos(),
1037 Func1::Exp => 10f64.powf(x),
1038 Func1::Expe => x.exp(),
1039 Func1::Int => x.trunc(),
1040 Func1::Log => x.log10(),
1041 Func1::Loge => x.ln(),
1042 Func1::Sign => {
1043 if x > 0.0 {
1044 1.0
1045 } else if x < 0.0 {
1046 -1.0
1047 } else {
1048 0.0
1049 }
1050 }
1051 Func1::Sin => x.sin(),
1052 Func1::Sqrt => x.sqrt(),
1053 Func1::Tan => x.tan(),
1054 Func1::Floor => x.floor(),
1055 }
1056 }
1057 Expr::Func2(f, a, b) => {
1058 let x = self.eval_expr(a)?;
1059 let y = self.eval_expr(b)?;
1060 match f {
1061 Func2::Atan2 => x.atan2(y),
1062 Func2::Max => x.max(y),
1063 Func2::Min => x.min(y),
1064 Func2::Pmod => x.rem_euclid(y),
1065 }
1066 }
1067 Expr::Rand(_) => 0.5, Expr::DotX(loc) => self.eval_loc(loc)?.x,
1069 Expr::DotY(loc) => self.eval_loc(loc)?.y,
1070 Expr::PlaceAttr(place, param) => {
1071 let idx = self.place_index(place)?;
1072 let pl = &self.placed[idx];
1073 match param {
1074 token::Param::Width => pl.bbox.width(),
1075 token::Param::Height => pl.bbox.height(),
1076 token::Param::Radius => pl.bbox.width() / 2.0,
1077 token::Param::Diameter => pl.bbox.width(),
1078 token::Param::Length => pl.start.dist(pl.end),
1079 token::Param::Thickness => pl.thick,
1080 }
1081 }
1082 })
1083 }
1084}
1085
1086fn apply_op(op: AssignOp, cur: f64, rhs: f64) -> f64 {
1089 match op {
1090 AssignOp::Set => rhs,
1091 AssignOp::Add => cur + rhs,
1092 AssignOp::Sub => cur - rhs,
1093 AssignOp::Mul => cur * rhs,
1094 AssignOp::Div => cur / rhs,
1095 AssignOp::Rem => cur % rhs,
1096 }
1097}
1098
1099fn bool_f(b: bool) -> f64 {
1100 if b { 1.0 } else { 0.0 }
1101}
1102
1103fn corner_offset(c: Corner, w: f64, h: f64) -> Point {
1104 let (hw, hh) = (w / 2.0, h / 2.0);
1105 match c {
1106 Corner::N => Point::new(0.0, hh),
1107 Corner::S => Point::new(0.0, -hh),
1108 Corner::E => Point::new(hw, 0.0),
1109 Corner::W => Point::new(-hw, 0.0),
1110 Corner::Ne => Point::new(hw, hh),
1111 Corner::Se => Point::new(hw, -hh),
1112 Corner::Nw => Point::new(-hw, hh),
1113 Corner::Sw => Point::new(-hw, -hh),
1114 Corner::Center | Corner::Start | Corner::End => Point::ZERO,
1115 }
1116}
1117
1118fn primobj_kind(o: &PrimObj) -> PKind {
1119 match o {
1120 PrimObj::Prim(p) => match p {
1121 Prim::Box => PKind::Box,
1122 Prim::Circle => PKind::Circle,
1123 Prim::Ellipse => PKind::Ellipse,
1124 Prim::Line | Prim::Arrow => PKind::Line,
1125 Prim::Move => PKind::Move,
1126 Prim::Spline => PKind::Spline,
1127 Prim::Arc => PKind::Arc,
1128 },
1129 PrimObj::Block | PrimObj::EmptyBrack => PKind::Block,
1130 PrimObj::Str(_) => PKind::Text,
1131 }
1132}
1133
1134fn want_name(k: PKind) -> &'static str {
1135 match k {
1136 PKind::Box => "box",
1137 PKind::Circle => "circle",
1138 PKind::Ellipse => "ellipse",
1139 PKind::Line => "line",
1140 PKind::Move => "move",
1141 PKind::Spline => "spline",
1142 PKind::Arc => "arc",
1143 PKind::Block => "block",
1144 PKind::Text => "text",
1145 }
1146}
1147
1148fn nearest_dir(v: Point) -> Dir {
1149 if v.x.abs() >= v.y.abs() {
1150 if v.x >= 0.0 { Dir::Right } else { Dir::Left }
1151 } else if v.y >= 0.0 {
1152 Dir::Up
1153 } else {
1154 Dir::Down
1155 }
1156}
1157
1158fn sprintf_fmt(fmt: &str, vals: &[f64]) -> String {
1161 let mut out = String::new();
1162 let mut chars = fmt.chars().peekable();
1163 let mut ai = 0usize;
1164 while let Some(c) = chars.next() {
1165 if c != '%' {
1166 out.push(c);
1167 continue;
1168 }
1169 let mut spec = String::new();
1170 while let Some(&n) = chars.peek() {
1171 if "+-0123456789. #".contains(n) {
1172 spec.push(n);
1173 chars.next();
1174 } else {
1175 break;
1176 }
1177 }
1178 let conv = match chars.next() {
1179 Some(c) => c,
1180 None => break,
1181 };
1182 if conv == '%' {
1183 out.push('%');
1184 continue;
1185 }
1186 let v = vals.get(ai).copied().unwrap_or(0.0);
1187 ai += 1;
1188 let prec = spec.split('.').nth(1).and_then(|p| {
1189 p.chars()
1190 .take_while(|c| c.is_ascii_digit())
1191 .collect::<String>()
1192 .parse::<usize>()
1193 .ok()
1194 });
1195 match conv {
1196 'd' | 'i' => out.push_str(&format!("{}", v.round() as i64)),
1197 'f' | 'F' => out.push_str(&format!("{:.*}", prec.unwrap_or(6), v)),
1198 'e' | 'E' => out.push_str(&format!("{:.*e}", prec.unwrap_or(6), v)),
1199 'g' | 'G' => out.push_str(&format!("{v}")),
1200 's' => out.push_str(&format!("{v}")),
1201 other => {
1202 out.push('%');
1203 out.push(other);
1204 }
1205 }
1206 }
1207 out
1208}
1209
1210fn stringexpr_lit(se: &StringExpr) -> String {
1211 match se {
1212 StringExpr::Lit(s) => s.clone(),
1213 StringExpr::Concat(a, b) => format!("{}{}", stringexpr_lit(a), stringexpr_lit(b)),
1214 StringExpr::Arg(n) => format!("${n}"),
1215 StringExpr::Sprintf(fmt, _) => stringexpr_lit(fmt),
1216 }
1217}
1218
1219fn translate_shape(sh: &mut Shape, d: Point) {
1220 let mv = |p: &mut Point| *p = *p + d;
1221 match sh {
1222 Shape::Box { c, .. } | Shape::Circle { c, .. } | Shape::Ellipse { c, .. } => mv(c),
1223 Shape::Path { pts, .. } | Shape::Spline { pts, .. } => {
1224 for p in pts {
1225 mv(p);
1226 }
1227 }
1228 Shape::Arc { c, .. } => mv(c),
1229 Shape::Text { at, .. } => mv(at),
1230 }
1231}
1232
1233#[cfg(test)]
1234mod tests {
1235 use super::*;
1236 use crate::parser::parse;
1237
1238 fn draw(src: &str) -> Drawing {
1239 eval(&parse(src).unwrap()).unwrap()
1240 }
1241
1242 #[test]
1243 fn pipeline_chains_left_to_right() {
1244 let d = draw(".PS\nellipse \"document\"\narrow\nbox \"PIC\"\narrow\nbox \"TROFF\"\n.PE");
1245 assert_eq!(d.shapes.len(), 5);
1247 if let Shape::Ellipse { c, .. } = &d.shapes[0] {
1249 assert!((c.x - 0.375).abs() < 1e-9, "ellipse x = {}", c.x);
1250 assert!(c.y.abs() < 1e-9);
1251 } else {
1252 panic!("expected ellipse");
1253 }
1254 assert!(d.bbox.width() > 2.0);
1256 assert!((d.bbox.height() - 0.5).abs() < 1e-9);
1257 }
1258
1259 #[test]
1260 fn box_at_absolute() {
1261 let d = draw("box ht 0.3 wid 0.5 at 1,2");
1262 let Shape::Box { c, w, h, .. } = &d.shapes[0] else {
1263 panic!()
1264 };
1265 assert_eq!(*c, Point::new(1.0, 2.0));
1266 assert!((*w - 0.5).abs() < 1e-9 && (*h - 0.3).abs() < 1e-9);
1267 }
1268
1269 #[test]
1270 fn diamond_is_closed() {
1271 let d = draw("line up right then down right then down left then up left");
1272 let Shape::Path { pts, .. } = &d.shapes[0] else {
1273 panic!()
1274 };
1275 assert_eq!(pts.len(), 5);
1276 assert!(pts[0].dist(*pts.last().unwrap()) < 1e-9);
1278 }
1279
1280 #[test]
1281 fn corners_and_labels() {
1282 let d = draw("A: box wid 1 ht 1 at 0,0\nbox wid 0.5 ht 0.5 with .sw at A.ne");
1283 let Shape::Box { c, .. } = &d.shapes[1] else {
1285 panic!()
1286 };
1287 assert!(
1288 (c.x - 0.75).abs() < 1e-9 && (c.y - 0.75).abs() < 1e-9,
1289 "c = {c:?}"
1290 );
1291 }
1292
1293 #[test]
1294 fn for_loop_repeats() {
1295 let d = draw("for i = 1 to 3 do { box }");
1296 assert_eq!(d.shapes.len(), 3);
1297 }
1298
1299 #[test]
1300 fn if_else_branches() {
1301 let d1 = draw("x = 1\nif x > 0 then { box } else { circle }");
1302 assert!(matches!(d1.shapes[0], Shape::Box { .. }));
1303 let d2 = draw("x = 0\nif x > 0 then { box } else { circle }");
1304 assert!(matches!(d2.shapes[0], Shape::Circle { .. }));
1305 }
1306
1307 #[test]
1308 fn define_macro_with_args() {
1309 let d = draw("define elem { box wid $1 }\nelem(0.5)\nelem(1.25)");
1310 assert_eq!(d.shapes.len(), 2);
1311 let Shape::Box { w: w0, .. } = &d.shapes[0] else {
1312 panic!()
1313 };
1314 let Shape::Box { w: w1, .. } = &d.shapes[1] else {
1315 panic!()
1316 };
1317 assert!((*w0 - 0.5).abs() < 1e-9 && (*w1 - 1.25).abs() < 1e-9);
1318 }
1319
1320 #[test]
1321 fn for_loop_places_in_a_row() {
1322 let d = draw("for i = 1 to 3 do { box; move }");
1324 assert!(d.bbox.width() > 2.0);
1325 }
1326
1327 #[test]
1328 fn animate_timing() {
1329 let d = draw(
1330 "A: box\narrow\nbox\nanimate A with \"fade\" for 0.5\nanimate last arrow with \"draw\"\nanimate 2nd box with \"pop\" after A",
1331 );
1332 assert_eq!(d.anims.len(), 3);
1333 assert_eq!(d.anims[0].effect, "fade");
1335 assert_eq!(d.anims[0].shape, 0);
1336 assert!((d.anims[0].start).abs() < 1e-9 && (d.anims[0].duration - 0.5).abs() < 1e-9);
1337 assert_eq!(d.anims[1].shape, 1);
1339 assert!((d.anims[1].start - 0.5).abs() < 1e-9);
1340 assert_eq!(d.anims[2].shape, 2);
1342 assert!((d.anims[2].start - 0.5).abs() < 1e-9);
1343 }
1344
1345 #[test]
1346 fn last_ordinal() {
1347 let d = draw("box at 0,0\nbox at 2,0\narrow from 1st box.e to 2nd box.w");
1348 let Shape::Path { pts, .. } = &d.shapes[2] else {
1349 panic!()
1350 };
1351 assert!(pts[0].x > 0.0 && pts.last().unwrap().x < 2.0);
1353 }
1354}