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: &mut 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 expanded = expand(&toks, macros, 0, includes)?;
233 let mut p = Parser::new(expanded);
234 p.parse_elementlist(&[])
235}
236
237pub(crate) fn parse_stringexpr_tokens(
238 toks: &[Spanned],
239 macros: &mut Macros,
240 includes: &IncludeCtx,
241) -> Result<StringExpr, ParseError> {
242 let mut input = toks.to_vec();
243 input.push(Spanned::new(Token::Eof, 0, 0));
244 let expanded = expand(&input, macros, 0, includes)?;
245 let mut p = Parser::new(expanded);
246 p.skip_newlines();
247 let expr = p.parse_stringexpr()?;
248 p.skip_newlines();
249 if !p.at(&Token::Eof) {
250 return p.err(format!("unexpected {:?} after string expression", p.cur()));
251 }
252 Ok(expr)
253}
254
255fn strip_backend_preamble(src: &str) -> String {
263 let mut out = String::with_capacity(src.len());
264 let mut in_verbatimtex = false;
265 let mut in_string = false;
266 let mut in_raw_sh = false;
267
268 for line in src.lines() {
269 let trimmed = line.trim_start();
270 if in_verbatimtex {
271 out.push('\n');
272 if starts_word(trimmed, "etex") {
273 in_verbatimtex = false;
274 }
275 continue;
276 }
277
278 if in_raw_sh {
279 out.push_str(line);
280 out.push('\n');
281 in_raw_sh = line_continues(line);
282 continue;
283 }
284
285 if !in_string && starts_word(trimmed, "verbatimtex") {
286 in_verbatimtex = true;
287 out.push('\n');
288 } else if !in_string && is_ignored_backend_line(trimmed) {
289 out.push('\n');
290 } else {
291 out.push_str(line);
292 out.push('\n');
293 if starts_word(trimmed, "sh") {
294 in_raw_sh = line_continues(line);
295 } else {
296 in_string = update_string_state(line, in_string);
297 }
298 }
299 }
300 out
301}
302
303fn line_continues(line: &str) -> bool {
304 line.trim_end_matches([' ', '\t', '\r']).ends_with('\\')
305}
306
307fn update_string_state(line: &str, mut in_string: bool) -> bool {
308 let mut slashes = 0usize;
309 for c in line.chars() {
310 if c == '\\' {
311 slashes += 1;
312 continue;
313 }
314 if c == '"' && slashes.is_multiple_of(2) {
315 in_string = !in_string;
316 }
317 slashes = 0;
318 }
319 in_string
320}
321
322fn is_ignored_backend_line(trimmed: &str) -> bool {
323 trimmed.starts_with("\\global") || trimmed.starts_with("\\psset")
324}
325
326fn starts_word(s: &str, word: &str) -> bool {
327 let Some(rest) = s.strip_prefix(word) else {
328 return false;
329 };
330 !rest
331 .chars()
332 .next()
333 .is_some_and(|c| c.is_alphanumeric() || c == '_')
334}
335
336use std::collections::HashMap;
344use std::path::Path;
345
346fn preprocess(
347 input: Vec<Spanned>,
348 includes: &IncludeCtx,
349) -> Result<(Vec<Spanned>, Macros), ParseError> {
350 let mut macros: Macros = builtin_unit_macros();
351 let out = expand(&input, &mut macros, 0, includes)?;
352 Ok((out, macros))
353}
354
355fn builtin_unit_macros() -> Macros {
359 let defs = [
360 ("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)"), ];
368 let mut m = Macros::new();
369 for (name, body) in defs {
370 if let Ok(toks) = lex(body) {
371 let body_toks: Vec<Spanned> =
372 toks.into_iter().filter(|s| s.tok != Token::Eof).collect();
373 m.insert(name.to_string(), body_toks);
374 }
375 }
376 m
377}
378
379fn loc(toks: &[Spanned], i: usize) -> Span {
380 toks.get(i)
381 .map(|s| s.span())
382 .unwrap_or_else(|| Span::new(0, 0, 0))
383}
384
385fn expand(
386 toks: &[Spanned],
387 macros: &mut HashMap<String, Vec<Spanned>>,
388 depth: usize,
389 includes: &IncludeCtx,
390) -> Result<Vec<Spanned>, ParseError> {
391 if depth > 64 {
392 return Err(ParseError::new(
393 "macro expansion too deep (recursive define?)",
394 Span::new(0, 0, 0),
395 ));
396 }
397 let mut out = Vec::new();
398 let mut i = 0;
399 while i < toks.len() {
400 match &toks[i].tok {
401 Token::Kw(Kw::Define) => {
402 let span = loc(toks, i);
403 i += 1;
404 let name = match toks.get(i).map(|s| &s.tok) {
405 Some(Token::Name(n)) | Some(Token::Label(n)) => n.clone(),
406 _ => {
407 return Err(ParseError::new(
408 "define: expected a macro name",
409 span.clone(),
410 ));
411 }
412 };
413 i += 1;
414 while toks.get(i).map(|s| &s.tok) == Some(&Token::Newline) {
416 i += 1;
417 }
418 let Some(delim) = toks.get(i).map(|s| s.tok.clone()) else {
419 return Err(ParseError::new(
420 "define: expected a body delimiter",
421 span.clone(),
422 ));
423 };
424 let body = if delim == Token::LeftBrace {
425 i += 1; let start = i;
427 let mut bd = 1;
428 while i < toks.len() && bd > 0 {
429 match &toks[i].tok {
430 Token::LeftBrace => bd += 1,
431 Token::RightBrace => {
432 bd -= 1;
433 if bd == 0 {
434 break;
435 }
436 }
437 _ => {}
438 }
439 i += 1;
440 }
441 if bd != 0 {
442 return Err(ParseError::new(
443 "define: unterminated `{` body",
444 span.clone(),
445 ));
446 }
447 let body = trim_edge_newlines(&toks[start..i]);
448 i += 1; body
450 } else {
451 if matches!(delim, Token::Eof | Token::Newline) {
452 let span = loc(toks, i);
453 return Err(ParseError::new(
454 "define: expected a body delimiter",
455 span.clone(),
456 ));
457 }
458 i += 1; let start = i;
460 while i < toks.len() && toks[i].tok != delim {
461 i += 1;
462 }
463 if i >= toks.len() {
464 return Err(ParseError::new(
465 "define: unterminated delimited body",
466 span.clone(),
467 ));
468 }
469 let body = trim_edge_newlines(&toks[start..i]);
470 i += 1; body
472 };
473 macros.insert(name, body);
474 }
475 Token::Kw(Kw::Undef) => {
476 i += 1;
477 if let Some(Token::Name(n)) | Some(Token::Label(n)) = toks.get(i).map(|s| &s.tok) {
478 macros.remove(n);
479 }
480 i += 1;
481 }
482 Token::Kw(Kw::If) => {
487 let if_tok = toks[i].clone();
488 out.push(toks[i].clone());
489 i += 1;
490 let Some(te) = find_kw_depth0(toks, i, Kw::Then) else {
491 continue; };
493 let cond = expand(&toks[i..te], macros, depth + 1, includes)?;
494 if let Some((then_body, after_then)) = read_braced_body(toks, te + 1)? {
495 let mut j = after_then;
496 while matches!(toks.get(j).map(|s| &s.tok), Some(Token::Newline)) {
497 j += 1;
498 }
499 let else_body =
500 if matches!(toks.get(j).map(|s| &s.tok), Some(Token::Kw(Kw::Else))) {
501 read_braced_body(toks, j + 1)?
502 } else {
503 None
504 };
505 if let Some(take_then) = static_truth(&cond) {
506 let after_static = else_body
507 .as_ref()
508 .map(|(_, after)| *after)
509 .unwrap_or(after_then);
510 out.pop(); if take_then {
512 out.extend(expand(&then_body, macros, depth + 1, includes)?);
513 i = after_static;
514 } else if let Some((body, after)) = else_body {
515 out.extend(expand(&body, macros, depth + 1, includes)?);
516 i = after;
517 } else {
518 i = after_then;
519 }
520 continue;
521 }
522 }
523 *out.last_mut().unwrap() = if_tok;
524 out.extend(cond);
525 out.push(toks[te].clone()); i = copy_braced(toks, te + 1, &mut out, macros)?;
527 let mut j = i;
529 while matches!(toks.get(j).map(|s| &s.tok), Some(Token::Newline)) {
530 j += 1;
531 }
532 if matches!(toks.get(j).map(|s| &s.tok), Some(Token::Kw(Kw::Else))) {
533 out.push(toks[j].clone());
534 i = copy_braced(toks, j + 1, &mut out, macros)?;
535 }
536 }
537 Token::Kw(Kw::For) => {
538 out.push(toks[i].clone());
539 i += 1;
540 let Some(de) = find_kw_depth0(toks, i, Kw::Do) else {
541 continue;
542 };
543 out.extend(expand(&toks[i..de], macros, depth + 1, includes)?);
544 out.push(toks[de].clone()); i = copy_braced(toks, de + 1, &mut out, macros)?;
546 }
547 Token::Name(n) | Token::Label(n) if macros.contains_key(n) => {
548 let body = macros.get(n).unwrap().clone();
549 i += 1;
550 let args = if toks.get(i).map(|s| &s.tok) == Some(&Token::Lparen) {
551 i += 1;
552 let (a, ni) = read_args(toks, i)?;
553 i = ni;
554 a
555 } else {
556 Vec::new()
557 };
558 let sub = substitute(&body, &args);
559 let expanded = expand(&sub, macros, depth + 1, includes)?;
560 out.extend(expanded);
561 }
562 Token::Kw(Kw::Copy) => {
564 let span = loc(toks, i);
565 i += 1;
566 let Some(Token::Str(fname)) = toks.get(i).map(|s| &s.tok) else {
567 return Err(ParseError::new(
568 "copy: expected a quoted file name (only `copy \"file\"` is supported)",
569 span.clone(),
570 ));
571 };
572 let fname = fname.clone();
573 i += 1;
574 let inc = include_file(includes, &fname, macros, depth, span)?;
575 out.extend(inc);
576 }
577 _ => {
578 out.push(toks[i].clone());
579 i += 1;
580 }
581 }
582 }
583 Ok(out)
584}
585
586fn read_args(toks: &[Spanned], mut i: usize) -> Result<(Vec<Vec<Spanned>>, usize), ParseError> {
589 let mut args: Vec<Vec<Spanned>> = Vec::new();
590 let mut cur: Vec<Spanned> = Vec::new();
591 let mut depth = 0i32;
592 loop {
593 let Some(s) = toks.get(i) else {
594 return Err(ParseError::new(
595 "unterminated macro arguments",
596 Span::new(0, 0, 0),
597 ));
598 };
599 match &s.tok {
600 Token::Lparen | Token::LeftBrack | Token::LeftBrace => {
601 depth += 1;
602 cur.push(s.clone());
603 i += 1;
604 }
605 Token::Rparen if depth == 0 => {
606 i += 1;
607 trim_trailing_newlines(&mut cur);
608 if !cur.is_empty() || !args.is_empty() {
609 args.push(cur);
610 }
611 break;
612 }
613 Token::Rparen | Token::RightBrack | Token::RightBrace => {
614 depth -= 1;
615 cur.push(s.clone());
616 i += 1;
617 }
618 Token::Comma if depth == 0 => {
619 trim_trailing_newlines(&mut cur);
620 args.push(std::mem::take(&mut cur));
621 i += 1;
622 }
623 Token::Newline if depth == 0 && cur.is_empty() => {
624 i += 1;
625 }
626 _ => {
627 cur.push(s.clone());
628 i += 1;
629 }
630 }
631 }
632 Ok((args, i))
633}
634
635fn trim_trailing_newlines(toks: &mut Vec<Spanned>) {
636 while matches!(toks.last().map(|s| &s.tok), Some(Token::Newline)) {
637 toks.pop();
638 }
639}
640
641fn trim_edge_newlines(toks: &[Spanned]) -> Vec<Spanned> {
644 let mut start = 0;
645 let mut end = toks.len();
646 while start < end && toks[start].tok == Token::Newline {
647 start += 1;
648 }
649 while end > start && toks[end - 1].tok == Token::Newline {
650 end -= 1;
651 }
652 toks[start..end].to_vec()
653}
654
655fn substitute(body: &[Spanned], args: &[Vec<Spanned>]) -> Vec<Spanned> {
657 let frame = std::sync::Arc::new(args.to_vec());
660 let mut out = Vec::new();
661 let mut i = 0;
662 while i < body.len() {
663 if matches!(body[i].tok, Token::Kw(Kw::Define))
664 && let Some((define, next)) = copy_define_verbatim(body, i)
665 {
666 out.extend(define);
667 i = next;
668 continue;
669 }
670
671 if let Some((pasted, next)) = paste_adjacent_args(body, args, i) {
672 out.push(pasted.with_arg_frame(&frame));
673 i = next;
674 continue;
675 }
676
677 let s = &body[i];
678 match &s.tok {
679 Token::Arg(k) => {
680 if let Some(a) = args.get((*k as usize).wrapping_sub(1)) {
681 out.extend(a.iter().cloned().map(|s| s.with_arg_frame(&frame)));
682 }
683 }
684 Token::ArgCount => out.push(
686 Spanned::new(Token::Float(args.len() as f64), s.line, s.col).with_arg_frame(&frame),
687 ),
688 Token::Str(text) if text.contains('$') => {
691 out.push(
692 Spanned::new(Token::Str(subst_in_string(text, args)), s.line, s.col)
693 .with_arg_frame(&frame),
694 );
695 }
696 _ => out.push(s.clone().with_arg_frame(&frame)),
697 }
698 i += 1;
699 }
700 out
701}
702
703fn copy_define_verbatim(body: &[Spanned], start: usize) -> Option<(Vec<Spanned>, usize)> {
704 let mut name_idx = start + 1;
705 while matches!(body.get(name_idx).map(|s| &s.tok), Some(Token::Newline)) {
706 name_idx += 1;
707 }
708 if !matches!(
709 body.get(name_idx).map(|s| &s.tok),
710 Some(Token::Name(_)) | Some(Token::Label(_))
711 ) {
712 return None;
713 }
714
715 let mut out = Vec::new();
716 let mut i = start;
717 while let Some(s) = body.get(i) {
718 out.push(s.clone());
719 i += 1;
720 if matches!(s.tok, Token::LeftBrace) {
721 break;
722 }
723 }
724
725 if !matches!(out.last().map(|s| &s.tok), Some(Token::LeftBrace)) {
726 return None;
727 }
728
729 let mut depth = 1i32;
730 while let Some(s) = body.get(i) {
731 match &s.tok {
732 Token::LeftBrace => depth += 1,
733 Token::RightBrace => depth -= 1,
734 _ => {}
735 }
736 out.push(s.clone());
737 i += 1;
738 if depth == 0 {
739 return Some((out, i));
740 }
741 }
742 None
743}
744
745fn paste_adjacent_args(
746 body: &[Spanned],
747 args: &[Vec<Spanned>],
748 start: usize,
749) -> Option<(Spanned, usize)> {
750 let first = body.get(start)?;
751 let Token::Arg(k) = &first.tok else {
752 return None;
753 };
754
755 let mut text = arg_text(*k, args);
756 let mut count = 1usize;
757 let mut end = start + 1;
758 let mut prev = first;
759 while let Some(next) = body.get(end) {
760 let Token::Arg(k) = &next.tok else {
761 break;
762 };
763 if !adjacent_arg_tokens(prev, next) {
764 break;
765 }
766 text.push_str(&arg_text(*k, args));
767 count += 1;
768 prev = next;
769 end += 1;
770 }
771
772 if count < 2 || text.is_empty() {
773 return None;
774 }
775
776 Some((tokenize_pasted_arg_text(&text, first.line, first.col), end))
777}
778
779fn arg_text(k: u32, args: &[Vec<Spanned>]) -> String {
780 args.get((k as usize).wrapping_sub(1))
781 .map(|a| tokens_to_text(a))
782 .unwrap_or_default()
783}
784
785fn adjacent_arg_tokens(left: &Spanned, right: &Spanned) -> bool {
786 left.line == right.line && arg_end_col(left) == Some(right.col)
787}
788
789fn arg_end_col(s: &Spanned) -> Option<u32> {
790 let Token::Arg(k) = &s.tok else {
791 return None;
792 };
793 Some(s.col + 1 + k.to_string().len() as u32)
794}
795
796fn tokenize_pasted_arg_text(text: &str, line: u32, col: u32) -> Spanned {
797 if let Ok(toks) = lex(text)
798 && toks.len() == 2
799 && matches!(toks[1].tok, Token::Eof)
800 {
801 return Spanned::new(toks[0].tok.clone(), line, col);
802 }
803
804 let tok = if text.chars().next().is_some_and(|c| c.is_ascii_uppercase()) {
805 Token::Label(text.to_string())
806 } else {
807 Token::Name(text.to_string())
808 };
809 Spanned::new(tok, line, col)
810}
811
812fn subst_in_string(text: &str, args: &[Vec<Spanned>]) -> String {
815 let chars: Vec<char> = text.chars().collect();
816 let mut out = String::new();
817 let mut i = 0;
818 while i < chars.len() {
819 if chars[i] == '$' && chars.get(i + 1) == Some(&'+') {
820 out.push_str(&args.len().to_string());
821 i += 2;
822 } else if chars[i] == '$' && chars.get(i + 1).is_some_and(|c| c.is_ascii_digit()) {
823 let mut j = i + 1;
824 let mut num = String::new();
825 while j < chars.len() && chars[j].is_ascii_digit() {
826 num.push(chars[j]);
827 j += 1;
828 }
829 if let Ok(k) = num.parse::<usize>()
830 && k >= 1
831 && let Some(a) = args.get(k - 1)
832 {
833 out.push_str(&tokens_to_text(a));
834 }
835 i = j;
836 } else {
837 out.push(chars[i]);
838 i += 1;
839 }
840 }
841 out
842}
843
844fn tokens_to_text(toks: &[Spanned]) -> String {
856 let material: Vec<&Spanned> = toks
857 .iter()
858 .filter(|s| !matches!(s.tok, Token::Eof))
859 .collect();
860 if let [one] = material.as_slice()
861 && let Token::Str(t) = &one.tok
862 {
863 return t.clone();
866 }
867 let mut s = String::new();
868 let mut prev: Option<(u32, u32)> = None; for t in material {
870 let piece = arg_token_text(&t.tok);
871 if piece.is_empty() {
872 continue;
873 }
874 if let Some((pline, pend)) = prev {
875 let sep = if t.line != pline {
880 true
881 } else if t.col >= pend {
882 t.col > pend
883 } else {
884 matches!((s.chars().last(), piece.chars().next()),
887 (Some(a), Some(b)) if wordish(a) && wordish(b))
888 };
889 if sep {
890 s.push(' ');
891 }
892 }
893 s.push_str(&piece);
894 prev = Some((t.line, t.end_col));
895 }
896 s
897}
898
899fn wordish(c: char) -> bool {
901 c.is_ascii_alphanumeric() || matches!(c, '_' | '"' | '$')
902}
903
904fn arg_token_text(tok: &Token) -> String {
906 match tok {
907 Token::Float(v) => format!("{v}"),
908 Token::Str(t) => format!("\"{}\"", t.replace('"', "\\\"")),
911 Token::Name(n) | Token::Label(n) => n.clone(),
912 Token::Arg(k) => format!("${k}"),
913 Token::ArgCount => "$+".into(),
914 Token::Dollar => "$".into(),
915 Token::Backslash => "\\".into(),
916 Token::Newline => ";".into(),
917 Token::DotPS => ".PS".into(),
918 Token::DotPE => ".PE".into(),
919 Token::Eof => String::new(),
920 Token::Lt => "<".into(),
921 Token::Gt => ">".into(),
922 Token::Le => "<=".into(),
923 Token::Ge => ">=".into(),
924 Token::EqEq => "==".into(),
925 Token::Neq => "!=".into(),
926 Token::Eq => "=".into(),
927 Token::ColonEq => ":=".into(),
928 Token::PlusEq => "+=".into(),
929 Token::MinusEq => "-=".into(),
930 Token::MultEq => "*=".into(),
931 Token::DivEq => "/=".into(),
932 Token::RemEq => "%=".into(),
933 Token::Not => "!".into(),
934 Token::AndAnd => "&&".into(),
935 Token::OrOr => "||".into(),
936 Token::Ampersand => "&".into(),
937 Token::Caret => "^".into(),
938 Token::Lparen => "(".into(),
939 Token::Rparen => ")".into(),
940 Token::LeftBrack => "[".into(),
941 Token::RightBrack => "]".into(),
942 Token::LeftBrace => "{".into(),
943 Token::RightBrace => "}".into(),
944 Token::Block => "[]".into(),
945 Token::LeftQuote => "`".into(),
946 Token::RightQuote => "'".into(),
947 Token::Comma => ",".into(),
948 Token::Colon => ":".into(),
949 Token::Dot => ".".into(),
950 Token::Plus => "+".into(),
951 Token::Minus => "-".into(),
952 Token::Mult => "*".into(),
953 Token::Div => "/".into(),
954 Token::Percent => "%".into(),
955 Token::DotX => ".x".into(),
956 Token::DotY => ".y".into(),
957 Token::Corner(c) => corner_text(*c).into(),
958 Token::Param(p) => param_text(*p).into(),
959 Token::LineType(l) => line_type_text(*l).into(),
960 Token::TextPos(p) => text_pos_text(*p).into(),
961 Token::Arrow(a) => arrow_text(*a).into(),
962 Token::Dir(d) => dir_text(*d).into(),
963 Token::Prim(p) => prim_text(*p).into(),
964 Token::Color(c) => color_text(*c).into(),
965 Token::Kw(k) => kw_text(*k).into(),
966 Token::Func1(f) => format!("{f:?}").to_ascii_lowercase(),
967 Token::Func2(f) => format!("{f:?}").to_ascii_lowercase(),
968 Token::EnvVar(e) => format!("{e:?}").to_ascii_lowercase(),
969 }
970}
971
972fn corner_text(c: Corner) -> &'static str {
973 match c {
974 Corner::N => ".n",
975 Corner::S => ".s",
976 Corner::E => ".e",
977 Corner::W => ".w",
978 Corner::Ne => ".ne",
979 Corner::Se => ".se",
980 Corner::Nw => ".nw",
981 Corner::Sw => ".sw",
982 Corner::Start => ".start",
983 Corner::End => ".end",
984 Corner::Center => ".c",
985 }
986}
987
988fn param_text(p: Param) -> &'static str {
989 match p {
990 Param::Height => ".ht",
991 Param::Width => ".wid",
992 Param::Radius => ".rad",
993 Param::Diameter => ".diam",
994 Param::Thickness => ".thick",
995 Param::Length => ".len",
996 }
997}
998
999fn line_type_text(l: LineType) -> &'static str {
1000 match l {
1001 LineType::Solid => "solid",
1002 LineType::Dotted => "dotted",
1003 LineType::Dashed => "dashed",
1004 LineType::Invis => "invis",
1005 }
1006}
1007
1008fn text_pos_text(p: TextPos) -> &'static str {
1009 match p {
1010 TextPos::Center => "center",
1011 TextPos::Ljust => "ljust",
1012 TextPos::Rjust => "rjust",
1013 TextPos::Above => "above",
1014 TextPos::Below => "below",
1015 }
1016}
1017
1018fn arrow_text(a: Arrow) -> &'static str {
1019 match a {
1020 Arrow::Left => "<-",
1021 Arrow::Right => "->",
1022 Arrow::Double => "<->",
1023 }
1024}
1025
1026fn dir_text(d: Dir) -> &'static str {
1027 match d {
1028 Dir::Up => "up",
1029 Dir::Down => "down",
1030 Dir::Right => "right",
1031 Dir::Left => "left",
1032 }
1033}
1034
1035fn prim_text(p: Prim) -> &'static str {
1036 match p {
1037 Prim::Box => "box",
1038 Prim::Circle => "circle",
1039 Prim::Ellipse => "ellipse",
1040 Prim::Arc => "arc",
1041 Prim::Line => "line",
1042 Prim::Arrow => "arrow",
1043 Prim::Move => "move",
1044 Prim::Spline => "spline",
1045 }
1046}
1047
1048fn color_text(c: Color) -> &'static str {
1049 match c {
1050 Color::Colored => "color",
1051 Color::Outlined => "outlined",
1052 Color::Shaded => "shaded",
1053 }
1054}
1055
1056fn kw_text(k: Kw) -> &'static str {
1057 match k {
1058 Kw::Ht => "ht",
1059 Kw::Wid => "wid",
1060 Kw::Rad => "rad",
1061 Kw::Diam => "diam",
1062 Kw::Thick => "thick",
1063 Kw::Thin => "thin",
1064 Kw::Scaled => "scaled",
1065 Kw::From => "from",
1066 Kw::To => "to",
1067 Kw::At => "at",
1068 Kw::With => "with",
1069 Kw::By => "by",
1070 Kw::Then => "then",
1071 Kw::Continue => "continue",
1072 Kw::Chop => "chop",
1073 Kw::Same => "same",
1074 Kw::Cw => "cw",
1075 Kw::Ccw => "ccw",
1076 Kw::Of => "of",
1077 Kw::The => "the",
1078 Kw::Way => "way",
1079 Kw::Between => "between",
1080 Kw::And => "and",
1081 Kw::Here => "Here",
1082 Kw::Last => "last",
1083 Kw::Fill => "fill",
1084 Kw::Nth => "ordinal suffix",
1085 Kw::Print => "print",
1086 Kw::Copy => "copy",
1087 Kw::Reset => "reset",
1088 Kw::Exec => "exec",
1089 Kw::Sh => "sh",
1090 Kw::Command => "command",
1091 Kw::Define => "define",
1092 Kw::Undef => "undef",
1093 Kw::Rand => "rand",
1094 Kw::If => "if",
1095 Kw::Else => "else",
1096 Kw::For => "for",
1097 Kw::Do => "do",
1098 Kw::Sprintf => "sprintf",
1099 Kw::Animate => "animate",
1100 Kw::After => "after",
1101 Kw::Delay => "delay",
1102 Kw::Repeat => "repeat",
1103 Kw::Yoyo => "yoyo",
1104 Kw::Ease => "ease",
1105 Kw::Along => "along",
1106 Kw::Stagger => "stagger",
1107 Kw::Out => "out",
1108 Kw::Scroll => "scroll",
1109 Kw::Into => "into",
1110 }
1111}
1112
1113fn token_text(t: &Token) -> String {
1114 match t {
1115 Token::Float(v) => fmt_float(*v),
1116 Token::Str(s) => format!("\"{s}\""),
1117 Token::Name(s) | Token::Label(s) => format!("`{s}`"),
1118 Token::Arg(n) => format!("${n}"),
1119 Token::ArgCount => "$+".into(),
1120 Token::Dollar => "$".into(),
1121 Token::Backslash => "\\".into(),
1122 Token::Newline => "end of line".into(),
1123 Token::DotPS => ".PS".into(),
1124 Token::DotPE => ".PE".into(),
1125 Token::Eof => "end of input".into(),
1126 Token::Lt => "<".into(),
1127 Token::Lparen => "(".into(),
1128 Token::Rparen => ")".into(),
1129 Token::Mult => "*".into(),
1130 Token::Plus => "+".into(),
1131 Token::Minus => "-".into(),
1132 Token::Div => "/".into(),
1133 Token::Percent => "%".into(),
1134 Token::Caret => "^".into(),
1135 Token::Not => "!".into(),
1136 Token::AndAnd => "&&".into(),
1137 Token::OrOr => "||".into(),
1138 Token::Ampersand => "&".into(),
1139 Token::Comma => ",".into(),
1140 Token::Colon => ":".into(),
1141 Token::LeftBrack => "[".into(),
1142 Token::RightBrack => "]".into(),
1143 Token::LeftBrace => "{".into(),
1144 Token::RightBrace => "}".into(),
1145 Token::Dot => ".".into(),
1146 Token::Block => "[]".into(),
1147 Token::LeftQuote => "`".into(),
1148 Token::RightQuote => "'".into(),
1149 Token::Eq => "=".into(),
1150 Token::ColonEq => ":=".into(),
1151 Token::PlusEq => "+=".into(),
1152 Token::MinusEq => "-=".into(),
1153 Token::MultEq => "*=".into(),
1154 Token::DivEq => "/=".into(),
1155 Token::RemEq => "%=".into(),
1156 Token::EqEq => "==".into(),
1157 Token::Neq => "!=".into(),
1158 Token::Ge => ">=".into(),
1159 Token::Le => "<=".into(),
1160 Token::Gt => ">".into(),
1161 Token::DotX => ".x".into(),
1162 Token::DotY => ".y".into(),
1163 Token::Kw(k) => kw_text(*k).into(),
1164 Token::Corner(c) => corner_text(*c).into(),
1165 Token::Param(p) => format!(".{}", param_text(*p)),
1166 Token::Func1(f) => format!("{f:?}").to_ascii_lowercase(),
1167 Token::Func2(f) => format!("{f:?}").to_ascii_lowercase(),
1168 Token::LineType(l) => line_type_text(*l).into(),
1169 Token::TextPos(p) => text_pos_text(*p).into(),
1170 Token::Arrow(a) => arrow_text(*a).into(),
1171 Token::Dir(d) => dir_text(*d).into(),
1172 Token::Prim(p) => prim_text(*p).into(),
1173 Token::Color(c) => color_text(*c).into(),
1174 Token::EnvVar(e) => format!("{e:?}").to_ascii_lowercase(),
1175 }
1176}
1177
1178fn fmt_float(v: f64) -> String {
1179 let mut s = v.to_string();
1180 if s.ends_with(".0") {
1181 s.truncate(s.len() - 2);
1182 }
1183 s
1184}
1185
1186fn suggest_object(word: &str) -> Option<&'static str> {
1187 const WORDS: &[&str] = &[
1188 "arc", "arrow", "box", "brace", "circle", "dot", "ellipse", "line", "move", "spline",
1189 ];
1190 crate::diagnostic::closest(word, WORDS)
1191}
1192
1193fn include_file(
1197 includes: &IncludeCtx,
1198 fname: &str,
1199 macros: &mut HashMap<String, Vec<Spanned>>,
1200 depth: usize,
1201 span: Span,
1202) -> Result<Vec<Spanned>, ParseError> {
1203 let mkerr = |msg: String| ParseError::new(msg, span.clone());
1204 let denied = |why: &str| {
1205 ParseError::new(format!("copy \"{fname}\": {why}"), span.clone())
1206 .with_kind("include_denied")
1207 };
1208 if fname == "circuits" {
1215 if macros.contains_key("__resistor") {
1216 return Ok(Vec::new());
1217 }
1218 let toks = lex_named(crate::CIRCUITS, "circuits")?;
1219 let mut expanded = expand(&toks, macros, depth + 1, includes)?;
1220 if matches!(expanded.last().map(|s| &s.tok), Some(Token::Eof)) {
1221 expanded.pop();
1222 }
1223 return Ok(expanded);
1224 }
1225 if includes.policy == IncludePolicy::Deny {
1226 return Err(denied(
1227 "filesystem includes are disabled by the include policy",
1228 ));
1229 }
1230 let p = Path::new(fname);
1231 if p.is_absolute() && includes.policy == IncludePolicy::SandboxedToBase {
1232 return Err(denied(
1233 "absolute paths are not allowed by the include policy",
1234 ));
1235 }
1236 let path = if p.is_absolute() {
1237 p.to_path_buf()
1238 } else {
1239 match includes.dir.as_deref() {
1240 Some(b) => b.join(p),
1241 None => {
1242 return Err(mkerr(format!(
1243 "copy \"{fname}\": file includes require a file path (unavailable here)"
1244 )));
1245 }
1246 }
1247 };
1248 if includes.policy == IncludePolicy::SandboxedToBase {
1249 let inside = includes
1254 .fence
1255 .as_deref()
1256 .is_some_and(|fence| std::fs::canonicalize(&path).is_ok_and(|c| c.starts_with(fence)));
1257 if !inside {
1258 return Err(denied("path resolves outside the include base directory"));
1259 }
1260 }
1261 let content =
1262 std::fs::read_to_string(&path).map_err(|e| mkerr(format!("copy \"{fname}\": {e}")))?;
1263 let toks = lex_named(&content, fname)?;
1264 let inc_base = path.parent().map(|d| d.to_path_buf());
1265 let inc_ctx = includes.child(inc_base);
1266 let mut expanded = expand(&toks, macros, depth + 1, &inc_ctx)?;
1267 if matches!(expanded.last().map(|s| &s.tok), Some(Token::Eof)) {
1268 expanded.pop();
1269 }
1270 Ok(expanded)
1271}
1272
1273fn find_kw_depth0(toks: &[Spanned], start: usize, kw: Kw) -> Option<usize> {
1275 let mut depth = 0i32;
1276 for (off, s) in toks[start..].iter().enumerate() {
1277 match &s.tok {
1278 Token::Lparen | Token::LeftBrace | Token::LeftBrack => depth += 1,
1279 Token::Rparen | Token::RightBrace | Token::RightBrack => {
1280 depth -= 1;
1281 if depth < 0 {
1282 return None;
1283 }
1284 }
1285 Token::Kw(k) if *k == kw && depth == 0 => return Some(start + off),
1286 _ => {}
1287 }
1288 }
1289 None
1290}
1291
1292fn copy_braced(
1295 toks: &[Spanned],
1296 mut i: usize,
1297 out: &mut Vec<Spanned>,
1298 macros: &HashMap<String, Vec<Spanned>>,
1299) -> Result<usize, ParseError> {
1300 while matches!(toks.get(i).map(|s| &s.tok), Some(Token::Newline)) {
1301 out.push(toks[i].clone());
1302 i += 1;
1303 }
1304 if !matches!(toks.get(i).map(|s| &s.tok), Some(Token::LeftBrace)) {
1305 return Ok(i); }
1307 let mut depth = 0i32;
1308 let mut tagged_body = false;
1309 while let Some(s) = toks.get(i) {
1310 let mut s = s.clone();
1311 let outer_left_brace = depth == 0 && matches!(s.tok, Token::LeftBrace);
1312 match &s.tok {
1313 Token::LeftBrace => depth += 1,
1314 Token::RightBrace => {
1315 out.push(s);
1316 i += 1;
1317 depth -= 1;
1318 if depth == 0 {
1319 return Ok(i);
1320 }
1321 continue;
1322 }
1323 _ => {}
1324 }
1325 if depth == 1 && !outer_left_brace && !tagged_body {
1326 s = s.with_macro_frame(macros);
1327 tagged_body = true;
1328 }
1329 out.push(s);
1330 i += 1;
1331 }
1332 Err(ParseError::new("unterminated `{` body", Span::new(0, 0, 0)))
1333}
1334
1335fn read_braced_body(
1336 toks: &[Spanned],
1337 mut i: usize,
1338) -> Result<Option<(Vec<Spanned>, usize)>, ParseError> {
1339 while matches!(toks.get(i).map(|s| &s.tok), Some(Token::Newline)) {
1340 i += 1;
1341 }
1342 if !matches!(toks.get(i).map(|s| &s.tok), Some(Token::LeftBrace)) {
1343 return Ok(None);
1344 }
1345 i += 1;
1346 let start = i;
1347 let mut depth = 1i32;
1348 while let Some(s) = toks.get(i) {
1349 match &s.tok {
1350 Token::LeftBrace => depth += 1,
1351 Token::RightBrace => {
1352 depth -= 1;
1353 if depth == 0 {
1354 return Ok(Some((toks[start..i].to_vec(), i + 1)));
1355 }
1356 }
1357 _ => {}
1358 }
1359 i += 1;
1360 }
1361 Err(ParseError::new("unterminated `{` body", Span::new(0, 0, 0)))
1362}
1363
1364fn static_truth(toks: &[Spanned]) -> Option<bool> {
1365 let toks = trim_trailing_eof(toks);
1366 match toks {
1367 [
1368 Spanned {
1369 tok: Token::Float(v),
1370 ..
1371 },
1372 ] => Some(*v != 0.0),
1373 [
1374 Spanned {
1375 tok: Token::Not, ..
1376 },
1377 Spanned {
1378 tok: Token::Float(v),
1379 ..
1380 },
1381 ] => Some(*v == 0.0),
1382 [a, op, b] => match op.tok {
1383 Token::EqEq | Token::Neq => {
1384 if let (Some(lhs), Some(rhs)) = (static_string(a), static_string(b)) {
1385 return Some(if matches!(op.tok, Token::EqEq) {
1386 lhs == rhs
1387 } else {
1388 lhs != rhs
1389 });
1390 }
1391 if let (Some(lhs), Some(rhs)) = (static_number(a), static_number(b)) {
1392 return Some(if matches!(op.tok, Token::EqEq) {
1393 (lhs - rhs).abs() < f64::EPSILON
1394 } else {
1395 (lhs - rhs).abs() >= f64::EPSILON
1396 });
1397 }
1398 None
1399 }
1400 _ => None,
1401 },
1402 [a, op, b, op2, c] if matches!(op2.tok, Token::Plus) => {
1403 let lhs = static_string(a)?;
1404 let mut rhs = static_string(b)?;
1405 rhs.push_str(&static_string(c)?);
1406 match op.tok {
1407 Token::EqEq => Some(lhs == rhs),
1408 Token::Neq => Some(lhs != rhs),
1409 _ => None,
1410 }
1411 }
1412 _ => None,
1413 }
1414}
1415
1416fn trim_trailing_eof(toks: &[Spanned]) -> &[Spanned] {
1417 if matches!(toks.last().map(|s| &s.tok), Some(Token::Eof)) {
1418 &toks[..toks.len() - 1]
1419 } else {
1420 toks
1421 }
1422}
1423
1424fn static_string(s: &Spanned) -> Option<String> {
1425 match &s.tok {
1426 Token::Str(v) => Some(v.clone()),
1427 _ => None,
1428 }
1429}
1430
1431fn static_number(s: &Spanned) -> Option<f64> {
1432 match &s.tok {
1433 Token::Float(v) => Some(*v),
1434 Token::Name(n) | Token::Label(n) => dpic_backend_constant(n),
1435 _ => None,
1436 }
1437}
1438
1439fn dpic_backend_constant(name: &str) -> Option<f64> {
1440 match name {
1443 "optMFpic" => Some(1.0),
1444 "optMpost" => Some(2.0),
1445 "optPDF" => Some(3.0),
1446 "optPGF" => Some(4.0),
1447 "optPict2e" => Some(5.0),
1448 "optPS" => Some(6.0),
1449 "optPSfrag" => Some(7.0),
1450 "optPSTricks" => Some(8.0),
1451 "optSVG" | "dpicopt" => Some(9.0),
1452 "optTeX" => Some(10.0),
1453 "opttTeX" => Some(11.0),
1454 "optxfig" => Some(12.0),
1455 _ => None,
1456 }
1457}
1458
1459type PResult<T> = Result<T, ParseError>;
1460
1461fn is_assign_op(t: &Token) -> bool {
1462 matches!(
1463 t,
1464 Token::Eq
1465 | Token::ColonEq
1466 | Token::PlusEq
1467 | Token::MinusEq
1468 | Token::MultEq
1469 | Token::DivEq
1470 | Token::RemEq
1471 )
1472}
1473
1474struct Parser {
1475 toks: Vec<Spanned>,
1476 idx: usize,
1477 depth: u32,
1478}
1479
1480const MAX_PARSE_DEPTH: u32 = 128;
1489const MAX_EXPR_CHAIN_OPS: u32 = MAX_PARSE_DEPTH;
1492
1493impl Parser {
1494 fn new(toks: Vec<Spanned>) -> Self {
1495 Parser {
1496 toks,
1497 idx: 0,
1498 depth: 0,
1499 }
1500 }
1501
1502 fn descend<T>(&mut self, f: impl FnOnce(&mut Self) -> PResult<T>) -> PResult<T> {
1506 self.depth += 1;
1507 if self.depth > MAX_PARSE_DEPTH {
1508 self.depth -= 1;
1509 return self.err("expression or block nested too deeply".to_string());
1510 }
1511 let r = f(self);
1512 self.depth -= 1;
1513 r
1514 }
1515
1516 fn check_expr_chain(&self, ops: u32) -> PResult<()> {
1517 if ops > MAX_EXPR_CHAIN_OPS {
1518 return self.err(format!(
1519 "expression has too many chained operators (maximum {MAX_EXPR_CHAIN_OPS})"
1520 ));
1521 }
1522 Ok(())
1523 }
1524
1525 fn cur(&self) -> &Token {
1528 &self.toks[self.idx].tok
1529 }
1530 fn cur_span(&self) -> Span {
1531 self.toks[self.idx].span()
1532 }
1533 fn peek(&self, n: usize) -> &Token {
1534 self.toks
1535 .get(self.idx + n)
1536 .map(|s| &s.tok)
1537 .unwrap_or(&Token::Eof)
1538 }
1539 fn at(&self, t: &Token) -> bool {
1540 self.cur() == t
1541 }
1542 fn bump(&mut self) -> Token {
1543 let t = self.toks[self.idx].tok.clone();
1544 if self.idx + 1 < self.toks.len() {
1545 self.idx += 1;
1546 }
1547 t
1548 }
1549 fn eat(&mut self, t: &Token) -> bool {
1550 if self.at(t) {
1551 self.bump();
1552 true
1553 } else {
1554 false
1555 }
1556 }
1557 fn expect(&mut self, t: &Token) -> PResult<()> {
1558 if self.eat(t) {
1559 Ok(())
1560 } else {
1561 self.expected_here(token_text(t))
1562 }
1563 }
1564 fn err<T>(&self, msg: impl Into<String>) -> PResult<T> {
1565 let s = &self.toks[self.idx];
1566 Err(ParseError::new(msg, s.span()))
1567 }
1568 fn expected_here<T>(&self, expected: impl Into<String>) -> PResult<T> {
1569 let s = &self.toks[self.idx];
1570 Err(ParseError::expected(expected, token_text(&s.tok), s.span()))
1571 }
1572 fn expected_object<T>(&self) -> PResult<T> {
1573 let s = &self.toks[self.idx];
1574 let mut e = ParseError::expected("an object", token_text(&s.tok), s.span());
1575 if let Token::Name(name) | Token::Label(name) = &s.tok
1576 && let Some(hint) = suggest_object(name)
1577 {
1578 e.detail.hint = Some(format!("did you mean `{hint}`?"));
1579 }
1580 Err(e)
1581 }
1582 fn at_kw(&self, k: Kw) -> bool {
1583 matches!(self.cur(), Token::Kw(x) if *x == k)
1584 }
1585 fn eat_kw(&mut self, k: Kw) -> bool {
1586 if self.at_kw(k) {
1587 self.bump();
1588 true
1589 } else {
1590 false
1591 }
1592 }
1593 fn skip_newlines(&mut self) {
1594 while self.at(&Token::Newline) {
1595 self.bump();
1596 }
1597 }
1598
1599 fn parse_picture(&mut self) -> PResult<Picture> {
1602 let (mut width, mut height) = (None, None);
1606 let mut seen_ps = false;
1607 let mut stmts = Vec::new();
1608 loop {
1609 self.skip_newlines();
1610 match self.cur() {
1611 Token::Eof => break,
1612 Token::DotPS => {
1613 self.bump();
1614 if !seen_ps && self.starts_scalar() {
1615 width = Some(self.parse_expr()?);
1616 if self.starts_scalar() {
1617 height = Some(self.parse_expr()?);
1618 }
1619 }
1620 seen_ps = true;
1621 while !self.at(&Token::Newline) && !self.at(&Token::Eof) {
1622 self.bump();
1623 }
1624 continue;
1625 }
1626 Token::DotPE => {
1627 self.bump();
1628 continue;
1629 }
1630 _ => {}
1631 }
1632 stmts.push(self.parse_element()?);
1633 if !self.at(&Token::Newline)
1634 && !self.at(&Token::Eof)
1635 && !self.at(&Token::DotPE)
1636 && !self.at(&Token::DotPS)
1637 {
1638 return self.err(format!("unexpected {:?} after statement", self.cur()));
1639 }
1640 }
1641 Ok(Picture {
1642 width,
1643 height,
1644 stmts,
1645 macros: HashMap::new(),
1646 includes: IncludeCtx::default(),
1647 })
1648 }
1649
1650 fn parse_elementlist(&mut self, terminators: &[Token]) -> PResult<Vec<Stmt>> {
1652 let mut stmts = Vec::new();
1653 loop {
1654 self.skip_newlines();
1655 if self.at(&Token::Eof) || terminators.iter().any(|t| self.at(t)) {
1656 break;
1657 }
1658 let s = self.parse_element()?;
1659 stmts.push(s);
1660 if !self.at(&Token::Newline)
1662 && !self.at(&Token::Eof)
1663 && !terminators.iter().any(|t| self.at(t))
1664 {
1665 return self.err(format!("unexpected {:?} after statement", self.cur()));
1666 }
1667 }
1668 Ok(stmts)
1669 }
1670
1671 fn parse_element(&mut self) -> PResult<Stmt> {
1674 if self.at(&Token::Percent) {
1678 while !self.at(&Token::Newline) && !self.at(&Token::Eof) {
1679 self.bump();
1680 }
1681 return Ok(Stmt::Print(PrintItem::Str(StringExpr::Lit(String::new()))));
1682 }
1683
1684 if self.at_kw(Kw::Animate) {
1686 if matches!(self.peek(1), Token::Kw(Kw::Scroll)) {
1689 self.bump();
1690 self.bump();
1691 return Ok(Stmt::AnimateScroll);
1692 }
1693 return Ok(Stmt::Animate(Box::new(self.parse_animate()?)));
1694 }
1695
1696 if matches!(self.cur(), Token::Name(n) if n == "class")
1700 && !is_assign_op(self.peek(1))
1701 && !matches!(self.peek(1), Token::LeftBrack)
1702 {
1703 self.bump();
1704 let target = self.parse_place()?;
1705 let class = self.parse_stringexpr()?;
1706 return Ok(Stmt::Class { target, class });
1707 }
1708
1709 if matches!(self.cur(), Token::Name(n) if n == "link")
1713 && !is_assign_op(self.peek(1))
1714 && !matches!(self.peek(1), Token::LeftBrack)
1715 {
1716 self.bump();
1717 let target = self.parse_place()?;
1718 let url = self.parse_stringexpr()?;
1719 return Ok(Stmt::Link { target, url });
1720 }
1721
1722 if matches!(self.cur(), Token::Name(n) if n == "draggable")
1726 && !is_assign_op(self.peek(1))
1727 && !matches!(self.peek(1), Token::LeftBrack)
1728 {
1729 return Ok(Stmt::Draggable(self.parse_draggable()?));
1730 }
1731
1732 if matches!(self.cur(), Token::Name(n) if n == "canvas")
1737 && matches!(self.peek(1), Token::Kw(Kw::From))
1738 {
1739 self.bump();
1740 self.bump();
1741 let from = self.parse_position()?;
1742 self.expect(&Token::Kw(Kw::To))?;
1743 let to = self.parse_position()?;
1744 return Ok(Stmt::Canvas { from, to });
1745 }
1746
1747 match self.cur() {
1749 Token::Kw(Kw::If) => return self.parse_if(),
1750 Token::Kw(Kw::For) => return self.parse_for(),
1751 Token::Kw(Kw::Print) => return self.parse_print(),
1752 Token::Kw(Kw::Exec) => return self.parse_exec(),
1753 Token::Kw(Kw::Reset) => return self.parse_reset(),
1754 _ => {}
1755 }
1756
1757 if let Token::Kw(k) = self.cur() {
1760 match k {
1761 Kw::Define | Kw::Undef => {
1762 return self.err("only the `define name { body }` macro form is supported");
1763 }
1764 Kw::Command | Kw::Sh => {
1770 self.bump();
1771 return Ok(Stmt::Group(Vec::new()));
1772 }
1773 Kw::Copy => {
1774 return self.err("`copy` is not supported yet (planned milestone)");
1775 }
1776 _ => {}
1777 }
1778 }
1779
1780 if self.eat(&Token::LeftBrace) {
1784 let stmts = self.descend(|p| p.parse_elementlist(&[Token::RightBrace]))?;
1785 self.expect(&Token::RightBrace)?;
1786 return Ok(Stmt::Group(stmts));
1787 }
1788
1789 if matches!(self.cur(), Token::Label(_)) && self.label_colon_ahead() {
1791 let label = self.parse_label()?;
1792 self.expect(&Token::Colon)?;
1793 if self.at_object_start() {
1794 let object = self.parse_object()?;
1795 return Ok(Stmt::Object {
1796 label: Some(label),
1797 object,
1798 });
1799 } else {
1800 let pos = self.parse_label_position()?;
1801 return Ok(Stmt::Place { label, pos });
1802 }
1803 }
1804
1805 if self.at_assignment_start() {
1807 return Ok(Stmt::Assign(self.parse_assignlist()?));
1808 }
1809
1810 if let Token::Dir(d) = self.cur() {
1812 let d = *d;
1813 if matches!(self.peek(1), Token::Newline | Token::Eof) {
1815 self.bump();
1816 return Ok(Stmt::Direction(d));
1817 }
1818 }
1819
1820 let object = self.parse_object()?;
1822 Ok(Stmt::Object {
1823 label: None,
1824 object,
1825 })
1826 }
1827
1828 fn parse_if(&mut self) -> PResult<Stmt> {
1829 self.expect_kw(Kw::If)?;
1830 let cond = self.parse_expr()?;
1831 self.expect_kw(Kw::Then)?;
1832 let then_body = self.capture_braced()?;
1833 let save = self.idx;
1836 self.skip_newlines();
1837 let else_body = if self.eat_kw(Kw::Else) {
1838 Some(self.capture_braced()?)
1839 } else {
1840 self.idx = save;
1841 None
1842 };
1843 Ok(Stmt::If {
1844 cond,
1845 then_body,
1846 else_body,
1847 })
1848 }
1849
1850 fn parse_for(&mut self) -> PResult<Stmt> {
1851 self.expect_kw(Kw::For)?;
1852 let var = match self.bump() {
1853 Token::Name(s) | Token::Label(s) => s,
1854 other => return self.err(format!("expected loop variable, found {other:?}")),
1855 };
1856 let subscript = if self.eat(&Token::LeftBrack) {
1857 let e = self.parse_subscript()?;
1858 self.expect(&Token::RightBrack)?;
1859 Some(e)
1860 } else {
1861 None
1862 };
1863 match self.bump() {
1864 Token::Eq | Token::ColonEq => {}
1865 other => return self.err(format!("expected `=` in for, found {other:?}")),
1866 }
1867 let from = self.parse_expr()?;
1868 self.expect_kw(Kw::To)?;
1869 let to = self.parse_expr()?;
1870 let mut by = Expr::Num(1.0);
1871 let mut mult = false;
1872 if self.eat_kw(Kw::By) {
1873 mult = self.eat(&Token::Mult);
1874 by = self.parse_expr()?;
1875 }
1876 self.expect_kw(Kw::Do)?;
1877 let body = self.capture_braced()?;
1878 Ok(Stmt::For {
1879 var,
1880 subscript,
1881 from,
1882 to,
1883 by,
1884 mult,
1885 body,
1886 })
1887 }
1888
1889 fn capture_braced(&mut self) -> PResult<Body> {
1892 self.skip_newlines();
1893 self.expect(&Token::LeftBrace)?;
1894 let start = self.idx;
1895 let mut depth = 1i32;
1896 loop {
1897 match self.cur() {
1898 Token::LeftBrace => depth += 1,
1899 Token::RightBrace => {
1900 depth -= 1;
1901 if depth == 0 {
1902 break;
1903 }
1904 }
1905 Token::Eof => return self.err("unterminated `{` body"),
1906 _ => {}
1907 }
1908 self.bump();
1909 }
1910 let body = self.toks[start..self.idx].to_vec();
1911 self.expect(&Token::RightBrace)?;
1912 Ok(body)
1913 }
1914
1915 fn parse_print(&mut self) -> PResult<Stmt> {
1916 self.expect_kw(Kw::Print)?;
1917 let item = if self.at_string_start() {
1918 PrintItem::Str(self.parse_stringexpr()?)
1919 } else {
1920 PrintItem::Expr(self.parse_expr()?)
1921 };
1922 Ok(Stmt::Print(item))
1923 }
1924
1925 fn parse_exec(&mut self) -> PResult<Stmt> {
1926 let arg_frame = self.toks[self.idx].arg_frame.clone();
1927 self.expect_kw(Kw::Exec)?;
1928 let command = self.parse_stringexpr()?;
1929 Ok(Stmt::Exec { command, arg_frame })
1930 }
1931
1932 fn parse_reset(&mut self) -> PResult<Stmt> {
1933 self.expect_kw(Kw::Reset)?;
1934 let mut list = Vec::new();
1935 if let Token::EnvVar(v) = self.cur() {
1936 list.push(*v);
1937 self.bump();
1938 while self.eat(&Token::Comma) {
1939 match self.cur() {
1940 Token::EnvVar(v) => {
1941 list.push(*v);
1942 self.bump();
1943 }
1944 other => {
1945 return self.err(format!("expected environment variable, found {other:?}"));
1946 }
1947 }
1948 }
1949 }
1950 Ok(Stmt::Reset(list))
1951 }
1952
1953 fn at_string_start(&self) -> bool {
1954 self.token_starts_string_at(0)
1955 }
1956
1957 fn parse_animate(&mut self) -> PResult<Animate> {
1958 self.expect_kw(Kw::Animate)?;
1959 let target = self.parse_place()?;
1960 self.expect_kw(Kw::With)?;
1961 let effect_span = Some(self.cur_span());
1962 let effect = self.parse_stringexpr()?;
1963 let is_draw = matches!(&effect, StringExpr::Lit(s) if s == "draw");
1968 let mut duration = None;
1969 let mut timing = Timing::Sequential;
1970 let mut delay = None;
1971 let mut repeat = None;
1972 let mut yoyo = false;
1973 let mut ease = None;
1974 let mut along = None;
1975 let mut color = None;
1976 let mut stagger = None;
1977 let mut out = false;
1978 let mut slide_from = None;
1979 let mut morph_into = None;
1980 let mut type_unit = None;
1981 let mut scramble_chars = None;
1982 let mut wiggles = None;
1983 let mut draw_from = None;
1984 let mut draw_to = None;
1985 loop {
1986 if self.eat_kw(Kw::For) {
1987 duration = Some(self.parse_expr()?);
1988 } else if self.eat_kw(Kw::At) {
1989 timing = Timing::At(self.parse_expr()?);
1990 } else if self.eat_kw(Kw::After) {
1991 timing = Timing::After(self.parse_place()?);
1992 } else if self.eat_kw(Kw::Delay) {
1993 delay = Some(self.parse_expr()?);
1994 } else if self.eat_kw(Kw::Repeat) {
1995 repeat = Some(self.parse_expr()?);
1996 } else if self.eat_kw(Kw::Yoyo) {
1997 yoyo = true;
1998 } else if self.eat_kw(Kw::Ease) {
1999 ease = Some(self.parse_stringexpr()?);
2000 } else if self.eat_kw(Kw::Along) {
2001 along = Some(self.parse_place()?);
2002 } else if self.eat_kw(Kw::To) {
2003 if is_draw {
2004 draw_to = Some(self.parse_draw_amount()?);
2005 } else {
2006 color = Some(self.parse_color_like()?);
2007 }
2008 } else if self.eat_kw(Kw::Stagger) {
2009 stagger = Some(self.parse_expr()?);
2010 } else if self.eat_kw(Kw::Out) {
2011 out = true;
2012 } else if self.eat_kw(Kw::From) {
2013 if is_draw {
2014 draw_from = Some(self.parse_draw_amount()?);
2015 } else {
2016 slide_from = Some(self.parse_dir()?);
2017 }
2018 } else if self.eat_kw(Kw::Into) {
2019 morph_into = Some(self.parse_place()?);
2020 } else if self.eat_kw(Kw::By) {
2021 if self.at_string_start() {
2024 scramble_chars = Some(self.parse_stringexpr()?);
2025 } else {
2026 type_unit = Some(self.parse_type_unit()?);
2027 }
2028 } else if matches!(self.cur(), Token::Name(n) if n == "wiggles") {
2029 self.bump();
2032 wiggles = Some(self.parse_expr()?);
2033 } else {
2034 break;
2035 }
2036 }
2037 Ok(Animate {
2038 target,
2039 effect,
2040 effect_span,
2041 duration,
2042 timing,
2043 delay,
2044 repeat,
2045 yoyo,
2046 ease,
2047 along,
2048 color,
2049 stagger,
2050 out,
2051 slide_from,
2052 morph_into,
2053 type_unit,
2054 scramble_chars,
2055 wiggles,
2056 draw_from,
2057 draw_to,
2058 })
2059 }
2060
2061 fn parse_draw_amount(&mut self) -> PResult<Expr> {
2065 let mut e = self.parse_unary()?;
2066 loop {
2067 let op = match self.cur() {
2068 Token::Mult => BinOp::Mul,
2069 Token::Div => BinOp::Div,
2070 _ => break,
2071 };
2072 self.bump();
2073 let r = self.parse_unary()?;
2074 e = Expr::Bin(op, Box::new(e), Box::new(r));
2075 }
2076 if matches!(self.cur(), Token::Percent) {
2077 self.bump();
2078 e = Expr::Bin(BinOp::Div, Box::new(e), Box::new(Expr::Num(100.0)));
2079 }
2080 Ok(e)
2081 }
2082
2083 fn parse_type_unit(&mut self) -> PResult<TypeUnit> {
2085 match self.cur() {
2086 Token::Name(n) if n == "word" => {
2087 self.bump();
2088 Ok(TypeUnit::Word)
2089 }
2090 Token::Name(n) if n == "char" => {
2091 self.bump();
2092 Ok(TypeUnit::Char)
2093 }
2094 _ => self.err("expected `word` or `char` after `by`"),
2095 }
2096 }
2097
2098 fn parse_draggable(&mut self) -> PResult<Draggable> {
2100 self.bump(); let target = self.parse_place()?;
2102 let mut inertia = false;
2103 let mut bounds = None;
2104 let mut axis = None;
2105 loop {
2106 match self.cur() {
2107 Token::Name(n) if n == "inertia" => {
2108 self.bump();
2109 inertia = true;
2110 }
2111 Token::Name(n) if n == "bounds" => {
2112 self.bump();
2113 bounds = Some(self.parse_place()?);
2114 }
2115 Token::Name(n) if n == "x" => {
2116 self.bump();
2117 axis = Some(DragAxis::X);
2118 }
2119 Token::Name(n) if n == "y" => {
2120 self.bump();
2121 axis = Some(DragAxis::Y);
2122 }
2123 _ => break,
2124 }
2125 }
2126 Ok(Draggable {
2127 target,
2128 inertia,
2129 bounds,
2130 axis,
2131 })
2132 }
2133
2134 fn parse_dir(&mut self) -> PResult<Dir> {
2136 if let Token::Dir(d) = self.cur() {
2137 let d = *d;
2138 self.bump();
2139 Ok(d)
2140 } else {
2141 self.expected_here("a direction (up, down, left, or right)")
2142 }
2143 }
2144
2145 fn label_colon_ahead(&self) -> bool {
2147 match self.peek(1) {
2148 Token::Colon => true,
2149 Token::LeftBrack => {
2150 let mut depth = 0;
2152 let mut i = self.idx + 1;
2153 while i < self.toks.len() {
2154 match &self.toks[i].tok {
2155 Token::LeftBrack => depth += 1,
2156 Token::RightBrack => {
2157 depth -= 1;
2158 if depth == 0 {
2159 return matches!(
2160 self.toks.get(i + 1).map(|s| &s.tok),
2161 Some(Token::Colon)
2162 );
2163 }
2164 }
2165 Token::Newline | Token::Eof => return false,
2166 _ => {}
2167 }
2168 i += 1;
2169 }
2170 false
2171 }
2172 _ => false,
2173 }
2174 }
2175
2176 fn at_object_start(&self) -> bool {
2177 matches!(
2178 self.cur(),
2179 Token::Prim(_)
2180 | Token::LeftBrack
2181 | Token::Block
2182 | Token::Str(_)
2183 | Token::Arg(_)
2184 | Token::Kw(Kw::Sprintf)
2185 ) || matches!(self.cur(), Token::Name(n) if n == "brace" || n == "dot")
2186 }
2187
2188 fn at_assignment_start(&self) -> bool {
2189 match self.cur() {
2190 Token::Name(_) | Token::Label(_) => self.assignment_op_after_var_ref(),
2191 Token::EnvVar(_) => is_assign_op(self.peek(1)),
2192 _ => false,
2193 }
2194 }
2195
2196 fn assignment_op_after_var_ref(&self) -> bool {
2197 let mut i = self.idx + 1;
2198 if matches!(self.toks.get(i).map(|s| &s.tok), Some(Token::LeftBrack)) {
2199 i += 1;
2200 let mut depth = 1i32;
2201 while let Some(tok) = self.toks.get(i).map(|s| &s.tok) {
2202 match tok {
2203 Token::LeftBrack => depth += 1,
2204 Token::RightBrack => {
2205 depth -= 1;
2206 if depth == 0 {
2207 i += 1;
2208 break;
2209 }
2210 }
2211 Token::Eof | Token::Newline if depth > 0 => return false,
2212 _ => {}
2213 }
2214 i += 1;
2215 }
2216 if depth != 0 {
2217 return false;
2218 }
2219 }
2220 self.toks.get(i).is_some_and(|s| is_assign_op(&s.tok))
2221 }
2222
2223 fn parse_label(&mut self) -> PResult<Label> {
2224 let name = match self.bump() {
2225 Token::Label(s) => s,
2226 other => return self.err(format!("expected label, found {other:?}")),
2227 };
2228 let subscript = if self.eat(&Token::LeftBrack) {
2229 let e = self.parse_subscript()?;
2230 self.expect(&Token::RightBrack)?;
2231 Some(e)
2232 } else {
2233 None
2234 };
2235 Ok(Label { name, subscript })
2236 }
2237
2238 fn parse_assignlist(&mut self) -> PResult<Vec<Assignment>> {
2239 let mut list = vec![self.parse_assignment()?];
2240 while self.eat(&Token::Comma) {
2241 list.push(self.parse_assignment()?);
2242 }
2243 Ok(list)
2244 }
2245
2246 fn parse_assignment(&mut self) -> PResult<Assignment> {
2247 let target = match self.cur().clone() {
2248 Token::Name(name) | Token::Label(name) => {
2249 self.bump();
2250 let sub = if self.eat(&Token::LeftBrack) {
2251 let e = self.parse_subscript()?;
2252 self.expect(&Token::RightBrack)?;
2253 Some(e)
2254 } else {
2255 None
2256 };
2257 AssignTarget::Var(name, sub)
2258 }
2259 Token::EnvVar(v) => {
2260 self.bump();
2261 AssignTarget::Env(v)
2262 }
2263 other => return self.err(format!("expected assignment target, found {other:?}")),
2264 };
2265 let op = match self.bump() {
2266 Token::Eq => AssignOp::Set,
2267 Token::ColonEq => AssignOp::ColonSet,
2268 Token::PlusEq => AssignOp::Add,
2269 Token::MinusEq => AssignOp::Sub,
2270 Token::MultEq => AssignOp::Mul,
2271 Token::DivEq => AssignOp::Div,
2272 Token::RemEq => AssignOp::Rem,
2273 other => return self.err(format!("expected assignment operator, found {other:?}")),
2274 };
2275 let value = self.parse_expr()?;
2276 Ok(Assignment { target, op, value })
2277 }
2278
2279 fn parse_subscript(&mut self) -> PResult<Expr> {
2280 let mut items = vec![self.parse_expr()?];
2281 while self.eat(&Token::Comma) {
2282 items.push(self.parse_expr()?);
2283 }
2284 if items.len() == 1 {
2285 Ok(items.pop().unwrap())
2286 } else {
2287 Ok(Expr::Index(items))
2288 }
2289 }
2290
2291 fn parse_object(&mut self) -> PResult<Object> {
2294 self.descend(Self::parse_object_inner)
2295 }
2296
2297 fn parse_object_inner(&mut self) -> PResult<Object> {
2298 let span = Some(self.cur_span());
2299 let mut attrs = Vec::new();
2300 if self.at_string_start() {
2303 attrs.push(Attr::Text(self.parse_stringexpr()?));
2304 while let Some(a) = self.parse_attr(false, false, false, false)? {
2305 attrs.push(a);
2306 }
2307 return Ok(Object {
2308 span,
2309 kind: ObjectKind::Text,
2310 attrs,
2311 });
2312 }
2313 let kind = match self.cur().clone() {
2314 Token::Prim(p) => {
2315 self.bump();
2316 ObjectKind::Primitive(p)
2317 }
2318 Token::Block => {
2319 self.bump();
2320 ObjectKind::Empty
2321 }
2322 Token::Name(n) if n == "brace" => {
2323 self.bump();
2324 ObjectKind::Brace
2325 }
2326 Token::Name(n) if n == "dot" => {
2327 self.bump();
2328 ObjectKind::Dot
2329 }
2330 Token::LeftBrack => {
2331 self.bump();
2332 let stmts = self.parse_elementlist(&[Token::RightBrack])?;
2333 self.expect(&Token::RightBrack)?;
2334 ObjectKind::Block(stmts)
2335 }
2336 Token::Str(s) => {
2337 self.bump();
2338 attrs.push(Attr::Text(self.continue_string(StringExpr::Lit(s))?));
2339 ObjectKind::Text
2340 }
2341 Token::Kw(Kw::Continue) => {
2342 self.bump();
2343 ObjectKind::Continue
2344 }
2345 Token::Name(_) | Token::Label(_) => return self.expected_object(),
2346 _ => return self.expected_here("an object"),
2347 };
2348 if matches!(kind, ObjectKind::Primitive(Prim::Spline)) && self.spline_tension_ahead() {
2353 attrs.push(Attr::SplineTension(self.parse_expr()?));
2354 }
2355 let allow_fit = matches!(
2356 kind,
2357 ObjectKind::Primitive(Prim::Box | Prim::Circle | Prim::Ellipse)
2358 );
2359 let allow_brace = matches!(kind, ObjectKind::Brace);
2360 let allow_dot_fill = matches!(kind, ObjectKind::Dot);
2361 let allow_hatch = matches!(
2362 kind,
2363 ObjectKind::Primitive(
2364 Prim::Box
2365 | Prim::Circle
2366 | Prim::Ellipse
2367 | Prim::Line
2368 | Prim::Arrow
2369 | Prim::Spline
2370 | Prim::Arc
2371 )
2372 );
2373 let allow_close = matches!(kind, ObjectKind::Primitive(Prim::Line));
2374 while let Some(a) = self.parse_attr(
2375 allow_fit,
2376 allow_brace,
2377 allow_hatch || allow_dot_fill,
2378 allow_close,
2379 )? {
2380 attrs.push(a);
2381 }
2382 Ok(Object { kind, attrs, span })
2383 }
2384
2385 fn spline_tension_ahead(&self) -> bool {
2389 matches!(
2390 self.cur(),
2391 Token::Float(_)
2392 | Token::Lparen
2393 | Token::EnvVar(_)
2394 | Token::Func1(_)
2395 | Token::Func2(_)
2396 | Token::Name(_)
2397 | Token::Minus
2398 | Token::Plus
2399 | Token::Kw(Kw::Rand)
2400 )
2401 }
2402
2403 fn parse_attr(
2404 &mut self,
2405 allow_fit: bool,
2406 allow_brace: bool,
2407 allow_hatch: bool,
2408 allow_close: bool,
2409 ) -> PResult<Option<Attr>> {
2410 if self.at_string_start() {
2412 return Ok(Some(Attr::Text(self.parse_stringexpr()?)));
2413 }
2414 let attr = match self.cur().clone() {
2415 Token::Kw(Kw::Ht) => {
2416 self.bump();
2417 Attr::Dim(DimKind::Ht, self.parse_expr()?)
2418 }
2419 Token::Kw(Kw::Wid) => {
2420 self.bump();
2421 Attr::Dim(DimKind::Wid, self.parse_expr()?)
2422 }
2423 Token::Kw(Kw::Rad) => {
2424 self.bump();
2425 Attr::Dim(DimKind::Rad, self.parse_expr()?)
2426 }
2427 Token::Kw(Kw::Diam) => {
2428 self.bump();
2429 Attr::Dim(DimKind::Diam, self.parse_expr()?)
2430 }
2431 Token::Kw(Kw::Thick) => {
2432 self.bump();
2433 Attr::Dim(DimKind::Thick, self.parse_expr()?)
2434 }
2435 Token::Kw(Kw::Thin) => {
2436 self.bump();
2437 Attr::Thin
2438 }
2439 Token::Kw(Kw::Scaled) => {
2440 self.bump();
2441 Attr::Dim(DimKind::Scaled, self.parse_expr()?)
2442 }
2443 Token::Dir(d) => {
2444 self.bump();
2445 Attr::Direction(
2446 d,
2447 self.opt_attr_expr(allow_fit, allow_brace, allow_hatch, allow_close)?,
2448 )
2449 }
2450 Token::LineType(lt) => {
2451 self.bump();
2452 Attr::LineStyle(
2453 lt,
2454 self.opt_attr_expr(allow_fit, allow_brace, allow_hatch, allow_close)?,
2455 )
2456 }
2457 Token::Kw(Kw::Chop) => {
2458 self.bump();
2459 Attr::Chop(self.opt_attr_expr(allow_fit, allow_brace, allow_hatch, allow_close)?)
2460 }
2461 Token::Kw(Kw::Fill) => {
2462 self.bump();
2463 Attr::Fill(self.opt_attr_expr(allow_fit, allow_brace, allow_hatch, allow_close)?)
2464 }
2465 Token::Arrow(a) => {
2466 self.bump();
2467 Attr::Arrowhead(
2468 a,
2469 self.opt_attr_expr(allow_fit, allow_brace, allow_hatch, allow_close)?,
2470 )
2471 }
2472 Token::Kw(Kw::Then) => {
2473 self.bump();
2474 Attr::Then
2475 }
2476 Token::Kw(Kw::Cw) => {
2477 self.bump();
2478 Attr::Cw
2479 }
2480 Token::Kw(Kw::Ccw) => {
2481 self.bump();
2482 Attr::Ccw
2483 }
2484 Token::Kw(Kw::Same) => {
2485 self.bump();
2486 Attr::Same
2487 }
2488 Token::Kw(Kw::Continue) => {
2489 self.bump();
2490 Attr::Continue
2491 }
2492 Token::Kw(Kw::From) => {
2493 self.bump();
2494 Attr::From(self.parse_position()?)
2495 }
2496 Token::Kw(Kw::To) => {
2497 self.bump();
2498 Attr::To(self.parse_position()?)
2499 }
2500 Token::Kw(Kw::At) => {
2501 self.bump();
2502 Attr::At(self.parse_position()?)
2503 }
2504 Token::Kw(Kw::By) => {
2505 self.bump();
2506 Attr::By(self.parse_position()?)
2507 }
2508 Token::Kw(Kw::With) => {
2509 self.bump();
2510 let anchor = if self.eat(&Token::Dot) {
2511 WithAnchor::Place(self.parse_place()?)
2512 } else if let Token::Corner(c) = self.cur() {
2513 let c = *c;
2514 self.bump();
2515 WithAnchor::Corner(c)
2516 } else if self.at(&Token::Lparen) {
2517 self.bump();
2518 let x = self.parse_expr()?;
2519 self.expect(&Token::Comma)?;
2520 let y = self.parse_expr()?;
2521 self.expect(&Token::Rparen)?;
2522 WithAnchor::Pair(x, y)
2523 } else {
2524 WithAnchor::Plain
2525 };
2526 self.expect_kw(Kw::At)?;
2527 Attr::With {
2528 anchor,
2529 at: self.parse_position()?,
2530 }
2531 }
2532 Token::TextPos(tp) => {
2533 self.bump();
2534 Attr::TextPos(tp)
2535 }
2536 Token::Color(c) => {
2537 self.bump();
2538 let span = Some(self.cur_span());
2541 Attr::Color(c, self.parse_color_like()?, span)
2542 }
2543 Token::Name(n) if allow_fit && n == "fit" => {
2544 self.bump();
2545 Attr::Fit
2546 }
2547 Token::Name(n) if allow_hatch && n == "hatch" => {
2548 self.bump();
2549 Attr::Hatch(HatchKind::Single)
2550 }
2551 Token::Name(n) if allow_hatch && n == "crosshatch" => {
2552 self.bump();
2553 Attr::Hatch(HatchKind::Cross)
2554 }
2555 Token::Name(n) if allow_hatch && n == "hatchangle" => {
2556 self.bump();
2557 Attr::HatchAngle(self.parse_expr()?)
2558 }
2559 Token::Name(n) if allow_hatch && n == "hatchsep" => {
2560 self.bump();
2561 Attr::HatchSep(self.parse_expr()?)
2562 }
2563 Token::Name(n) if allow_hatch && (n == "hatchwid" || n == "hatchwidth") => {
2564 self.bump();
2565 Attr::HatchWidth(self.parse_expr()?)
2566 }
2567 Token::Name(n) if allow_hatch && n == "hatchcolor" => {
2568 self.bump();
2569 let span = Some(self.cur_span());
2570 Attr::HatchColor(self.parse_color_like()?, span)
2571 }
2572 Token::Name(n) if allow_hatch && n == "gradient" => {
2573 self.bump();
2574 let from_span = Some(self.cur_span());
2575 let from = self.parse_color_like()?;
2576 let to_span = Some(self.cur_span());
2577 let to = self.parse_color_like()?;
2578 Attr::Gradient(from, from_span, to, to_span)
2579 }
2580 Token::Name(n) if allow_hatch && n == "gradientangle" => {
2581 self.bump();
2582 Attr::GradientAngle(self.parse_expr()?)
2583 }
2584 Token::Name(n) if n == "opacity" => {
2585 self.bump();
2586 Attr::Opacity(self.parse_expr()?)
2587 }
2588 Token::Name(n) if allow_close && n == "close" => {
2589 self.bump();
2590 Attr::Close
2591 }
2592 Token::Name(n) if allow_brace && n == "bracepos" => {
2593 self.bump();
2594 Attr::BracePos(self.parse_expr()?)
2595 }
2596 Token::Name(n) if allow_brace && n == "labeloffset" => {
2597 self.bump();
2598 Attr::BraceLabelOffset(self.parse_expr()?)
2599 }
2600 Token::Name(n) if n == "bold" => {
2601 self.bump();
2602 Attr::Bold
2603 }
2604 Token::Name(n) if n == "italic" => {
2605 self.bump();
2606 Attr::Italic
2607 }
2608 Token::Name(n) if n == "mono" => {
2609 self.bump();
2610 Attr::Mono
2611 }
2612 Token::Name(n) if n == "font" => {
2613 self.bump();
2614 let s = match self.cur().clone() {
2617 Token::Name(n) | Token::Label(n) => {
2618 self.bump();
2619 StringExpr::Lit(n)
2620 }
2621 _ => self.parse_stringexpr()?,
2622 };
2623 Attr::Font(s)
2624 }
2625 Token::Name(n) if n == "fontsize" => {
2626 self.bump();
2627 Attr::FontSize(self.parse_expr()?)
2628 }
2629 Token::Name(n) if n == "rotated" => {
2630 self.bump();
2631 Attr::Rotated(self.parse_expr()?)
2632 }
2633 Token::Name(n) if n == "aligned" => {
2634 self.bump();
2635 Attr::Aligned
2636 }
2637 Token::Name(n) if n == "big" => {
2638 self.bump();
2639 Attr::Sized(true)
2640 }
2641 Token::Name(n) if n == "small" => {
2642 self.bump();
2643 Attr::Sized(false)
2644 }
2645 Token::Name(n) if n == "behind" => {
2646 self.bump();
2647 Attr::Behind(self.parse_place()?)
2648 }
2649 Token::Name(n) if n == "class" => {
2650 self.bump();
2651 Attr::Class(self.parse_stringexpr()?)
2652 }
2653 Token::Name(n) if n == "link" => {
2654 self.bump();
2655 Attr::Link(self.parse_stringexpr()?)
2656 }
2657 Token::Float(_)
2660 | Token::Lparen
2661 | Token::EnvVar(_)
2662 | Token::Func1(_)
2663 | Token::Func2(_)
2664 | Token::Name(_)
2665 | Token::Minus
2666 | Token::Plus
2667 | Token::Kw(Kw::Rand) => {
2668 let span = self.cur_span();
2669 Attr::Dist(self.parse_expr()?, Some(span))
2670 }
2671 _ if self.place_is_scalar_ahead() => {
2672 let span = self.cur_span();
2673 Attr::Dist(self.parse_expr()?, Some(span))
2674 }
2675 _ => return Ok(None),
2676 };
2677 Ok(Some(attr))
2678 }
2679
2680 fn expect_kw(&mut self, k: Kw) -> PResult<()> {
2681 if self.eat_kw(k) {
2682 Ok(())
2683 } else {
2684 self.expected_here(kw_text(k))
2685 }
2686 }
2687
2688 fn parse_stringexpr(&mut self) -> PResult<StringExpr> {
2691 let first = self.parse_string_atom()?;
2692 self.continue_string(first)
2693 }
2694
2695 fn continue_string(&mut self, first: StringExpr) -> PResult<StringExpr> {
2697 let mut e = first;
2698 let mut ops = 0u32;
2701 while self.at(&Token::Plus) && self.string_after_plus() {
2702 self.bump();
2703 let rhs = self.parse_string_atom()?;
2704 e = StringExpr::Concat(Box::new(e), Box::new(rhs));
2705 ops += 1;
2706 self.check_expr_chain(ops)?;
2707 }
2708 Ok(e)
2709 }
2710
2711 fn string_after_plus(&self) -> bool {
2712 self.token_starts_string_at(1)
2713 }
2714
2715 fn token_starts_string_at(&self, offset: usize) -> bool {
2716 match self.toks.get(self.idx + offset).map(|s| &s.tok) {
2717 Some(Token::Str(_) | Token::Arg(_) | Token::Kw(Kw::Sprintf)) => true,
2718 Some(Token::Name(n)) if n == "svg_font" => {
2719 matches!(
2720 self.toks.get(self.idx + offset + 1).map(|s| &s.tok),
2721 Some(Token::Lparen)
2722 )
2723 }
2724 _ => false,
2725 }
2726 }
2727
2728 fn parse_string_atom(&mut self) -> PResult<StringExpr> {
2729 match self.cur().clone() {
2730 Token::Str(s) => {
2731 self.bump();
2732 Ok(StringExpr::Lit(s))
2733 }
2734 Token::Arg(n) => {
2735 self.bump();
2736 Ok(StringExpr::Arg(n))
2737 }
2738 Token::Kw(Kw::Sprintf) => {
2739 self.bump();
2740 self.expect(&Token::Lparen)?;
2741 let fmt = self.parse_stringexpr()?;
2742 let mut args = Vec::new();
2743 while self.eat(&Token::Comma) {
2744 args.push(self.parse_expr()?);
2745 }
2746 self.expect(&Token::Rparen)?;
2747 Ok(StringExpr::Sprintf(Box::new(fmt), args))
2748 }
2749 Token::Name(n) if n == "svg_font" && matches!(self.peek(1), Token::Lparen) => {
2750 self.bump();
2751 self.expect(&Token::Lparen)?;
2752 let mut args = Vec::new();
2753 if !self.at(&Token::Rparen) {
2754 args.push(self.parse_expr()?);
2755 while self.eat(&Token::Comma) {
2756 args.push(self.parse_expr()?);
2757 }
2758 }
2759 self.expect(&Token::Rparen)?;
2760 Ok(StringExpr::SvgFont(args))
2761 }
2762 other => self.err(format!("expected a string, found {other:?}")),
2763 }
2764 }
2765
2766 fn parse_label_position(&mut self) -> PResult<Position> {
2769 let save = self.idx;
2770 if let Ok(x) = self.parse_expr()
2771 && self.eat(&Token::Comma)
2772 {
2773 let y = self.parse_expr()?;
2774 return Ok(Position::Pair(x, y));
2775 }
2776 self.idx = save;
2777 self.parse_position()
2778 }
2779
2780 fn parse_position(&mut self) -> PResult<Position> {
2782 self.descend(Self::parse_position_inner)
2783 }
2784
2785 fn parse_position_inner(&mut self) -> PResult<Position> {
2786 let mut left = self.parse_pos_mul()?;
2787 loop {
2788 let sign = if self.at(&Token::Plus) {
2789 Sign::Plus
2790 } else if self.at(&Token::Minus) {
2791 Sign::Minus
2792 } else {
2793 break;
2794 };
2795 self.bump();
2796 let right = self.parse_pos_mul()?;
2797 left = Position::Sum(sign, Box::new(left), Box::new(right));
2798 }
2799 Ok(left)
2800 }
2801
2802 fn parse_pos_mul(&mut self) -> PResult<Position> {
2804 let mut left = self.parse_pos_primary()?;
2805 loop {
2806 if self.eat(&Token::Mult) {
2807 left = Position::Scale(Box::new(left), self.parse_unary()?, false);
2808 } else if self.eat(&Token::Div) {
2809 left = Position::Scale(Box::new(left), self.parse_unary()?, true);
2810 } else {
2811 break;
2812 }
2813 }
2814 Ok(left)
2815 }
2816
2817 fn parse_pos_primary(&mut self) -> PResult<Position> {
2818 if let Some(p) = self.try_fraction()? {
2822 return Ok(p);
2823 }
2824 if self.eat(&Token::Lparen) {
2829 let save = self.idx;
2830 if let Ok(p1) = self.parse_position() {
2835 let p = if self.eat(&Token::Comma) {
2836 let p2 = self.parse_position()?;
2837 Position::Place(Location::ParenPair(Box::new(p1), Box::new(p2)))
2838 } else {
2839 p1 };
2841 self.expect(&Token::Rparen)?;
2842 return Ok(p);
2843 }
2844 self.idx = save;
2845 let e1 = self.parse_add()?;
2846 self.expect(&Token::Comma)?;
2847 let e2 = self.parse_add()?;
2848 self.expect(&Token::Rparen)?;
2849 return Ok(Position::Pair(e1, e2));
2850 }
2851 if self.at_place_start() && !self.place_is_scalar_ahead() {
2854 return Ok(Position::Place(Location::Place(self.parse_place()?)));
2855 }
2856 let e1 = self.parse_add()?;
2858 if self.eat(&Token::Comma) {
2859 let e2 = self.parse_add()?;
2860 return Ok(Position::Pair(e1, e2));
2861 }
2862 self.err("expected `,`, `between`, or `of the way between` in position")
2863 }
2864
2865 fn try_fraction(&mut self) -> PResult<Option<Position>> {
2869 let save = self.idx;
2870 let Ok(frac) = self.parse_add() else {
2871 self.idx = save;
2872 return Ok(None);
2873 };
2874 let mk = |frac, a, b, of_the_way| {
2875 Ok(Some(Position::Between {
2876 frac: Box::new(frac),
2877 a: Box::new(a),
2878 b: Box::new(b),
2879 of_the_way,
2880 }))
2881 };
2882 if self.eat(&Token::Lt) {
2883 let a = self.parse_position()?;
2884 self.expect(&Token::Comma)?;
2885 let b = self.parse_position()?;
2886 self.expect(&Token::Gt)?;
2887 return mk(frac, a, b, false);
2888 }
2889 let of_the_way = if self.at_kw(Kw::Of) {
2890 self.eat_kw(Kw::Of);
2891 if !(self.eat_kw(Kw::The) && self.eat_kw(Kw::Way) && self.eat_kw(Kw::Between)) {
2892 self.idx = save;
2893 return Ok(None);
2894 }
2895 true
2896 } else if self.eat_kw(Kw::Between) {
2897 false
2898 } else {
2899 self.idx = save;
2900 return Ok(None);
2901 };
2902 let a = self.parse_position()?;
2903 self.expect_kw(Kw::And)?;
2904 let b = self.parse_position()?;
2905 mk(frac, a, b, of_the_way)
2906 }
2907
2908 fn place_is_scalar_ahead(&mut self) -> bool {
2911 let save = self.idx;
2912 let parsed = self.parse_place().is_ok();
2913 let scalar = parsed && matches!(self.cur(), Token::DotX | Token::DotY | Token::Param(_));
2914 self.idx = save;
2915 scalar
2916 }
2917
2918 fn at_place_start(&self) -> bool {
2919 match self.cur() {
2920 Token::Label(_) | Token::Block | Token::Corner(_) => true,
2921 Token::Kw(Kw::Last) | Token::Kw(Kw::Here) => true,
2922 Token::Float(_) => matches!(self.peek(1), Token::Kw(Kw::Nth)),
2923 Token::LeftBrace | Token::LeftQuote => true,
2926 _ => false,
2927 }
2928 }
2929
2930 fn parse_place(&mut self) -> PResult<Place> {
2931 if let Token::Corner(c) = self.cur() {
2934 let c = *c;
2935 self.bump();
2936 self.eat_kw(Kw::Of);
2937 let inner = self.descend(Self::parse_place)?;
2938 return Ok(Place::CornerOf(c, Box::new(inner)));
2939 }
2940
2941 let mut place = self.parse_place_base()?;
2942
2943 let mut accessors = 0u32;
2947 loop {
2948 if let Token::Corner(c) = self.cur() {
2949 let c = *c;
2950 self.bump();
2951 place = Place::Corner(Box::new(place), c);
2952 } else if self.at(&Token::Dot) {
2953 self.bump();
2954 let rhs = self.parse_place_base()?;
2955 place = Place::Member(Box::new(place), Box::new(rhs));
2956 } else {
2957 break;
2958 }
2959 accessors += 1;
2960 self.check_expr_chain(accessors)?;
2961 }
2962 Ok(place)
2963 }
2964
2965 fn parse_place_base(&mut self) -> PResult<Place> {
2966 match self.cur().clone() {
2967 Token::Kw(Kw::Here) => {
2968 self.bump();
2969 Ok(Place::Here)
2970 }
2971 Token::Label(name) => {
2972 let span = Some(self.cur_span());
2973 self.bump();
2974 let subscript = if self.eat(&Token::LeftBrack) {
2975 let e = self.parse_subscript()?;
2976 self.expect(&Token::RightBrack)?;
2977 Some(Box::new(e))
2978 } else {
2979 None
2980 };
2981 Ok(Place::Name {
2982 name,
2983 subscript,
2984 span,
2985 })
2986 }
2987 Token::Kw(Kw::Last) | Token::Float(_) | Token::LeftBrace | Token::LeftQuote => {
2988 let span = Some(self.cur_span());
2989 let count = self.parse_nth()?;
2990 let obj = if self.at_primobj() {
2994 self.parse_primobj()?
2995 } else {
2996 PrimObj::Any
2997 };
2998 Ok(Place::Nth { count, obj, span })
2999 }
3000 other => self.err(format!("expected a place, found {other:?}")),
3001 }
3002 }
3003
3004 fn parse_nth(&mut self) -> PResult<Nth> {
3005 if self.eat_kw(Kw::Last) {
3006 return Ok(Nth::Last);
3007 }
3008 let e = self.parse_ncount()?;
3010 self.expect_kw(Kw::Nth)?;
3011 let from_last = self.eat_kw(Kw::Last);
3012 Ok(Nth::Count(Box::new(e), from_last))
3013 }
3014
3015 fn parse_ncount(&mut self) -> PResult<Expr> {
3019 match self.cur().clone() {
3020 Token::Float(v) => {
3021 self.bump();
3022 Ok(Expr::Num(v))
3023 }
3024 Token::LeftBrace => {
3025 self.bump();
3026 let e = self.parse_expr()?;
3027 self.expect(&Token::RightBrace)?;
3028 Ok(e)
3029 }
3030 Token::LeftQuote => {
3031 self.bump();
3032 let e = self.parse_expr()?;
3033 self.expect(&Token::RightQuote)?;
3034 Ok(e)
3035 }
3036 other => self.err(format!("expected an ordinal count, found {other:?}")),
3037 }
3038 }
3039
3040 fn at_primobj(&self) -> bool {
3043 matches!(
3044 self.cur(),
3045 Token::Prim(_) | Token::Block | Token::Str(_) | Token::LeftBrack
3046 ) || matches!(self.cur(), Token::Name(n) if n == "brace")
3047 }
3048
3049 fn parse_primobj(&mut self) -> PResult<PrimObj> {
3050 match self.cur().clone() {
3051 Token::Prim(p) => {
3052 self.bump();
3053 Ok(PrimObj::Prim(p))
3054 }
3055 Token::Name(n) if n == "brace" => {
3056 self.bump();
3057 Ok(PrimObj::Brace)
3058 }
3059 Token::Block => {
3060 self.bump();
3061 Ok(PrimObj::Block)
3062 }
3063 Token::Str(s) => {
3064 self.bump();
3065 Ok(PrimObj::Str(s))
3066 }
3067 Token::LeftBrack => {
3068 self.bump();
3069 self.expect(&Token::RightBrack)?;
3070 Ok(PrimObj::EmptyBrack)
3071 }
3072 other => self.err(format!("expected a primitive object, found {other:?}")),
3073 }
3074 }
3075
3076 fn opt_expr(&mut self) -> PResult<Option<Expr>> {
3079 if self.starts_scalar() || self.place_is_scalar_ahead() {
3080 Ok(Some(self.parse_expr()?))
3081 } else {
3082 Ok(None)
3083 }
3084 }
3085
3086 fn parse_color_like(&mut self) -> PResult<StringExpr> {
3089 match self.cur().clone() {
3090 Token::Name(n) if n == "rgb" && matches!(self.peek(1), Token::Lparen) => {
3092 self.bump();
3093 self.expect(&Token::Lparen)?;
3094 let r = self.parse_expr()?;
3095 self.expect(&Token::Comma)?;
3096 let g = self.parse_expr()?;
3097 self.expect(&Token::Comma)?;
3098 let b = self.parse_expr()?;
3099 self.expect(&Token::Rparen)?;
3100 Ok(StringExpr::Rgb(Box::new([r, g, b])))
3101 }
3102 Token::Name(n) | Token::Label(n) => {
3103 self.bump();
3104 Ok(StringExpr::Lit(n))
3105 }
3106 Token::Float(_) | Token::Lparen => {
3110 Ok(StringExpr::ColorNum(Box::new(self.parse_expr()?)))
3111 }
3112 _ => self.parse_stringexpr(),
3113 }
3114 }
3115
3116 fn opt_attr_expr(
3117 &mut self,
3118 allow_fit: bool,
3119 allow_brace: bool,
3120 allow_hatch: bool,
3121 allow_close: bool,
3122 ) -> PResult<Option<Expr>> {
3123 if self.contextual_attr_ahead(allow_fit, allow_brace, allow_hatch, allow_close) {
3124 Ok(None)
3125 } else {
3126 self.opt_expr()
3127 }
3128 }
3129
3130 fn contextual_attr_ahead(
3131 &self,
3132 allow_fit: bool,
3133 allow_brace: bool,
3134 allow_hatch: bool,
3135 allow_close: bool,
3136 ) -> bool {
3137 matches!(
3138 self.cur(),
3139 Token::Name(n)
3140 if (allow_fit && n == "fit")
3141 || (allow_hatch
3142 && matches!(
3143 n.as_str(),
3144 "hatch"
3145 | "crosshatch"
3146 | "hatchangle"
3147 | "hatchsep"
3148 | "hatchwid"
3149 | "hatchwidth"
3150 | "hatchcolor"
3151 | "gradient"
3152 | "gradientangle"
3153 ))
3154 || n == "opacity"
3155 || (allow_brace && matches!(n.as_str(), "bracepos" | "labeloffset"))
3156 || n == "behind"
3157 || n == "class"
3158 || n == "link"
3159 || (allow_close && n == "close")
3160 )
3161 }
3162
3163 fn starts_scalar(&self) -> bool {
3164 matches!(
3165 self.cur(),
3166 Token::Float(_)
3167 | Token::Name(_)
3168 | Token::EnvVar(_)
3169 | Token::Lparen
3170 | Token::Minus
3171 | Token::Plus
3172 | Token::Not
3173 | Token::Func1(_)
3174 | Token::Func2(_)
3175 | Token::Kw(Kw::Rand)
3176 | Token::ArgCount
3177 )
3178 }
3179
3180 fn parse_expr(&mut self) -> PResult<Expr> {
3181 self.descend(Self::parse_or)
3182 }
3183
3184 fn parse_or(&mut self) -> PResult<Expr> {
3185 let mut e = self.parse_and()?;
3186 let mut ops = 0;
3187 while self.eat(&Token::OrOr) {
3188 ops += 1;
3189 self.check_expr_chain(ops)?;
3190 let r = self.parse_and()?;
3191 e = Expr::Bin(BinOp::Or, Box::new(e), Box::new(r));
3192 }
3193 Ok(e)
3194 }
3195
3196 fn parse_and(&mut self) -> PResult<Expr> {
3197 let mut e = self.parse_cmp()?;
3198 let mut ops = 0;
3199 while self.eat(&Token::AndAnd) {
3200 ops += 1;
3201 self.check_expr_chain(ops)?;
3202 let r = self.parse_cmp()?;
3203 e = Expr::Bin(BinOp::And, Box::new(e), Box::new(r));
3204 }
3205 Ok(e)
3206 }
3207
3208 fn parse_cmp(&mut self) -> PResult<Expr> {
3209 let mut e = self.parse_add()?;
3210 let mut ops = 0;
3211 loop {
3212 let op = match self.cur() {
3213 Token::EqEq => BinOp::Eq,
3214 Token::Neq => BinOp::Ne,
3215 Token::Lt => BinOp::Lt,
3216 Token::Le => BinOp::Le,
3217 Token::Gt => BinOp::Gt,
3218 Token::Ge => BinOp::Ge,
3219 _ => break,
3220 };
3221 self.bump();
3222 ops += 1;
3223 self.check_expr_chain(ops)?;
3224 let r = self.parse_add()?;
3225 e = Expr::Bin(op, Box::new(e), Box::new(r));
3226 }
3227 Ok(e)
3228 }
3229
3230 fn parse_add(&mut self) -> PResult<Expr> {
3231 let mut e = self.parse_mul()?;
3232 let mut ops = 0;
3233 loop {
3234 let op = match self.cur() {
3235 Token::Plus => BinOp::Add,
3236 Token::Minus => BinOp::Sub,
3237 _ => break,
3238 };
3239 self.bump();
3240 ops += 1;
3241 self.check_expr_chain(ops)?;
3242 let r = self.parse_mul()?;
3243 e = Expr::Bin(op, Box::new(e), Box::new(r));
3244 }
3245 Ok(e)
3246 }
3247
3248 fn parse_mul(&mut self) -> PResult<Expr> {
3249 let mut e = self.parse_unary()?;
3250 let mut ops = 0;
3251 loop {
3252 let op = match self.cur() {
3253 Token::Mult => BinOp::Mul,
3254 Token::Div => BinOp::Div,
3255 Token::Percent => BinOp::Mod,
3256 _ => break,
3257 };
3258 self.bump();
3259 ops += 1;
3260 self.check_expr_chain(ops)?;
3261 let r = self.parse_unary()?;
3262 e = Expr::Bin(op, Box::new(e), Box::new(r));
3263 }
3264 Ok(e)
3265 }
3266
3267 fn parse_unary(&mut self) -> PResult<Expr> {
3268 let mut ops = Vec::new();
3269 loop {
3270 let op = match self.cur() {
3271 Token::Minus => Some(UnOp::Neg),
3272 Token::Plus => Some(UnOp::Pos),
3273 Token::Not => Some(UnOp::Not),
3274 _ => None,
3275 };
3276 let Some(op) = op else {
3277 break;
3278 };
3279 self.bump();
3280 ops.push(op);
3281 self.check_expr_chain(ops.len() as u32)?;
3282 }
3283 let mut e = self.parse_pow()?;
3284 for op in ops.into_iter().rev() {
3285 e = Expr::Unary(op, Box::new(e));
3286 }
3287 Ok(e)
3288 }
3289
3290 fn parse_pow(&mut self) -> PResult<Expr> {
3291 let base = self.parse_primary()?;
3292 if self.eat(&Token::Caret) {
3293 let exp = self.descend(Self::parse_unary)?; Ok(Expr::Bin(BinOp::Pow, Box::new(base), Box::new(exp)))
3295 } else {
3296 Ok(base)
3297 }
3298 }
3299
3300 fn paren_followed_by_dot_xy(&self) -> bool {
3303 let mut depth = 1i32;
3304 let mut i = self.idx;
3305 while let Some(s) = self.toks.get(i) {
3306 match &s.tok {
3307 Token::Lparen => depth += 1,
3308 Token::Rparen => {
3309 depth -= 1;
3310 if depth == 0 {
3311 return matches!(
3312 self.toks.get(i + 1).map(|t| &t.tok),
3313 Some(Token::DotX | Token::DotY)
3314 );
3315 }
3316 }
3317 Token::Eof => return false,
3318 _ => {}
3319 }
3320 i += 1;
3321 }
3322 false
3323 }
3324
3325 fn parse_primary(&mut self) -> PResult<Expr> {
3326 if self.at_place_start() && self.place_is_scalar_ahead() {
3328 return self.parse_place_scalar();
3329 }
3330 if self.at_string_start() {
3332 return Ok(Expr::Str(self.parse_stringexpr()?));
3333 }
3334 match self.cur().clone() {
3335 Token::Float(v) => {
3336 self.bump();
3337 Ok(Expr::Num(v))
3338 }
3339 Token::Name(name) | Token::Label(name) => {
3340 self.bump();
3341 let subscript = if self.eat(&Token::LeftBrack) {
3342 let e = self.parse_subscript()?;
3343 self.expect(&Token::RightBrack)?;
3344 Some(Box::new(e))
3345 } else {
3346 None
3347 };
3348 Ok(Expr::Var(name, subscript))
3349 }
3350 Token::EnvVar(v) => {
3351 self.bump();
3352 Ok(Expr::Env(v))
3353 }
3354 Token::Lparen => {
3355 self.bump();
3356 if matches!(self.cur(), Token::Name(_) | Token::Label(_))
3358 && self.assignment_op_after_var_ref()
3359 {
3360 let name = match self.bump() {
3361 Token::Name(n) | Token::Label(n) => n,
3362 _ => unreachable!(),
3363 };
3364 let subscript = if self.eat(&Token::LeftBrack) {
3365 let e = self.parse_subscript()?;
3366 self.expect(&Token::RightBrack)?;
3367 Some(Box::new(e))
3368 } else {
3369 None
3370 };
3371 self.bump(); let v = self.parse_expr()?;
3373 self.expect(&Token::Rparen)?;
3374 return Ok(Expr::Assign(name, subscript, Box::new(v)));
3375 }
3376 if self.paren_followed_by_dot_xy() {
3381 let pos = self.parse_position()?;
3382 self.expect(&Token::Rparen)?;
3383 let loc = Location::Paren(Box::new(pos));
3384 return Ok(if self.eat(&Token::DotX) {
3385 Expr::DotX(loc)
3386 } else {
3387 self.expect(&Token::DotY)?;
3388 Expr::DotY(loc)
3389 });
3390 }
3391 let e = self.parse_expr()?;
3393 self.expect(&Token::Rparen)?;
3394 Ok(e)
3395 }
3396 Token::ArgCount => {
3398 self.bump();
3399 Ok(Expr::Num(0.0))
3400 }
3401 Token::Func1(f) => {
3402 self.bump();
3403 self.expect(&Token::Lparen)?;
3404 let e = self.parse_expr()?;
3405 self.expect(&Token::Rparen)?;
3406 Ok(Expr::Func1(f, Box::new(e)))
3407 }
3408 Token::Func2(f) => {
3409 self.bump();
3410 self.expect(&Token::Lparen)?;
3411 let a = self.parse_expr()?;
3412 self.expect(&Token::Comma)?;
3413 let b = self.parse_expr()?;
3414 self.expect(&Token::Rparen)?;
3415 Ok(Expr::Func2(f, Box::new(a), Box::new(b)))
3416 }
3417 Token::Kw(Kw::Rand) => {
3418 self.bump();
3419 self.expect(&Token::Lparen)?;
3420 let arg = if self.at(&Token::Rparen) {
3421 None
3422 } else {
3423 Some(Box::new(self.parse_expr()?))
3424 };
3425 self.expect(&Token::Rparen)?;
3426 Ok(Expr::Rand(arg))
3427 }
3428 other => self.err(format!("expected an expression, found {other:?}")),
3429 }
3430 }
3431
3432 fn parse_place_scalar(&mut self) -> PResult<Expr> {
3434 let place = self.parse_place()?;
3435 match self.cur().clone() {
3436 Token::DotX => {
3437 self.bump();
3438 Ok(Expr::DotX(Location::Place(place)))
3439 }
3440 Token::DotY => {
3441 self.bump();
3442 Ok(Expr::DotY(Location::Place(place)))
3443 }
3444 Token::Param(p) => {
3445 self.bump();
3446 Ok(Expr::PlaceAttr(place, p))
3447 }
3448 other => self.err(format!(
3449 "a place is not a number here; expected `.x`, `.y`, or an attribute, found {other:?}"
3450 )),
3451 }
3452 }
3453}
3454
3455#[cfg(test)]
3456mod tests {
3457 use super::*;
3458
3459 fn pic(src: &str) -> Picture {
3460 parse(src).unwrap_or_else(|e| panic!("parse error: {e}"))
3461 }
3462
3463 #[test]
3464 fn kernighan_pipeline() {
3465 let p = pic(r#".PS
3466ellipse "document"
3467arrow
3468box "PIC"
3469arrow
3470box "TBL/EQN" "(optional)" dashed
3471arrow
3472box "TROFF"
3473arrow
3474ellipse "typesetter"
3475.PE
3476"#);
3477 assert_eq!(p.stmts.len(), 9);
3478 if let Stmt::Object { object, .. } = &p.stmts[4] {
3480 assert_eq!(object.kind, ObjectKind::Primitive(Prim::Box));
3481 let texts = object
3482 .attrs
3483 .iter()
3484 .filter(|a| matches!(a, Attr::Text(_)))
3485 .count();
3486 assert_eq!(texts, 2);
3487 assert!(
3488 object
3489 .attrs
3490 .iter()
3491 .any(|a| matches!(a, Attr::LineStyle(LineType::Dashed, _)))
3492 );
3493 } else {
3494 panic!("expected object");
3495 }
3496 }
3497
3498 #[test]
3499 fn box_with_dims_and_at() {
3500 let p = pic("box ht 0.3 wid 0.5 at 0.25,0.15");
3501 let Stmt::Object { object, .. } = &p.stmts[0] else {
3502 panic!()
3503 };
3504 assert!(matches!(object.attrs[0], Attr::Dim(DimKind::Ht, _)));
3505 assert!(matches!(object.attrs[1], Attr::Dim(DimKind::Wid, _)));
3506 assert!(matches!(object.attrs[2], Attr::At(Position::Pair(_, _))));
3507 }
3508
3509 #[test]
3510 fn behind_parses_as_contextual_extension_attribute() {
3511 let p = pic("A: box\nbox behind A");
3512 let Stmt::Object { object, .. } = &p.stmts[1] else {
3513 panic!()
3514 };
3515 let Some(Attr::Behind(Place::Name {
3516 name, subscript, ..
3517 })) = object.attrs.last()
3518 else {
3519 panic!("expected behind attribute");
3520 };
3521 assert_eq!(name, "A");
3522 assert!(subscript.is_none());
3523
3524 let p = pic("behind = 2\nbox wid behind");
3525 assert_eq!(p.stmts.len(), 2);
3526 assert!(matches!(p.stmts[0], Stmt::Assign(_)));
3527 }
3528
3529 #[test]
3530 fn fit_parses_as_contextual_extension_attribute() {
3531 let p = pic("box \"long label\" fit");
3532 let Stmt::Object { object, .. } = &p.stmts[0] else {
3533 panic!()
3534 };
3535 assert!(object.attrs.iter().any(|a| matches!(a, Attr::Fit)));
3536
3537 let p = pic("fit = 2\nbox wid fit");
3538 assert_eq!(p.stmts.len(), 2);
3539 assert!(matches!(p.stmts[0], Stmt::Assign(_)));
3540
3541 let p = pic("fit = 2\nline fit");
3542 let Stmt::Object { object, .. } = &p.stmts[1] else {
3543 panic!()
3544 };
3545 assert!(matches!(object.attrs[0], Attr::Dist(_, _)));
3546 }
3547
3548 #[test]
3549 fn hatch_parses_as_contextual_extension_attribute() {
3550 let p = pic("box hatch hatchangle 30 hatchsep .05 hatchwid 1.2 hatchcolor red");
3551 let Stmt::Object { object, .. } = &p.stmts[0] else {
3552 panic!()
3553 };
3554 assert!(
3555 object
3556 .attrs
3557 .iter()
3558 .any(|a| matches!(a, Attr::Hatch(HatchKind::Single)))
3559 );
3560 assert!(
3561 object
3562 .attrs
3563 .iter()
3564 .any(|a| matches!(a, Attr::HatchAngle(_)))
3565 );
3566 assert!(object.attrs.iter().any(|a| matches!(a, Attr::HatchSep(_))));
3567 assert!(
3568 object
3569 .attrs
3570 .iter()
3571 .any(|a| matches!(a, Attr::HatchWidth(_)))
3572 );
3573 assert!(
3574 object
3575 .attrs
3576 .iter()
3577 .any(|a| matches!(a, Attr::HatchColor(..)))
3578 );
3579
3580 let p = pic("hatch = 2\nbox wid hatch");
3581 assert_eq!(p.stmts.len(), 2);
3582 assert!(matches!(p.stmts[0], Stmt::Assign(_)));
3583 let Stmt::Object { object, .. } = &p.stmts[1] else {
3584 panic!()
3585 };
3586 assert!(matches!(object.attrs[0], Attr::Dim(DimKind::Wid, _)));
3587 }
3588
3589 #[test]
3590 fn opacity_parses_as_contextual_extension_attribute() {
3591 let p = pic("box opacity 0.5");
3592 let Stmt::Object { object, .. } = &p.stmts[0] else {
3593 panic!()
3594 };
3595 assert!(object.attrs.iter().any(|a| matches!(a, Attr::Opacity(_))));
3596
3597 let p = pic("opacity = 2\nbox wid opacity");
3598 assert_eq!(p.stmts.len(), 2);
3599 assert!(matches!(p.stmts[0], Stmt::Assign(_)));
3600 let Stmt::Object { object, .. } = &p.stmts[1] else {
3601 panic!()
3602 };
3603 assert!(matches!(object.attrs[0], Attr::Dim(DimKind::Wid, _)));
3604 }
3605
3606 #[test]
3607 fn close_parses_as_contextual_line_extension_attribute() {
3608 let p = pic("line right then up close");
3609 let Stmt::Object { object, .. } = &p.stmts[0] else {
3610 panic!()
3611 };
3612 assert!(object.attrs.iter().any(|a| matches!(a, Attr::Close)));
3613
3614 let p = pic("close = 2\nbox wid close");
3615 assert_eq!(p.stmts.len(), 2);
3616 assert!(matches!(p.stmts[0], Stmt::Assign(_)));
3617 let Stmt::Object { object, .. } = &p.stmts[1] else {
3618 panic!()
3619 };
3620 assert!(matches!(object.attrs[0], Attr::Dim(DimKind::Wid, _)));
3621 }
3622
3623 #[test]
3624 fn gradient_parses_as_contextual_extension_attribute() {
3625 let p = pic("box gradient \"steelblue\" white gradientangle 45");
3626 let Stmt::Object { object, .. } = &p.stmts[0] else {
3627 panic!()
3628 };
3629 assert!(object.attrs.iter().any(|a| matches!(a, Attr::Gradient(..))));
3630 assert!(
3631 object
3632 .attrs
3633 .iter()
3634 .any(|a| matches!(a, Attr::GradientAngle(_)))
3635 );
3636
3637 let p = pic("gradient = 2\nbox wid gradient");
3639 assert!(matches!(p.stmts[0], Stmt::Assign(_)));
3640 let Stmt::Object { object, .. } = &p.stmts[1] else {
3641 panic!()
3642 };
3643 assert!(matches!(object.attrs[0], Attr::Dim(DimKind::Wid, _)));
3644 }
3645
3646 #[test]
3647 fn class_parses_inline_and_statement_forms() {
3648 let p = pic("box class \"critical\"");
3649 let Stmt::Object { object, .. } = &p.stmts[0] else {
3650 panic!()
3651 };
3652 assert!(object.attrs.iter().any(|a| matches!(a, Attr::Class(_))));
3653
3654 let p = pic("A: box\nclass A \"hot\"\nclass last box \"cold\"");
3655 assert!(matches!(p.stmts[1], Stmt::Class { .. }));
3656 assert!(matches!(p.stmts[2], Stmt::Class { .. }));
3657
3658 let p = pic("class = 2\nbox wid class");
3660 assert!(matches!(p.stmts[0], Stmt::Assign(_)));
3661 let Stmt::Object { object, .. } = &p.stmts[1] else {
3662 panic!()
3663 };
3664 assert!(matches!(object.attrs[0], Attr::Dim(DimKind::Wid, _)));
3665 }
3666
3667 #[test]
3668 fn link_parses_inline_and_statement_forms() {
3669 let p = pic("box link \"https://rpic.dev\"");
3670 let Stmt::Object { object, .. } = &p.stmts[0] else {
3671 panic!()
3672 };
3673 assert!(object.attrs.iter().any(|a| matches!(a, Attr::Link(_))));
3674
3675 let p = pic("A: box\nlink A \"https://rpic.dev\"\nlink last box \"#top\"");
3676 assert!(matches!(p.stmts[1], Stmt::Link { .. }));
3677 assert!(matches!(p.stmts[2], Stmt::Link { .. }));
3678
3679 let p = pic("link = 2\nbox wid link");
3681 assert!(matches!(p.stmts[0], Stmt::Assign(_)));
3682 let Stmt::Object { object, .. } = &p.stmts[1] else {
3683 panic!()
3684 };
3685 assert!(matches!(object.attrs[0], Attr::Dim(DimKind::Wid, _)));
3686 }
3687
3688 #[test]
3689 fn brace_parses_as_contextual_extension_object() {
3690 let p = pic(
3691 "A: box\nB: box\nbrace from A.e to B.w down \"group\" wid .2 bracepos .4 labeloffset .1",
3692 );
3693 let Stmt::Object { object, .. } = &p.stmts[2] else {
3694 panic!()
3695 };
3696 assert_eq!(object.kind, ObjectKind::Brace);
3697 assert!(object.attrs.iter().any(|a| matches!(a, Attr::From(_))));
3698 assert!(object.attrs.iter().any(|a| matches!(a, Attr::To(_))));
3699 assert!(
3700 object
3701 .attrs
3702 .iter()
3703 .any(|a| matches!(a, Attr::Direction(Dir::Down, None)))
3704 );
3705 assert!(object.attrs.iter().any(|a| matches!(a, Attr::Text(_))));
3706 assert!(object.attrs.iter().any(|a| matches!(a, Attr::BracePos(_))));
3707 assert!(
3708 object
3709 .attrs
3710 .iter()
3711 .any(|a| matches!(a, Attr::BraceLabelOffset(_)))
3712 );
3713
3714 let p = pic("brace = 2\nline right brace");
3715 assert!(matches!(p.stmts[0], Stmt::Assign(_)));
3716 let Stmt::Object { object, .. } = &p.stmts[1] else {
3717 panic!()
3718 };
3719 assert_eq!(object.kind, ObjectKind::Primitive(Prim::Line));
3720
3721 let p = pic("brace from 0,0 to 1,0\nline from last brace.start to last brace.end");
3722 assert_eq!(p.stmts.len(), 2);
3723 }
3724
3725 #[test]
3726 fn labeled_and_corners() {
3727 let p = pic("B1: box\narc -> from top of B1 to last box.ne");
3728 assert!(matches!(p.stmts[0], Stmt::Object { label: Some(_), .. }));
3729 let Stmt::Object { object, .. } = &p.stmts[1] else {
3730 panic!()
3731 };
3732 assert_eq!(object.kind, ObjectKind::Primitive(Prim::Arc));
3733 assert!(
3734 object
3735 .attrs
3736 .iter()
3737 .any(|a| matches!(a, Attr::Arrowhead(Arrow::Right, _)))
3738 );
3739 assert!(object.attrs.iter().any(|a| matches!(
3741 a,
3742 Attr::From(Position::Place(Location::Place(Place::CornerOf(
3743 Corner::N,
3744 _
3745 ))))
3746 )));
3747 }
3748
3749 #[test]
3750 fn with_at_and_shift() {
3751 let p = pic("ellipse \"2\" with .nw at last ellipse.se + (0.1,0)");
3752 let Stmt::Object { object, .. } = &p.stmts[0] else {
3753 panic!()
3754 };
3755 let with = object
3756 .attrs
3757 .iter()
3758 .find(|a| matches!(a, Attr::With { .. }))
3759 .unwrap();
3760 let Attr::With { anchor, at } = with else {
3761 panic!()
3762 };
3763 assert_eq!(*anchor, WithAnchor::Corner(Corner::Nw));
3764 assert!(matches!(at, Position::Sum(Sign::Plus, _, _)));
3766 }
3767
3768 #[test]
3769 fn with_member_anchor_parses() {
3770 let p = pic("[ A: box ] with .A.c at Here");
3771 let Stmt::Object { object, .. } = &p.stmts[0] else {
3772 panic!()
3773 };
3774 let with = object
3775 .attrs
3776 .iter()
3777 .find(|a| matches!(a, Attr::With { .. }))
3778 .unwrap();
3779 let Attr::With { anchor, .. } = with else {
3780 panic!()
3781 };
3782 assert!(matches!(
3783 anchor,
3784 WithAnchor::Place(Place::Corner(inner, Corner::Center))
3785 if matches!(inner.as_ref(), Place::Name { name, .. } if name == "A")
3786 ));
3787 }
3788
3789 #[test]
3790 fn expression_precedence() {
3791 let p = pic("x = 2 + 3 * 4 ^ 2");
3793 let Stmt::Assign(list) = &p.stmts[0] else {
3794 panic!()
3795 };
3796 let Expr::Bin(BinOp::Add, _, rhs) = &list[0].value else {
3798 panic!("expected top-level add")
3799 };
3800 assert!(matches!(**rhs, Expr::Bin(BinOp::Mul, _, _)));
3801 }
3802
3803 #[test]
3804 fn between_position() {
3805 let p = pic("arrow from 1/3 of the way between A.ne and A.se");
3806 let Stmt::Object { object, .. } = &p.stmts[0] else {
3807 panic!()
3808 };
3809 assert!(object.attrs.iter().any(|a| matches!(
3810 a,
3811 Attr::From(Position::Between {
3812 of_the_way: true,
3813 ..
3814 })
3815 )));
3816 }
3817
3818 #[test]
3819 fn assignment_list_and_envvar() {
3820 let p = pic("boxht = 0.3; boxwid = 2 * boxht");
3821 assert_eq!(p.stmts.len(), 2);
3822 let Stmt::Assign(a0) = &p.stmts[0] else {
3823 panic!()
3824 };
3825 assert_eq!(a0[0].target, AssignTarget::Env(EnvVar::Boxht));
3826 }
3827
3828 #[test]
3829 fn dpic_svg_font_stub_parses_as_string() {
3830 let p = pic("print svg_font(\"Times\", 12)");
3831 let Stmt::Print(PrintItem::Str(StringExpr::SvgFont(args))) = &p.stmts[0] else {
3832 panic!()
3833 };
3834 assert_eq!(args.len(), 2);
3835 }
3836
3837 #[test]
3838 fn subscripted_variable_refs_parse() {
3839 let p = pic("P[1] = 2\nx = P[1]");
3840 let Stmt::Assign(a0) = &p.stmts[0] else {
3841 panic!()
3842 };
3843 assert!(matches!(&a0[0].target, AssignTarget::Var(name, Some(_)) if name == "P"));
3844
3845 let Stmt::Assign(a1) = &p.stmts[1] else {
3846 panic!()
3847 };
3848 assert!(matches!(&a1[0].value, Expr::Var(name, Some(_)) if name == "P"));
3849 }
3850
3851 #[test]
3852 fn block_object() {
3853 let p = pic("[ box; circle ] with .nw at Here");
3854 let Stmt::Object { object, .. } = &p.stmts[0] else {
3855 panic!()
3856 };
3857 let ObjectKind::Block(inner) = &object.kind else {
3858 panic!()
3859 };
3860 assert_eq!(inner.len(), 2);
3861 }
3862
3863 #[test]
3864 fn diamond_line_with_then() {
3865 let p = pic("line up right then down right then down left then up left");
3866 let Stmt::Object { object, .. } = &p.stmts[0] else {
3867 panic!()
3868 };
3869 let thens = object
3870 .attrs
3871 .iter()
3872 .filter(|a| matches!(a, Attr::Then))
3873 .count();
3874 assert_eq!(thens, 3);
3875 }
3876
3877 #[test]
3878 fn place_scalar_in_coord_pair() {
3879 let p = pic("A: box\n\"t\" at (A.x, A.y - 0.5)");
3881 let Stmt::Object { object, .. } = &p.stmts[1] else {
3882 panic!()
3883 };
3884 assert!(
3885 object
3886 .attrs
3887 .iter()
3888 .any(|a| matches!(a, Attr::At(Position::Pair(_, _))))
3889 );
3890 let q = pic("A: box\nbox at A.ne");
3892 let Stmt::Object { object, .. } = &q.stmts[1] else {
3893 panic!()
3894 };
3895 assert!(object.attrs.iter().any(|a| matches!(
3896 a,
3897 Attr::At(Position::Place(Location::Place(Place::Corner(_, _))))
3898 )));
3899 }
3900
3901 #[test]
3902 fn ignores_non_svg_backend_preambles() {
3903 let p = pic(r#".PS
3904verbatimtex
3905\global\def\foo#1{#1}
3906etex
3907\global\def\bar#1{#1}
3908\psset{arrowsize=4pt}
3909box
3910.PE
3911"#);
3912 assert_eq!(p.stmts.len(), 1);
3913 let Stmt::Object { object, .. } = &p.stmts[0] else {
3914 panic!()
3915 };
3916 assert_eq!(object.kind, ObjectKind::Primitive(Prim::Box));
3917 }
3918
3919 #[test]
3920 fn backend_filter_keeps_global_lines_inside_strings() {
3921 let p = pic(
3922 "sh \"echo -n \\\"print \\\\\"\\\" > x\"\nif dpicopt==optPGF then { command \"cycle; \\\n\\global\\let\\dpicdraw=x\" } else { box }",
3923 );
3924 assert_eq!(p.stmts.len(), 2);
3925 let Stmt::Object { object, .. } = &p.stmts[1] else {
3926 panic!()
3927 };
3928 assert_eq!(object.kind, ObjectKind::Primitive(Prim::Box));
3929 }
3930
3931 #[test]
3932 fn static_if_copy_defines_macros_before_following_statements() {
3933 let dir = std::env::temp_dir().join(format!("rpic_static_if_{}", std::process::id()));
3934 std::fs::create_dir_all(&dir).unwrap();
3935 std::fs::write(dir.join("macros.pic"), "define makebox { box wid $1 }\n").unwrap();
3936 std::fs::write(
3937 dir.join("inc.pic"),
3938 "define makecircle { circle rad 0.1 }\n",
3939 )
3940 .unwrap();
3941 let p = parse_in_dir(
3942 "if \"plotlib\" != \"1\" then { copy \"macros.pic\" }\ndefine choose { if \"$1\"==\"\" then { box } else { copy \"$1/inc.pic\" } }\nchoose(.)\nmakecircle()\nmakebox(0.4)",
3943 Some(dir.as_path()),
3944 )
3945 .unwrap_or_else(|e| panic!("parse error: {e}"));
3946 let _ = std::fs::remove_dir_all(&dir);
3947 assert_eq!(p.stmts.len(), 2);
3948 let Stmt::Object { object, .. } = &p.stmts[0] else {
3949 panic!()
3950 };
3951 assert_eq!(object.kind, ObjectKind::Primitive(Prim::Circle));
3952 let Stmt::Object { object, .. } = &p.stmts[1] else {
3953 panic!()
3954 };
3955 assert_eq!(object.kind, ObjectKind::Primitive(Prim::Box));
3956 }
3957
3958 #[test]
3959 fn unsupported_control_is_clear() {
3960 let e = parse("copy \"x\"").unwrap_err();
3962 assert!(e.msg.contains("copy") && e.msg.contains("file"));
3963 }
3964
3965 #[test]
3966 fn deeply_nested_input_errors_instead_of_overflowing() {
3967 std::thread::Builder::new()
3973 .stack_size(32 * 1024 * 1024)
3974 .spawn(|| {
3975 let deep_parens = format!("box wid {}1{}", "(".repeat(5000), ")".repeat(5000));
3976 let e = parse(&deep_parens).unwrap_err();
3977 assert!(e.msg.contains("nested too deeply"), "{}", e.msg);
3978 let deep_blocks = format!("{}box{}", "[".repeat(5000), "]".repeat(5000));
3979 assert!(
3980 parse(&deep_blocks)
3981 .unwrap_err()
3982 .msg
3983 .contains("nested too deeply")
3984 );
3985 assert!(parse("[[[[ box ]]]]").is_ok());
3987 assert!(parse(&format!("box wid {}1{}", "(".repeat(64), ")".repeat(64))).is_ok());
3988 })
3989 .unwrap()
3990 .join()
3991 .unwrap();
3992 }
3993
3994 #[test]
3995 fn flat_binary_expression_chain_errors_instead_of_overflowing() {
3996 let expr = (0..2000).map(|_| "1").collect::<Vec<_>>().join("+");
3999 let e = parse(&format!("x = {expr}")).unwrap_err();
4000 assert!(e.msg.contains("too many chained operators"), "{}", e.msg);
4001 }
4002
4003 #[test]
4004 fn recursion_and_ast_depth_vectors_error_instead_of_overflowing() {
4005 std::thread::Builder::new()
4010 .stack_size(32 * 1024 * 1024)
4011 .spawn(|| {
4012 let braces = format!("{}box{}", "{".repeat(5000), "}".repeat(5000));
4014 assert!(
4015 parse(&braces)
4016 .unwrap_err()
4017 .msg
4018 .contains("nested too deeply"),
4019 "brace group"
4020 );
4021 let corners = format!("line to {}Here", ".n".repeat(5000));
4023 assert!(
4024 parse(&corners)
4025 .unwrap_err()
4026 .msg
4027 .contains("nested too deeply"),
4028 "corner chain"
4029 );
4030 let concat = vec!["\"a\""; 2000].join("+");
4032 assert!(
4033 parse(&format!("print {concat}"))
4034 .unwrap_err()
4035 .msg
4036 .contains("too many chained operators"),
4037 "string concat"
4038 );
4039 let members = format!("line to A{}.sw", ".B".repeat(2000));
4041 assert!(
4042 parse(&format!("box\n{members}"))
4043 .unwrap_err()
4044 .msg
4045 .contains("too many chained operators"),
4046 "member chain"
4047 );
4048 assert!(parse("{{{{ box }}}}").is_ok());
4050 assert!(parse("box\nline to A.n.sw").is_ok());
4051 })
4052 .unwrap()
4053 .join()
4054 .unwrap();
4055 }
4056
4057 #[test]
4058 fn control_constructs_parse() {
4059 assert!(parse("for i = 1 to 3 do { box }").is_ok());
4060 assert!(parse("if 1 > 0 then { box } else { circle }").is_ok());
4061 assert!(parse("reset boxht, boxwid").is_ok());
4062 let p = parse("define e { box }\ne\ne").unwrap();
4064 assert_eq!(p.stmts.len(), 2);
4065 }
4066}