1use crate::ast::*;
10use crate::lexer::{LexError, Spanned, lex};
11use crate::token::*;
12
13#[derive(Debug, Clone, PartialEq)]
15pub struct ParseError {
16 pub msg: String,
17 pub line: u32,
18 pub col: u32,
19}
20
21impl std::fmt::Display for ParseError {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 write!(f, "{}:{}: {}", self.line, self.col, self.msg)
24 }
25}
26
27impl From<LexError> for ParseError {
28 fn from(e: LexError) -> Self {
29 ParseError {
30 msg: e.msg,
31 line: e.line,
32 col: e.col,
33 }
34 }
35}
36
37pub fn parse(src: &str) -> Result<Picture, ParseError> {
40 parse_in_dir(src, None)
41}
42
43pub fn parse_in_dir(src: &str, base: Option<&Path>) -> Result<Picture, ParseError> {
45 let src = strip_backend_preamble(src);
46 let toks = lex(&src)?;
47 let (toks, macros) = preprocess(toks, base)?;
48 let mut pic = Parser::new(toks).parse_picture()?;
49 pic.macros = macros;
50 pic.base_dir = base.map(|p| p.to_path_buf());
51 Ok(pic)
52}
53
54pub fn parse_body_tokens(
59 toks: &[Spanned],
60 macros: &mut Macros,
61 base: Option<&Path>,
62) -> Result<Vec<Stmt>, ParseError> {
63 let before = body_macro_frame(toks).unwrap_or_else(|| macros.clone());
64 let mut m = before.clone();
65 let mut input = toks.to_vec();
66 input.push(Spanned::new(Token::Eof, 0, 0));
67 let expanded = expand(&input, &mut m, 0, base)?;
68 propagate_macro_changes(macros, &before, &m);
69 let mut p = Parser::new(expanded);
70 p.parse_elementlist(&[])
71}
72
73fn body_macro_frame(toks: &[Spanned]) -> Option<Macros> {
74 toks.iter()
75 .find_map(|s| s.macro_frame.as_ref().map(|m| m.as_ref().clone()))
76}
77
78fn propagate_macro_changes(macros: &mut Macros, before: &Macros, after: &Macros) {
79 for name in before.keys() {
80 if !after.contains_key(name) {
81 macros.remove(name);
82 }
83 }
84 for (name, body) in after {
85 if before.get(name) != Some(body) {
86 macros.insert(name.clone(), body.clone());
87 }
88 }
89}
90
91pub(crate) fn parse_exec_source(
94 src: &str,
95 macros: &Macros,
96 base: Option<&Path>,
97 arg_frame: Option<&[Vec<Spanned>]>,
98) -> Result<Vec<Stmt>, ParseError> {
99 let mut toks = lex(src)?;
100 if let Some(args) = arg_frame {
101 toks = substitute(&toks, args);
102 }
103 let mut m = macros.clone();
104 let expanded = expand(&toks, &mut m, 0, base)?;
105 let mut p = Parser::new(expanded);
106 p.parse_elementlist(&[])
107}
108
109pub(crate) fn parse_stringexpr_tokens(
110 toks: &[Spanned],
111 macros: &mut Macros,
112 base: Option<&Path>,
113) -> Result<StringExpr, ParseError> {
114 let mut input = toks.to_vec();
115 input.push(Spanned::new(Token::Eof, 0, 0));
116 let expanded = expand(&input, macros, 0, base)?;
117 let mut p = Parser::new(expanded);
118 p.skip_newlines();
119 let expr = p.parse_stringexpr()?;
120 p.skip_newlines();
121 if !p.at(&Token::Eof) {
122 return p.err(format!("unexpected {:?} after string expression", p.cur()));
123 }
124 Ok(expr)
125}
126
127fn strip_backend_preamble(src: &str) -> String {
135 let mut out = String::with_capacity(src.len());
136 let mut in_verbatimtex = false;
137 let mut in_string = false;
138 let mut in_raw_sh = false;
139
140 for line in src.lines() {
141 let trimmed = line.trim_start();
142 if in_verbatimtex {
143 out.push('\n');
144 if starts_word(trimmed, "etex") {
145 in_verbatimtex = false;
146 }
147 continue;
148 }
149
150 if in_raw_sh {
151 out.push_str(line);
152 out.push('\n');
153 in_raw_sh = line_continues(line);
154 continue;
155 }
156
157 if !in_string && starts_word(trimmed, "verbatimtex") {
158 in_verbatimtex = true;
159 out.push('\n');
160 } else if !in_string && is_ignored_backend_line(trimmed) {
161 out.push('\n');
162 } else {
163 out.push_str(line);
164 out.push('\n');
165 if starts_word(trimmed, "sh") {
166 in_raw_sh = line_continues(line);
167 } else {
168 in_string = update_string_state(line, in_string);
169 }
170 }
171 }
172 out
173}
174
175fn line_continues(line: &str) -> bool {
176 line.trim_end_matches([' ', '\t', '\r']).ends_with('\\')
177}
178
179fn update_string_state(line: &str, mut in_string: bool) -> bool {
180 let mut slashes = 0usize;
181 for c in line.chars() {
182 if c == '\\' {
183 slashes += 1;
184 continue;
185 }
186 if c == '"' && slashes.is_multiple_of(2) {
187 in_string = !in_string;
188 }
189 slashes = 0;
190 }
191 in_string
192}
193
194fn is_ignored_backend_line(trimmed: &str) -> bool {
195 trimmed.starts_with("\\global") || trimmed.starts_with("\\psset")
196}
197
198fn starts_word(s: &str, word: &str) -> bool {
199 let Some(rest) = s.strip_prefix(word) else {
200 return false;
201 };
202 !rest
203 .chars()
204 .next()
205 .is_some_and(|c| c.is_alphanumeric() || c == '_')
206}
207
208use std::collections::HashMap;
216use std::path::Path;
217
218fn preprocess(
219 input: Vec<Spanned>,
220 base: Option<&Path>,
221) -> Result<(Vec<Spanned>, Macros), ParseError> {
222 let mut macros: Macros = builtin_unit_macros();
223 let out = expand(&input, &mut macros, 0, base)?;
224 Ok((out, macros))
225}
226
227fn builtin_unit_macros() -> Macros {
231 let defs = [
232 ("bp__", "*(scale/72)"), ("pt__", "*(scale/72.27)"), ("pc__", "*(12*scale/72.27)"), ("in__", "*scale"), ("cm__", "*(scale/2.54)"), ("mm__", "*(scale/25.4)"), ("px__", "*(scale/96)"), ];
240 let mut m = Macros::new();
241 for (name, body) in defs {
242 if let Ok(toks) = lex(body) {
243 let body_toks: Vec<Spanned> =
244 toks.into_iter().filter(|s| s.tok != Token::Eof).collect();
245 m.insert(name.to_string(), body_toks);
246 }
247 }
248 m
249}
250
251fn loc(toks: &[Spanned], i: usize) -> (u32, u32) {
252 toks.get(i).map(|s| (s.line, s.col)).unwrap_or((0, 0))
253}
254
255fn expand(
256 toks: &[Spanned],
257 macros: &mut HashMap<String, Vec<Spanned>>,
258 depth: usize,
259 base: Option<&Path>,
260) -> Result<Vec<Spanned>, ParseError> {
261 if depth > 64 {
262 return Err(ParseError {
263 msg: "macro expansion too deep (recursive define?)".into(),
264 line: 0,
265 col: 0,
266 });
267 }
268 let mut out = Vec::new();
269 let mut i = 0;
270 while i < toks.len() {
271 match &toks[i].tok {
272 Token::Kw(Kw::Define) => {
273 let (l, c) = loc(toks, i);
274 i += 1;
275 let name = match toks.get(i).map(|s| &s.tok) {
276 Some(Token::Name(n)) | Some(Token::Label(n)) => n.clone(),
277 _ => {
278 return Err(ParseError {
279 msg: "define: expected a macro name".into(),
280 line: l,
281 col: c,
282 });
283 }
284 };
285 i += 1;
286 while toks.get(i).map(|s| &s.tok) == Some(&Token::Newline) {
288 i += 1;
289 }
290 let Some(delim) = toks.get(i).map(|s| s.tok.clone()) else {
291 return Err(ParseError {
292 msg: "define: expected a body delimiter".into(),
293 line: l,
294 col: c,
295 });
296 };
297 let body = if delim == Token::LeftBrace {
298 i += 1; let start = i;
300 let mut bd = 1;
301 while i < toks.len() && bd > 0 {
302 match &toks[i].tok {
303 Token::LeftBrace => bd += 1,
304 Token::RightBrace => {
305 bd -= 1;
306 if bd == 0 {
307 break;
308 }
309 }
310 _ => {}
311 }
312 i += 1;
313 }
314 if bd != 0 {
315 return Err(ParseError {
316 msg: "define: unterminated `{` body".into(),
317 line: l,
318 col: c,
319 });
320 }
321 let body = trim_edge_newlines(&toks[start..i]);
322 i += 1; body
324 } else {
325 if matches!(delim, Token::Eof | Token::Newline) {
326 let (l, c) = loc(toks, i);
327 return Err(ParseError {
328 msg: "define: expected a body delimiter".into(),
329 line: l,
330 col: c,
331 });
332 }
333 i += 1; let start = i;
335 while i < toks.len() && toks[i].tok != delim {
336 i += 1;
337 }
338 if i >= toks.len() {
339 return Err(ParseError {
340 msg: "define: unterminated delimited body".into(),
341 line: l,
342 col: c,
343 });
344 }
345 let body = trim_edge_newlines(&toks[start..i]);
346 i += 1; body
348 };
349 macros.insert(name, body);
350 }
351 Token::Kw(Kw::Undef) => {
352 i += 1;
353 if let Some(Token::Name(n)) | Some(Token::Label(n)) = toks.get(i).map(|s| &s.tok) {
354 macros.remove(n);
355 }
356 i += 1;
357 }
358 Token::Kw(Kw::If) => {
363 let if_tok = toks[i].clone();
364 out.push(toks[i].clone());
365 i += 1;
366 let Some(te) = find_kw_depth0(toks, i, Kw::Then) else {
367 continue; };
369 let cond = expand(&toks[i..te], macros, depth + 1, base)?;
370 if let Some((then_body, after_then)) = read_braced_body(toks, te + 1)? {
371 let mut j = after_then;
372 while matches!(toks.get(j).map(|s| &s.tok), Some(Token::Newline)) {
373 j += 1;
374 }
375 let else_body =
376 if matches!(toks.get(j).map(|s| &s.tok), Some(Token::Kw(Kw::Else))) {
377 read_braced_body(toks, j + 1)?
378 } else {
379 None
380 };
381 if let Some(take_then) = static_truth(&cond) {
382 let after_static = else_body
383 .as_ref()
384 .map(|(_, after)| *after)
385 .unwrap_or(after_then);
386 out.pop(); if take_then {
388 out.extend(expand(&then_body, macros, depth + 1, base)?);
389 i = after_static;
390 } else if let Some((body, after)) = else_body {
391 out.extend(expand(&body, macros, depth + 1, base)?);
392 i = after;
393 } else {
394 i = after_then;
395 }
396 continue;
397 }
398 }
399 *out.last_mut().unwrap() = if_tok;
400 out.extend(cond);
401 out.push(toks[te].clone()); i = copy_braced(toks, te + 1, &mut out, macros)?;
403 let mut j = i;
405 while matches!(toks.get(j).map(|s| &s.tok), Some(Token::Newline)) {
406 j += 1;
407 }
408 if matches!(toks.get(j).map(|s| &s.tok), Some(Token::Kw(Kw::Else))) {
409 out.push(toks[j].clone());
410 i = copy_braced(toks, j + 1, &mut out, macros)?;
411 }
412 }
413 Token::Kw(Kw::For) => {
414 out.push(toks[i].clone());
415 i += 1;
416 let Some(de) = find_kw_depth0(toks, i, Kw::Do) else {
417 continue;
418 };
419 out.extend(expand(&toks[i..de], macros, depth + 1, base)?);
420 out.push(toks[de].clone()); i = copy_braced(toks, de + 1, &mut out, macros)?;
422 }
423 Token::Name(n) | Token::Label(n) if macros.contains_key(n) => {
424 let body = macros.get(n).unwrap().clone();
425 i += 1;
426 let args = if toks.get(i).map(|s| &s.tok) == Some(&Token::Lparen) {
427 i += 1;
428 let (a, ni) = read_args(toks, i)?;
429 i = ni;
430 a
431 } else {
432 Vec::new()
433 };
434 let sub = substitute(&body, &args);
435 let expanded = expand(&sub, macros, depth + 1, base)?;
436 out.extend(expanded);
437 }
438 Token::Kw(Kw::Copy) => {
440 let (l, c) = loc(toks, i);
441 i += 1;
442 let Some(Token::Str(fname)) = toks.get(i).map(|s| &s.tok) else {
443 return Err(ParseError {
444 msg:
445 "copy: expected a quoted file name (only `copy \"file\"` is supported)"
446 .into(),
447 line: l,
448 col: c,
449 });
450 };
451 let fname = fname.clone();
452 i += 1;
453 let inc = include_file(base, &fname, macros, depth, l, c)?;
454 out.extend(inc);
455 }
456 _ => {
457 out.push(toks[i].clone());
458 i += 1;
459 }
460 }
461 }
462 Ok(out)
463}
464
465fn read_args(toks: &[Spanned], mut i: usize) -> Result<(Vec<Vec<Spanned>>, usize), ParseError> {
468 let mut args: Vec<Vec<Spanned>> = Vec::new();
469 let mut cur: Vec<Spanned> = Vec::new();
470 let mut depth = 0i32;
471 loop {
472 let Some(s) = toks.get(i) else {
473 return Err(ParseError {
474 msg: "unterminated macro arguments".into(),
475 line: 0,
476 col: 0,
477 });
478 };
479 match &s.tok {
480 Token::Lparen | Token::LeftBrack | Token::LeftBrace => {
481 depth += 1;
482 cur.push(s.clone());
483 i += 1;
484 }
485 Token::Rparen if depth == 0 => {
486 i += 1;
487 trim_trailing_newlines(&mut cur);
488 if !cur.is_empty() || !args.is_empty() {
489 args.push(cur);
490 }
491 break;
492 }
493 Token::Rparen | Token::RightBrack | Token::RightBrace => {
494 depth -= 1;
495 cur.push(s.clone());
496 i += 1;
497 }
498 Token::Comma if depth == 0 => {
499 trim_trailing_newlines(&mut cur);
500 args.push(std::mem::take(&mut cur));
501 i += 1;
502 }
503 Token::Newline if depth == 0 && cur.is_empty() => {
504 i += 1;
505 }
506 _ => {
507 cur.push(s.clone());
508 i += 1;
509 }
510 }
511 }
512 Ok((args, i))
513}
514
515fn trim_trailing_newlines(toks: &mut Vec<Spanned>) {
516 while matches!(toks.last().map(|s| &s.tok), Some(Token::Newline)) {
517 toks.pop();
518 }
519}
520
521fn trim_edge_newlines(toks: &[Spanned]) -> Vec<Spanned> {
524 let mut start = 0;
525 let mut end = toks.len();
526 while start < end && toks[start].tok == Token::Newline {
527 start += 1;
528 }
529 while end > start && toks[end - 1].tok == Token::Newline {
530 end -= 1;
531 }
532 toks[start..end].to_vec()
533}
534
535fn substitute(body: &[Spanned], args: &[Vec<Spanned>]) -> Vec<Spanned> {
537 let mut out = Vec::new();
538 let mut i = 0;
539 while i < body.len() {
540 if matches!(body[i].tok, Token::Kw(Kw::Define))
541 && let Some((define, next)) = copy_define_verbatim(body, i)
542 {
543 out.extend(define);
544 i = next;
545 continue;
546 }
547
548 if let Some((pasted, next)) = paste_adjacent_args(body, args, i) {
549 out.push(pasted.with_arg_frame(args));
550 i = next;
551 continue;
552 }
553
554 let s = &body[i];
555 match &s.tok {
556 Token::Arg(k) => {
557 if let Some(a) = args.get((*k as usize).wrapping_sub(1)) {
558 out.extend(a.iter().cloned().map(|s| s.with_arg_frame(args)));
559 }
560 }
561 Token::ArgCount => out.push(
563 Spanned::new(Token::Float(args.len() as f64), s.line, s.col).with_arg_frame(args),
564 ),
565 Token::Str(text) if text.contains('$') => {
568 out.push(
569 Spanned::new(Token::Str(subst_in_string(text, args)), s.line, s.col)
570 .with_arg_frame(args),
571 );
572 }
573 _ => out.push(s.clone().with_arg_frame(args)),
574 }
575 i += 1;
576 }
577 out
578}
579
580fn copy_define_verbatim(body: &[Spanned], start: usize) -> Option<(Vec<Spanned>, usize)> {
581 let mut name_idx = start + 1;
582 while matches!(body.get(name_idx).map(|s| &s.tok), Some(Token::Newline)) {
583 name_idx += 1;
584 }
585 if !matches!(
586 body.get(name_idx).map(|s| &s.tok),
587 Some(Token::Name(_)) | Some(Token::Label(_))
588 ) {
589 return None;
590 }
591
592 let mut out = Vec::new();
593 let mut i = start;
594 while let Some(s) = body.get(i) {
595 out.push(s.clone());
596 i += 1;
597 if matches!(s.tok, Token::LeftBrace) {
598 break;
599 }
600 }
601
602 if !matches!(out.last().map(|s| &s.tok), Some(Token::LeftBrace)) {
603 return None;
604 }
605
606 let mut depth = 1i32;
607 while let Some(s) = body.get(i) {
608 match &s.tok {
609 Token::LeftBrace => depth += 1,
610 Token::RightBrace => depth -= 1,
611 _ => {}
612 }
613 out.push(s.clone());
614 i += 1;
615 if depth == 0 {
616 return Some((out, i));
617 }
618 }
619 None
620}
621
622fn paste_adjacent_args(
623 body: &[Spanned],
624 args: &[Vec<Spanned>],
625 start: usize,
626) -> Option<(Spanned, usize)> {
627 let first = body.get(start)?;
628 let Token::Arg(k) = &first.tok else {
629 return None;
630 };
631
632 let mut text = arg_text(*k, args);
633 let mut count = 1usize;
634 let mut end = start + 1;
635 let mut prev = first;
636 while let Some(next) = body.get(end) {
637 let Token::Arg(k) = &next.tok else {
638 break;
639 };
640 if !adjacent_arg_tokens(prev, next) {
641 break;
642 }
643 text.push_str(&arg_text(*k, args));
644 count += 1;
645 prev = next;
646 end += 1;
647 }
648
649 if count < 2 || text.is_empty() {
650 return None;
651 }
652
653 Some((tokenize_pasted_arg_text(&text, first.line, first.col), end))
654}
655
656fn arg_text(k: u32, args: &[Vec<Spanned>]) -> String {
657 args.get((k as usize).wrapping_sub(1))
658 .map(|a| tokens_to_text(a))
659 .unwrap_or_default()
660}
661
662fn adjacent_arg_tokens(left: &Spanned, right: &Spanned) -> bool {
663 left.line == right.line && arg_end_col(left) == Some(right.col)
664}
665
666fn arg_end_col(s: &Spanned) -> Option<u32> {
667 let Token::Arg(k) = &s.tok else {
668 return None;
669 };
670 Some(s.col + 1 + k.to_string().len() as u32)
671}
672
673fn tokenize_pasted_arg_text(text: &str, line: u32, col: u32) -> Spanned {
674 if let Ok(toks) = lex(text)
675 && toks.len() == 2
676 && matches!(toks[1].tok, Token::Eof)
677 {
678 return Spanned::new(toks[0].tok.clone(), line, col);
679 }
680
681 let tok = if text.chars().next().is_some_and(|c| c.is_ascii_uppercase()) {
682 Token::Label(text.to_string())
683 } else {
684 Token::Name(text.to_string())
685 };
686 Spanned::new(tok, line, col)
687}
688
689fn subst_in_string(text: &str, args: &[Vec<Spanned>]) -> String {
692 let chars: Vec<char> = text.chars().collect();
693 let mut out = String::new();
694 let mut i = 0;
695 while i < chars.len() {
696 if chars[i] == '$' && chars.get(i + 1) == Some(&'+') {
697 out.push_str(&args.len().to_string());
698 i += 2;
699 } else if chars[i] == '$' && chars.get(i + 1).is_some_and(|c| c.is_ascii_digit()) {
700 let mut j = i + 1;
701 let mut num = String::new();
702 while j < chars.len() && chars[j].is_ascii_digit() {
703 num.push(chars[j]);
704 j += 1;
705 }
706 if let Ok(k) = num.parse::<usize>()
707 && k >= 1
708 && let Some(a) = args.get(k - 1)
709 {
710 out.push_str(&tokens_to_text(a));
711 }
712 i = j;
713 } else {
714 out.push(chars[i]);
715 i += 1;
716 }
717 }
718 out
719}
720
721fn tokens_to_text(toks: &[Spanned]) -> String {
724 let mut s = String::new();
725 for t in toks {
726 match &t.tok {
727 Token::Float(v) => s.push_str(&format!("{v}")),
728 Token::Str(t) => s.push_str(t),
729 Token::Name(n) | Token::Label(n) => s.push_str(n),
730 Token::Lparen => s.push('('),
731 Token::Rparen => s.push(')'),
732 Token::LeftBrack => s.push('['),
733 Token::RightBrack => s.push(']'),
734 Token::LeftBrace => s.push('{'),
735 Token::RightBrace => s.push('}'),
736 Token::Comma => s.push(','),
737 Token::Colon => s.push(':'),
738 Token::Dot => s.push('.'),
739 Token::Plus => s.push('+'),
740 Token::Minus => s.push('-'),
741 Token::Mult => s.push('*'),
742 Token::Div => s.push('/'),
743 Token::Percent => s.push('%'),
744 Token::Dollar => s.push('$'),
745 Token::Backslash => s.push('\\'),
746 Token::DotX => s.push_str(".x"),
747 Token::DotY => s.push_str(".y"),
748 Token::DotPS => s.push_str(".PS"),
749 Token::DotPE => s.push_str(".PE"),
750 Token::Corner(c) => s.push_str(corner_text(*c)),
751 Token::Param(p) => s.push_str(param_text(*p)),
752 Token::LineType(l) => s.push_str(line_type_text(*l)),
753 Token::TextPos(p) => s.push_str(text_pos_text(*p)),
754 Token::Arrow(a) => s.push_str(arrow_text(*a)),
755 Token::Dir(d) => s.push_str(dir_text(*d)),
756 Token::Prim(p) => s.push_str(prim_text(*p)),
757 Token::Color(c) => s.push_str(color_text(*c)),
758 _ => {}
759 }
760 }
761 s
762}
763
764fn corner_text(c: Corner) -> &'static str {
765 match c {
766 Corner::N => ".n",
767 Corner::S => ".s",
768 Corner::E => ".e",
769 Corner::W => ".w",
770 Corner::Ne => ".ne",
771 Corner::Se => ".se",
772 Corner::Nw => ".nw",
773 Corner::Sw => ".sw",
774 Corner::Start => ".start",
775 Corner::End => ".end",
776 Corner::Center => ".c",
777 }
778}
779
780fn param_text(p: Param) -> &'static str {
781 match p {
782 Param::Height => ".ht",
783 Param::Width => ".wid",
784 Param::Radius => ".rad",
785 Param::Diameter => ".diam",
786 Param::Thickness => ".thick",
787 Param::Length => ".len",
788 }
789}
790
791fn line_type_text(l: LineType) -> &'static str {
792 match l {
793 LineType::Solid => "solid",
794 LineType::Dotted => "dotted",
795 LineType::Dashed => "dashed",
796 LineType::Invis => "invis",
797 }
798}
799
800fn text_pos_text(p: TextPos) -> &'static str {
801 match p {
802 TextPos::Center => "center",
803 TextPos::Ljust => "ljust",
804 TextPos::Rjust => "rjust",
805 TextPos::Above => "above",
806 TextPos::Below => "below",
807 }
808}
809
810fn arrow_text(a: Arrow) -> &'static str {
811 match a {
812 Arrow::Left => "<-",
813 Arrow::Right => "->",
814 Arrow::Double => "<->",
815 }
816}
817
818fn dir_text(d: Dir) -> &'static str {
819 match d {
820 Dir::Up => "up",
821 Dir::Down => "down",
822 Dir::Right => "right",
823 Dir::Left => "left",
824 }
825}
826
827fn prim_text(p: Prim) -> &'static str {
828 match p {
829 Prim::Box => "box",
830 Prim::Circle => "circle",
831 Prim::Ellipse => "ellipse",
832 Prim::Arc => "arc",
833 Prim::Line => "line",
834 Prim::Arrow => "arrow",
835 Prim::Move => "move",
836 Prim::Spline => "spline",
837 }
838}
839
840fn color_text(c: Color) -> &'static str {
841 match c {
842 Color::Colored => "color",
843 Color::Outlined => "outlined",
844 Color::Shaded => "shaded",
845 }
846}
847
848fn include_file(
852 base: Option<&Path>,
853 fname: &str,
854 macros: &mut HashMap<String, Vec<Spanned>>,
855 depth: usize,
856 l: u32,
857 c: u32,
858) -> Result<Vec<Spanned>, ParseError> {
859 let mkerr = |msg: String| ParseError {
860 msg,
861 line: l,
862 col: c,
863 };
864 let p = Path::new(fname);
865 let path = if p.is_absolute() {
866 p.to_path_buf()
867 } else {
868 match base {
869 Some(b) => b.join(p),
870 None => {
871 return Err(mkerr(format!(
872 "copy \"{fname}\": file includes require a file path (unavailable here)"
873 )));
874 }
875 }
876 };
877 let content =
878 std::fs::read_to_string(&path).map_err(|e| mkerr(format!("copy \"{fname}\": {e}")))?;
879 let toks = lex(&content)?;
880 let inc_base = path.parent().map(|d| d.to_path_buf());
881 let mut expanded = expand(&toks, macros, depth + 1, inc_base.as_deref())?;
882 if matches!(expanded.last().map(|s| &s.tok), Some(Token::Eof)) {
883 expanded.pop();
884 }
885 Ok(expanded)
886}
887
888fn find_kw_depth0(toks: &[Spanned], start: usize, kw: Kw) -> Option<usize> {
890 let mut depth = 0i32;
891 for (off, s) in toks[start..].iter().enumerate() {
892 match &s.tok {
893 Token::Lparen | Token::LeftBrace | Token::LeftBrack => depth += 1,
894 Token::Rparen | Token::RightBrace | Token::RightBrack => {
895 depth -= 1;
896 if depth < 0 {
897 return None;
898 }
899 }
900 Token::Kw(k) if *k == kw && depth == 0 => return Some(start + off),
901 _ => {}
902 }
903 }
904 None
905}
906
907fn copy_braced(
910 toks: &[Spanned],
911 mut i: usize,
912 out: &mut Vec<Spanned>,
913 macros: &HashMap<String, Vec<Spanned>>,
914) -> Result<usize, ParseError> {
915 while matches!(toks.get(i).map(|s| &s.tok), Some(Token::Newline)) {
916 out.push(toks[i].clone());
917 i += 1;
918 }
919 if !matches!(toks.get(i).map(|s| &s.tok), Some(Token::LeftBrace)) {
920 return Ok(i); }
922 let mut depth = 0i32;
923 let mut tagged_body = false;
924 while let Some(s) = toks.get(i) {
925 let mut s = s.clone();
926 let outer_left_brace = depth == 0 && matches!(s.tok, Token::LeftBrace);
927 match &s.tok {
928 Token::LeftBrace => depth += 1,
929 Token::RightBrace => {
930 out.push(s);
931 i += 1;
932 depth -= 1;
933 if depth == 0 {
934 return Ok(i);
935 }
936 continue;
937 }
938 _ => {}
939 }
940 if depth == 1 && !outer_left_brace && !tagged_body {
941 s = s.with_macro_frame(macros);
942 tagged_body = true;
943 }
944 out.push(s);
945 i += 1;
946 }
947 Err(ParseError {
948 msg: "unterminated `{` body".into(),
949 line: 0,
950 col: 0,
951 })
952}
953
954fn read_braced_body(
955 toks: &[Spanned],
956 mut i: usize,
957) -> Result<Option<(Vec<Spanned>, usize)>, ParseError> {
958 while matches!(toks.get(i).map(|s| &s.tok), Some(Token::Newline)) {
959 i += 1;
960 }
961 if !matches!(toks.get(i).map(|s| &s.tok), Some(Token::LeftBrace)) {
962 return Ok(None);
963 }
964 i += 1;
965 let start = i;
966 let mut depth = 1i32;
967 while let Some(s) = toks.get(i) {
968 match &s.tok {
969 Token::LeftBrace => depth += 1,
970 Token::RightBrace => {
971 depth -= 1;
972 if depth == 0 {
973 return Ok(Some((toks[start..i].to_vec(), i + 1)));
974 }
975 }
976 _ => {}
977 }
978 i += 1;
979 }
980 Err(ParseError {
981 msg: "unterminated `{` body".into(),
982 line: 0,
983 col: 0,
984 })
985}
986
987fn static_truth(toks: &[Spanned]) -> Option<bool> {
988 let toks = trim_trailing_eof(toks);
989 match toks {
990 [
991 Spanned {
992 tok: Token::Float(v),
993 ..
994 },
995 ] => Some(*v != 0.0),
996 [
997 Spanned {
998 tok: Token::Not, ..
999 },
1000 Spanned {
1001 tok: Token::Float(v),
1002 ..
1003 },
1004 ] => Some(*v == 0.0),
1005 [a, op, b] => match op.tok {
1006 Token::EqEq | Token::Neq => {
1007 if let (Some(lhs), Some(rhs)) = (static_string(a), static_string(b)) {
1008 return Some(if matches!(op.tok, Token::EqEq) {
1009 lhs == rhs
1010 } else {
1011 lhs != rhs
1012 });
1013 }
1014 if let (Some(lhs), Some(rhs)) = (static_number(a), static_number(b)) {
1015 return Some(if matches!(op.tok, Token::EqEq) {
1016 (lhs - rhs).abs() < f64::EPSILON
1017 } else {
1018 (lhs - rhs).abs() >= f64::EPSILON
1019 });
1020 }
1021 None
1022 }
1023 _ => None,
1024 },
1025 [a, op, b, op2, c] if matches!(op2.tok, Token::Plus) => {
1026 let lhs = static_string(a)?;
1027 let mut rhs = static_string(b)?;
1028 rhs.push_str(&static_string(c)?);
1029 match op.tok {
1030 Token::EqEq => Some(lhs == rhs),
1031 Token::Neq => Some(lhs != rhs),
1032 _ => None,
1033 }
1034 }
1035 _ => None,
1036 }
1037}
1038
1039fn trim_trailing_eof(toks: &[Spanned]) -> &[Spanned] {
1040 if matches!(toks.last().map(|s| &s.tok), Some(Token::Eof)) {
1041 &toks[..toks.len() - 1]
1042 } else {
1043 toks
1044 }
1045}
1046
1047fn static_string(s: &Spanned) -> Option<String> {
1048 match &s.tok {
1049 Token::Str(v) => Some(v.clone()),
1050 _ => None,
1051 }
1052}
1053
1054fn static_number(s: &Spanned) -> Option<f64> {
1055 match &s.tok {
1056 Token::Float(v) => Some(*v),
1057 Token::Name(n) | Token::Label(n) => dpic_backend_constant(n),
1058 _ => None,
1059 }
1060}
1061
1062fn dpic_backend_constant(name: &str) -> Option<f64> {
1063 match name {
1064 "optMFpic" => Some(0.0),
1065 "optMpost" => Some(1.0),
1066 "optPDF" => Some(2.0),
1067 "optPGF" => Some(3.0),
1068 "optPict2e" => Some(4.0),
1069 "optPS" => Some(5.0),
1070 "optPSfrag" => Some(6.0),
1071 "optPSTricks" => Some(7.0),
1072 "optSVG" | "dpicopt" => Some(8.0),
1073 "optTeX" => Some(9.0),
1074 "opttTeX" => Some(10.0),
1075 "optxfig" => Some(11.0),
1076 _ => None,
1077 }
1078}
1079
1080type PResult<T> = Result<T, ParseError>;
1081
1082fn is_assign_op(t: &Token) -> bool {
1083 matches!(
1084 t,
1085 Token::Eq
1086 | Token::ColonEq
1087 | Token::PlusEq
1088 | Token::MinusEq
1089 | Token::MultEq
1090 | Token::DivEq
1091 | Token::RemEq
1092 )
1093}
1094
1095struct Parser {
1096 toks: Vec<Spanned>,
1097 idx: usize,
1098}
1099
1100impl Parser {
1101 fn new(toks: Vec<Spanned>) -> Self {
1102 Parser { toks, idx: 0 }
1103 }
1104
1105 fn cur(&self) -> &Token {
1108 &self.toks[self.idx].tok
1109 }
1110 fn peek(&self, n: usize) -> &Token {
1111 self.toks
1112 .get(self.idx + n)
1113 .map(|s| &s.tok)
1114 .unwrap_or(&Token::Eof)
1115 }
1116 fn at(&self, t: &Token) -> bool {
1117 self.cur() == t
1118 }
1119 fn bump(&mut self) -> Token {
1120 let t = self.toks[self.idx].tok.clone();
1121 if self.idx + 1 < self.toks.len() {
1122 self.idx += 1;
1123 }
1124 t
1125 }
1126 fn eat(&mut self, t: &Token) -> bool {
1127 if self.at(t) {
1128 self.bump();
1129 true
1130 } else {
1131 false
1132 }
1133 }
1134 fn expect(&mut self, t: &Token) -> PResult<()> {
1135 if self.eat(t) {
1136 Ok(())
1137 } else {
1138 self.err(format!("expected {t:?}, found {:?}", self.cur()))
1139 }
1140 }
1141 fn err<T>(&self, msg: impl Into<String>) -> PResult<T> {
1142 let s = &self.toks[self.idx];
1143 Err(ParseError {
1144 msg: msg.into(),
1145 line: s.line,
1146 col: s.col,
1147 })
1148 }
1149 fn at_kw(&self, k: Kw) -> bool {
1150 matches!(self.cur(), Token::Kw(x) if *x == k)
1151 }
1152 fn eat_kw(&mut self, k: Kw) -> bool {
1153 if self.at_kw(k) {
1154 self.bump();
1155 true
1156 } else {
1157 false
1158 }
1159 }
1160 fn skip_newlines(&mut self) {
1161 while self.at(&Token::Newline) {
1162 self.bump();
1163 }
1164 }
1165
1166 fn parse_picture(&mut self) -> PResult<Picture> {
1169 let (mut width, mut height) = (None, None);
1173 let mut seen_ps = false;
1174 let mut stmts = Vec::new();
1175 loop {
1176 self.skip_newlines();
1177 match self.cur() {
1178 Token::Eof => break,
1179 Token::DotPS => {
1180 self.bump();
1181 if !seen_ps && self.starts_scalar() {
1182 width = Some(self.parse_expr()?);
1183 if self.starts_scalar() {
1184 height = Some(self.parse_expr()?);
1185 }
1186 }
1187 seen_ps = true;
1188 while !self.at(&Token::Newline) && !self.at(&Token::Eof) {
1189 self.bump();
1190 }
1191 continue;
1192 }
1193 Token::DotPE => {
1194 self.bump();
1195 continue;
1196 }
1197 _ => {}
1198 }
1199 stmts.push(self.parse_element()?);
1200 if !self.at(&Token::Newline)
1201 && !self.at(&Token::Eof)
1202 && !self.at(&Token::DotPE)
1203 && !self.at(&Token::DotPS)
1204 {
1205 return self.err(format!("unexpected {:?} after statement", self.cur()));
1206 }
1207 }
1208 Ok(Picture {
1209 width,
1210 height,
1211 stmts,
1212 macros: HashMap::new(),
1213 base_dir: None,
1214 })
1215 }
1216
1217 fn parse_elementlist(&mut self, terminators: &[Token]) -> PResult<Vec<Stmt>> {
1219 let mut stmts = Vec::new();
1220 loop {
1221 self.skip_newlines();
1222 if self.at(&Token::Eof) || terminators.iter().any(|t| self.at(t)) {
1223 break;
1224 }
1225 let s = self.parse_element()?;
1226 stmts.push(s);
1227 if !self.at(&Token::Newline)
1229 && !self.at(&Token::Eof)
1230 && !terminators.iter().any(|t| self.at(t))
1231 {
1232 return self.err(format!("unexpected {:?} after statement", self.cur()));
1233 }
1234 }
1235 Ok(stmts)
1236 }
1237
1238 fn parse_element(&mut self) -> PResult<Stmt> {
1241 if self.at(&Token::Percent) {
1245 while !self.at(&Token::Newline) && !self.at(&Token::Eof) {
1246 self.bump();
1247 }
1248 return Ok(Stmt::Print(PrintItem::Str(StringExpr::Lit(String::new()))));
1249 }
1250
1251 if self.at_kw(Kw::Animate) {
1253 return Ok(Stmt::Animate(self.parse_animate()?));
1254 }
1255
1256 if matches!(self.cur(), Token::Name(n) if n == "class")
1260 && !is_assign_op(self.peek(1))
1261 && !matches!(self.peek(1), Token::LeftBrack)
1262 {
1263 self.bump();
1264 let target = self.parse_place()?;
1265 let class = self.parse_stringexpr()?;
1266 return Ok(Stmt::Class { target, class });
1267 }
1268
1269 match self.cur() {
1271 Token::Kw(Kw::If) => return self.parse_if(),
1272 Token::Kw(Kw::For) => return self.parse_for(),
1273 Token::Kw(Kw::Print) => return self.parse_print(),
1274 Token::Kw(Kw::Exec) => return self.parse_exec(),
1275 Token::Kw(Kw::Reset) => return self.parse_reset(),
1276 _ => {}
1277 }
1278
1279 if let Token::Kw(k) = self.cur() {
1282 match k {
1283 Kw::Define | Kw::Undef => {
1284 return self.err("only the `define name { body }` macro form is supported");
1285 }
1286 Kw::Command | Kw::Sh => {
1292 self.bump();
1293 return Ok(Stmt::Group(Vec::new()));
1294 }
1295 Kw::Copy => {
1296 return self.err("`copy` is not supported yet (planned milestone)");
1297 }
1298 _ => {}
1299 }
1300 }
1301
1302 if self.eat(&Token::LeftBrace) {
1304 let stmts = self.parse_elementlist(&[Token::RightBrace])?;
1305 self.expect(&Token::RightBrace)?;
1306 return Ok(Stmt::Group(stmts));
1307 }
1308
1309 if matches!(self.cur(), Token::Label(_)) && self.label_colon_ahead() {
1311 let label = self.parse_label()?;
1312 self.expect(&Token::Colon)?;
1313 if self.at_object_start() {
1314 let object = self.parse_object()?;
1315 return Ok(Stmt::Object {
1316 label: Some(label),
1317 object,
1318 });
1319 } else {
1320 let pos = self.parse_label_position()?;
1321 return Ok(Stmt::Place { label, pos });
1322 }
1323 }
1324
1325 if self.at_assignment_start() {
1327 return Ok(Stmt::Assign(self.parse_assignlist()?));
1328 }
1329
1330 if let Token::Dir(d) = self.cur() {
1332 let d = *d;
1333 if matches!(self.peek(1), Token::Newline | Token::Eof) {
1335 self.bump();
1336 return Ok(Stmt::Direction(d));
1337 }
1338 }
1339
1340 let object = self.parse_object()?;
1342 Ok(Stmt::Object {
1343 label: None,
1344 object,
1345 })
1346 }
1347
1348 fn parse_if(&mut self) -> PResult<Stmt> {
1349 self.expect_kw(Kw::If)?;
1350 let cond = self.parse_expr()?;
1351 self.expect_kw(Kw::Then)?;
1352 let then_body = self.capture_braced()?;
1353 let save = self.idx;
1356 self.skip_newlines();
1357 let else_body = if self.eat_kw(Kw::Else) {
1358 Some(self.capture_braced()?)
1359 } else {
1360 self.idx = save;
1361 None
1362 };
1363 Ok(Stmt::If {
1364 cond,
1365 then_body,
1366 else_body,
1367 })
1368 }
1369
1370 fn parse_for(&mut self) -> PResult<Stmt> {
1371 self.expect_kw(Kw::For)?;
1372 let var = match self.bump() {
1373 Token::Name(s) | Token::Label(s) => s,
1374 other => return self.err(format!("expected loop variable, found {other:?}")),
1375 };
1376 let subscript = if self.eat(&Token::LeftBrack) {
1377 let e = self.parse_subscript()?;
1378 self.expect(&Token::RightBrack)?;
1379 Some(e)
1380 } else {
1381 None
1382 };
1383 match self.bump() {
1384 Token::Eq | Token::ColonEq => {}
1385 other => return self.err(format!("expected `=` in for, found {other:?}")),
1386 }
1387 let from = self.parse_expr()?;
1388 self.expect_kw(Kw::To)?;
1389 let to = self.parse_expr()?;
1390 let mut by = Expr::Num(1.0);
1391 let mut mult = false;
1392 if self.eat_kw(Kw::By) {
1393 mult = self.eat(&Token::Mult);
1394 by = self.parse_expr()?;
1395 }
1396 self.expect_kw(Kw::Do)?;
1397 let body = self.capture_braced()?;
1398 Ok(Stmt::For {
1399 var,
1400 subscript,
1401 from,
1402 to,
1403 by,
1404 mult,
1405 body,
1406 })
1407 }
1408
1409 fn capture_braced(&mut self) -> PResult<Body> {
1412 self.skip_newlines();
1413 self.expect(&Token::LeftBrace)?;
1414 let start = self.idx;
1415 let mut depth = 1i32;
1416 loop {
1417 match self.cur() {
1418 Token::LeftBrace => depth += 1,
1419 Token::RightBrace => {
1420 depth -= 1;
1421 if depth == 0 {
1422 break;
1423 }
1424 }
1425 Token::Eof => return self.err("unterminated `{` body"),
1426 _ => {}
1427 }
1428 self.bump();
1429 }
1430 let body = self.toks[start..self.idx].to_vec();
1431 self.expect(&Token::RightBrace)?;
1432 Ok(body)
1433 }
1434
1435 fn parse_print(&mut self) -> PResult<Stmt> {
1436 self.expect_kw(Kw::Print)?;
1437 let item = if self.at_string_start() {
1438 PrintItem::Str(self.parse_stringexpr()?)
1439 } else {
1440 PrintItem::Expr(self.parse_expr()?)
1441 };
1442 Ok(Stmt::Print(item))
1443 }
1444
1445 fn parse_exec(&mut self) -> PResult<Stmt> {
1446 let arg_frame = self.toks[self.idx].arg_frame.clone();
1447 self.expect_kw(Kw::Exec)?;
1448 let command = self.parse_stringexpr()?;
1449 Ok(Stmt::Exec { command, arg_frame })
1450 }
1451
1452 fn parse_reset(&mut self) -> PResult<Stmt> {
1453 self.expect_kw(Kw::Reset)?;
1454 let mut list = Vec::new();
1455 if let Token::EnvVar(v) = self.cur() {
1456 list.push(*v);
1457 self.bump();
1458 while self.eat(&Token::Comma) {
1459 match self.cur() {
1460 Token::EnvVar(v) => {
1461 list.push(*v);
1462 self.bump();
1463 }
1464 other => {
1465 return self.err(format!("expected environment variable, found {other:?}"));
1466 }
1467 }
1468 }
1469 }
1470 Ok(Stmt::Reset(list))
1471 }
1472
1473 fn at_string_start(&self) -> bool {
1474 self.token_starts_string_at(0)
1475 }
1476
1477 fn parse_animate(&mut self) -> PResult<Animate> {
1478 self.expect_kw(Kw::Animate)?;
1479 let target = self.parse_place()?;
1480 self.expect_kw(Kw::With)?;
1481 let effect = self.parse_stringexpr()?;
1482 let mut duration = None;
1483 let mut timing = Timing::Sequential;
1484 let mut delay = None;
1485 loop {
1486 if self.eat_kw(Kw::For) {
1487 duration = Some(self.parse_expr()?);
1488 } else if self.eat_kw(Kw::At) {
1489 timing = Timing::At(self.parse_expr()?);
1490 } else if self.eat_kw(Kw::After) {
1491 timing = Timing::After(self.parse_place()?);
1492 } else if self.eat_kw(Kw::Delay) {
1493 delay = Some(self.parse_expr()?);
1494 } else {
1495 break;
1496 }
1497 }
1498 Ok(Animate {
1499 target,
1500 effect,
1501 duration,
1502 timing,
1503 delay,
1504 })
1505 }
1506
1507 fn label_colon_ahead(&self) -> bool {
1509 match self.peek(1) {
1510 Token::Colon => true,
1511 Token::LeftBrack => {
1512 let mut depth = 0;
1514 let mut i = self.idx + 1;
1515 while i < self.toks.len() {
1516 match &self.toks[i].tok {
1517 Token::LeftBrack => depth += 1,
1518 Token::RightBrack => {
1519 depth -= 1;
1520 if depth == 0 {
1521 return matches!(
1522 self.toks.get(i + 1).map(|s| &s.tok),
1523 Some(Token::Colon)
1524 );
1525 }
1526 }
1527 Token::Newline | Token::Eof => return false,
1528 _ => {}
1529 }
1530 i += 1;
1531 }
1532 false
1533 }
1534 _ => false,
1535 }
1536 }
1537
1538 fn at_object_start(&self) -> bool {
1539 matches!(
1540 self.cur(),
1541 Token::Prim(_)
1542 | Token::LeftBrack
1543 | Token::Block
1544 | Token::Str(_)
1545 | Token::Arg(_)
1546 | Token::Kw(Kw::Sprintf)
1547 ) || matches!(self.cur(), Token::Name(n) if n == "brace")
1548 }
1549
1550 fn at_assignment_start(&self) -> bool {
1551 match self.cur() {
1552 Token::Name(_) | Token::Label(_) => self.assignment_op_after_var_ref(),
1553 Token::EnvVar(_) => is_assign_op(self.peek(1)),
1554 _ => false,
1555 }
1556 }
1557
1558 fn assignment_op_after_var_ref(&self) -> bool {
1559 let mut i = self.idx + 1;
1560 if matches!(self.toks.get(i).map(|s| &s.tok), Some(Token::LeftBrack)) {
1561 i += 1;
1562 let mut depth = 1i32;
1563 while let Some(tok) = self.toks.get(i).map(|s| &s.tok) {
1564 match tok {
1565 Token::LeftBrack => depth += 1,
1566 Token::RightBrack => {
1567 depth -= 1;
1568 if depth == 0 {
1569 i += 1;
1570 break;
1571 }
1572 }
1573 Token::Eof | Token::Newline if depth > 0 => return false,
1574 _ => {}
1575 }
1576 i += 1;
1577 }
1578 if depth != 0 {
1579 return false;
1580 }
1581 }
1582 self.toks.get(i).is_some_and(|s| is_assign_op(&s.tok))
1583 }
1584
1585 fn parse_label(&mut self) -> PResult<Label> {
1586 let name = match self.bump() {
1587 Token::Label(s) => s,
1588 other => return self.err(format!("expected label, found {other:?}")),
1589 };
1590 let subscript = if self.eat(&Token::LeftBrack) {
1591 let e = self.parse_subscript()?;
1592 self.expect(&Token::RightBrack)?;
1593 Some(e)
1594 } else {
1595 None
1596 };
1597 Ok(Label { name, subscript })
1598 }
1599
1600 fn parse_assignlist(&mut self) -> PResult<Vec<Assignment>> {
1601 let mut list = vec![self.parse_assignment()?];
1602 while self.eat(&Token::Comma) {
1603 list.push(self.parse_assignment()?);
1604 }
1605 Ok(list)
1606 }
1607
1608 fn parse_assignment(&mut self) -> PResult<Assignment> {
1609 let target = match self.cur().clone() {
1610 Token::Name(name) | Token::Label(name) => {
1611 self.bump();
1612 let sub = if self.eat(&Token::LeftBrack) {
1613 let e = self.parse_subscript()?;
1614 self.expect(&Token::RightBrack)?;
1615 Some(e)
1616 } else {
1617 None
1618 };
1619 AssignTarget::Var(name, sub)
1620 }
1621 Token::EnvVar(v) => {
1622 self.bump();
1623 AssignTarget::Env(v)
1624 }
1625 other => return self.err(format!("expected assignment target, found {other:?}")),
1626 };
1627 let op = match self.bump() {
1628 Token::Eq => AssignOp::Set,
1629 Token::ColonEq => AssignOp::ColonSet,
1630 Token::PlusEq => AssignOp::Add,
1631 Token::MinusEq => AssignOp::Sub,
1632 Token::MultEq => AssignOp::Mul,
1633 Token::DivEq => AssignOp::Div,
1634 Token::RemEq => AssignOp::Rem,
1635 other => return self.err(format!("expected assignment operator, found {other:?}")),
1636 };
1637 let value = self.parse_expr()?;
1638 Ok(Assignment { target, op, value })
1639 }
1640
1641 fn parse_subscript(&mut self) -> PResult<Expr> {
1642 let mut items = vec![self.parse_expr()?];
1643 while self.eat(&Token::Comma) {
1644 items.push(self.parse_expr()?);
1645 }
1646 if items.len() == 1 {
1647 Ok(items.pop().unwrap())
1648 } else {
1649 Ok(Expr::Index(items))
1650 }
1651 }
1652
1653 fn parse_object(&mut self) -> PResult<Object> {
1656 let mut attrs = Vec::new();
1657 if self.at_string_start() {
1660 attrs.push(Attr::Text(self.parse_stringexpr()?));
1661 while let Some(a) = self.parse_attr(false, false, false, false)? {
1662 attrs.push(a);
1663 }
1664 return Ok(Object {
1665 kind: ObjectKind::Text,
1666 attrs,
1667 });
1668 }
1669 let kind = match self.cur().clone() {
1670 Token::Prim(p) => {
1671 self.bump();
1672 ObjectKind::Primitive(p)
1673 }
1674 Token::Block => {
1675 self.bump();
1676 ObjectKind::Empty
1677 }
1678 Token::Name(n) if n == "brace" => {
1679 self.bump();
1680 ObjectKind::Brace
1681 }
1682 Token::LeftBrack => {
1683 self.bump();
1684 let stmts = self.parse_elementlist(&[Token::RightBrack])?;
1685 self.expect(&Token::RightBrack)?;
1686 ObjectKind::Block(stmts)
1687 }
1688 Token::Str(s) => {
1689 self.bump();
1690 attrs.push(Attr::Text(self.continue_string(StringExpr::Lit(s))?));
1691 ObjectKind::Text
1692 }
1693 Token::Kw(Kw::Continue) => {
1694 self.bump();
1695 ObjectKind::Continue
1696 }
1697 other => return self.err(format!("expected an object, found {other:?}")),
1698 };
1699 if matches!(kind, ObjectKind::Primitive(Prim::Spline)) && self.spline_tension_ahead() {
1704 attrs.push(Attr::SplineTension(self.parse_expr()?));
1705 }
1706 let allow_fit = matches!(
1707 kind,
1708 ObjectKind::Primitive(Prim::Box | Prim::Circle | Prim::Ellipse)
1709 );
1710 let allow_brace = matches!(kind, ObjectKind::Brace);
1711 let allow_hatch = matches!(
1712 kind,
1713 ObjectKind::Primitive(
1714 Prim::Box
1715 | Prim::Circle
1716 | Prim::Ellipse
1717 | Prim::Line
1718 | Prim::Arrow
1719 | Prim::Spline
1720 | Prim::Arc
1721 )
1722 );
1723 let allow_close = matches!(kind, ObjectKind::Primitive(Prim::Line));
1724 while let Some(a) = self.parse_attr(allow_fit, allow_brace, allow_hatch, allow_close)? {
1725 attrs.push(a);
1726 }
1727 Ok(Object { kind, attrs })
1728 }
1729
1730 fn spline_tension_ahead(&self) -> bool {
1734 matches!(
1735 self.cur(),
1736 Token::Float(_)
1737 | Token::Lparen
1738 | Token::EnvVar(_)
1739 | Token::Func1(_)
1740 | Token::Func2(_)
1741 | Token::Name(_)
1742 | Token::Minus
1743 | Token::Plus
1744 | Token::Kw(Kw::Rand)
1745 )
1746 }
1747
1748 fn parse_attr(
1749 &mut self,
1750 allow_fit: bool,
1751 allow_brace: bool,
1752 allow_hatch: bool,
1753 allow_close: bool,
1754 ) -> PResult<Option<Attr>> {
1755 if self.at_string_start() {
1757 return Ok(Some(Attr::Text(self.parse_stringexpr()?)));
1758 }
1759 let attr = match self.cur().clone() {
1760 Token::Kw(Kw::Ht) => {
1761 self.bump();
1762 Attr::Dim(DimKind::Ht, self.parse_expr()?)
1763 }
1764 Token::Kw(Kw::Wid) => {
1765 self.bump();
1766 Attr::Dim(DimKind::Wid, self.parse_expr()?)
1767 }
1768 Token::Kw(Kw::Rad) => {
1769 self.bump();
1770 Attr::Dim(DimKind::Rad, self.parse_expr()?)
1771 }
1772 Token::Kw(Kw::Diam) => {
1773 self.bump();
1774 Attr::Dim(DimKind::Diam, self.parse_expr()?)
1775 }
1776 Token::Kw(Kw::Thick) => {
1777 self.bump();
1778 Attr::Dim(DimKind::Thick, self.parse_expr()?)
1779 }
1780 Token::Kw(Kw::Scaled) => {
1781 self.bump();
1782 Attr::Dim(DimKind::Scaled, self.parse_expr()?)
1783 }
1784 Token::Dir(d) => {
1785 self.bump();
1786 Attr::Direction(
1787 d,
1788 self.opt_attr_expr(allow_fit, allow_brace, allow_hatch, allow_close)?,
1789 )
1790 }
1791 Token::LineType(lt) => {
1792 self.bump();
1793 Attr::LineStyle(
1794 lt,
1795 self.opt_attr_expr(allow_fit, allow_brace, allow_hatch, allow_close)?,
1796 )
1797 }
1798 Token::Kw(Kw::Chop) => {
1799 self.bump();
1800 Attr::Chop(self.opt_attr_expr(allow_fit, allow_brace, allow_hatch, allow_close)?)
1801 }
1802 Token::Kw(Kw::Fill) => {
1803 self.bump();
1804 Attr::Fill(self.opt_attr_expr(allow_fit, allow_brace, allow_hatch, allow_close)?)
1805 }
1806 Token::Arrow(a) => {
1807 self.bump();
1808 Attr::Arrowhead(
1809 a,
1810 self.opt_attr_expr(allow_fit, allow_brace, allow_hatch, allow_close)?,
1811 )
1812 }
1813 Token::Kw(Kw::Then) => {
1814 self.bump();
1815 Attr::Then
1816 }
1817 Token::Kw(Kw::Cw) => {
1818 self.bump();
1819 Attr::Cw
1820 }
1821 Token::Kw(Kw::Ccw) => {
1822 self.bump();
1823 Attr::Ccw
1824 }
1825 Token::Kw(Kw::Same) => {
1826 self.bump();
1827 Attr::Same
1828 }
1829 Token::Kw(Kw::Continue) => {
1830 self.bump();
1831 Attr::Continue
1832 }
1833 Token::Kw(Kw::From) => {
1834 self.bump();
1835 Attr::From(self.parse_position()?)
1836 }
1837 Token::Kw(Kw::To) => {
1838 self.bump();
1839 Attr::To(self.parse_position()?)
1840 }
1841 Token::Kw(Kw::At) => {
1842 self.bump();
1843 Attr::At(self.parse_position()?)
1844 }
1845 Token::Kw(Kw::By) => {
1846 self.bump();
1847 Attr::By(self.parse_position()?)
1848 }
1849 Token::Kw(Kw::With) => {
1850 self.bump();
1851 let anchor = if self.eat(&Token::Dot) {
1852 WithAnchor::Place(self.parse_place()?)
1853 } else if let Token::Corner(c) = self.cur() {
1854 let c = *c;
1855 self.bump();
1856 WithAnchor::Corner(c)
1857 } else if self.at(&Token::Lparen) {
1858 self.bump();
1859 let x = self.parse_expr()?;
1860 self.expect(&Token::Comma)?;
1861 let y = self.parse_expr()?;
1862 self.expect(&Token::Rparen)?;
1863 WithAnchor::Pair(x, y)
1864 } else {
1865 WithAnchor::Plain
1866 };
1867 self.expect_kw(Kw::At)?;
1868 Attr::With {
1869 anchor,
1870 at: self.parse_position()?,
1871 }
1872 }
1873 Token::TextPos(tp) => {
1874 self.bump();
1875 Attr::TextPos(tp)
1876 }
1877 Token::Color(c) => {
1878 self.bump();
1879 let s = match self.cur().clone() {
1882 Token::Name(n) | Token::Label(n) => {
1883 self.bump();
1884 StringExpr::Lit(n)
1885 }
1886 _ => self.parse_stringexpr()?,
1887 };
1888 Attr::Color(c, s)
1889 }
1890 Token::Name(n) if allow_fit && n == "fit" => {
1891 self.bump();
1892 Attr::Fit
1893 }
1894 Token::Name(n) if allow_hatch && n == "hatch" => {
1895 self.bump();
1896 Attr::Hatch(HatchKind::Single)
1897 }
1898 Token::Name(n) if allow_hatch && n == "crosshatch" => {
1899 self.bump();
1900 Attr::Hatch(HatchKind::Cross)
1901 }
1902 Token::Name(n) if allow_hatch && n == "hatchangle" => {
1903 self.bump();
1904 Attr::HatchAngle(self.parse_expr()?)
1905 }
1906 Token::Name(n) if allow_hatch && n == "hatchsep" => {
1907 self.bump();
1908 Attr::HatchSep(self.parse_expr()?)
1909 }
1910 Token::Name(n) if allow_hatch && (n == "hatchwid" || n == "hatchwidth") => {
1911 self.bump();
1912 Attr::HatchWidth(self.parse_expr()?)
1913 }
1914 Token::Name(n) if allow_hatch && n == "hatchcolor" => {
1915 self.bump();
1916 let s = match self.cur().clone() {
1917 Token::Name(n) | Token::Label(n) => {
1918 self.bump();
1919 StringExpr::Lit(n)
1920 }
1921 _ => self.parse_stringexpr()?,
1922 };
1923 Attr::HatchColor(s)
1924 }
1925 Token::Name(n) if allow_hatch && n == "gradient" => {
1926 self.bump();
1927 let from = self.parse_color_like()?;
1928 let to = self.parse_color_like()?;
1929 Attr::Gradient(from, to)
1930 }
1931 Token::Name(n) if allow_hatch && n == "gradientangle" => {
1932 self.bump();
1933 Attr::GradientAngle(self.parse_expr()?)
1934 }
1935 Token::Name(n) if n == "opacity" => {
1936 self.bump();
1937 Attr::Opacity(self.parse_expr()?)
1938 }
1939 Token::Name(n) if allow_close && n == "close" => {
1940 self.bump();
1941 Attr::Close
1942 }
1943 Token::Name(n) if allow_brace && n == "bracepos" => {
1944 self.bump();
1945 Attr::BracePos(self.parse_expr()?)
1946 }
1947 Token::Name(n) if allow_brace && n == "labeloffset" => {
1948 self.bump();
1949 Attr::BraceLabelOffset(self.parse_expr()?)
1950 }
1951 Token::Name(n) if n == "behind" => {
1952 self.bump();
1953 Attr::Behind(self.parse_place()?)
1954 }
1955 Token::Name(n) if n == "class" => {
1956 self.bump();
1957 Attr::Class(self.parse_stringexpr()?)
1958 }
1959 Token::Float(_)
1962 | Token::Lparen
1963 | Token::EnvVar(_)
1964 | Token::Func1(_)
1965 | Token::Func2(_)
1966 | Token::Name(_)
1967 | Token::Minus
1968 | Token::Plus
1969 | Token::Kw(Kw::Rand) => Attr::Dist(self.parse_expr()?),
1970 _ if self.place_is_scalar_ahead() => Attr::Dist(self.parse_expr()?),
1971 _ => return Ok(None),
1972 };
1973 Ok(Some(attr))
1974 }
1975
1976 fn expect_kw(&mut self, k: Kw) -> PResult<()> {
1977 if self.eat_kw(k) {
1978 Ok(())
1979 } else {
1980 self.err(format!("expected `{k:?}`, found {:?}", self.cur()))
1981 }
1982 }
1983
1984 fn parse_stringexpr(&mut self) -> PResult<StringExpr> {
1987 let first = self.parse_string_atom()?;
1988 self.continue_string(first)
1989 }
1990
1991 fn continue_string(&mut self, first: StringExpr) -> PResult<StringExpr> {
1993 let mut e = first;
1994 while self.at(&Token::Plus) && self.string_after_plus() {
1995 self.bump();
1996 let rhs = self.parse_string_atom()?;
1997 e = StringExpr::Concat(Box::new(e), Box::new(rhs));
1998 }
1999 Ok(e)
2000 }
2001
2002 fn string_after_plus(&self) -> bool {
2003 self.token_starts_string_at(1)
2004 }
2005
2006 fn token_starts_string_at(&self, offset: usize) -> bool {
2007 match self.toks.get(self.idx + offset).map(|s| &s.tok) {
2008 Some(Token::Str(_) | Token::Arg(_) | Token::Kw(Kw::Sprintf)) => true,
2009 Some(Token::Name(n)) if n == "svg_font" => {
2010 matches!(
2011 self.toks.get(self.idx + offset + 1).map(|s| &s.tok),
2012 Some(Token::Lparen)
2013 )
2014 }
2015 _ => false,
2016 }
2017 }
2018
2019 fn parse_string_atom(&mut self) -> PResult<StringExpr> {
2020 match self.cur().clone() {
2021 Token::Str(s) => {
2022 self.bump();
2023 Ok(StringExpr::Lit(s))
2024 }
2025 Token::Arg(n) => {
2026 self.bump();
2027 Ok(StringExpr::Arg(n))
2028 }
2029 Token::Kw(Kw::Sprintf) => {
2030 self.bump();
2031 self.expect(&Token::Lparen)?;
2032 let fmt = self.parse_stringexpr()?;
2033 let mut args = Vec::new();
2034 while self.eat(&Token::Comma) {
2035 args.push(self.parse_expr()?);
2036 }
2037 self.expect(&Token::Rparen)?;
2038 Ok(StringExpr::Sprintf(Box::new(fmt), args))
2039 }
2040 Token::Name(n) if n == "svg_font" && matches!(self.peek(1), Token::Lparen) => {
2041 self.bump();
2042 self.expect(&Token::Lparen)?;
2043 let mut args = Vec::new();
2044 if !self.at(&Token::Rparen) {
2045 args.push(self.parse_expr()?);
2046 while self.eat(&Token::Comma) {
2047 args.push(self.parse_expr()?);
2048 }
2049 }
2050 self.expect(&Token::Rparen)?;
2051 Ok(StringExpr::SvgFont(args))
2052 }
2053 other => self.err(format!("expected a string, found {other:?}")),
2054 }
2055 }
2056
2057 fn parse_label_position(&mut self) -> PResult<Position> {
2060 let save = self.idx;
2061 if let Ok(x) = self.parse_expr()
2062 && self.eat(&Token::Comma)
2063 {
2064 let y = self.parse_expr()?;
2065 return Ok(Position::Pair(x, y));
2066 }
2067 self.idx = save;
2068 self.parse_position()
2069 }
2070
2071 fn parse_position(&mut self) -> PResult<Position> {
2073 let mut left = self.parse_pos_mul()?;
2074 loop {
2075 let sign = if self.at(&Token::Plus) {
2076 Sign::Plus
2077 } else if self.at(&Token::Minus) {
2078 Sign::Minus
2079 } else {
2080 break;
2081 };
2082 self.bump();
2083 let right = self.parse_pos_mul()?;
2084 left = Position::Sum(sign, Box::new(left), Box::new(right));
2085 }
2086 Ok(left)
2087 }
2088
2089 fn parse_pos_mul(&mut self) -> PResult<Position> {
2091 let mut left = self.parse_pos_primary()?;
2092 loop {
2093 if self.eat(&Token::Mult) {
2094 left = Position::Scale(Box::new(left), self.parse_unary()?, false);
2095 } else if self.eat(&Token::Div) {
2096 left = Position::Scale(Box::new(left), self.parse_unary()?, true);
2097 } else {
2098 break;
2099 }
2100 }
2101 Ok(left)
2102 }
2103
2104 fn parse_pos_primary(&mut self) -> PResult<Position> {
2105 if let Some(p) = self.try_fraction()? {
2109 return Ok(p);
2110 }
2111 if self.eat(&Token::Lparen) {
2116 let save = self.idx;
2117 if let Ok(p1) = self.parse_position() {
2122 let p = if self.eat(&Token::Comma) {
2123 let p2 = self.parse_position()?;
2124 Position::Place(Location::ParenPair(Box::new(p1), Box::new(p2)))
2125 } else {
2126 p1 };
2128 self.expect(&Token::Rparen)?;
2129 return Ok(p);
2130 }
2131 self.idx = save;
2132 let e1 = self.parse_add()?;
2133 self.expect(&Token::Comma)?;
2134 let e2 = self.parse_add()?;
2135 self.expect(&Token::Rparen)?;
2136 return Ok(Position::Pair(e1, e2));
2137 }
2138 if self.at_place_start() && !self.place_is_scalar_ahead() {
2141 return Ok(Position::Place(Location::Place(self.parse_place()?)));
2142 }
2143 let e1 = self.parse_add()?;
2145 if self.eat(&Token::Comma) {
2146 let e2 = self.parse_add()?;
2147 return Ok(Position::Pair(e1, e2));
2148 }
2149 self.err("expected `,`, `between`, or `of the way between` in position")
2150 }
2151
2152 fn try_fraction(&mut self) -> PResult<Option<Position>> {
2156 let save = self.idx;
2157 let Ok(frac) = self.parse_add() else {
2158 self.idx = save;
2159 return Ok(None);
2160 };
2161 let mk = |frac, a, b, of_the_way| {
2162 Ok(Some(Position::Between {
2163 frac: Box::new(frac),
2164 a: Box::new(a),
2165 b: Box::new(b),
2166 of_the_way,
2167 }))
2168 };
2169 if self.eat(&Token::Lt) {
2170 let a = self.parse_position()?;
2171 self.expect(&Token::Comma)?;
2172 let b = self.parse_position()?;
2173 self.expect(&Token::Gt)?;
2174 return mk(frac, a, b, false);
2175 }
2176 let of_the_way = if self.at_kw(Kw::Of) {
2177 self.eat_kw(Kw::Of);
2178 if !(self.eat_kw(Kw::The) && self.eat_kw(Kw::Way) && self.eat_kw(Kw::Between)) {
2179 self.idx = save;
2180 return Ok(None);
2181 }
2182 true
2183 } else if self.eat_kw(Kw::Between) {
2184 false
2185 } else {
2186 self.idx = save;
2187 return Ok(None);
2188 };
2189 let a = self.parse_position()?;
2190 self.expect_kw(Kw::And)?;
2191 let b = self.parse_position()?;
2192 mk(frac, a, b, of_the_way)
2193 }
2194
2195 fn place_is_scalar_ahead(&mut self) -> bool {
2198 let save = self.idx;
2199 let parsed = self.parse_place().is_ok();
2200 let scalar = parsed && matches!(self.cur(), Token::DotX | Token::DotY | Token::Param(_));
2201 self.idx = save;
2202 scalar
2203 }
2204
2205 fn at_place_start(&self) -> bool {
2206 match self.cur() {
2207 Token::Label(_) | Token::Block | Token::Corner(_) => true,
2208 Token::Kw(Kw::Last) | Token::Kw(Kw::Here) => true,
2209 Token::Float(_) => matches!(self.peek(1), Token::Kw(Kw::Nth)),
2210 Token::LeftBrace | Token::LeftQuote => true,
2213 _ => false,
2214 }
2215 }
2216
2217 fn parse_place(&mut self) -> PResult<Place> {
2218 if let Token::Corner(c) = self.cur() {
2220 let c = *c;
2221 self.bump();
2222 self.eat_kw(Kw::Of);
2223 let inner = self.parse_place()?;
2224 return Ok(Place::CornerOf(c, Box::new(inner)));
2225 }
2226
2227 let mut place = self.parse_place_base()?;
2228
2229 loop {
2231 if let Token::Corner(c) = self.cur() {
2232 let c = *c;
2233 self.bump();
2234 place = Place::Corner(Box::new(place), c);
2235 } else if self.at(&Token::Dot) {
2236 self.bump();
2237 let rhs = self.parse_place_base()?;
2238 place = Place::Member(Box::new(place), Box::new(rhs));
2239 } else {
2240 break;
2241 }
2242 }
2243 Ok(place)
2244 }
2245
2246 fn parse_place_base(&mut self) -> PResult<Place> {
2247 match self.cur().clone() {
2248 Token::Kw(Kw::Here) => {
2249 self.bump();
2250 Ok(Place::Here)
2251 }
2252 Token::Label(name) => {
2253 self.bump();
2254 let subscript = if self.eat(&Token::LeftBrack) {
2255 let e = self.parse_subscript()?;
2256 self.expect(&Token::RightBrack)?;
2257 Some(Box::new(e))
2258 } else {
2259 None
2260 };
2261 Ok(Place::Name { name, subscript })
2262 }
2263 Token::Kw(Kw::Last) | Token::Float(_) | Token::LeftBrace | Token::LeftQuote => {
2264 let count = self.parse_nth()?;
2265 let obj = if self.at_primobj() {
2269 self.parse_primobj()?
2270 } else {
2271 PrimObj::Any
2272 };
2273 Ok(Place::Nth { count, obj })
2274 }
2275 other => self.err(format!("expected a place, found {other:?}")),
2276 }
2277 }
2278
2279 fn parse_nth(&mut self) -> PResult<Nth> {
2280 if self.eat_kw(Kw::Last) {
2281 return Ok(Nth::Last);
2282 }
2283 let e = self.parse_ncount()?;
2285 self.expect_kw(Kw::Nth)?;
2286 let from_last = self.eat_kw(Kw::Last);
2287 Ok(Nth::Count(Box::new(e), from_last))
2288 }
2289
2290 fn parse_ncount(&mut self) -> PResult<Expr> {
2294 match self.cur().clone() {
2295 Token::Float(v) => {
2296 self.bump();
2297 Ok(Expr::Num(v))
2298 }
2299 Token::LeftBrace => {
2300 self.bump();
2301 let e = self.parse_expr()?;
2302 self.expect(&Token::RightBrace)?;
2303 Ok(e)
2304 }
2305 Token::LeftQuote => {
2306 self.bump();
2307 let e = self.parse_expr()?;
2308 self.expect(&Token::RightQuote)?;
2309 Ok(e)
2310 }
2311 other => self.err(format!("expected an ordinal count, found {other:?}")),
2312 }
2313 }
2314
2315 fn at_primobj(&self) -> bool {
2318 matches!(
2319 self.cur(),
2320 Token::Prim(_) | Token::Block | Token::Str(_) | Token::LeftBrack
2321 ) || matches!(self.cur(), Token::Name(n) if n == "brace")
2322 }
2323
2324 fn parse_primobj(&mut self) -> PResult<PrimObj> {
2325 match self.cur().clone() {
2326 Token::Prim(p) => {
2327 self.bump();
2328 Ok(PrimObj::Prim(p))
2329 }
2330 Token::Name(n) if n == "brace" => {
2331 self.bump();
2332 Ok(PrimObj::Brace)
2333 }
2334 Token::Block => {
2335 self.bump();
2336 Ok(PrimObj::Block)
2337 }
2338 Token::Str(s) => {
2339 self.bump();
2340 Ok(PrimObj::Str(s))
2341 }
2342 Token::LeftBrack => {
2343 self.bump();
2344 self.expect(&Token::RightBrack)?;
2345 Ok(PrimObj::EmptyBrack)
2346 }
2347 other => self.err(format!("expected a primitive object, found {other:?}")),
2348 }
2349 }
2350
2351 fn opt_expr(&mut self) -> PResult<Option<Expr>> {
2354 if self.starts_scalar() || self.place_is_scalar_ahead() {
2355 Ok(Some(self.parse_expr()?))
2356 } else {
2357 Ok(None)
2358 }
2359 }
2360
2361 fn parse_color_like(&mut self) -> PResult<StringExpr> {
2364 match self.cur().clone() {
2365 Token::Name(n) | Token::Label(n) => {
2366 self.bump();
2367 Ok(StringExpr::Lit(n))
2368 }
2369 _ => self.parse_stringexpr(),
2370 }
2371 }
2372
2373 fn opt_attr_expr(
2374 &mut self,
2375 allow_fit: bool,
2376 allow_brace: bool,
2377 allow_hatch: bool,
2378 allow_close: bool,
2379 ) -> PResult<Option<Expr>> {
2380 if self.contextual_attr_ahead(allow_fit, allow_brace, allow_hatch, allow_close) {
2381 Ok(None)
2382 } else {
2383 self.opt_expr()
2384 }
2385 }
2386
2387 fn contextual_attr_ahead(
2388 &self,
2389 allow_fit: bool,
2390 allow_brace: bool,
2391 allow_hatch: bool,
2392 allow_close: bool,
2393 ) -> bool {
2394 matches!(
2395 self.cur(),
2396 Token::Name(n)
2397 if (allow_fit && n == "fit")
2398 || (allow_hatch
2399 && matches!(
2400 n.as_str(),
2401 "hatch"
2402 | "crosshatch"
2403 | "hatchangle"
2404 | "hatchsep"
2405 | "hatchwid"
2406 | "hatchwidth"
2407 | "hatchcolor"
2408 | "gradient"
2409 | "gradientangle"
2410 ))
2411 || n == "opacity"
2412 || (allow_brace && matches!(n.as_str(), "bracepos" | "labeloffset"))
2413 || n == "behind"
2414 || n == "class"
2415 || (allow_close && n == "close")
2416 )
2417 }
2418
2419 fn starts_scalar(&self) -> bool {
2420 matches!(
2421 self.cur(),
2422 Token::Float(_)
2423 | Token::Name(_)
2424 | Token::EnvVar(_)
2425 | Token::Lparen
2426 | Token::Minus
2427 | Token::Plus
2428 | Token::Not
2429 | Token::Func1(_)
2430 | Token::Func2(_)
2431 | Token::Kw(Kw::Rand)
2432 | Token::ArgCount
2433 )
2434 }
2435
2436 fn parse_expr(&mut self) -> PResult<Expr> {
2437 self.parse_or()
2438 }
2439
2440 fn parse_or(&mut self) -> PResult<Expr> {
2441 let mut e = self.parse_and()?;
2442 while self.eat(&Token::OrOr) {
2443 let r = self.parse_and()?;
2444 e = Expr::Bin(BinOp::Or, Box::new(e), Box::new(r));
2445 }
2446 Ok(e)
2447 }
2448
2449 fn parse_and(&mut self) -> PResult<Expr> {
2450 let mut e = self.parse_cmp()?;
2451 while self.eat(&Token::AndAnd) {
2452 let r = self.parse_cmp()?;
2453 e = Expr::Bin(BinOp::And, Box::new(e), Box::new(r));
2454 }
2455 Ok(e)
2456 }
2457
2458 fn parse_cmp(&mut self) -> PResult<Expr> {
2459 let mut e = self.parse_add()?;
2460 loop {
2461 let op = match self.cur() {
2462 Token::EqEq => BinOp::Eq,
2463 Token::Neq => BinOp::Ne,
2464 Token::Lt => BinOp::Lt,
2465 Token::Le => BinOp::Le,
2466 Token::Gt => BinOp::Gt,
2467 Token::Ge => BinOp::Ge,
2468 _ => break,
2469 };
2470 self.bump();
2471 let r = self.parse_add()?;
2472 e = Expr::Bin(op, Box::new(e), Box::new(r));
2473 }
2474 Ok(e)
2475 }
2476
2477 fn parse_add(&mut self) -> PResult<Expr> {
2478 let mut e = self.parse_mul()?;
2479 loop {
2480 let op = match self.cur() {
2481 Token::Plus => BinOp::Add,
2482 Token::Minus => BinOp::Sub,
2483 _ => break,
2484 };
2485 self.bump();
2486 let r = self.parse_mul()?;
2487 e = Expr::Bin(op, Box::new(e), Box::new(r));
2488 }
2489 Ok(e)
2490 }
2491
2492 fn parse_mul(&mut self) -> PResult<Expr> {
2493 let mut e = self.parse_unary()?;
2494 loop {
2495 let op = match self.cur() {
2496 Token::Mult => BinOp::Mul,
2497 Token::Div => BinOp::Div,
2498 Token::Percent => BinOp::Mod,
2499 _ => break,
2500 };
2501 self.bump();
2502 let r = self.parse_unary()?;
2503 e = Expr::Bin(op, Box::new(e), Box::new(r));
2504 }
2505 Ok(e)
2506 }
2507
2508 fn parse_unary(&mut self) -> PResult<Expr> {
2509 let op = match self.cur() {
2510 Token::Minus => Some(UnOp::Neg),
2511 Token::Plus => Some(UnOp::Pos),
2512 Token::Not => Some(UnOp::Not),
2513 _ => None,
2514 };
2515 if let Some(op) = op {
2516 self.bump();
2517 let e = self.parse_unary()?;
2518 Ok(Expr::Unary(op, Box::new(e)))
2519 } else {
2520 self.parse_pow()
2521 }
2522 }
2523
2524 fn parse_pow(&mut self) -> PResult<Expr> {
2525 let base = self.parse_primary()?;
2526 if self.eat(&Token::Caret) {
2527 let exp = self.parse_unary()?; Ok(Expr::Bin(BinOp::Pow, Box::new(base), Box::new(exp)))
2529 } else {
2530 Ok(base)
2531 }
2532 }
2533
2534 fn paren_followed_by_dot_xy(&self) -> bool {
2537 let mut depth = 1i32;
2538 let mut i = self.idx;
2539 while let Some(s) = self.toks.get(i) {
2540 match &s.tok {
2541 Token::Lparen => depth += 1,
2542 Token::Rparen => {
2543 depth -= 1;
2544 if depth == 0 {
2545 return matches!(
2546 self.toks.get(i + 1).map(|t| &t.tok),
2547 Some(Token::DotX | Token::DotY)
2548 );
2549 }
2550 }
2551 Token::Eof => return false,
2552 _ => {}
2553 }
2554 i += 1;
2555 }
2556 false
2557 }
2558
2559 fn parse_primary(&mut self) -> PResult<Expr> {
2560 if self.at_place_start() && self.place_is_scalar_ahead() {
2562 return self.parse_place_scalar();
2563 }
2564 if self.at_string_start() {
2566 return Ok(Expr::Str(self.parse_stringexpr()?));
2567 }
2568 match self.cur().clone() {
2569 Token::Float(v) => {
2570 self.bump();
2571 Ok(Expr::Num(v))
2572 }
2573 Token::Name(name) | Token::Label(name) => {
2574 self.bump();
2575 let subscript = if self.eat(&Token::LeftBrack) {
2576 let e = self.parse_subscript()?;
2577 self.expect(&Token::RightBrack)?;
2578 Some(Box::new(e))
2579 } else {
2580 None
2581 };
2582 Ok(Expr::Var(name, subscript))
2583 }
2584 Token::EnvVar(v) => {
2585 self.bump();
2586 Ok(Expr::Env(v))
2587 }
2588 Token::Lparen => {
2589 self.bump();
2590 if matches!(self.cur(), Token::Name(_) | Token::Label(_))
2592 && self.assignment_op_after_var_ref()
2593 {
2594 let name = match self.bump() {
2595 Token::Name(n) | Token::Label(n) => n,
2596 _ => unreachable!(),
2597 };
2598 let subscript = if self.eat(&Token::LeftBrack) {
2599 let e = self.parse_subscript()?;
2600 self.expect(&Token::RightBrack)?;
2601 Some(Box::new(e))
2602 } else {
2603 None
2604 };
2605 self.bump(); let v = self.parse_expr()?;
2607 self.expect(&Token::Rparen)?;
2608 return Ok(Expr::Assign(name, subscript, Box::new(v)));
2609 }
2610 if self.paren_followed_by_dot_xy() {
2615 let pos = self.parse_position()?;
2616 self.expect(&Token::Rparen)?;
2617 let loc = Location::Paren(Box::new(pos));
2618 return Ok(if self.eat(&Token::DotX) {
2619 Expr::DotX(loc)
2620 } else {
2621 self.expect(&Token::DotY)?;
2622 Expr::DotY(loc)
2623 });
2624 }
2625 let e = self.parse_expr()?;
2627 self.expect(&Token::Rparen)?;
2628 Ok(e)
2629 }
2630 Token::ArgCount => {
2632 self.bump();
2633 Ok(Expr::Num(0.0))
2634 }
2635 Token::Func1(f) => {
2636 self.bump();
2637 self.expect(&Token::Lparen)?;
2638 let e = self.parse_expr()?;
2639 self.expect(&Token::Rparen)?;
2640 Ok(Expr::Func1(f, Box::new(e)))
2641 }
2642 Token::Func2(f) => {
2643 self.bump();
2644 self.expect(&Token::Lparen)?;
2645 let a = self.parse_expr()?;
2646 self.expect(&Token::Comma)?;
2647 let b = self.parse_expr()?;
2648 self.expect(&Token::Rparen)?;
2649 Ok(Expr::Func2(f, Box::new(a), Box::new(b)))
2650 }
2651 Token::Kw(Kw::Rand) => {
2652 self.bump();
2653 self.expect(&Token::Lparen)?;
2654 let arg = if self.at(&Token::Rparen) {
2655 None
2656 } else {
2657 Some(Box::new(self.parse_expr()?))
2658 };
2659 self.expect(&Token::Rparen)?;
2660 Ok(Expr::Rand(arg))
2661 }
2662 other => self.err(format!("expected an expression, found {other:?}")),
2663 }
2664 }
2665
2666 fn parse_place_scalar(&mut self) -> PResult<Expr> {
2668 let place = self.parse_place()?;
2669 match self.cur().clone() {
2670 Token::DotX => {
2671 self.bump();
2672 Ok(Expr::DotX(Location::Place(place)))
2673 }
2674 Token::DotY => {
2675 self.bump();
2676 Ok(Expr::DotY(Location::Place(place)))
2677 }
2678 Token::Param(p) => {
2679 self.bump();
2680 Ok(Expr::PlaceAttr(place, p))
2681 }
2682 other => self.err(format!(
2683 "a place is not a number here; expected `.x`, `.y`, or an attribute, found {other:?}"
2684 )),
2685 }
2686 }
2687}
2688
2689#[cfg(test)]
2690mod tests {
2691 use super::*;
2692
2693 fn pic(src: &str) -> Picture {
2694 parse(src).unwrap_or_else(|e| panic!("parse error: {e}"))
2695 }
2696
2697 #[test]
2698 fn kernighan_pipeline() {
2699 let p = pic(r#".PS
2700ellipse "document"
2701arrow
2702box "PIC"
2703arrow
2704box "TBL/EQN" "(optional)" dashed
2705arrow
2706box "TROFF"
2707arrow
2708ellipse "typesetter"
2709.PE
2710"#);
2711 assert_eq!(p.stmts.len(), 9);
2712 if let Stmt::Object { object, .. } = &p.stmts[4] {
2714 assert_eq!(object.kind, ObjectKind::Primitive(Prim::Box));
2715 let texts = object
2716 .attrs
2717 .iter()
2718 .filter(|a| matches!(a, Attr::Text(_)))
2719 .count();
2720 assert_eq!(texts, 2);
2721 assert!(
2722 object
2723 .attrs
2724 .iter()
2725 .any(|a| matches!(a, Attr::LineStyle(LineType::Dashed, _)))
2726 );
2727 } else {
2728 panic!("expected object");
2729 }
2730 }
2731
2732 #[test]
2733 fn box_with_dims_and_at() {
2734 let p = pic("box ht 0.3 wid 0.5 at 0.25,0.15");
2735 let Stmt::Object { object, .. } = &p.stmts[0] else {
2736 panic!()
2737 };
2738 assert!(matches!(object.attrs[0], Attr::Dim(DimKind::Ht, _)));
2739 assert!(matches!(object.attrs[1], Attr::Dim(DimKind::Wid, _)));
2740 assert!(matches!(object.attrs[2], Attr::At(Position::Pair(_, _))));
2741 }
2742
2743 #[test]
2744 fn behind_parses_as_contextual_extension_attribute() {
2745 let p = pic("A: box\nbox behind A");
2746 let Stmt::Object { object, .. } = &p.stmts[1] else {
2747 panic!()
2748 };
2749 let Some(Attr::Behind(Place::Name {
2750 name, subscript, ..
2751 })) = object.attrs.last()
2752 else {
2753 panic!("expected behind attribute");
2754 };
2755 assert_eq!(name, "A");
2756 assert!(subscript.is_none());
2757
2758 let p = pic("behind = 2\nbox wid behind");
2759 assert_eq!(p.stmts.len(), 2);
2760 assert!(matches!(p.stmts[0], Stmt::Assign(_)));
2761 }
2762
2763 #[test]
2764 fn fit_parses_as_contextual_extension_attribute() {
2765 let p = pic("box \"long label\" fit");
2766 let Stmt::Object { object, .. } = &p.stmts[0] else {
2767 panic!()
2768 };
2769 assert!(object.attrs.iter().any(|a| matches!(a, Attr::Fit)));
2770
2771 let p = pic("fit = 2\nbox wid fit");
2772 assert_eq!(p.stmts.len(), 2);
2773 assert!(matches!(p.stmts[0], Stmt::Assign(_)));
2774
2775 let p = pic("fit = 2\nline fit");
2776 let Stmt::Object { object, .. } = &p.stmts[1] else {
2777 panic!()
2778 };
2779 assert!(matches!(object.attrs[0], Attr::Dist(_)));
2780 }
2781
2782 #[test]
2783 fn hatch_parses_as_contextual_extension_attribute() {
2784 let p = pic("box hatch hatchangle 30 hatchsep .05 hatchwid 1.2 hatchcolor red");
2785 let Stmt::Object { object, .. } = &p.stmts[0] else {
2786 panic!()
2787 };
2788 assert!(
2789 object
2790 .attrs
2791 .iter()
2792 .any(|a| matches!(a, Attr::Hatch(HatchKind::Single)))
2793 );
2794 assert!(
2795 object
2796 .attrs
2797 .iter()
2798 .any(|a| matches!(a, Attr::HatchAngle(_)))
2799 );
2800 assert!(object.attrs.iter().any(|a| matches!(a, Attr::HatchSep(_))));
2801 assert!(
2802 object
2803 .attrs
2804 .iter()
2805 .any(|a| matches!(a, Attr::HatchWidth(_)))
2806 );
2807 assert!(
2808 object
2809 .attrs
2810 .iter()
2811 .any(|a| matches!(a, Attr::HatchColor(_)))
2812 );
2813
2814 let p = pic("hatch = 2\nbox wid hatch");
2815 assert_eq!(p.stmts.len(), 2);
2816 assert!(matches!(p.stmts[0], Stmt::Assign(_)));
2817 let Stmt::Object { object, .. } = &p.stmts[1] else {
2818 panic!()
2819 };
2820 assert!(matches!(object.attrs[0], Attr::Dim(DimKind::Wid, _)));
2821 }
2822
2823 #[test]
2824 fn opacity_parses_as_contextual_extension_attribute() {
2825 let p = pic("box opacity 0.5");
2826 let Stmt::Object { object, .. } = &p.stmts[0] else {
2827 panic!()
2828 };
2829 assert!(object.attrs.iter().any(|a| matches!(a, Attr::Opacity(_))));
2830
2831 let p = pic("opacity = 2\nbox wid opacity");
2832 assert_eq!(p.stmts.len(), 2);
2833 assert!(matches!(p.stmts[0], Stmt::Assign(_)));
2834 let Stmt::Object { object, .. } = &p.stmts[1] else {
2835 panic!()
2836 };
2837 assert!(matches!(object.attrs[0], Attr::Dim(DimKind::Wid, _)));
2838 }
2839
2840 #[test]
2841 fn close_parses_as_contextual_line_extension_attribute() {
2842 let p = pic("line right then up close");
2843 let Stmt::Object { object, .. } = &p.stmts[0] else {
2844 panic!()
2845 };
2846 assert!(object.attrs.iter().any(|a| matches!(a, Attr::Close)));
2847
2848 let p = pic("close = 2\nbox wid close");
2849 assert_eq!(p.stmts.len(), 2);
2850 assert!(matches!(p.stmts[0], Stmt::Assign(_)));
2851 let Stmt::Object { object, .. } = &p.stmts[1] else {
2852 panic!()
2853 };
2854 assert!(matches!(object.attrs[0], Attr::Dim(DimKind::Wid, _)));
2855 }
2856
2857 #[test]
2858 fn gradient_parses_as_contextual_extension_attribute() {
2859 let p = pic("box gradient \"steelblue\" white gradientangle 45");
2860 let Stmt::Object { object, .. } = &p.stmts[0] else {
2861 panic!()
2862 };
2863 assert!(object.attrs.iter().any(|a| matches!(a, Attr::Gradient(..))));
2864 assert!(
2865 object
2866 .attrs
2867 .iter()
2868 .any(|a| matches!(a, Attr::GradientAngle(_)))
2869 );
2870
2871 let p = pic("gradient = 2\nbox wid gradient");
2873 assert!(matches!(p.stmts[0], Stmt::Assign(_)));
2874 let Stmt::Object { object, .. } = &p.stmts[1] else {
2875 panic!()
2876 };
2877 assert!(matches!(object.attrs[0], Attr::Dim(DimKind::Wid, _)));
2878 }
2879
2880 #[test]
2881 fn class_parses_inline_and_statement_forms() {
2882 let p = pic("box class \"critical\"");
2883 let Stmt::Object { object, .. } = &p.stmts[0] else {
2884 panic!()
2885 };
2886 assert!(object.attrs.iter().any(|a| matches!(a, Attr::Class(_))));
2887
2888 let p = pic("A: box\nclass A \"hot\"\nclass last box \"cold\"");
2889 assert!(matches!(p.stmts[1], Stmt::Class { .. }));
2890 assert!(matches!(p.stmts[2], Stmt::Class { .. }));
2891
2892 let p = pic("class = 2\nbox wid class");
2894 assert!(matches!(p.stmts[0], Stmt::Assign(_)));
2895 let Stmt::Object { object, .. } = &p.stmts[1] else {
2896 panic!()
2897 };
2898 assert!(matches!(object.attrs[0], Attr::Dim(DimKind::Wid, _)));
2899 }
2900
2901 #[test]
2902 fn brace_parses_as_contextual_extension_object() {
2903 let p = pic(
2904 "A: box\nB: box\nbrace from A.e to B.w down \"group\" wid .2 bracepos .4 labeloffset .1",
2905 );
2906 let Stmt::Object { object, .. } = &p.stmts[2] else {
2907 panic!()
2908 };
2909 assert_eq!(object.kind, ObjectKind::Brace);
2910 assert!(object.attrs.iter().any(|a| matches!(a, Attr::From(_))));
2911 assert!(object.attrs.iter().any(|a| matches!(a, Attr::To(_))));
2912 assert!(
2913 object
2914 .attrs
2915 .iter()
2916 .any(|a| matches!(a, Attr::Direction(Dir::Down, None)))
2917 );
2918 assert!(object.attrs.iter().any(|a| matches!(a, Attr::Text(_))));
2919 assert!(object.attrs.iter().any(|a| matches!(a, Attr::BracePos(_))));
2920 assert!(
2921 object
2922 .attrs
2923 .iter()
2924 .any(|a| matches!(a, Attr::BraceLabelOffset(_)))
2925 );
2926
2927 let p = pic("brace = 2\nline right brace");
2928 assert!(matches!(p.stmts[0], Stmt::Assign(_)));
2929 let Stmt::Object { object, .. } = &p.stmts[1] else {
2930 panic!()
2931 };
2932 assert_eq!(object.kind, ObjectKind::Primitive(Prim::Line));
2933
2934 let p = pic("brace from 0,0 to 1,0\nline from last brace.start to last brace.end");
2935 assert_eq!(p.stmts.len(), 2);
2936 }
2937
2938 #[test]
2939 fn labeled_and_corners() {
2940 let p = pic("B1: box\narc -> from top of B1 to last box.ne");
2941 assert!(matches!(p.stmts[0], Stmt::Object { label: Some(_), .. }));
2942 let Stmt::Object { object, .. } = &p.stmts[1] else {
2943 panic!()
2944 };
2945 assert_eq!(object.kind, ObjectKind::Primitive(Prim::Arc));
2946 assert!(
2947 object
2948 .attrs
2949 .iter()
2950 .any(|a| matches!(a, Attr::Arrowhead(Arrow::Right, _)))
2951 );
2952 assert!(object.attrs.iter().any(|a| matches!(
2954 a,
2955 Attr::From(Position::Place(Location::Place(Place::CornerOf(
2956 Corner::N,
2957 _
2958 ))))
2959 )));
2960 }
2961
2962 #[test]
2963 fn with_at_and_shift() {
2964 let p = pic("ellipse \"2\" with .nw at last ellipse.se + (0.1,0)");
2965 let Stmt::Object { object, .. } = &p.stmts[0] else {
2966 panic!()
2967 };
2968 let with = object
2969 .attrs
2970 .iter()
2971 .find(|a| matches!(a, Attr::With { .. }))
2972 .unwrap();
2973 let Attr::With { anchor, at } = with else {
2974 panic!()
2975 };
2976 assert_eq!(*anchor, WithAnchor::Corner(Corner::Nw));
2977 assert!(matches!(at, Position::Sum(Sign::Plus, _, _)));
2979 }
2980
2981 #[test]
2982 fn with_member_anchor_parses() {
2983 let p = pic("[ A: box ] with .A.c at Here");
2984 let Stmt::Object { object, .. } = &p.stmts[0] else {
2985 panic!()
2986 };
2987 let with = object
2988 .attrs
2989 .iter()
2990 .find(|a| matches!(a, Attr::With { .. }))
2991 .unwrap();
2992 let Attr::With { anchor, .. } = with else {
2993 panic!()
2994 };
2995 assert!(matches!(
2996 anchor,
2997 WithAnchor::Place(Place::Corner(inner, Corner::Center))
2998 if matches!(inner.as_ref(), Place::Name { name, .. } if name == "A")
2999 ));
3000 }
3001
3002 #[test]
3003 fn expression_precedence() {
3004 let p = pic("x = 2 + 3 * 4 ^ 2");
3006 let Stmt::Assign(list) = &p.stmts[0] else {
3007 panic!()
3008 };
3009 let Expr::Bin(BinOp::Add, _, rhs) = &list[0].value else {
3011 panic!("expected top-level add")
3012 };
3013 assert!(matches!(**rhs, Expr::Bin(BinOp::Mul, _, _)));
3014 }
3015
3016 #[test]
3017 fn between_position() {
3018 let p = pic("arrow from 1/3 of the way between A.ne and A.se");
3019 let Stmt::Object { object, .. } = &p.stmts[0] else {
3020 panic!()
3021 };
3022 assert!(object.attrs.iter().any(|a| matches!(
3023 a,
3024 Attr::From(Position::Between {
3025 of_the_way: true,
3026 ..
3027 })
3028 )));
3029 }
3030
3031 #[test]
3032 fn assignment_list_and_envvar() {
3033 let p = pic("boxht = 0.3; boxwid = 2 * boxht");
3034 assert_eq!(p.stmts.len(), 2);
3035 let Stmt::Assign(a0) = &p.stmts[0] else {
3036 panic!()
3037 };
3038 assert_eq!(a0[0].target, AssignTarget::Env(EnvVar::Boxht));
3039 }
3040
3041 #[test]
3042 fn dpic_svg_font_stub_parses_as_string() {
3043 let p = pic("print svg_font(\"Times\", 12)");
3044 let Stmt::Print(PrintItem::Str(StringExpr::SvgFont(args))) = &p.stmts[0] else {
3045 panic!()
3046 };
3047 assert_eq!(args.len(), 2);
3048 }
3049
3050 #[test]
3051 fn subscripted_variable_refs_parse() {
3052 let p = pic("P[1] = 2\nx = P[1]");
3053 let Stmt::Assign(a0) = &p.stmts[0] else {
3054 panic!()
3055 };
3056 assert!(matches!(&a0[0].target, AssignTarget::Var(name, Some(_)) if name == "P"));
3057
3058 let Stmt::Assign(a1) = &p.stmts[1] else {
3059 panic!()
3060 };
3061 assert!(matches!(&a1[0].value, Expr::Var(name, Some(_)) if name == "P"));
3062 }
3063
3064 #[test]
3065 fn block_object() {
3066 let p = pic("[ box; circle ] with .nw at Here");
3067 let Stmt::Object { object, .. } = &p.stmts[0] else {
3068 panic!()
3069 };
3070 let ObjectKind::Block(inner) = &object.kind else {
3071 panic!()
3072 };
3073 assert_eq!(inner.len(), 2);
3074 }
3075
3076 #[test]
3077 fn diamond_line_with_then() {
3078 let p = pic("line up right then down right then down left then up left");
3079 let Stmt::Object { object, .. } = &p.stmts[0] else {
3080 panic!()
3081 };
3082 let thens = object
3083 .attrs
3084 .iter()
3085 .filter(|a| matches!(a, Attr::Then))
3086 .count();
3087 assert_eq!(thens, 3);
3088 }
3089
3090 #[test]
3091 fn place_scalar_in_coord_pair() {
3092 let p = pic("A: box\n\"t\" at (A.x, A.y - 0.5)");
3094 let Stmt::Object { object, .. } = &p.stmts[1] else {
3095 panic!()
3096 };
3097 assert!(
3098 object
3099 .attrs
3100 .iter()
3101 .any(|a| matches!(a, Attr::At(Position::Pair(_, _))))
3102 );
3103 let q = pic("A: box\nbox at A.ne");
3105 let Stmt::Object { object, .. } = &q.stmts[1] else {
3106 panic!()
3107 };
3108 assert!(object.attrs.iter().any(|a| matches!(
3109 a,
3110 Attr::At(Position::Place(Location::Place(Place::Corner(_, _))))
3111 )));
3112 }
3113
3114 #[test]
3115 fn ignores_non_svg_backend_preambles() {
3116 let p = pic(r#".PS
3117verbatimtex
3118\global\def\foo#1{#1}
3119etex
3120\global\def\bar#1{#1}
3121\psset{arrowsize=4pt}
3122box
3123.PE
3124"#);
3125 assert_eq!(p.stmts.len(), 1);
3126 let Stmt::Object { object, .. } = &p.stmts[0] else {
3127 panic!()
3128 };
3129 assert_eq!(object.kind, ObjectKind::Primitive(Prim::Box));
3130 }
3131
3132 #[test]
3133 fn backend_filter_keeps_global_lines_inside_strings() {
3134 let p = pic(
3135 "sh \"echo -n \\\"print \\\\\"\\\" > x\"\nif dpicopt==optPGF then { command \"cycle; \\\n\\global\\let\\dpicdraw=x\" } else { box }",
3136 );
3137 assert_eq!(p.stmts.len(), 2);
3138 let Stmt::Object { object, .. } = &p.stmts[1] else {
3139 panic!()
3140 };
3141 assert_eq!(object.kind, ObjectKind::Primitive(Prim::Box));
3142 }
3143
3144 #[test]
3145 fn static_if_copy_defines_macros_before_following_statements() {
3146 let dir = std::env::temp_dir().join(format!("rpic_static_if_{}", std::process::id()));
3147 std::fs::create_dir_all(&dir).unwrap();
3148 std::fs::write(dir.join("macros.pic"), "define makebox { box wid $1 }\n").unwrap();
3149 std::fs::write(
3150 dir.join("inc.pic"),
3151 "define makecircle { circle rad 0.1 }\n",
3152 )
3153 .unwrap();
3154 let p = parse_in_dir(
3155 "if \"plotlib\" != \"1\" then { copy \"macros.pic\" }\ndefine choose { if \"$1\"==\"\" then { box } else { copy \"$1/inc.pic\" } }\nchoose(.)\nmakecircle()\nmakebox(0.4)",
3156 Some(dir.as_path()),
3157 )
3158 .unwrap_or_else(|e| panic!("parse error: {e}"));
3159 let _ = std::fs::remove_dir_all(&dir);
3160 assert_eq!(p.stmts.len(), 2);
3161 let Stmt::Object { object, .. } = &p.stmts[0] else {
3162 panic!()
3163 };
3164 assert_eq!(object.kind, ObjectKind::Primitive(Prim::Circle));
3165 let Stmt::Object { object, .. } = &p.stmts[1] else {
3166 panic!()
3167 };
3168 assert_eq!(object.kind, ObjectKind::Primitive(Prim::Box));
3169 }
3170
3171 #[test]
3172 fn unsupported_control_is_clear() {
3173 let e = parse("copy \"x\"").unwrap_err();
3175 assert!(e.msg.contains("copy") && e.msg.contains("file"));
3176 }
3177
3178 #[test]
3179 fn control_constructs_parse() {
3180 assert!(parse("for i = 1 to 3 do { box }").is_ok());
3181 assert!(parse("if 1 > 0 then { box } else { circle }").is_ok());
3182 assert!(parse("reset boxht, boxwid").is_ok());
3183 let p = parse("define e { box }\ne\ne").unwrap();
3185 assert_eq!(p.stmts.len(), 2);
3186 }
3187}