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