Skip to main content

fabula_dsl/
parser.rs

1//! Recursive descent parser for the fabula DSL.
2//!
3//! The parser is designed for composability: downstream DSLs can reuse it to
4//! parse fabula pattern syntax embedded in their own blocks. Key entry points:
5//!
6//! - [`Parser::parse_pattern_body()`] — parse stages, negations, and temporals
7//!   without the `pattern name { }` wrapper
8//! - [`Parser::pos()`] / [`Parser::into_inner()`] — read or recover the cursor
9//!   position for resumable parsing
10//! - [`Parser::from_tokens_at()`] — construct a parser at a specific position
11//!   in an existing token stream
12
13use crate::ast::*;
14use crate::error::ParseError;
15use crate::lexer::{Token, TokenKind};
16
17/// Parser state: a cursor over a token stream.
18pub struct Parser {
19    tokens: Vec<Token>,
20    pos: usize,
21}
22
23impl Parser {
24    /// Create a new parser from a token stream, starting at position 0.
25    pub fn new(tokens: Vec<Token>) -> Self {
26        Self { tokens, pos: 0 }
27    }
28
29    /// Create a parser starting at a specific position in the token stream.
30    ///
31    /// Use this to resume parsing after handing the token stream to another
32    /// parser (e.g., a downstream DSL parser that calls fabula's parser for
33    /// pattern sections).
34    pub fn from_tokens_at(tokens: Vec<Token>, pos: usize) -> Self {
35        Self { tokens, pos }
36    }
37
38    /// Current cursor position in the token stream.
39    pub fn pos(&self) -> usize {
40        self.pos
41    }
42
43    /// Consume the parser, returning the token stream and cursor position.
44    ///
45    /// Use this to recover the tokens after parsing a section, so a
46    /// downstream DSL can continue parsing from where fabula left off.
47    pub fn into_inner(self) -> (Vec<Token>, usize) {
48        (self.tokens, self.pos)
49    }
50
51    // ---- Document-level parsing ----
52
53    /// Parse a complete document (patterns, graphs, and compose directives).
54    pub fn parse_document(&mut self) -> Result<Document, ParseError> {
55        let mut items = Vec::new();
56
57        while !self.at_eof() {
58            match &self.peek().kind {
59                TokenKind::Ident(ref s) if s == "private" => {
60                    self.advance(); // consume "private"
61                                    // Next token must be "pattern"
62                    if !self.check(TokenKind::Pattern) {
63                        return Err(self.error("expected 'pattern' after 'private'"));
64                    }
65                    let mut pat = self.parse_pattern()?;
66                    pat.private = true;
67                    items.push(DocumentItem::Pattern(pat));
68                }
69                TokenKind::Pattern => items.push(DocumentItem::Pattern(self.parse_pattern()?)),
70                TokenKind::Graph => items.push(DocumentItem::Graph(self.parse_graph()?)),
71                TokenKind::Compose => items.push(DocumentItem::Compose(self.parse_compose()?)),
72                _ => return Err(self.error("expected 'pattern', 'graph', or 'compose'")),
73            }
74        }
75
76        Ok(Document { items })
77    }
78
79    /// Parse a single pattern declaration, then assert EOF.
80    pub fn parse_pattern_only(&mut self) -> Result<PatternAst, ParseError> {
81        let pat = self.parse_pattern()?;
82        if !self.at_eof() {
83            return Err(self.error("unexpected content after pattern"));
84        }
85        Ok(pat)
86    }
87
88    /// Parse a single graph declaration, then assert EOF.
89    pub fn parse_graph_only(&mut self) -> Result<GraphAst, ParseError> {
90        let g = self.parse_graph()?;
91        if !self.at_eof() {
92            return Err(self.error("unexpected content after graph"));
93        }
94        Ok(g)
95    }
96
97    // ---- Pattern parsing ----
98
99    /// Parse a full `pattern name { ... }` declaration.
100    pub fn parse_pattern(&mut self) -> Result<PatternAst, ParseError> {
101        self.expect(TokenKind::Pattern)?;
102        let name = self.expect_ident()?;
103        self.expect(TokenKind::LBrace)?;
104        let body = self.parse_pattern_body()?;
105        self.expect(TokenKind::RBrace)?;
106        Ok(PatternAst {
107            name,
108            stages: body.stages,
109            negations: body.negations,
110            temporals: body.temporals,
111            metadata: body.metadata,
112            deadline: body.deadline,
113            unordered_groups: body.unordered_groups,
114            private: false,
115        })
116    }
117
118    /// Parse the body of a pattern — stages, negations, and temporal
119    /// constraints — without the `pattern name { }` wrapper.
120    ///
121    /// Stops when it sees `}` or EOF but does **not** consume the closing
122    /// brace. The caller owns the block structure and is responsible for
123    /// consuming the delimiter.
124    ///
125    /// This is the primary composability entry point for downstream DSLs
126    /// that embed fabula pattern syntax in their own blocks:
127    ///
128    /// ```rust,ignore
129    /// // salience-dsl example
130    /// parser.expect_ident()?;             // "precondition"
131    /// parser.expect(TokenKind::LBrace)?;  // {
132    /// let body = parser.parse_pattern_body()?;
133    /// parser.expect(TokenKind::RBrace)?;  // }
134    /// let pattern = compile_pattern_body_with("name", &body, &mapper)?;
135    /// ```
136    pub fn parse_pattern_body(&mut self) -> Result<PatternBody, ParseError> {
137        let mut stages = Vec::new();
138        let mut negations = Vec::new();
139        let mut temporals = Vec::new();
140        let mut metadata = Vec::new();
141        let mut deadline = None;
142        let mut unordered_groups = Vec::new();
143
144        while !self.check(TokenKind::RBrace) && !self.at_eof() {
145            match &self.peek().kind {
146                TokenKind::Stage => stages.push(self.parse_stage()?),
147                TokenKind::Unless => negations.push(self.parse_negation()?),
148                TokenKind::Temporal => temporals.push(self.parse_temporal()?),
149                TokenKind::Concurrent => {
150                    self.advance();
151                    self.expect(TokenKind::LBrace)?;
152                    let group_start = stages.len();
153                    while !self.check(TokenKind::RBrace) && !self.at_eof() {
154                        if !self.check(TokenKind::Stage) {
155                            return Err(
156                                self.error("only 'stage' blocks are allowed inside 'concurrent'")
157                            );
158                        }
159                        stages.push(self.parse_stage()?);
160                    }
161                    self.expect(TokenKind::RBrace)?;
162                    let group_end = stages.len();
163                    if group_end > group_start {
164                        let indices: Vec<usize> = (group_start..group_end).collect();
165                        unordered_groups.push(indices);
166                    }
167                }
168                TokenKind::Ident(s) if s == "meta" => {
169                    metadata.push(self.parse_meta()?);
170                }
171                TokenKind::Ident(s) if s == "deadline" => {
172                    self.advance();
173                    deadline = Some(self.expect_number()?);
174                }
175                _ => return Err(self.error(
176                    "expected 'stage', 'unless', 'temporal', 'concurrent', 'meta', or 'deadline'",
177                )),
178            }
179        }
180
181        Ok(PatternBody {
182            stages,
183            negations,
184            temporals,
185            metadata,
186            deadline,
187            unordered_groups,
188            private: false,
189        })
190    }
191
192    /// Parse a `meta("key", "value")` clause.
193    fn parse_meta(&mut self) -> Result<(String, String), ParseError> {
194        // "meta" identifier already matched by caller; consume it
195        self.advance();
196        self.expect(TokenKind::LParen)?;
197        let key = match &self.peek().kind {
198            TokenKind::String(_) => {
199                if let TokenKind::String(s) = &self.advance().kind {
200                    s.clone()
201                } else {
202                    unreachable!()
203                }
204            }
205            _ => return Err(self.error("expected string literal for meta key")),
206        };
207        self.expect(TokenKind::Comma)?;
208        let value = match &self.peek().kind {
209            TokenKind::String(_) => {
210                if let TokenKind::String(s) = &self.advance().kind {
211                    s.clone()
212                } else {
213                    unreachable!()
214                }
215            }
216            _ => return Err(self.error("expected string literal for meta value")),
217        };
218        self.expect(TokenKind::RParen)?;
219        Ok((key, value))
220    }
221
222    /// Parse a `stage anchor { clauses... }` block.
223    pub fn parse_stage(&mut self) -> Result<StageAst, ParseError> {
224        self.expect(TokenKind::Stage)?;
225        let anchor = self.expect_ident()?;
226        self.expect(TokenKind::LBrace)?;
227
228        let mut clauses = Vec::new();
229        while !self.check(TokenKind::RBrace) {
230            clauses.push(self.parse_clause()?);
231        }
232
233        self.expect(TokenKind::RBrace)?;
234        Ok(StageAst { anchor, clauses })
235    }
236
237    /// Parse an `unless [between|after] ... { clauses }` negation block.
238    pub fn parse_negation(&mut self) -> Result<NegationAst, ParseError> {
239        self.expect(TokenKind::Unless)?;
240
241        let kind = if self.check(TokenKind::Between) {
242            self.advance();
243            let start = self.expect_ident()?;
244            let end = self.expect_ident()?;
245            NegationKind::Between(start, end)
246        } else if self.check(TokenKind::After) {
247            self.advance();
248            let start = self.expect_ident()?;
249            NegationKind::After(start)
250        } else {
251            // Global negation: unless { ... }
252            NegationKind::Global
253        };
254
255        self.expect(TokenKind::LBrace)?;
256        let mut clauses = Vec::new();
257        while !self.check(TokenKind::RBrace) {
258            clauses.push(self.parse_clause()?);
259        }
260        self.expect(TokenKind::RBrace)?;
261
262        Ok(NegationAst { kind, clauses })
263    }
264
265    /// Parse a `temporal left relation right [gap range]` constraint.
266    pub fn parse_temporal(&mut self) -> Result<TemporalAst, ParseError> {
267        self.expect(TokenKind::Temporal)?;
268        let left = self.expect_ident()?;
269        let relation = self.expect_ident()?;
270        let right = self.expect_ident()?;
271
272        // Optional: gap min..max
273        let (gap_min, gap_max) = if matches!(self.peek().kind, TokenKind::Ident(ref s) if s == "gap")
274        {
275            self.advance();
276            self.parse_gap_range()?
277        } else {
278            (None, None)
279        };
280
281        Ok(TemporalAst {
282            left,
283            relation,
284            right,
285            gap_min,
286            gap_max,
287        })
288    }
289
290    fn parse_gap_range(&mut self) -> Result<(Option<f64>, Option<f64>), ParseError> {
291        // Syntax: 3..10, ..10, 3..
292        if self.check(TokenKind::DotDot) {
293            // ..max (no min)
294            self.advance();
295            let max = self.expect_number()?;
296            Ok((None, Some(max)))
297        } else if matches!(self.peek().kind, TokenKind::Number(_) | TokenKind::Minus) {
298            let first = self.expect_number()?;
299            if self.check(TokenKind::DotDot) {
300                self.advance();
301                // min.. or min..max
302                if matches!(self.peek().kind, TokenKind::Number(_) | TokenKind::Minus) {
303                    let second = self.expect_number()?;
304                    Ok((Some(first), Some(second)))
305                } else {
306                    // min.. (no max)
307                    Ok((Some(first), None))
308                }
309            } else {
310                // Single number = exact gap (min == max)
311                Ok((Some(first), Some(first)))
312            }
313        } else {
314            Err(self.error("expected gap range (e.g., '3..10', '..10', '3..')"))
315        }
316    }
317
318    // ---- Compose parsing ----
319
320    /// Parse a `compose name = ...` directive.
321    pub fn parse_compose(&mut self) -> Result<ComposeAst, ParseError> {
322        self.expect(TokenKind::Compose)?;
323        let name = self.expect_ident()?;
324        self.expect(TokenKind::Eq)?;
325
326        // First operand is always a pattern name
327        let first = self.expect_ident()?;
328
329        // Determine operator: >> (sequence), | (choice), * (repeat)
330        let body = if self.check(TokenKind::GtGt) {
331            // Sequence: first >> second sharing(...)
332            self.advance();
333            let second = self.expect_ident()?;
334            let shared = self.parse_sharing_clause()?;
335            ComposeBody::Sequence {
336                left: first,
337                right: second,
338                shared,
339            }
340        } else if self.check(TokenKind::Pipe) {
341            // Choice: first | second | third ...
342            let mut alternatives = vec![first];
343            while self.check(TokenKind::Pipe) {
344                self.advance();
345                alternatives.push(self.expect_ident()?);
346            }
347            let exclusive = match &self.peek().kind {
348                TokenKind::Ident(s) if s == "nonexclusive" => {
349                    self.advance();
350                    false
351                }
352                _ => true,
353            };
354            ComposeBody::Choice {
355                alternatives,
356                exclusive,
357            }
358        } else if self.check(TokenKind::Star) {
359            // Repeat: first * N sharing(...) or first * N..M sharing(...) or first * N.. sharing(...)
360            self.advance();
361            let min = self.expect_number()? as usize;
362            let max = if self.check(TokenKind::DotDot) {
363                self.advance();
364                // Check for explicit max or unbounded
365                if matches!(self.peek().kind, TokenKind::Number(_) | TokenKind::Minus) {
366                    Some(Some(self.expect_number()? as usize))
367                } else {
368                    Some(None) // unbounded: N..
369                }
370            } else {
371                None // exact: N
372            };
373            let shared = self.parse_sharing_clause()?;
374            match max {
375                None => {
376                    // Exact: * N → min=N, max=Some(N)
377                    ComposeBody::Repeat {
378                        pattern: first,
379                        min,
380                        max: Some(min),
381                        shared,
382                    }
383                }
384                Some(max_val) => {
385                    // Range: * N..M or * N..
386                    ComposeBody::Repeat {
387                        pattern: first,
388                        min,
389                        max: max_val,
390                        shared,
391                    }
392                }
393            }
394        } else {
395            return Err(self.error("expected '>>' (sequence), '|' (choice), or '*' (repeat)"));
396        };
397
398        Ok(ComposeAst { name, body })
399    }
400
401    fn parse_sharing_clause(&mut self) -> Result<Vec<String>, ParseError> {
402        if !self.check(TokenKind::Sharing) {
403            return Ok(Vec::new());
404        }
405        self.advance(); // consume 'sharing'
406        self.expect(TokenKind::LParen)?;
407
408        let mut vars = vec![self.expect_ident()?];
409        while self.check(TokenKind::Comma) {
410            self.advance();
411            vars.push(self.expect_ident()?);
412        }
413
414        self.expect(TokenKind::RParen)?;
415        Ok(vars)
416    }
417
418    /// Parse a single clause: `[!] [?]source.label = | -> | < | > | <= | >= target`.
419    pub fn parse_clause(&mut self) -> Result<ClauseAst, ParseError> {
420        // Optional negation prefix: !
421        let negated = if self.check(TokenKind::Bang) {
422            self.advance();
423            true
424        } else {
425            false
426        };
427
428        // Check for ?var source (variable reference) vs bare literal
429        let source_kind = if self.check(TokenKind::Question) {
430            self.advance();
431            // Must be followed by an identifier (the variable name)
432            if !matches!(self.peek().kind, TokenKind::Ident(_)) {
433                return Err(self.error("expected variable name after '?'"));
434            }
435            SourceKind::Var
436        } else {
437            SourceKind::Literal
438        };
439
440        let source = self.expect_ident()?;
441        self.expect(TokenKind::Dot)?;
442        let label = self.expect_ident_or_string()?;
443
444        // Now: = value, = ?var, -> ?var, -> node, < num, < ?var, > num, > ?var, <= num, <= ?var, >= num, >= ?var
445        let target = if self.check(TokenKind::Eq) {
446            self.advance();
447            if self.check(TokenKind::Question) {
448                self.advance();
449                let var = self.expect_ident()?;
450                ClauseTarget::ConstraintVar(ConstraintOp::Eq, var)
451            } else {
452                self.parse_literal_target()?
453            }
454        } else if self.check(TokenKind::Arrow) {
455            self.advance();
456            if self.check(TokenKind::Question) {
457                self.advance();
458                let var = self.expect_ident()?;
459                ClauseTarget::Bind(var)
460            } else {
461                let node = self.expect_ident()?;
462                ClauseTarget::NodeRef(node)
463            }
464        } else if self.check(TokenKind::Lt) {
465            self.advance();
466            self.parse_constraint_target(ConstraintOp::Lt)?
467        } else if self.check(TokenKind::Gt) {
468            self.advance();
469            self.parse_constraint_target(ConstraintOp::Gt)?
470        } else if self.check(TokenKind::Lte) {
471            self.advance();
472            self.parse_constraint_target(ConstraintOp::Lte)?
473        } else if self.check(TokenKind::Gte) {
474            self.advance();
475            self.parse_constraint_target(ConstraintOp::Gte)?
476        } else {
477            return Err(self.error("expected '=', '->', '<', '>', '<=', or '>='"));
478        };
479
480        Ok(ClauseAst {
481            source,
482            source_kind,
483            label,
484            target,
485            negated,
486        })
487    }
488
489    fn parse_literal_target(&mut self) -> Result<ClauseTarget, ParseError> {
490        // Handle optional leading minus for negative number literals
491        if self.check(TokenKind::Minus) {
492            self.advance();
493            if let TokenKind::Number(n) = &self.advance().kind {
494                return Ok(ClauseTarget::LiteralNum(-n));
495            }
496            return Err(self.error("expected number after '-'"));
497        }
498        match &self.peek().kind {
499            TokenKind::String(_) => {
500                if let TokenKind::String(s) = &self.advance().kind {
501                    Ok(ClauseTarget::LiteralStr(s.clone()))
502                } else {
503                    unreachable!()
504                }
505            }
506            TokenKind::Number(_) => {
507                if let TokenKind::Number(n) = &self.advance().kind {
508                    Ok(ClauseTarget::LiteralNum(*n))
509                } else {
510                    unreachable!()
511                }
512            }
513            TokenKind::True => {
514                self.advance();
515                Ok(ClauseTarget::LiteralBool(true))
516            }
517            TokenKind::False => {
518                self.advance();
519                Ok(ClauseTarget::LiteralBool(false))
520            }
521            _ => Err(self.error("expected a string, number, or boolean value")),
522        }
523    }
524
525    fn parse_constraint_target(&mut self, op: ConstraintOp) -> Result<ClauseTarget, ParseError> {
526        if self.check(TokenKind::Question) {
527            self.advance();
528            let var = self.expect_ident()?;
529            Ok(ClauseTarget::ConstraintVar(op, var))
530        } else {
531            let val = self.parse_constraint_value()?;
532            Ok(ClauseTarget::Constraint(op, val))
533        }
534    }
535
536    fn parse_constraint_value(&mut self) -> Result<ConstraintValue, ParseError> {
537        let negative = if self.check(TokenKind::Minus) {
538            self.advance();
539            true
540        } else {
541            false
542        };
543        match &self.peek().kind {
544            TokenKind::Number(_) => {
545                if let TokenKind::Number(n) = &self.advance().kind {
546                    Ok(ConstraintValue::Num(if negative { -*n } else { *n }))
547                } else {
548                    unreachable!()
549                }
550            }
551            TokenKind::String(_) if !negative => {
552                if let TokenKind::String(s) = &self.advance().kind {
553                    Ok(ConstraintValue::Str(s.clone()))
554                } else {
555                    unreachable!()
556                }
557            }
558            _ => Err(self.error("expected a number or string value")),
559        }
560    }
561
562    // ---- Graph parsing ----
563
564    /// Parse a `graph { ... }` declaration.
565    pub fn parse_graph(&mut self) -> Result<GraphAst, ParseError> {
566        self.expect(TokenKind::Graph)?;
567        self.expect(TokenKind::LBrace)?;
568
569        let mut edges = Vec::new();
570        let mut now = None;
571
572        while !self.check(TokenKind::RBrace) {
573            if self.check(TokenKind::Now) {
574                self.advance();
575                self.expect(TokenKind::Eq)?;
576                let n = self.expect_number()?;
577                now = Some(n as i64);
578            } else if self.check(TokenKind::At) {
579                edges.push(self.parse_graph_edge()?);
580            } else {
581                return Err(self.error("expected '@' (edge) or 'now' in graph block"));
582            }
583        }
584
585        self.expect(TokenKind::RBrace)?;
586        Ok(GraphAst { edges, now })
587    }
588
589    fn parse_graph_edge(&mut self) -> Result<EdgeAst, ParseError> {
590        self.expect(TokenKind::At)?;
591        let time_start = self.expect_number()? as i64;
592
593        // Check for bounded interval: @1..5
594        let time_end = if self.check(TokenKind::DotDot) {
595            self.advance();
596            Some(self.expect_number()? as i64)
597        } else {
598            None
599        };
600
601        let source = self.expect_ident()?;
602        self.expect(TokenKind::Dot)?;
603        let label = self.expect_ident_or_string()?;
604
605        let target = if self.check(TokenKind::Eq) {
606            self.advance();
607            self.parse_edge_target_literal()?
608        } else if self.check(TokenKind::Arrow) {
609            self.advance();
610            let node = self.expect_ident()?;
611            EdgeTarget::NodeRef(node)
612        } else {
613            return Err(self.error("expected '=' or '->' in graph edge"));
614        };
615
616        Ok(EdgeAst {
617            time_start,
618            time_end,
619            source,
620            label,
621            target,
622        })
623    }
624
625    fn parse_edge_target_literal(&mut self) -> Result<EdgeTarget, ParseError> {
626        if self.check(TokenKind::Minus) {
627            self.advance();
628            if let TokenKind::Number(n) = &self.advance().kind {
629                return Ok(EdgeTarget::Num(-n));
630            }
631            return Err(self.error("expected number after '-'"));
632        }
633        match &self.peek().kind {
634            TokenKind::String(_) => {
635                if let TokenKind::String(s) = &self.advance().kind {
636                    Ok(EdgeTarget::Str(s.clone()))
637                } else {
638                    unreachable!()
639                }
640            }
641            TokenKind::Number(_) => {
642                if let TokenKind::Number(n) = &self.advance().kind {
643                    Ok(EdgeTarget::Num(*n))
644                } else {
645                    unreachable!()
646                }
647            }
648            TokenKind::True => {
649                self.advance();
650                Ok(EdgeTarget::Bool(true))
651            }
652            TokenKind::False => {
653                self.advance();
654                Ok(EdgeTarget::Bool(false))
655            }
656            _ => Err(self.error("expected a string, number, or boolean value")),
657        }
658    }
659
660    // ---- Token cursor utilities ----
661
662    /// Peek at the current token without advancing.
663    pub fn peek(&self) -> &Token {
664        &self.tokens[self.pos]
665    }
666
667    /// Advance the cursor and return the consumed token.
668    pub fn advance(&mut self) -> &Token {
669        let tok = &self.tokens[self.pos];
670        if self.pos + 1 < self.tokens.len() {
671            self.pos += 1;
672        }
673        tok
674    }
675
676    /// Check if the cursor is at the end of the token stream.
677    pub fn at_eof(&self) -> bool {
678        matches!(self.tokens[self.pos].kind, TokenKind::Eof)
679    }
680
681    /// Check if the current token matches the given kind (by discriminant).
682    pub fn check(&self, kind: TokenKind) -> bool {
683        std::mem::discriminant(&self.tokens[self.pos].kind) == std::mem::discriminant(&kind)
684    }
685
686    /// Expect the current token to be of the given kind, advance, and return it.
687    pub fn expect(&mut self, expected: TokenKind) -> Result<&Token, ParseError> {
688        if self.check(expected.clone()) {
689            Ok(self.advance())
690        } else {
691            Err(self.error(&format!("expected {:?}", expected)))
692        }
693    }
694
695    /// Expect and consume an identifier token. Some keywords are allowed as
696    /// identifiers in certain positions (between, after, compose, sharing).
697    pub fn expect_ident(&mut self) -> Result<String, ParseError> {
698        match &self.peek().kind {
699            TokenKind::Ident(_) => {
700                if let TokenKind::Ident(s) = &self.advance().kind {
701                    Ok(s.clone())
702                } else {
703                    unreachable!()
704                }
705            }
706            // Allow keywords as identifiers in certain positions
707            TokenKind::Between => {
708                self.advance();
709                Ok("between".to_string())
710            }
711            TokenKind::After => {
712                self.advance();
713                Ok("after".to_string())
714            }
715            TokenKind::Compose => {
716                self.advance();
717                Ok("compose".to_string())
718            }
719            TokenKind::Sharing => {
720                self.advance();
721                Ok("sharing".to_string())
722            }
723            TokenKind::Concurrent => {
724                self.advance();
725                Ok("concurrent".to_string())
726            }
727            _ => Err(self.error("expected identifier")),
728        }
729    }
730
731    /// Expect and consume an identifier or string literal token.
732    pub fn expect_ident_or_string(&mut self) -> Result<String, ParseError> {
733        match &self.peek().kind {
734            TokenKind::Ident(_) => {
735                if let TokenKind::Ident(s) = &self.advance().kind {
736                    Ok(s.clone())
737                } else {
738                    unreachable!()
739                }
740            }
741            TokenKind::String(_) => {
742                if let TokenKind::String(s) = &self.advance().kind {
743                    Ok(s.clone())
744                } else {
745                    unreachable!()
746                }
747            }
748            _ => Err(self.error("expected identifier or string")),
749        }
750    }
751
752    /// Expect and consume a number literal token, with optional leading `-`.
753    pub fn expect_number(&mut self) -> Result<f64, ParseError> {
754        let negative = if self.check(TokenKind::Minus) {
755            self.advance();
756            true
757        } else {
758            false
759        };
760        match &self.peek().kind {
761            TokenKind::Number(_) => {
762                if let TokenKind::Number(n) = &self.advance().kind {
763                    Ok(if negative { -*n } else { *n })
764                } else {
765                    unreachable!()
766                }
767            }
768            _ => Err(self.error("expected number")),
769        }
770    }
771
772    /// Create a parse error at the current token position.
773    pub fn error(&self, msg: &str) -> ParseError {
774        let tok = &self.tokens[self.pos];
775        ParseError {
776            line: tok.line,
777            column: tok.column,
778            span: tok.span(),
779            message: msg.to_string(),
780        }
781    }
782}