1use crate::ast::*;
6use crate::diag::Diagnostic;
7use crate::lexer::{Token, TokenKind};
8use crate::span::Span;
9
10pub struct ParseOutput {
11 pub file: File,
12 pub diagnostics: Vec<Diagnostic>,
13}
14
15pub fn parse(src: &str, tokens: Vec<Token>) -> ParseOutput {
16 let mut p = Parser {
17 src,
18 tokens,
19 pos: 0,
20 diags: Vec::new(),
21 };
22 let file = p.parse_file();
23 ParseOutput {
24 file,
25 diagnostics: p.diags,
26 }
27}
28
29const STMT_KEYWORDS: &[&str] = &[
30 "canvas",
31 "def",
32 "group",
33 "class",
34 "constrain",
35 "pin",
36 "for",
37 "port",
38];
39
40struct Parser<'a> {
41 src: &'a str,
42 tokens: Vec<Token>,
43 pos: usize,
44 diags: Vec<Diagnostic>,
45}
46
47struct Bail;
49type PResult<T> = Result<T, Bail>;
50
51impl<'a> Parser<'a> {
52 fn peek(&self) -> &TokenKind {
55 &self.tokens[self.pos].kind
56 }
57
58 fn peek_at(&self, ahead: usize) -> &TokenKind {
59 let idx = (self.pos + ahead).min(self.tokens.len() - 1);
60 &self.tokens[idx].kind
61 }
62
63 fn span(&self) -> Span {
64 self.tokens[self.pos].span
65 }
66
67 fn prev_span(&self) -> Span {
68 self.tokens[self.pos.saturating_sub(1)].span
69 }
70
71 fn bump(&mut self) -> Token {
72 let t = self.tokens[self.pos].clone();
73 if self.pos < self.tokens.len() - 1 {
74 self.pos += 1;
75 }
76 t
77 }
78
79 fn at_eof(&self) -> bool {
80 matches!(self.peek(), TokenKind::Eof)
81 }
82
83 fn eat(&mut self, kind: &TokenKind) -> bool {
84 if self.peek() == kind {
85 self.bump();
86 true
87 } else {
88 false
89 }
90 }
91
92 fn skip_trivia(&mut self) {
95 while matches!(self.peek(), TokenKind::Newline | TokenKind::Comment(_)) {
96 self.bump();
97 }
98 }
99
100 fn expect(&mut self, kind: TokenKind, ctx: &str) -> PResult<Token> {
101 if self.peek() == &kind {
102 return Ok(self.bump());
103 }
104 let found = self.peek().describe();
105 self.diags.push(
106 Diagnostic::error(
107 "E0103",
108 format!("expected {} {ctx}, found {found}", kind.describe()),
109 )
110 .with_label(self.span(), format!("expected {} here", kind.describe())),
111 );
112 Err(Bail)
113 }
114
115 fn expect_ident(&mut self, ctx: &str) -> PResult<Ident> {
116 if let TokenKind::Ident(name) = self.peek() {
117 let name = name.clone();
118 let t = self.bump();
119 return Ok(Ident { name, span: t.span });
120 }
121 let found = self.peek().describe();
122 self.diags.push(
123 Diagnostic::error("E0103", format!("expected a name {ctx}, found {found}"))
124 .with_label(self.span(), "expected an identifier here"),
125 );
126 Err(Bail)
127 }
128
129 fn at_kw(&self, kw: &str) -> bool {
131 matches!(self.peek(), TokenKind::Ident(n) if n == kw)
132 }
133
134 fn recover(&mut self) {
138 let mut depth = 0usize;
139 loop {
140 match self.peek() {
141 TokenKind::Eof => return,
142 TokenKind::LBrace => {
143 depth += 1;
144 self.bump();
145 }
146 TokenKind::RBrace => {
147 if depth == 0 {
148 return; }
150 depth -= 1;
151 self.bump();
152 }
153 TokenKind::Newline | TokenKind::Semi if depth == 0 => {
154 self.bump();
155 return;
156 }
157 _ => {
158 self.bump();
159 }
160 }
161 }
162 }
163
164 fn parse_file(&mut self) -> File {
167 let header = self.parse_header();
168 let stmts = self.parse_stmt_list(true);
169 File { header, stmts }
170 }
171
172 fn parse_header(&mut self) -> Option<Header> {
173 self.skip_trivia();
174 if !self.at_kw("drawl") {
175 let span = self.span();
176 self.diags.push(
177 Diagnostic::warning("W0101", "missing `drawl` version header")
178 .with_label(
179 Span::new(span.start, span.start),
180 "file should start with a version header",
181 )
182 .with_help("add `drawl 0.1` as the first line"),
183 );
184 return None;
185 }
186 let kw = self.bump();
187 let (version, vspan) = match self.peek().clone() {
188 TokenKind::Float(v) => {
189 let t = self.bump();
190 (format!("{v}"), t.span)
191 }
192 TokenKind::Int(v) => {
193 let t = self.bump();
194 (format!("{v}"), t.span)
195 }
196 other => {
197 self.diags.push(
198 Diagnostic::error(
199 "E0103",
200 format!(
201 "expected a version number after `drawl`, found {}",
202 other.describe()
203 ),
204 )
205 .with_label(self.span(), "expected something like `0.1`"),
206 );
207 self.recover();
208 return Some(Header {
209 version: "0.1".into(),
210 span: kw.span,
211 });
212 }
213 };
214 if version != "0.1" {
215 self.diags.push(
216 Diagnostic::error("E0106", format!("unsupported drawl version `{version}`"))
217 .with_label(vspan, "this tool understands version `0.1`")
218 .with_help("change the header to `drawl 0.1`"),
219 );
220 }
221 Some(Header {
222 version,
223 span: kw.span.to(vspan),
224 })
225 }
226
227 fn parse_stmt_list(&mut self, top_level: bool) -> Vec<Stmt> {
229 let mut stmts = Vec::new();
230 loop {
231 let trivia = self.collect_leading_trivia();
232 match self.peek() {
233 TokenKind::Eof => {
234 self.flush_orphan_comments(trivia, &mut stmts);
235 return stmts;
236 }
237 TokenKind::RBrace => {
238 if top_level {
239 self.diags.push(
240 Diagnostic::error("E0110", "unmatched `}`")
241 .with_label(self.span(), "no open block to close here"),
242 );
243 self.bump();
244 continue;
245 }
246 self.flush_orphan_comments(trivia, &mut stmts);
247 return stmts;
248 }
249 _ => {}
250 }
251 let start = self.span();
252 match self.parse_stmt() {
253 Ok(mut stmt) => {
254 stmt.trivia = trivia;
255 self.attach_trailing_comment(&mut stmt);
256 stmts.push(stmt);
257 }
258 Err(Bail) => {
259 self.recover();
260 if self.span() == start && !self.at_eof() {
262 self.bump();
263 }
264 }
265 }
266 }
267 }
268
269 fn collect_leading_trivia(&mut self) -> Trivia {
270 let mut trivia = Trivia::default();
271 let mut newline_run = 0usize;
272 loop {
273 match self.peek() {
274 TokenKind::Semi => {
276 self.bump();
277 }
278 TokenKind::Newline => {
279 newline_run += 1;
280 if newline_run >= 2 && !trivia.leading.is_empty() {
281 }
285 if newline_run >= 2 {
286 trivia.blank_before = true;
287 }
288 self.bump();
289 }
290 TokenKind::Comment(text) => {
291 let text = text.clone();
292 trivia.leading.push(text);
293 newline_run = 0;
294 self.bump();
295 }
296 _ => return trivia,
297 }
298 }
299 }
300
301 fn flush_orphan_comments(&mut self, trivia: Trivia, stmts: &mut Vec<Stmt>) {
304 if !trivia.leading.is_empty() {
305 stmts.push(Stmt {
306 kind: StmtKind::Prop(Prop {
307 key: Vec::new(),
308 value: Value::Num(0.0, Span::DUMMY),
309 span: Span::DUMMY,
310 }),
311 span: Span::DUMMY,
312 trivia,
313 });
314 }
315 }
316
317 fn attach_trailing_comment(&mut self, stmt: &mut Stmt) {
318 if let TokenKind::Comment(text) = self.peek() {
319 stmt.trivia.trailing = Some(text.clone());
320 self.bump();
321 }
322 }
323
324 fn parse_stmt(&mut self) -> PResult<Stmt> {
327 let start = self.span();
328 let next_is_ident = matches!(self.peek_at(1), TokenKind::Ident(_));
331 let next_is_lbrace = matches!(self.peek_at(1), TokenKind::LBrace);
332 let kind = if self.at_kw("canvas") && next_is_lbrace {
333 self.bump();
334 StmtKind::Canvas(self.parse_block()?)
335 } else if self.at_kw("def") && next_is_ident {
336 self.bump();
337 StmtKind::Def(self.parse_def()?)
338 } else if self.at_kw("group")
339 && (next_is_ident || matches!(self.peek_at(1), TokenKind::Str(_)))
340 {
341 self.bump();
342 StmtKind::Group(self.parse_group()?)
343 } else if self.at_kw("class") && next_is_ident {
344 self.bump();
345 let name = self.expect_ident("for the class")?;
346 let body = self.parse_block()?;
347 StmtKind::Class(Class { name, body })
348 } else if self.at_kw("constrain") && next_is_lbrace {
349 self.bump();
350 StmtKind::Constrain(self.parse_constrain_block()?)
351 } else if self.at_kw("pin") && next_is_ident {
352 self.bump();
353 StmtKind::Pin(self.parse_pin()?)
354 } else if self.at_kw("for") && next_is_ident {
355 self.bump();
356 StmtKind::For(self.parse_for()?)
357 } else if self.at_kw("port") && next_is_ident {
358 self.bump();
359 let name = self.expect_ident("for the port")?;
360 let body = if matches!(self.peek(), TokenKind::LBrace) {
361 self.parse_block()?
362 } else {
363 Block {
364 stmts: Vec::new(),
365 span: self.prev_span(),
366 }
367 };
368 StmtKind::Port(Port { name, body })
369 } else if matches!(self.peek(), TokenKind::Ident(_)) {
370 self.parse_ident_stmt()?
371 } else {
372 let found = self.peek().describe();
373 let mut d = Diagnostic::error("E0110", format!("expected a statement, found {found}"))
374 .with_label(self.span(), "not the start of any drawlang statement");
375 if let TokenKind::Ident(name) = self.peek() {
376 if let Some(s) = crate::diag::suggest(name, STMT_KEYWORDS.iter().copied()) {
377 d = d.with_help(format!("did you mean `{s}`?"));
378 }
379 }
380 d = d.with_help(
381 "statements are nodes (`id { ... }`), edges (`a -> b`), properties \
382 (`key: value`), or the keywords canvas/def/group/class/constrain/pin/for/port",
383 );
384 self.diags.push(d);
385 return Err(Bail);
386 };
387 let span = start.to(self.prev_span());
388 Ok(Stmt {
389 kind,
390 span,
391 trivia: Trivia::default(),
392 })
393 }
394
395 fn parse_ident_stmt(&mut self) -> PResult<StmtKind> {
398 if let (TokenKind::Ident(first), TokenKind::Ident(_)) = (self.peek(), self.peek_at(1)) {
401 if let Some(s) = crate::diag::suggest(first, STMT_KEYWORDS.iter().copied()) {
402 let first = first.clone();
403 self.diags.push(
404 Diagnostic::error("E0110", format!("unknown statement `{first}`"))
405 .with_label(self.span(), "not a drawlang keyword")
406 .with_help(format!("did you mean `{s}`?")),
407 );
408 return Err(Bail);
409 }
410 }
411
412 let path = self.parse_path(false)?;
413
414 match self.peek() {
415 TokenKind::Arrow | TokenKind::BidiArrow | TokenKind::BackArrow => {
417 self.parse_edge_rest(path)
418 }
419 TokenKind::LBrace => {
421 let name = self.path_as_single_name(path, "node")?;
422 let body = self.parse_block()?;
423 Ok(StmtKind::Node(Node {
424 name: Some(name),
425 kind: NodeKind::Plain { body },
426 }))
427 }
428 TokenKind::LParen => {
430 let callee = self.path_as_single_name(path, "component")?;
431 let args = self.parse_call_args()?;
432 let body = if matches!(self.peek(), TokenKind::LBrace) {
433 Some(self.parse_block()?)
434 } else {
435 None
436 };
437 Ok(StmtKind::Node(Node {
438 name: None,
439 kind: NodeKind::Call { callee, args, body },
440 }))
441 }
442 TokenKind::Colon => {
444 self.bump();
445 self.parse_after_colon(path)
446 }
447 TokenKind::Newline
449 | TokenKind::Semi
450 | TokenKind::Comment(_)
451 | TokenKind::RBrace
452 | TokenKind::Eof => {
453 let name = self.path_as_single_name(path, "node")?;
454 let span = name.span;
455 Ok(StmtKind::Node(Node {
456 name: Some(name),
457 kind: NodeKind::Plain {
458 body: Block {
459 stmts: Vec::new(),
460 span,
461 },
462 },
463 }))
464 }
465 other => {
466 let found = other.describe();
467 self.diags.push(
468 Diagnostic::error("E0103", format!(
469 "expected `{{`, `:`, `(`, or an edge arrow after `{}`, found {found}",
470 path.display()
471 ))
472 .with_label(self.span(), "unexpected here")
473 .with_help("write `id { ... }` for a node, `id: value` for a property, or `a -> b` for an edge"),
474 );
475 Err(Bail)
476 }
477 }
478 }
479
480 fn parse_after_colon(&mut self, key_path: PathRef) -> PResult<StmtKind> {
481 if (self.at_kw("row") || self.at_kw("column"))
483 && matches!(self.peek_at(1), TokenKind::LBrace)
484 {
485 let name = self.path_as_single_name(key_path, "container")?;
486 let kw = self.bump();
487 let ctype = if let TokenKind::Ident(k) = &kw.kind {
488 if k == "row" {
489 ContainerType::Row
490 } else {
491 ContainerType::Column
492 }
493 } else {
494 unreachable!()
495 };
496 let body = self.parse_block()?;
497 return Ok(StmtKind::Node(Node {
498 name: Some(name),
499 kind: NodeKind::Container {
500 ctype,
501 ctype_span: kw.span,
502 body,
503 },
504 }));
505 }
506 if self.at_kw("grid") {
507 let name = self.path_as_single_name(key_path, "container")?;
508 let kw = self.bump();
509 let (cols, rows, dspan) = match self.peek().clone() {
510 TokenKind::Dimension(c, r) => {
511 let t = self.bump();
512 (c, r, t.span)
513 }
514 other => {
515 self.diags.push(
516 Diagnostic::error(
517 "E0103",
518 format!(
519 "expected grid dimensions after `grid`, found {}",
520 other.describe()
521 ),
522 )
523 .with_label(
524 self.span(),
525 "expected something like `2x4` (columns x rows)",
526 ),
527 );
528 return Err(Bail);
529 }
530 };
531 if cols == 0 || rows == 0 {
532 self.diags.push(
533 Diagnostic::error("E0105", "grid dimensions must be at least 1x1")
534 .with_label(dspan, "zero-sized grid"),
535 );
536 }
537 let body = self.parse_block()?;
538 return Ok(StmtKind::Node(Node {
539 name: Some(name),
540 kind: NodeKind::Container {
541 ctype: ContainerType::Grid { cols, rows },
542 ctype_span: kw.span.to(dspan),
543 body,
544 },
545 }));
546 }
547 if matches!(self.peek(), TokenKind::Ident(_))
549 && matches!(self.peek_at(1), TokenKind::LParen)
550 {
551 let name = self.path_as_single_name(key_path, "node")?;
552 let callee = self.expect_ident("for the component")?;
553 let args = self.parse_call_args()?;
554 let body = if matches!(self.peek(), TokenKind::LBrace) {
555 Some(self.parse_block()?)
556 } else {
557 None
558 };
559 return Ok(StmtKind::Node(Node {
560 name: Some(name),
561 kind: NodeKind::Call { callee, args, body },
562 }));
563 }
564 let key = self.path_as_prop_key(key_path)?;
566 let value = self.parse_value()?;
567 let span = key
568 .first()
569 .map(|k| k.span)
570 .unwrap_or(Span::DUMMY)
571 .to(value.span());
572 Ok(StmtKind::Prop(Prop { key, value, span }))
573 }
574
575 fn parse_edge_rest(&mut self, from: PathRef) -> PResult<StmtKind> {
576 let op_tok = self.bump();
577 let to = self.parse_path(false)?;
578 let (from, op, to) = match op_tok.kind {
580 TokenKind::Arrow => (from, EdgeOp::Forward, to),
581 TokenKind::BidiArrow => (from, EdgeOp::Bidirectional, to),
582 TokenKind::BackArrow => (to, EdgeOp::Forward, from),
583 _ => unreachable!(),
584 };
585 let label = if self.eat(&TokenKind::Colon) {
586 match self.peek().clone() {
587 TokenKind::Str(_) => Some(self.parse_strlit()?),
588 other => {
589 self.diags.push(
590 Diagnostic::error(
591 "E0103",
592 format!(
593 "expected a string label after `:`, found {}",
594 other.describe()
595 ),
596 )
597 .with_label(
598 self.span(),
599 r#"edge labels are strings, like `: "PCIe 5.0 x16"`"#,
600 ),
601 );
602 return Err(Bail);
603 }
604 }
605 } else {
606 None
607 };
608 let props = if matches!(self.peek(), TokenKind::LBrace) {
609 Some(self.parse_block()?)
610 } else {
611 None
612 };
613 Ok(StmtKind::Edge(Edge {
614 from,
615 op,
616 op_span: op_tok.span,
617 to,
618 label,
619 props,
620 }))
621 }
622
623 fn parse_def(&mut self) -> PResult<Def> {
624 let name = self.expect_ident("for the component")?;
625 self.expect(TokenKind::LParen, "to open the parameter list")?;
626 let mut params = Vec::new();
627 self.skip_trivia();
628 while !matches!(self.peek(), TokenKind::RParen | TokenKind::Eof) {
629 params.push(self.expect_ident("for the parameter")?);
630 self.skip_trivia();
631 if !self.eat(&TokenKind::Comma) {
632 break;
633 }
634 self.skip_trivia();
635 }
636 self.expect(TokenKind::RParen, "to close the parameter list")?;
637 let body = self.parse_block()?;
638 Ok(Def { name, params, body })
639 }
640
641 fn parse_group(&mut self) -> PResult<Group> {
642 let name = self.expect_ident("for the group")?;
643 let label = if matches!(self.peek(), TokenKind::Str(_)) {
644 Some(self.parse_strlit()?)
645 } else {
646 None
647 };
648 let body = self.parse_block()?;
649 Ok(Group { name, label, body })
650 }
651
652 fn parse_pin(&mut self) -> PResult<Pin> {
653 let target = self.parse_path(false)?;
654 if !self.at_kw("at") {
655 self.diags.push(
656 Diagnostic::error(
657 "E0103",
658 format!(
659 "expected `at` after the pin target, found {}",
660 self.peek().describe()
661 ),
662 )
663 .with_label(self.span(), "write `pin <element> at (x, y)`"),
664 );
665 return Err(Bail);
666 }
667 self.bump();
668 self.expect(TokenKind::LParen, "to open the position")?;
669 let x = self.parse_expr()?;
670 self.expect(TokenKind::Comma, "between the x and y coordinates")?;
671 let y = self.parse_expr()?;
672 self.expect(TokenKind::RParen, "to close the position")?;
673 Ok(Pin { target, x, y })
674 }
675
676 fn parse_for(&mut self) -> PResult<For> {
677 let var = self.expect_ident("for the loop variable")?;
678 if !self.at_kw("in") {
679 self.diags.push(
680 Diagnostic::error(
681 "E0103",
682 format!(
683 "expected `in` after the loop variable, found {}",
684 self.peek().describe()
685 ),
686 )
687 .with_label(self.span(), "write `for i in 0..4 { ... }`"),
688 );
689 return Err(Bail);
690 }
691 self.bump();
692 let start = self.parse_expr()?;
693 self.expect(TokenKind::DotDot, "in the loop range")?;
694 let end = self.parse_expr()?;
695 let body = self.parse_block()?;
696 Ok(For {
697 var,
698 start,
699 end,
700 body,
701 })
702 }
703
704 fn parse_constrain_block(&mut self) -> PResult<Vec<Constraint>> {
705 self.expect(TokenKind::LBrace, "to open the constrain block")?;
706 let mut constraints = Vec::new();
707 loop {
708 let trivia = self.collect_leading_trivia();
709 match self.peek() {
710 TokenKind::RBrace => {
711 self.bump();
712 return Ok(constraints);
713 }
714 TokenKind::Eof => {
715 self.diags.push(
716 Diagnostic::error("E0103", "unclosed `constrain` block")
717 .with_label(self.span(), "expected `}` before end of file"),
718 );
719 return Ok(constraints);
720 }
721 _ => {}
722 }
723 let before = self.pos;
724 match self.parse_constraint(trivia) {
725 Ok(c) => constraints.push(c),
726 Err(Bail) => {
727 self.recover();
728 if self.pos == before && !self.at_eof() {
729 self.bump();
730 }
731 }
732 }
733 }
734 }
735
736 fn parse_constraint(&mut self, trivia: Trivia) -> PResult<Constraint> {
737 let start = self.span();
738 let mut name = self.expect_ident("for the constraint")?;
741 while matches!(self.peek(), TokenKind::Minus)
742 && self.span().start == name.span.end
743 && matches!(self.peek_at(1), TokenKind::Ident(_))
744 {
745 self.bump(); let part = self.expect_ident("after `-`")?;
747 name = Ident {
748 name: format!("{}-{}", name.name, part.name),
749 span: name.span.to(part.span),
750 };
751 }
752 self.expect(TokenKind::LParen, "to open the constraint arguments")?;
753 let mut args = Vec::new();
754 self.skip_trivia();
755 while !matches!(self.peek(), TokenKind::RParen | TokenKind::Eof) {
756 args.push(self.parse_constraint_arg()?);
757 self.skip_trivia();
758 if !self.eat(&TokenKind::Comma) {
759 break;
760 }
761 self.skip_trivia();
762 }
763 self.expect(TokenKind::RParen, "to close the constraint arguments")?;
764 let mut c = Constraint {
765 name,
766 args,
767 span: start.to(self.prev_span()),
768 trivia: Trivia::default(),
769 };
770 c.trivia = trivia;
771 if let TokenKind::Comment(text) = self.peek() {
773 c.trivia.trailing = Some(text.clone());
774 self.bump();
775 }
776 Ok(c)
777 }
778
779 fn parse_constraint_arg(&mut self) -> PResult<ConstraintArg> {
780 match self.peek().clone() {
781 TokenKind::Int(v) => {
782 let t = self.bump();
783 Ok(ConstraintArg::Num(v as f64, t.span))
784 }
785 TokenKind::Float(v) => {
786 let t = self.bump();
787 Ok(ConstraintArg::Num(v, t.span))
788 }
789 TokenKind::Minus => {
790 let start = self.bump().span;
791 match self.peek().clone() {
792 TokenKind::Int(v) => {
793 let t = self.bump();
794 Ok(ConstraintArg::Num(-(v as f64), start.to(t.span)))
795 }
796 TokenKind::Float(v) => {
797 let t = self.bump();
798 Ok(ConstraintArg::Num(-v, start.to(t.span)))
799 }
800 other => {
801 self.diags.push(
802 Diagnostic::error(
803 "E0103",
804 format!("expected a number after `-`, found {}", other.describe()),
805 )
806 .with_label(self.span(), "expected a number"),
807 );
808 Err(Bail)
809 }
810 }
811 }
812 TokenKind::Ident(_) => Ok(ConstraintArg::Path(self.parse_path(true)?)),
813 other => {
814 self.diags.push(
815 Diagnostic::error(
816 "E0103",
817 format!(
818 "expected an element path or number, found {}",
819 other.describe()
820 ),
821 )
822 .with_label(
823 self.span(),
824 "constraint arguments are element paths, keywords, or numbers",
825 ),
826 );
827 Err(Bail)
828 }
829 }
830 }
831
832 fn parse_block(&mut self) -> PResult<Block> {
835 let open = self.expect(TokenKind::LBrace, "to open the block")?;
836 let stmts = self.parse_stmt_list(false);
837 let close = if matches!(self.peek(), TokenKind::RBrace) {
838 self.bump().span
839 } else {
840 self.diags.push(
841 Diagnostic::error("E0103", "unclosed block")
842 .with_label(open.span, "this `{` is never closed")
843 .with_label(self.span(), "expected `}` before this point"),
844 );
845 self.span()
846 };
847 Ok(Block {
848 stmts,
849 span: open.span.to(close),
850 })
851 }
852
853 fn parse_call_args(&mut self) -> PResult<Vec<Expr>> {
854 self.expect(TokenKind::LParen, "to open the arguments")?;
855 let mut args = Vec::new();
856 self.skip_trivia();
857 while !matches!(self.peek(), TokenKind::RParen | TokenKind::Eof) {
858 args.push(self.parse_expr()?);
859 self.skip_trivia();
860 if !self.eat(&TokenKind::Comma) {
861 break;
862 }
863 self.skip_trivia();
864 }
865 self.expect(TokenKind::RParen, "to close the arguments")?;
866 Ok(args)
867 }
868
869 fn path_as_single_name(&mut self, path: PathRef, what: &str) -> PResult<Ident> {
870 match path.segments.as_slice() {
871 [PathSeg::Name(id)] => Ok(id.clone()),
872 _ => {
873 self.diags.push(
874 Diagnostic::error("E0107", format!("{what} names must be a single identifier"))
875 .with_label(
876 path.span,
877 format!("`{}` is a path, not a name", path.display()),
878 )
879 .with_help(
880 "dots and indices are for *referring* to elements, not declaring them",
881 ),
882 );
883 Err(Bail)
884 }
885 }
886 }
887
888 fn path_as_prop_key(&mut self, path: PathRef) -> PResult<Vec<Ident>> {
889 let mut key = Vec::new();
890 for seg in &path.segments {
891 match seg {
892 PathSeg::Name(id) => key.push(id.clone()),
893 _ => {
894 self.diags.push(
895 Diagnostic::error("E0107", "property keys cannot contain indices")
896 .with_label(path.span, "expected a key like `label.wrap`"),
897 );
898 return Err(Bail);
899 }
900 }
901 }
902 Ok(key)
903 }
904
905 fn parse_path(&mut self, allow_wildcard: bool) -> PResult<PathRef> {
906 let first = self.expect_ident("to start an element path")?;
907 let start = first.span;
908 let mut segments = vec![PathSeg::Name(first)];
909 loop {
910 match self.peek() {
911 TokenKind::Dot => {
912 self.bump();
913 let id = self.expect_ident("after `.`")?;
914 segments.push(PathSeg::Name(id));
915 }
916 TokenKind::LBracket => {
917 self.bump();
918 if matches!(self.peek(), TokenKind::Star) {
919 let star = self.bump();
920 if !allow_wildcard {
921 self.diags.push(
922 Diagnostic::error(
923 "E0108",
924 "`[*]` is only allowed in constraint arguments",
925 )
926 .with_label(star.span, "wildcard not allowed here")
927 .with_help("name a specific index, like `[0]`"),
928 );
929 }
930 segments.push(PathSeg::Wildcard(star.span));
931 } else {
932 let expr = self.parse_expr()?;
933 segments.push(PathSeg::Index(expr));
934 }
935 self.expect(TokenKind::RBracket, "to close the index")?;
936 }
937 _ => break,
938 }
939 }
940 let span = start.to(self.prev_span());
941 Ok(PathRef { segments, span })
942 }
943
944 fn parse_value(&mut self) -> PResult<Value> {
945 match self.peek().clone() {
946 TokenKind::Str(_) => Ok(Value::Str(self.parse_strlit()?)),
947 TokenKind::Int(v) => {
948 let t = self.bump();
949 Ok(Value::Num(v as f64, t.span))
950 }
951 TokenKind::Float(v) => {
952 let t = self.bump();
953 Ok(Value::Num(v, t.span))
954 }
955 TokenKind::Minus => {
956 let start = self.bump().span;
957 match self.peek().clone() {
958 TokenKind::Int(v) => {
959 let t = self.bump();
960 Ok(Value::Num(-(v as f64), start.to(t.span)))
961 }
962 TokenKind::Float(v) => {
963 let t = self.bump();
964 Ok(Value::Num(-v, start.to(t.span)))
965 }
966 other => {
967 self.diags.push(
968 Diagnostic::error(
969 "E0103",
970 format!("expected a number after `-`, found {}", other.describe()),
971 )
972 .with_label(self.span(), "expected a number"),
973 );
974 Err(Bail)
975 }
976 }
977 }
978 TokenKind::Ident(name) => {
979 let t = self.bump();
980 Ok(Value::Word(Ident { name, span: t.span }))
981 }
982 TokenKind::AtIdent(name) => {
983 let t = self.bump();
984 Ok(Value::ThemeToken(Ident { name, span: t.span }))
985 }
986 TokenKind::HexColor(hex) => {
987 let t = self.bump();
988 Ok(Value::Color(hex, t.span))
989 }
990 other => {
991 self.diags.push(
992 Diagnostic::error(
993 "E0103",
994 format!("expected a property value, found {}", other.describe()),
995 )
996 .with_label(
997 self.span(),
998 "expected a string, number, word, `@token`, or `#color`",
999 ),
1000 );
1001 Err(Bail)
1002 }
1003 }
1004 }
1005
1006 fn parse_expr(&mut self) -> PResult<Expr> {
1009 self.parse_additive()
1010 }
1011
1012 fn parse_additive(&mut self) -> PResult<Expr> {
1013 let mut lhs = self.parse_multiplicative()?;
1014 loop {
1015 let op = match self.peek() {
1016 TokenKind::Plus => BinOp::Add,
1017 TokenKind::Minus => BinOp::Sub,
1018 _ => return Ok(lhs),
1019 };
1020 self.bump();
1021 let rhs = self.parse_multiplicative()?;
1022 let span = lhs.span.to(rhs.span);
1023 lhs = Expr {
1024 kind: ExprKind::Binary(op, Box::new(lhs), Box::new(rhs)),
1025 span,
1026 };
1027 }
1028 }
1029
1030 fn parse_multiplicative(&mut self) -> PResult<Expr> {
1031 let mut lhs = self.parse_unary()?;
1032 loop {
1033 let op = match self.peek() {
1034 TokenKind::Star => BinOp::Mul,
1035 TokenKind::Slash => BinOp::Div,
1036 TokenKind::Percent => BinOp::Mod,
1037 _ => return Ok(lhs),
1038 };
1039 self.bump();
1040 let rhs = self.parse_unary()?;
1041 let span = lhs.span.to(rhs.span);
1042 lhs = Expr {
1043 kind: ExprKind::Binary(op, Box::new(lhs), Box::new(rhs)),
1044 span,
1045 };
1046 }
1047 }
1048
1049 fn parse_unary(&mut self) -> PResult<Expr> {
1050 if matches!(self.peek(), TokenKind::Minus) {
1051 let start = self.bump().span;
1052 let inner = self.parse_unary()?;
1053 let span = start.to(inner.span);
1054 return Ok(Expr {
1055 kind: ExprKind::Unary(UnOp::Neg, Box::new(inner)),
1056 span,
1057 });
1058 }
1059 self.parse_primary()
1060 }
1061
1062 fn parse_primary(&mut self) -> PResult<Expr> {
1063 match self.peek().clone() {
1064 TokenKind::Int(v) => {
1065 let t = self.bump();
1066 Ok(Expr {
1067 kind: ExprKind::Num(v as f64),
1068 span: t.span,
1069 })
1070 }
1071 TokenKind::Float(v) => {
1072 let t = self.bump();
1073 Ok(Expr {
1074 kind: ExprKind::Num(v),
1075 span: t.span,
1076 })
1077 }
1078 TokenKind::Str(_) => {
1079 let s = self.parse_strlit()?;
1080 let span = s.span;
1081 Ok(Expr {
1082 kind: ExprKind::Str(Box::new(s)),
1083 span,
1084 })
1085 }
1086 TokenKind::Ident(name) => {
1087 let t = self.bump();
1088 Ok(Expr {
1089 kind: ExprKind::Var(Ident { name, span: t.span }),
1090 span: t.span,
1091 })
1092 }
1093 TokenKind::LParen => {
1094 self.bump();
1095 let inner = self.parse_expr()?;
1096 self.expect(TokenKind::RParen, "to close the parenthesized expression")?;
1097 Ok(inner)
1098 }
1099 other => {
1100 self.diags.push(
1101 Diagnostic::error(
1102 "E0103",
1103 format!("expected an expression, found {}", other.describe()),
1104 )
1105 .with_label(self.span(), "expected a number, variable, or `(expr)`"),
1106 );
1107 Err(Bail)
1108 }
1109 }
1110 }
1111
1112 fn parse_strlit(&mut self) -> PResult<StrLit> {
1118 let tok = self.bump();
1119 let TokenKind::Str(_) = &tok.kind else {
1120 unreachable!("caller checked")
1121 };
1122 let span = tok.span;
1123 let inner_start = span.start + 1;
1125 let inner_end = span.end.saturating_sub(1).max(inner_start);
1126 let raw = &self.src[inner_start.min(self.src.len())..inner_end.min(self.src.len())];
1127
1128 let mut parts = Vec::new();
1129 let mut text = String::new();
1130 let bytes = raw.as_bytes();
1131 let mut i = 0;
1132 while i < bytes.len() {
1133 match bytes[i] {
1134 b'\\' if i + 1 < bytes.len() => {
1135 match bytes[i + 1] {
1136 b'n' => text.push('\n'),
1137 b't' => text.push('\t'),
1138 b'"' => text.push('"'),
1139 b'\\' => text.push('\\'),
1140 b'{' => text.push('{'),
1141 b'}' => text.push('}'),
1142 other => text.push(other as char), }
1144 i += 2;
1145 }
1146 b'{' => {
1147 let expr_start = i + 1;
1149 let mut depth = 1;
1150 let mut j = expr_start;
1151 while j < bytes.len() && depth > 0 {
1152 match bytes[j] {
1153 b'{' => depth += 1,
1154 b'}' => depth -= 1,
1155 _ => {}
1156 }
1157 j += 1;
1158 }
1159 let expr_end = j - 1; if depth > 0 {
1161 text.push_str(&raw[i..]);
1163 i = bytes.len();
1164 continue;
1165 }
1166 if !text.is_empty() {
1167 parts.push(StrPart::Text(std::mem::take(&mut text)));
1168 }
1169 let expr_src = &raw[expr_start..expr_end];
1170 let abs_offset = inner_start + expr_start;
1171 parts.push(StrPart::Expr(
1172 self.parse_embedded_expr(expr_src, abs_offset)?,
1173 ));
1174 i = j;
1175 }
1176 _ => {
1177 let ch = raw[i..].chars().next().unwrap();
1178 text.push(ch);
1179 i += ch.len_utf8();
1180 }
1181 }
1182 }
1183 if !text.is_empty() {
1184 parts.push(StrPart::Text(text));
1185 }
1186 Ok(StrLit { parts, span })
1187 }
1188
1189 fn parse_embedded_expr(&mut self, expr_src: &str, abs_offset: usize) -> PResult<Expr> {
1190 if expr_src.trim().is_empty() {
1191 self.diags.push(
1192 Diagnostic::error("E0104", "empty interpolation `{}` in string")
1193 .with_label(
1194 Span::new(abs_offset - 1, abs_offset + expr_src.len() + 1),
1195 "nothing to interpolate",
1196 )
1197 .with_help(r#"put an expression inside the braces, like `"GPU {i}"`"#),
1198 );
1199 return Err(Bail);
1200 }
1201 let lexed = crate::lexer::lex(expr_src);
1202 for mut d in lexed.diagnostics {
1204 for l in &mut d.labels {
1205 l.span = Span::new(l.span.start + abs_offset, l.span.end + abs_offset);
1206 }
1207 self.diags.push(d);
1208 }
1209 let tokens: Vec<Token> = lexed
1210 .tokens
1211 .into_iter()
1212 .map(|t| Token {
1213 kind: t.kind,
1214 span: Span::new(t.span.start + abs_offset, t.span.end + abs_offset),
1215 })
1216 .collect();
1217 let mut sub = Parser {
1218 src: self.src,
1219 tokens,
1220 pos: 0,
1221 diags: Vec::new(),
1222 };
1223 let result = sub.parse_expr();
1224 let leftover = !matches!(sub.peek(), TokenKind::Newline | TokenKind::Eof);
1225 self.diags.extend(sub.diags);
1226 match result {
1227 Ok(expr) => {
1228 if leftover {
1229 self.diags.push(
1230 Diagnostic::error("E0104", "unexpected trailing tokens in interpolation")
1231 .with_label(
1232 Span::new(abs_offset, abs_offset + expr_src.len()),
1233 "only a single expression is allowed inside `{...}`",
1234 ),
1235 );
1236 return Err(Bail);
1237 }
1238 Ok(expr)
1239 }
1240 Err(Bail) => Err(Bail),
1241 }
1242 }
1243}