Skip to main content

rpic_core/
parser.rs

1//! Recursive-descent parser for the pic drawing core.
2//!
3//! Follows dpic's `grammar.txt`. Implemented: pictures (`.PS … .PE`), primitives
4//! with the full attribute set, positions (pairs, places, corners, ordinals,
5//! `between`, `± shifts`), expressions with proper precedence, `[ … ]` blocks,
6//! `{ … }` groups, labels and assignments. Control constructs
7//! (`if`/`for`/`define`/`print`/`sh`/…) are reported as unsupported for now.
8
9use crate::ast::*;
10use crate::lexer::{LexError, Spanned, lex};
11use crate::token::*;
12
13/// A parse error with source location.
14#[derive(Debug, Clone, PartialEq)]
15pub struct ParseError {
16    pub msg: String,
17    pub line: u32,
18    pub col: u32,
19}
20
21impl std::fmt::Display for ParseError {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        write!(f, "{}:{}: {}", self.line, self.col, self.msg)
24    }
25}
26
27impl From<LexError> for ParseError {
28    fn from(e: LexError) -> Self {
29        ParseError {
30            msg: e.msg,
31            line: e.line,
32            col: e.col,
33        }
34    }
35}
36
37/// Parse a full source string into a [`Picture`].
38pub fn parse(src: &str) -> Result<Picture, ParseError> {
39    let toks = lex(src)?;
40    let toks = preprocess(toks)?;
41    Parser::new(toks).parse_picture()
42}
43
44// ---- macro preprocessor ----------------------------------------------------
45//
46// Handles `define name { body }` (brace-delimited) with `$1..$9` argument
47// substitution at the token level, before parsing. Invocations `name(a, b)` (or
48// bare `name`) are replaced by the body with arguments spliced in; the result is
49// re-expanded so macros may call macros. `undef name` removes a definition.
50
51use std::collections::HashMap;
52
53fn preprocess(input: Vec<Spanned>) -> Result<Vec<Spanned>, ParseError> {
54    let mut macros: HashMap<String, Vec<Spanned>> = HashMap::new();
55    expand(&input, &mut macros, 0)
56}
57
58fn loc(toks: &[Spanned], i: usize) -> (u32, u32) {
59    toks.get(i).map(|s| (s.line, s.col)).unwrap_or((0, 0))
60}
61
62fn expand(
63    toks: &[Spanned],
64    macros: &mut HashMap<String, Vec<Spanned>>,
65    depth: usize,
66) -> Result<Vec<Spanned>, ParseError> {
67    if depth > 64 {
68        return Err(ParseError {
69            msg: "macro expansion too deep (recursive define?)".into(),
70            line: 0,
71            col: 0,
72        });
73    }
74    let mut out = Vec::new();
75    let mut i = 0;
76    while i < toks.len() {
77        match &toks[i].tok {
78            Token::Kw(Kw::Define) => {
79                let (l, c) = loc(toks, i);
80                i += 1;
81                let name = match toks.get(i).map(|s| &s.tok) {
82                    Some(Token::Name(n)) | Some(Token::Label(n)) => n.clone(),
83                    _ => {
84                        return Err(ParseError {
85                            msg: "define: expected a macro name".into(),
86                            line: l,
87                            col: c,
88                        });
89                    }
90                };
91                i += 1;
92                if toks.get(i).map(|s| &s.tok) != Some(&Token::LeftBrace) {
93                    let (l, c) = loc(toks, i);
94                    return Err(ParseError {
95                        msg: "define: expected `{` (only `define name { body }` is supported)"
96                            .into(),
97                        line: l,
98                        col: c,
99                    });
100                }
101                i += 1; // past `{`
102                let start = i;
103                let mut bd = 1;
104                while i < toks.len() && bd > 0 {
105                    match &toks[i].tok {
106                        Token::LeftBrace => bd += 1,
107                        Token::RightBrace => {
108                            bd -= 1;
109                            if bd == 0 {
110                                break;
111                            }
112                        }
113                        _ => {}
114                    }
115                    i += 1;
116                }
117                if bd != 0 {
118                    return Err(ParseError {
119                        msg: "define: unterminated `{` body".into(),
120                        line: l,
121                        col: c,
122                    });
123                }
124                let body = toks[start..i].to_vec();
125                i += 1; // past `}`
126                macros.insert(name, body);
127            }
128            Token::Kw(Kw::Undef) => {
129                i += 1;
130                if let Some(Token::Name(n)) | Some(Token::Label(n)) = toks.get(i).map(|s| &s.tok) {
131                    macros.remove(n);
132                }
133                i += 1;
134            }
135            Token::Name(n) if macros.contains_key(n) => {
136                let body = macros.get(n).unwrap().clone();
137                i += 1;
138                let args = if toks.get(i).map(|s| &s.tok) == Some(&Token::Lparen) {
139                    i += 1;
140                    let (a, ni) = read_args(toks, i)?;
141                    i = ni;
142                    a
143                } else {
144                    Vec::new()
145                };
146                let sub = substitute(&body, &args);
147                let expanded = expand(&sub, macros, depth + 1)?;
148                out.extend(expanded);
149            }
150            _ => {
151                out.push(toks[i].clone());
152                i += 1;
153            }
154        }
155    }
156    Ok(out)
157}
158
159/// Read comma-separated argument token-lists after a `(` (index `i` is just past
160/// it), returning the args and the index after the matching `)`.
161fn read_args(toks: &[Spanned], mut i: usize) -> Result<(Vec<Vec<Spanned>>, usize), ParseError> {
162    let mut args: Vec<Vec<Spanned>> = Vec::new();
163    let mut cur: Vec<Spanned> = Vec::new();
164    let mut depth = 0i32;
165    loop {
166        let Some(s) = toks.get(i) else {
167            return Err(ParseError {
168                msg: "unterminated macro arguments".into(),
169                line: 0,
170                col: 0,
171            });
172        };
173        match &s.tok {
174            Token::Lparen | Token::LeftBrack | Token::LeftBrace => {
175                depth += 1;
176                cur.push(s.clone());
177                i += 1;
178            }
179            Token::Rparen if depth == 0 => {
180                i += 1;
181                if !cur.is_empty() || !args.is_empty() {
182                    args.push(cur);
183                }
184                break;
185            }
186            Token::Rparen | Token::RightBrack | Token::RightBrace => {
187                depth -= 1;
188                cur.push(s.clone());
189                i += 1;
190            }
191            Token::Comma if depth == 0 => {
192                args.push(std::mem::take(&mut cur));
193                i += 1;
194            }
195            _ => {
196                cur.push(s.clone());
197                i += 1;
198            }
199        }
200    }
201    Ok((args, i))
202}
203
204/// Replace `$k` argument tokens in a macro body with the k-th argument's tokens.
205fn substitute(body: &[Spanned], args: &[Vec<Spanned>]) -> Vec<Spanned> {
206    let mut out = Vec::new();
207    for s in body {
208        if let Token::Arg(k) = s.tok {
209            if let Some(a) = args.get((k as usize).wrapping_sub(1)) {
210                out.extend(a.iter().cloned());
211            }
212        } else {
213            out.push(s.clone());
214        }
215    }
216    out
217}
218
219type PResult<T> = Result<T, ParseError>;
220
221struct Parser {
222    toks: Vec<Spanned>,
223    idx: usize,
224}
225
226impl Parser {
227    fn new(toks: Vec<Spanned>) -> Self {
228        Parser { toks, idx: 0 }
229    }
230
231    // ---- cursor helpers ----------------------------------------------------
232
233    fn cur(&self) -> &Token {
234        &self.toks[self.idx].tok
235    }
236    fn peek(&self, n: usize) -> &Token {
237        self.toks
238            .get(self.idx + n)
239            .map(|s| &s.tok)
240            .unwrap_or(&Token::Eof)
241    }
242    fn at(&self, t: &Token) -> bool {
243        self.cur() == t
244    }
245    fn bump(&mut self) -> Token {
246        let t = self.toks[self.idx].tok.clone();
247        if self.idx + 1 < self.toks.len() {
248            self.idx += 1;
249        }
250        t
251    }
252    fn eat(&mut self, t: &Token) -> bool {
253        if self.at(t) {
254            self.bump();
255            true
256        } else {
257            false
258        }
259    }
260    fn expect(&mut self, t: &Token) -> PResult<()> {
261        if self.eat(t) {
262            Ok(())
263        } else {
264            self.err(format!("expected {t:?}, found {:?}", self.cur()))
265        }
266    }
267    fn err<T>(&self, msg: impl Into<String>) -> PResult<T> {
268        let s = &self.toks[self.idx];
269        Err(ParseError {
270            msg: msg.into(),
271            line: s.line,
272            col: s.col,
273        })
274    }
275    fn at_kw(&self, k: Kw) -> bool {
276        matches!(self.cur(), Token::Kw(x) if *x == k)
277    }
278    fn eat_kw(&mut self, k: Kw) -> bool {
279        if self.at_kw(k) {
280            self.bump();
281            true
282        } else {
283            false
284        }
285    }
286    fn skip_newlines(&mut self) {
287        while self.at(&Token::Newline) {
288            self.bump();
289        }
290    }
291
292    // ---- top level ---------------------------------------------------------
293
294    fn parse_picture(&mut self) -> PResult<Picture> {
295        // `.PS`/`.PE` are treated as markers that may appear anywhere; statements
296        // (including `animate`) are collected across them up to EOF. The first
297        // `.PS` may carry optional width/height.
298        let (mut width, mut height) = (None, None);
299        let mut seen_ps = false;
300        let mut stmts = Vec::new();
301        loop {
302            self.skip_newlines();
303            match self.cur() {
304                Token::Eof => break,
305                Token::DotPS => {
306                    self.bump();
307                    if !seen_ps && self.starts_scalar() {
308                        width = Some(self.parse_expr()?);
309                        if self.starts_scalar() {
310                            height = Some(self.parse_expr()?);
311                        }
312                    }
313                    seen_ps = true;
314                    while !self.at(&Token::Newline) && !self.at(&Token::Eof) {
315                        self.bump();
316                    }
317                    continue;
318                }
319                Token::DotPE => {
320                    self.bump();
321                    continue;
322                }
323                _ => {}
324            }
325            stmts.push(self.parse_element()?);
326            if !self.at(&Token::Newline)
327                && !self.at(&Token::Eof)
328                && !self.at(&Token::DotPE)
329                && !self.at(&Token::DotPS)
330            {
331                return self.err(format!("unexpected {:?} after statement", self.cur()));
332            }
333        }
334        Ok(Picture {
335            width,
336            height,
337            stmts,
338        })
339    }
340
341    /// Parse elements until one of `terminators` (or EOF) is the current token.
342    fn parse_elementlist(&mut self, terminators: &[Token]) -> PResult<Vec<Stmt>> {
343        let mut stmts = Vec::new();
344        loop {
345            self.skip_newlines();
346            if self.at(&Token::Eof) || terminators.iter().any(|t| self.at(t)) {
347                break;
348            }
349            let s = self.parse_element()?;
350            stmts.push(s);
351            // a statement must end at a newline, a terminator, or EOF
352            if !self.at(&Token::Newline)
353                && !self.at(&Token::Eof)
354                && !terminators.iter().any(|t| self.at(t))
355            {
356                return self.err(format!("unexpected {:?} after statement", self.cur()));
357            }
358        }
359        Ok(stmts)
360    }
361
362    // ---- statements --------------------------------------------------------
363
364    fn parse_element(&mut self) -> PResult<Stmt> {
365        // rpic animation directive.
366        if self.at_kw(Kw::Animate) {
367            return Ok(Stmt::Animate(self.parse_animate()?));
368        }
369
370        // control constructs
371        match self.cur() {
372            Token::Kw(Kw::If) => return self.parse_if(),
373            Token::Kw(Kw::For) => return self.parse_for(),
374            Token::Kw(Kw::Print) => return self.parse_print(),
375            Token::Kw(Kw::Reset) => return self.parse_reset(),
376            _ => {}
377        }
378
379        // `define`/`undef` are handled by the macro preprocessor before parsing;
380        // reaching here means a non-brace form we don't support.
381        if let Token::Kw(k) = self.cur() {
382            match k {
383                Kw::Define | Kw::Undef => {
384                    return self.err("only the `define name { body }` macro form is supported");
385                }
386                Kw::Sh | Kw::Exec | Kw::Command | Kw::Copy => {
387                    let kw = format!("{k:?}").to_lowercase();
388                    return self.err(format!("`{kw}` is not supported yet (planned milestone)"));
389                }
390                _ => {}
391            }
392        }
393
394        // `{ … }` grouping
395        if self.eat(&Token::LeftBrace) {
396            let stmts = self.parse_elementlist(&[Token::RightBrace])?;
397            self.expect(&Token::RightBrace)?;
398            return Ok(Stmt::Group(stmts));
399        }
400
401        // Labelled element: `Label [suffix] : (object | position)`
402        if matches!(self.cur(), Token::Label(_)) && self.label_colon_ahead() {
403            let label = self.parse_label()?;
404            self.expect(&Token::Colon)?;
405            if self.at_object_start() {
406                let object = self.parse_object()?;
407                return Ok(Stmt::Object {
408                    label: Some(label),
409                    object,
410                });
411            } else {
412                let pos = self.parse_position()?;
413                return Ok(Stmt::Place { label, pos });
414            }
415        }
416
417        // Assignment: `name [suffix] op …` or `envvar op …`
418        if self.at_assignment_start() {
419            return Ok(Stmt::Assign(self.parse_assignlist()?));
420        }
421
422        // Bare direction change.
423        if let Token::Dir(d) = self.cur() {
424            let d = *d;
425            // Only a standalone direction (next token ends the statement).
426            if matches!(self.peek(1), Token::Newline | Token::Eof) {
427                self.bump();
428                return Ok(Stmt::Direction(d));
429            }
430        }
431
432        // Otherwise: an unlabelled object.
433        let object = self.parse_object()?;
434        Ok(Stmt::Object {
435            label: None,
436            object,
437        })
438    }
439
440    fn parse_if(&mut self) -> PResult<Stmt> {
441        self.expect_kw(Kw::If)?;
442        let cond = self.parse_expr()?;
443        self.expect_kw(Kw::Then)?;
444        self.expect(&Token::LeftBrace)?;
445        let then_body = self.parse_elementlist(&[Token::RightBrace])?;
446        self.expect(&Token::RightBrace)?;
447        let else_body = if self.eat_kw(Kw::Else) {
448            self.expect(&Token::LeftBrace)?;
449            let b = self.parse_elementlist(&[Token::RightBrace])?;
450            self.expect(&Token::RightBrace)?;
451            Some(b)
452        } else {
453            None
454        };
455        Ok(Stmt::If {
456            cond,
457            then_body,
458            else_body,
459        })
460    }
461
462    fn parse_for(&mut self) -> PResult<Stmt> {
463        self.expect_kw(Kw::For)?;
464        let var = match self.bump() {
465            Token::Name(s) => s,
466            other => return self.err(format!("expected loop variable, found {other:?}")),
467        };
468        match self.bump() {
469            Token::Eq | Token::ColonEq => {}
470            other => return self.err(format!("expected `=` in for, found {other:?}")),
471        }
472        let from = self.parse_expr()?;
473        self.expect_kw(Kw::To)?;
474        let to = self.parse_expr()?;
475        let mut by = Expr::Num(1.0);
476        let mut mult = false;
477        if self.eat_kw(Kw::By) {
478            mult = self.eat(&Token::Mult);
479            by = self.parse_expr()?;
480        }
481        self.expect_kw(Kw::Do)?;
482        self.expect(&Token::LeftBrace)?;
483        let body = self.parse_elementlist(&[Token::RightBrace])?;
484        self.expect(&Token::RightBrace)?;
485        Ok(Stmt::For {
486            var,
487            from,
488            to,
489            by,
490            mult,
491            body,
492        })
493    }
494
495    fn parse_print(&mut self) -> PResult<Stmt> {
496        self.expect_kw(Kw::Print)?;
497        let item = if self.at_string_start() {
498            PrintItem::Str(self.parse_stringexpr()?)
499        } else {
500            PrintItem::Expr(self.parse_expr()?)
501        };
502        Ok(Stmt::Print(item))
503    }
504
505    fn parse_reset(&mut self) -> PResult<Stmt> {
506        self.expect_kw(Kw::Reset)?;
507        let mut list = Vec::new();
508        if let Token::EnvVar(v) = self.cur() {
509            list.push(*v);
510            self.bump();
511            while self.eat(&Token::Comma) {
512                match self.cur() {
513                    Token::EnvVar(v) => {
514                        list.push(*v);
515                        self.bump();
516                    }
517                    other => {
518                        return self.err(format!("expected environment variable, found {other:?}"));
519                    }
520                }
521            }
522        }
523        Ok(Stmt::Reset(list))
524    }
525
526    fn at_string_start(&self) -> bool {
527        matches!(
528            self.cur(),
529            Token::Str(_) | Token::Arg(_) | Token::Kw(Kw::Sprintf)
530        )
531    }
532
533    fn parse_animate(&mut self) -> PResult<Animate> {
534        self.expect_kw(Kw::Animate)?;
535        let target = self.parse_place()?;
536        self.expect_kw(Kw::With)?;
537        let effect = self.parse_stringexpr()?;
538        let mut duration = None;
539        let mut timing = Timing::Sequential;
540        let mut delay = None;
541        loop {
542            if self.eat_kw(Kw::For) {
543                duration = Some(self.parse_expr()?);
544            } else if self.eat_kw(Kw::At) {
545                timing = Timing::At(self.parse_expr()?);
546            } else if self.eat_kw(Kw::After) {
547                timing = Timing::After(self.parse_place()?);
548            } else if self.eat_kw(Kw::Delay) {
549                delay = Some(self.parse_expr()?);
550            } else {
551                break;
552            }
553        }
554        Ok(Animate {
555            target,
556            effect,
557            duration,
558            timing,
559            delay,
560        })
561    }
562
563    /// True if the current `Label` is followed by `:` (allowing a `[suffix]`).
564    fn label_colon_ahead(&self) -> bool {
565        match self.peek(1) {
566            Token::Colon => true,
567            Token::LeftBrack => {
568                // scan past a balanced [ … ] suffix to find ':'
569                let mut depth = 0;
570                let mut i = self.idx + 1;
571                while i < self.toks.len() {
572                    match &self.toks[i].tok {
573                        Token::LeftBrack => depth += 1,
574                        Token::RightBrack => {
575                            depth -= 1;
576                            if depth == 0 {
577                                return matches!(
578                                    self.toks.get(i + 1).map(|s| &s.tok),
579                                    Some(Token::Colon)
580                                );
581                            }
582                        }
583                        Token::Newline | Token::Eof => return false,
584                        _ => {}
585                    }
586                    i += 1;
587                }
588                false
589            }
590            _ => false,
591        }
592    }
593
594    fn at_object_start(&self) -> bool {
595        matches!(
596            self.cur(),
597            Token::Prim(_) | Token::LeftBrack | Token::Block | Token::Str(_)
598        )
599    }
600
601    fn at_assignment_start(&self) -> bool {
602        let assignop = |t: &Token| {
603            matches!(
604                t,
605                Token::Eq
606                    | Token::ColonEq
607                    | Token::PlusEq
608                    | Token::MinusEq
609                    | Token::MultEq
610                    | Token::DivEq
611                    | Token::RemEq
612            )
613        };
614        match self.cur() {
615            Token::Name(_) => assignop(self.peek(1)) || matches!(self.peek(1), Token::LeftBrack),
616            Token::EnvVar(_) => assignop(self.peek(1)),
617            _ => false,
618        }
619    }
620
621    fn parse_label(&mut self) -> PResult<Label> {
622        let name = match self.bump() {
623            Token::Label(s) => s,
624            other => return self.err(format!("expected label, found {other:?}")),
625        };
626        let subscript = if self.eat(&Token::LeftBrack) {
627            let e = self.parse_expr()?;
628            self.expect(&Token::RightBrack)?;
629            Some(e)
630        } else {
631            None
632        };
633        Ok(Label { name, subscript })
634    }
635
636    fn parse_assignlist(&mut self) -> PResult<Vec<Assignment>> {
637        let mut list = vec![self.parse_assignment()?];
638        while self.eat(&Token::Comma) {
639            list.push(self.parse_assignment()?);
640        }
641        Ok(list)
642    }
643
644    fn parse_assignment(&mut self) -> PResult<Assignment> {
645        let target = match self.cur().clone() {
646            Token::Name(name) => {
647                self.bump();
648                let sub = if self.eat(&Token::LeftBrack) {
649                    let e = self.parse_expr()?;
650                    self.expect(&Token::RightBrack)?;
651                    Some(e)
652                } else {
653                    None
654                };
655                AssignTarget::Var(name, sub)
656            }
657            Token::EnvVar(v) => {
658                self.bump();
659                AssignTarget::Env(v)
660            }
661            other => return self.err(format!("expected assignment target, found {other:?}")),
662        };
663        let op = match self.bump() {
664            Token::Eq | Token::ColonEq => AssignOp::Set,
665            Token::PlusEq => AssignOp::Add,
666            Token::MinusEq => AssignOp::Sub,
667            Token::MultEq => AssignOp::Mul,
668            Token::DivEq => AssignOp::Div,
669            Token::RemEq => AssignOp::Rem,
670            other => return self.err(format!("expected assignment operator, found {other:?}")),
671        };
672        let value = self.parse_expr()?;
673        Ok(Assignment { target, op, value })
674    }
675
676    // ---- objects & attributes ---------------------------------------------
677
678    fn parse_object(&mut self) -> PResult<Object> {
679        let mut attrs = Vec::new();
680        let kind = match self.cur().clone() {
681            Token::Prim(p) => {
682                self.bump();
683                ObjectKind::Primitive(p)
684            }
685            Token::Block => {
686                self.bump();
687                ObjectKind::Empty
688            }
689            Token::LeftBrack => {
690                self.bump();
691                let stmts = self.parse_elementlist(&[Token::RightBrack])?;
692                self.expect(&Token::RightBrack)?;
693                ObjectKind::Block(stmts)
694            }
695            Token::Str(s) => {
696                self.bump();
697                attrs.push(Attr::Text(self.continue_string(StringExpr::Lit(s))?));
698                ObjectKind::Text
699            }
700            other => return self.err(format!("expected an object, found {other:?}")),
701        };
702        loop {
703            match self.parse_attr()? {
704                Some(a) => attrs.push(a),
705                None => break,
706            }
707        }
708        Ok(Object { kind, attrs })
709    }
710
711    fn parse_attr(&mut self) -> PResult<Option<Attr>> {
712        // any string expression (literal, sprintf, $arg, concatenation) is text
713        if self.at_string_start() {
714            return Ok(Some(Attr::Text(self.parse_stringexpr()?)));
715        }
716        let attr = match self.cur().clone() {
717            Token::Kw(Kw::Ht) => {
718                self.bump();
719                Attr::Dim(DimKind::Ht, self.parse_expr()?)
720            }
721            Token::Kw(Kw::Wid) => {
722                self.bump();
723                Attr::Dim(DimKind::Wid, self.parse_expr()?)
724            }
725            Token::Kw(Kw::Rad) => {
726                self.bump();
727                Attr::Dim(DimKind::Rad, self.parse_expr()?)
728            }
729            Token::Kw(Kw::Diam) => {
730                self.bump();
731                Attr::Dim(DimKind::Diam, self.parse_expr()?)
732            }
733            Token::Kw(Kw::Thick) => {
734                self.bump();
735                Attr::Dim(DimKind::Thick, self.parse_expr()?)
736            }
737            Token::Kw(Kw::Scaled) => {
738                self.bump();
739                Attr::Dim(DimKind::Scaled, self.parse_expr()?)
740            }
741            Token::Dir(d) => {
742                self.bump();
743                Attr::Direction(d, self.opt_expr()?)
744            }
745            Token::LineType(lt) => {
746                self.bump();
747                Attr::LineStyle(lt, self.opt_expr()?)
748            }
749            Token::Kw(Kw::Chop) => {
750                self.bump();
751                Attr::Chop(self.opt_expr()?)
752            }
753            Token::Kw(Kw::Fill) => {
754                self.bump();
755                Attr::Fill(self.opt_expr()?)
756            }
757            Token::Arrow(a) => {
758                self.bump();
759                Attr::Arrowhead(a, self.opt_expr()?)
760            }
761            Token::Kw(Kw::Then) => {
762                self.bump();
763                Attr::Then
764            }
765            Token::Kw(Kw::Cw) => {
766                self.bump();
767                Attr::Cw
768            }
769            Token::Kw(Kw::Ccw) => {
770                self.bump();
771                Attr::Ccw
772            }
773            Token::Kw(Kw::Same) => {
774                self.bump();
775                Attr::Same
776            }
777            Token::Kw(Kw::Continue) => {
778                self.bump();
779                Attr::Continue
780            }
781            Token::Kw(Kw::From) => {
782                self.bump();
783                Attr::From(self.parse_position()?)
784            }
785            Token::Kw(Kw::To) => {
786                self.bump();
787                Attr::To(self.parse_position()?)
788            }
789            Token::Kw(Kw::At) => {
790                self.bump();
791                Attr::At(self.parse_position()?)
792            }
793            Token::Kw(Kw::By) => {
794                self.bump();
795                Attr::By(self.parse_position()?)
796            }
797            Token::Kw(Kw::With) => {
798                self.bump();
799                let anchor = if let Token::Corner(c) = self.cur() {
800                    let c = *c;
801                    self.bump();
802                    WithAnchor::Corner(c)
803                } else if self.at(&Token::Lparen) {
804                    self.bump();
805                    let x = self.parse_expr()?;
806                    self.expect(&Token::Comma)?;
807                    let y = self.parse_expr()?;
808                    self.expect(&Token::Rparen)?;
809                    WithAnchor::Pair(x, y)
810                } else {
811                    WithAnchor::Plain
812                };
813                self.expect_kw(Kw::At)?;
814                Attr::With {
815                    anchor,
816                    at: self.parse_position()?,
817                }
818            }
819            Token::TextPos(tp) => {
820                self.bump();
821                Attr::TextPos(tp)
822            }
823            Token::Color(c) => {
824                self.bump();
825                let s = self.parse_stringexpr()?;
826                Attr::Color(c, s)
827            }
828            _ => return Ok(None),
829        };
830        Ok(Some(attr))
831    }
832
833    fn expect_kw(&mut self, k: Kw) -> PResult<()> {
834        if self.eat_kw(k) {
835            Ok(())
836        } else {
837            self.err(format!("expected `{k:?}`, found {:?}", self.cur()))
838        }
839    }
840
841    // ---- string expressions ------------------------------------------------
842
843    fn parse_stringexpr(&mut self) -> PResult<StringExpr> {
844        let first = self.parse_string_atom()?;
845        self.continue_string(first)
846    }
847
848    /// Continue a string expression with trailing `+ string` parts.
849    fn continue_string(&mut self, first: StringExpr) -> PResult<StringExpr> {
850        let mut e = first;
851        while self.at(&Token::Plus) && self.string_after_plus() {
852            self.bump();
853            let rhs = self.parse_string_atom()?;
854            e = StringExpr::Concat(Box::new(e), Box::new(rhs));
855        }
856        Ok(e)
857    }
858
859    fn string_after_plus(&self) -> bool {
860        matches!(
861            self.peek(1),
862            Token::Str(_) | Token::Arg(_) | Token::Kw(Kw::Sprintf)
863        )
864    }
865
866    fn parse_string_atom(&mut self) -> PResult<StringExpr> {
867        match self.cur().clone() {
868            Token::Str(s) => {
869                self.bump();
870                Ok(StringExpr::Lit(s))
871            }
872            Token::Arg(n) => {
873                self.bump();
874                Ok(StringExpr::Arg(n))
875            }
876            Token::Kw(Kw::Sprintf) => {
877                self.bump();
878                self.expect(&Token::Lparen)?;
879                let fmt = self.parse_stringexpr()?;
880                let mut args = Vec::new();
881                while self.eat(&Token::Comma) {
882                    args.push(self.parse_expr()?);
883                }
884                self.expect(&Token::Rparen)?;
885                Ok(StringExpr::Sprintf(Box::new(fmt), args))
886            }
887            other => self.err(format!("expected a string, found {other:?}")),
888        }
889    }
890
891    // ---- positions ---------------------------------------------------------
892
893    fn parse_position(&mut self) -> PResult<Position> {
894        if self.at(&Token::Lparen) || self.at_place_start() {
895            let loc = self.parse_location_operand()?;
896            let shifts = self.parse_shifts()?;
897            return Ok(Position::Place(loc, shifts));
898        }
899        // expression-led: pair or between
900        let e1 = self.parse_expr()?;
901        if self.eat(&Token::Comma) {
902            let e2 = self.parse_expr()?;
903            return Ok(Position::Pair(e1, e2));
904        }
905        let of_the_way = if self.eat_kw(Kw::Of) {
906            self.expect_kw(Kw::The)?;
907            self.expect_kw(Kw::Way)?;
908            self.expect_kw(Kw::Between)?;
909            true
910        } else if self.eat_kw(Kw::Between) {
911            false
912        } else {
913            return self.err("expected `,`, `between`, or `of the way between` in position");
914        };
915        let a = self.parse_position()?;
916        self.expect_kw(Kw::And)?;
917        let b = self.parse_position()?;
918        Ok(Position::Between {
919            frac: Box::new(e1),
920            a: Box::new(a),
921            b: Box::new(b),
922            of_the_way,
923        })
924    }
925
926    fn parse_shifts(&mut self) -> PResult<Vec<Shift>> {
927        let mut shifts = Vec::new();
928        loop {
929            let sign = if self.at(&Token::Plus) {
930                Sign::Plus
931            } else if self.at(&Token::Minus) {
932                Sign::Minus
933            } else {
934                break;
935            };
936            self.bump();
937            let loc = self.parse_location_operand()?;
938            shifts.push(Shift { sign, loc });
939        }
940        Ok(shifts)
941    }
942
943    /// A location used as a position component or shift operand (no trailing
944    /// shifts of its own).
945    fn parse_location_operand(&mut self) -> PResult<Location> {
946        if self.eat(&Token::Lparen) {
947            let p1 = self.parse_position()?;
948            if self.eat(&Token::Comma) {
949                let p2 = self.parse_position()?;
950                self.expect(&Token::Rparen)?;
951                Ok(Location::ParenPair(Box::new(p1), Box::new(p2)))
952            } else {
953                self.expect(&Token::Rparen)?;
954                Ok(Location::Paren(Box::new(p1)))
955            }
956        } else {
957            Ok(Location::Place(self.parse_place()?))
958        }
959    }
960
961    fn at_place_start(&self) -> bool {
962        match self.cur() {
963            Token::Label(_) | Token::Block | Token::Corner(_) => true,
964            Token::Kw(Kw::Last) | Token::Kw(Kw::Here) => true,
965            Token::Float(_) => matches!(self.peek(1), Token::Kw(Kw::Nth)),
966            _ => false,
967        }
968    }
969
970    fn parse_place(&mut self) -> PResult<Place> {
971        // `corner [of] placename`
972        if let Token::Corner(c) = self.cur() {
973            let c = *c;
974            self.bump();
975            self.eat_kw(Kw::Of);
976            let inner = self.parse_place()?;
977            return Ok(Place::CornerOf(c, Box::new(inner)));
978        }
979
980        let mut place = self.parse_place_base()?;
981
982        // trailing `.corner`, `.label`, `.nth primobj`
983        loop {
984            if let Token::Corner(c) = self.cur() {
985                let c = *c;
986                self.bump();
987                place = Place::Corner(Box::new(place), c);
988            } else if self.at(&Token::Dot) {
989                self.bump();
990                let rhs = self.parse_place_base()?;
991                place = Place::Member(Box::new(place), Box::new(rhs));
992            } else {
993                break;
994            }
995        }
996        Ok(place)
997    }
998
999    fn parse_place_base(&mut self) -> PResult<Place> {
1000        match self.cur().clone() {
1001            Token::Kw(Kw::Here) => {
1002                self.bump();
1003                Ok(Place::Here)
1004            }
1005            Token::Label(name) => {
1006                self.bump();
1007                let subscript = if self.eat(&Token::LeftBrack) {
1008                    let e = self.parse_expr()?;
1009                    self.expect(&Token::RightBrack)?;
1010                    Some(Box::new(e))
1011                } else {
1012                    None
1013                };
1014                Ok(Place::Name { name, subscript })
1015            }
1016            Token::Kw(Kw::Last) | Token::Float(_) => {
1017                let count = self.parse_nth()?;
1018                let obj = self.parse_primobj()?;
1019                Ok(Place::Nth { count, obj })
1020            }
1021            other => self.err(format!("expected a place, found {other:?}")),
1022        }
1023    }
1024
1025    fn parse_nth(&mut self) -> PResult<Nth> {
1026        if self.eat_kw(Kw::Last) {
1027            return Ok(Nth::Last);
1028        }
1029        // ncount ordinal [last]
1030        let e = self.parse_ncount()?;
1031        self.expect_kw(Kw::Nth)?;
1032        let from_last = self.eat_kw(Kw::Last);
1033        Ok(Nth::Count(Box::new(e), from_last))
1034    }
1035
1036    /// An ordinal count: a number, `` `expr' ``, or `{ expr }` (grammar:
1037    /// `ncount`). Must NOT recurse into the general expression grammar on a
1038    /// bare number, or `2nd` would re-enter place parsing.
1039    fn parse_ncount(&mut self) -> PResult<Expr> {
1040        match self.cur().clone() {
1041            Token::Float(v) => {
1042                self.bump();
1043                Ok(Expr::Num(v))
1044            }
1045            Token::LeftBrace => {
1046                self.bump();
1047                let e = self.parse_expr()?;
1048                self.expect(&Token::RightBrace)?;
1049                Ok(e)
1050            }
1051            Token::LeftQuote => {
1052                self.bump();
1053                let e = self.parse_expr()?;
1054                self.expect(&Token::RightQuote)?;
1055                Ok(e)
1056            }
1057            other => self.err(format!("expected an ordinal count, found {other:?}")),
1058        }
1059    }
1060
1061    fn parse_primobj(&mut self) -> PResult<PrimObj> {
1062        match self.cur().clone() {
1063            Token::Prim(p) => {
1064                self.bump();
1065                Ok(PrimObj::Prim(p))
1066            }
1067            Token::Block => {
1068                self.bump();
1069                Ok(PrimObj::Block)
1070            }
1071            Token::Str(s) => {
1072                self.bump();
1073                Ok(PrimObj::Str(s))
1074            }
1075            Token::LeftBrack => {
1076                self.bump();
1077                self.expect(&Token::RightBrack)?;
1078                Ok(PrimObj::EmptyBrack)
1079            }
1080            other => self.err(format!("expected a primitive object, found {other:?}")),
1081        }
1082    }
1083
1084    // ---- expressions -------------------------------------------------------
1085
1086    fn opt_expr(&mut self) -> PResult<Option<Expr>> {
1087        if self.starts_scalar() {
1088            Ok(Some(self.parse_expr()?))
1089        } else {
1090            Ok(None)
1091        }
1092    }
1093
1094    fn starts_scalar(&self) -> bool {
1095        matches!(
1096            self.cur(),
1097            Token::Float(_)
1098                | Token::Name(_)
1099                | Token::EnvVar(_)
1100                | Token::Lparen
1101                | Token::Minus
1102                | Token::Plus
1103                | Token::Not
1104                | Token::Func1(_)
1105                | Token::Func2(_)
1106                | Token::Kw(Kw::Rand)
1107        )
1108    }
1109
1110    fn parse_expr(&mut self) -> PResult<Expr> {
1111        self.parse_or()
1112    }
1113
1114    fn parse_or(&mut self) -> PResult<Expr> {
1115        let mut e = self.parse_and()?;
1116        while self.eat(&Token::OrOr) {
1117            let r = self.parse_and()?;
1118            e = Expr::Bin(BinOp::Or, Box::new(e), Box::new(r));
1119        }
1120        Ok(e)
1121    }
1122
1123    fn parse_and(&mut self) -> PResult<Expr> {
1124        let mut e = self.parse_cmp()?;
1125        while self.eat(&Token::AndAnd) {
1126            let r = self.parse_cmp()?;
1127            e = Expr::Bin(BinOp::And, Box::new(e), Box::new(r));
1128        }
1129        Ok(e)
1130    }
1131
1132    fn parse_cmp(&mut self) -> PResult<Expr> {
1133        let mut e = self.parse_add()?;
1134        loop {
1135            let op = match self.cur() {
1136                Token::EqEq => BinOp::Eq,
1137                Token::Neq => BinOp::Ne,
1138                Token::Lt => BinOp::Lt,
1139                Token::Le => BinOp::Le,
1140                Token::Gt => BinOp::Gt,
1141                Token::Ge => BinOp::Ge,
1142                _ => break,
1143            };
1144            self.bump();
1145            let r = self.parse_add()?;
1146            e = Expr::Bin(op, Box::new(e), Box::new(r));
1147        }
1148        Ok(e)
1149    }
1150
1151    fn parse_add(&mut self) -> PResult<Expr> {
1152        let mut e = self.parse_mul()?;
1153        loop {
1154            let op = match self.cur() {
1155                Token::Plus => BinOp::Add,
1156                Token::Minus => BinOp::Sub,
1157                _ => break,
1158            };
1159            self.bump();
1160            let r = self.parse_mul()?;
1161            e = Expr::Bin(op, Box::new(e), Box::new(r));
1162        }
1163        Ok(e)
1164    }
1165
1166    fn parse_mul(&mut self) -> PResult<Expr> {
1167        let mut e = self.parse_unary()?;
1168        loop {
1169            let op = match self.cur() {
1170                Token::Mult => BinOp::Mul,
1171                Token::Div => BinOp::Div,
1172                Token::Percent => BinOp::Mod,
1173                _ => break,
1174            };
1175            self.bump();
1176            let r = self.parse_unary()?;
1177            e = Expr::Bin(op, Box::new(e), Box::new(r));
1178        }
1179        Ok(e)
1180    }
1181
1182    fn parse_unary(&mut self) -> PResult<Expr> {
1183        let op = match self.cur() {
1184            Token::Minus => Some(UnOp::Neg),
1185            Token::Plus => Some(UnOp::Pos),
1186            Token::Not => Some(UnOp::Not),
1187            _ => None,
1188        };
1189        if let Some(op) = op {
1190            self.bump();
1191            let e = self.parse_unary()?;
1192            Ok(Expr::Unary(op, Box::new(e)))
1193        } else {
1194            self.parse_pow()
1195        }
1196    }
1197
1198    fn parse_pow(&mut self) -> PResult<Expr> {
1199        let base = self.parse_primary()?;
1200        if self.eat(&Token::Caret) {
1201            let exp = self.parse_unary()?; // right-associative
1202            Ok(Expr::Bin(BinOp::Pow, Box::new(base), Box::new(exp)))
1203        } else {
1204            Ok(base)
1205        }
1206    }
1207
1208    fn parse_primary(&mut self) -> PResult<Expr> {
1209        // place-derived scalars: location.x / location.y / place.attr
1210        if self.at_place_start() {
1211            return self.parse_place_scalar();
1212        }
1213        match self.cur().clone() {
1214            Token::Float(v) => {
1215                self.bump();
1216                Ok(Expr::Num(v))
1217            }
1218            Token::Name(name) => {
1219                self.bump();
1220                // optional subscript suffix is parsed and ignored for now
1221                if self.eat(&Token::LeftBrack) {
1222                    let _ = self.parse_expr()?;
1223                    self.expect(&Token::RightBrack)?;
1224                }
1225                Ok(Expr::Var(name))
1226            }
1227            Token::EnvVar(v) => {
1228                self.bump();
1229                Ok(Expr::Env(v))
1230            }
1231            Token::Lparen => {
1232                self.bump();
1233                // could be ( expr ) or a parenthesised location used with .x/.y
1234                let e = self.parse_expr()?;
1235                self.expect(&Token::Rparen)?;
1236                Ok(e)
1237            }
1238            Token::Func1(f) => {
1239                self.bump();
1240                self.expect(&Token::Lparen)?;
1241                let e = self.parse_expr()?;
1242                self.expect(&Token::Rparen)?;
1243                Ok(Expr::Func1(f, Box::new(e)))
1244            }
1245            Token::Func2(f) => {
1246                self.bump();
1247                self.expect(&Token::Lparen)?;
1248                let a = self.parse_expr()?;
1249                self.expect(&Token::Comma)?;
1250                let b = self.parse_expr()?;
1251                self.expect(&Token::Rparen)?;
1252                Ok(Expr::Func2(f, Box::new(a), Box::new(b)))
1253            }
1254            Token::Kw(Kw::Rand) => {
1255                self.bump();
1256                self.expect(&Token::Lparen)?;
1257                let arg = if self.at(&Token::Rparen) {
1258                    None
1259                } else {
1260                    Some(Box::new(self.parse_expr()?))
1261                };
1262                self.expect(&Token::Rparen)?;
1263                Ok(Expr::Rand(arg))
1264            }
1265            other => self.err(format!("expected an expression, found {other:?}")),
1266        }
1267    }
1268
1269    /// Parse a place followed by `.x` / `.y` / `.attr` to yield a scalar.
1270    fn parse_place_scalar(&mut self) -> PResult<Expr> {
1271        let place = self.parse_place()?;
1272        match self.cur().clone() {
1273            Token::DotX => {
1274                self.bump();
1275                Ok(Expr::DotX(Location::Place(place)))
1276            }
1277            Token::DotY => {
1278                self.bump();
1279                Ok(Expr::DotY(Location::Place(place)))
1280            }
1281            Token::Param(p) => {
1282                self.bump();
1283                Ok(Expr::PlaceAttr(place, p))
1284            }
1285            other => self.err(format!(
1286                "a place is not a number here; expected `.x`, `.y`, or an attribute, found {other:?}"
1287            )),
1288        }
1289    }
1290}
1291
1292#[cfg(test)]
1293mod tests {
1294    use super::*;
1295
1296    fn pic(src: &str) -> Picture {
1297        parse(src).unwrap_or_else(|e| panic!("parse error: {e}"))
1298    }
1299
1300    #[test]
1301    fn kernighan_pipeline() {
1302        let p = pic(r#".PS
1303ellipse "document"
1304arrow
1305box "PIC"
1306arrow
1307box "TBL/EQN" "(optional)" dashed
1308arrow
1309box "TROFF"
1310arrow
1311ellipse "typesetter"
1312.PE
1313"#);
1314        assert_eq!(p.stmts.len(), 9);
1315        // the dashed box with two strings
1316        if let Stmt::Object { object, .. } = &p.stmts[4] {
1317            assert_eq!(object.kind, ObjectKind::Primitive(Prim::Box));
1318            let texts = object
1319                .attrs
1320                .iter()
1321                .filter(|a| matches!(a, Attr::Text(_)))
1322                .count();
1323            assert_eq!(texts, 2);
1324            assert!(
1325                object
1326                    .attrs
1327                    .iter()
1328                    .any(|a| matches!(a, Attr::LineStyle(LineType::Dashed, _)))
1329            );
1330        } else {
1331            panic!("expected object");
1332        }
1333    }
1334
1335    #[test]
1336    fn box_with_dims_and_at() {
1337        let p = pic("box ht 0.3 wid 0.5 at 0.25,0.15");
1338        let Stmt::Object { object, .. } = &p.stmts[0] else {
1339            panic!()
1340        };
1341        assert!(matches!(object.attrs[0], Attr::Dim(DimKind::Ht, _)));
1342        assert!(matches!(object.attrs[1], Attr::Dim(DimKind::Wid, _)));
1343        assert!(matches!(object.attrs[2], Attr::At(Position::Pair(_, _))));
1344    }
1345
1346    #[test]
1347    fn labeled_and_corners() {
1348        let p = pic("B1: box\narc -> from top of B1 to last box.ne");
1349        assert!(matches!(p.stmts[0], Stmt::Object { label: Some(_), .. }));
1350        let Stmt::Object { object, .. } = &p.stmts[1] else {
1351            panic!()
1352        };
1353        assert_eq!(object.kind, ObjectKind::Primitive(Prim::Arc));
1354        assert!(
1355            object
1356                .attrs
1357                .iter()
1358                .any(|a| matches!(a, Attr::Arrowhead(Arrow::Right, _)))
1359        );
1360        // from top of B1
1361        assert!(object.attrs.iter().any(|a| matches!(
1362            a,
1363            Attr::From(Position::Place(
1364                Location::Place(Place::CornerOf(Corner::N, _)),
1365                _
1366            ))
1367        )));
1368    }
1369
1370    #[test]
1371    fn with_at_and_shift() {
1372        let p = pic("ellipse \"2\" with .nw at last ellipse.se + (0.1,0)");
1373        let Stmt::Object { object, .. } = &p.stmts[0] else {
1374            panic!()
1375        };
1376        let with = object
1377            .attrs
1378            .iter()
1379            .find(|a| matches!(a, Attr::With { .. }))
1380            .unwrap();
1381        let Attr::With { anchor, at } = with else {
1382            panic!()
1383        };
1384        assert_eq!(*anchor, WithAnchor::Corner(Corner::Nw));
1385        assert!(matches!(at, Position::Place(_, shifts) if shifts.len() == 1));
1386    }
1387
1388    #[test]
1389    fn expression_precedence() {
1390        // 2 + 3 * 4 ^ 2  ==  2 + (3 * (4^2)) = 50
1391        let p = pic("x = 2 + 3 * 4 ^ 2");
1392        let Stmt::Assign(list) = &p.stmts[0] else {
1393            panic!()
1394        };
1395        // structure: Add(2, Mul(3, Pow(4,2)))
1396        let Expr::Bin(BinOp::Add, _, rhs) = &list[0].value else {
1397            panic!("expected top-level add")
1398        };
1399        assert!(matches!(**rhs, Expr::Bin(BinOp::Mul, _, _)));
1400    }
1401
1402    #[test]
1403    fn between_position() {
1404        let p = pic("arrow from 1/3 of the way between A.ne and A.se");
1405        let Stmt::Object { object, .. } = &p.stmts[0] else {
1406            panic!()
1407        };
1408        assert!(object.attrs.iter().any(|a| matches!(
1409            a,
1410            Attr::From(Position::Between {
1411                of_the_way: true,
1412                ..
1413            })
1414        )));
1415    }
1416
1417    #[test]
1418    fn assignment_list_and_envvar() {
1419        let p = pic("boxht = 0.3; boxwid = 2 * boxht");
1420        assert_eq!(p.stmts.len(), 2);
1421        let Stmt::Assign(a0) = &p.stmts[0] else {
1422            panic!()
1423        };
1424        assert_eq!(a0[0].target, AssignTarget::Env(EnvVar::Boxht));
1425    }
1426
1427    #[test]
1428    fn block_object() {
1429        let p = pic("[ box; circle ] with .nw at Here");
1430        let Stmt::Object { object, .. } = &p.stmts[0] else {
1431            panic!()
1432        };
1433        let ObjectKind::Block(inner) = &object.kind else {
1434            panic!()
1435        };
1436        assert_eq!(inner.len(), 2);
1437    }
1438
1439    #[test]
1440    fn diamond_line_with_then() {
1441        let p = pic("line up right then down right then down left then up left");
1442        let Stmt::Object { object, .. } = &p.stmts[0] else {
1443            panic!()
1444        };
1445        let thens = object
1446            .attrs
1447            .iter()
1448            .filter(|a| matches!(a, Attr::Then))
1449            .count();
1450        assert_eq!(thens, 3);
1451    }
1452
1453    #[test]
1454    fn unsupported_control_is_clear() {
1455        let e = parse("sh \"ls\"").unwrap_err();
1456        assert!(e.msg.contains("not supported yet"));
1457    }
1458
1459    #[test]
1460    fn control_constructs_parse() {
1461        assert!(parse("for i = 1 to 3 do { box }").is_ok());
1462        assert!(parse("if 1 > 0 then { box } else { circle }").is_ok());
1463        assert!(parse("reset boxht, boxwid").is_ok());
1464        // define is consumed by the preprocessor and expanded
1465        let p = parse("define e { box }\ne\ne").unwrap();
1466        assert_eq!(p.stmts.len(), 2);
1467    }
1468}