1use crate::ast::*;
44use crate::lexer::{Span, Token, TokenKind};
45
46struct Parser {
48 tokens: Vec<Token>,
49 pos: usize,
50 tile_defaults: TileOptions,
53}
54
55impl Parser {
56 fn new(tokens: Vec<Token>) -> Self {
57 Self {
58 tokens,
59 pos: 0,
60 tile_defaults: TileOptions::default(),
61 }
62 }
63
64 fn peek(&self) -> &TokenKind {
65 &self.tokens[self.pos].kind
66 }
67
68 fn span(&self) -> Span {
69 self.tokens[self.pos].span
70 }
71
72 fn advance(&mut self) -> &Token {
73 let tok = &self.tokens[self.pos];
74 if self.pos < self.tokens.len() - 1 {
75 self.pos += 1;
76 }
77 tok
78 }
79
80 fn expect(&mut self, expected: &TokenKind) -> Result<&Token, String> {
81 if self.peek() == expected {
82 Ok(self.advance())
83 } else {
84 Err(format!(
85 "expected {:?}, got {:?} at line {}, col {}",
86 expected,
87 self.peek(),
88 self.span().line,
89 self.span().col
90 ))
91 }
92 }
93
94 fn expect_ident(&mut self) -> Result<String, String> {
95 match self.peek().clone() {
96 TokenKind::Ident(name) => {
97 self.advance();
98 Ok(name)
99 }
100 TokenKind::Input => {
107 self.advance();
108 Ok("input".to_string())
109 }
110 TokenKind::Cursor => {
115 self.advance();
116 Ok("cursor".to_string())
117 }
118 TokenKind::Over => {
123 self.advance();
124 Ok("over".to_string())
125 }
126 _ => Err(format!(
127 "expected identifier, got {:?} at line {}, col {}",
128 self.peek(),
129 self.span().line,
130 self.span().col
131 )),
132 }
133 }
134
135 fn at_eof(&self) -> bool {
136 matches!(self.peek(), TokenKind::Eof)
137 }
138}
139
140pub fn parse(tokens: Vec<Token>) -> Result<PolydatFile, String> {
142 parse_with_tile_defaults(tokens, &TileOptions::default())
143}
144
145pub fn parse_with_tile_defaults(
154 tokens: Vec<Token>,
155 tile_defaults: &TileOptions,
156) -> Result<PolydatFile, String> {
157 let mut parser = Parser::new(tokens);
158 parser.tile_defaults = tile_defaults.clone();
159 let mut statements = Vec::new();
160
161 while !parser.at_eof() {
162 parse_statement_into(&mut parser, &mut statements)?;
163 }
164
165 Ok(PolydatFile { statements })
166}
167
168pub fn parse_expression(tokens: Vec<Token>) -> Result<Expr, String> {
186 let mut parser = Parser::new(tokens);
187 let expr = parse_expr(&mut parser)?;
188 if !parser.at_eof() {
189 let span = parser.span();
190 return Err(format!(
191 "expected end of expression at line {}, col {}, got {:?}",
192 span.line,
193 span.col,
194 parser.peek()
195 ));
196 }
197 Ok(expr)
198}
199
200fn parse_statement_into(p: &mut Parser, out: &mut Vec<Statement>) -> Result<(), String> {
206 match p.peek() {
207 TokenKind::Pragma => out.push(parse_pragma(p)?),
208 TokenKind::Input => parse_input_decl(p, out)?,
209 TokenKind::Extern => out.push(parse_extern_port(p)?),
210 TokenKind::Cursor => out.push(parse_cursor_decl(p)?),
211 TokenKind::For(_) => out.push(parse_for_statement(p)?),
212 TokenKind::Tile => out.push(parse_tile(p)?),
213 TokenKind::Const | TokenKind::Shared | TokenKind::Volatile => {
214 out.push(parse_modified_binding(p)?);
215 }
216 TokenKind::LParen => out.push(parse_destructuring_binding(p)?),
217 TokenKind::Ident(_) => {
218 if is_module_def(p) {
222 out.push(parse_module_def(p)?);
223 } else if is_polytile_binding(p) {
224 out.push(parse_polytile_binding(p)?);
225 } else {
226 out.push(parse_cycle_binding(p)?);
227 }
228 }
229 _ => {
230 return Err(format!(
231 "unexpected token {:?} at line {}, col {}",
232 p.peek(),
233 p.span().line,
234 p.span().col
235 ));
236 }
237 }
238 Ok(())
239}
240
241fn parse_for_statement(p: &mut Parser) -> Result<Statement, String> {
251 let span = p.span();
252 let text = match p.peek().clone() {
253 TokenKind::For(text) => text,
254 other => {
255 return Err(format!(
256 "expected `for`, got {other:?} at line {}, col {}",
257 span.line, span.col
258 ));
259 }
260 };
261 p.advance();
262 let source = for_source_from_text(&text, span, true)?;
263 if !matches!(p.peek(), TokenKind::LBrace) {
264 return Err(format!(
265 "`for {text}` at line {}, col {} needs a `{{` block on the same line; \
266 to bind a producer instead, write `name := for {text}`",
267 span.line, span.col
268 ));
269 }
270 p.advance();
271 let mut body = Vec::new();
272 while !matches!(p.peek(), TokenKind::RBrace | TokenKind::Eof) {
273 parse_statement_into(p, &mut body)?;
274 }
275 p.expect(&TokenKind::RBrace)?;
276 Ok(Statement::For(ForStmt { source, body, span }))
277}
278
279fn parse_tile(p: &mut Parser) -> Result<Statement, String> {
284 let span = p.span();
285 p.expect(&TokenKind::Tile)?;
286 let name = p.expect_ident()?;
287 let encoding = if matches!(p.peek(), TokenKind::Colon) {
288 p.advance();
289 Some(p.expect_ident()?)
290 } else {
291 None
292 };
293 let mut options = p.tile_defaults.clone();
294 if matches!(p.peek(), TokenKind::LParen) {
295 p.advance();
296 while !matches!(p.peek(), TokenKind::RParen | TokenKind::Eof) {
297 let key = p.expect_ident()?;
298 match key.as_str() {
299 "delims" => {
300 options.open = expect_string(p, "delims open")?;
301 options.close = expect_string(p, "delims close")?;
302 if options.open.is_empty() || options.close.is_empty() {
303 return Err(format!(
304 "tile '{name}' at line {}, col {}: delimiters must not be empty",
305 span.line, span.col
306 ));
307 }
308 }
309 "sigil" => {
310 options.sigil = expect_string(p, "sigil")?;
311 if options.sigil.is_empty() {
312 return Err(format!(
313 "tile '{name}' at line {}, col {}: sigil must not be empty",
314 span.line, span.col
315 ));
316 }
317 }
318 "strict" => options.strict = true,
319 "instring" => options.in_string = true,
320 other => {
321 return Err(format!(
322 "tile '{name}' at line {}, col {}: unknown option '{other}'; options are delims, sigil, strict, instring",
323 span.line, span.col
324 ));
325 }
326 }
327 if matches!(p.peek(), TokenKind::Comma) {
328 p.advance();
329 }
330 }
331 p.expect(&TokenKind::RParen)?;
332 }
333 if !matches!(p.peek(), TokenKind::ColonEq) {
336 return Err(format!(
337 "tile '{name}' at line {}, col {}: expected `:=` before the body; a tile binds a wire, \
338 as in `tile {name} : json := {{ ... }}` or `tile {name} := \"...\"`, got {:?}",
339 span.line,
340 span.col,
341 p.peek()
342 ));
343 }
344 p.advance();
345 let (body_kind, body) = match p.peek().clone() {
346 TokenKind::TileBody(text, kind) => {
347 p.advance();
348 (kind, text)
349 }
350 TokenKind::StringLit(s) => {
351 p.advance();
352 (TileBodyKind::Literal, s)
353 }
354 other => {
355 return Err(format!(
356 "tile '{name}' at line {}, col {}: expected a body (a `{{ }}` or `[ ]` block, `<<< >>>` heredoc, or string), got {other:?}",
357 span.line, span.col
358 ));
359 }
360 };
361 if let Some(enc) = &encoding
362 && !matches!(enc.as_str(), "json" | "text" | "csv")
363 {
364 return Err(format!(
365 "tile '{name}' at line {}, col {}: unknown encoding '{enc}'; encodings are json, text, csv",
366 span.line, span.col
367 ));
368 }
369 let pieces = super::tile::parse_template(&body, &options, span)
370 .map_err(|e| format!("tile '{name}': {e}"))?;
371 Ok(Statement::Tile(TileDef {
372 name,
373 encoding,
374 options,
375 body_kind,
376 pieces,
377 span,
378 }))
379}
380
381fn is_polytile_binding(p: &Parser) -> bool {
384 p.pos + 3 < p.tokens.len()
385 && matches!(&p.tokens[p.pos].kind, TokenKind::Ident(_))
386 && matches!(&p.tokens[p.pos + 1].kind, TokenKind::ColonEq)
387 && matches!(&p.tokens[p.pos + 2].kind, TokenKind::Ident(f) if f == "polytile" || f == "polytile_json")
388 && matches!(&p.tokens[p.pos + 3].kind, TokenKind::LParen)
389}
390
391fn parse_polytile_binding(p: &mut Parser) -> Result<Statement, String> {
399 let span = p.span();
400 let name = p.expect_ident()?;
401 p.expect(&TokenKind::ColonEq)?;
402 let func = p.expect_ident()?;
403 p.expect(&TokenKind::LParen)?;
404 let at = |what: &str| {
405 format!(
406 "{func} for '{name}' at line {}, col {}: {what}",
407 span.line, span.col
408 )
409 };
410 let encoding = if func == "polytile" {
411 let e = expect_string(p, "the encoding").map_err(|m| at(&m))?;
412 if !matches!(p.peek(), TokenKind::Comma) {
413 return Err(at("expected `,` and then the template"));
414 }
415 p.advance();
416 Some(e)
417 } else {
418 None
419 };
420 let body = expect_string(p, "the template body (a string or a `<<< >>>` heredoc)")
421 .map_err(|m| at(&m))?;
422 let mut options = p.tile_defaults.clone();
423 while matches!(p.peek(), TokenKind::Comma) {
424 p.advance();
425 if matches!(p.peek(), TokenKind::RParen) {
426 break;
427 }
428 let key = p.expect_ident()?;
429 p.expect(&TokenKind::Colon)
430 .map_err(|_| at(&format!("option `{key}` needs `: \"value\"`")))?;
431 match key.as_str() {
432 "open" => options.open = expect_string(p, "open").map_err(|m| at(&m))?,
433 "close" => options.close = expect_string(p, "close").map_err(|m| at(&m))?,
434 "sigil" => options.sigil = expect_string(p, "sigil").map_err(|m| at(&m))?,
435 "strict" => {
436 let v = p.expect_ident().map_err(|m| at(&m))?;
437 options.strict = v == "true";
438 }
439 "instring" => {
440 let v = p.expect_ident().map_err(|m| at(&m))?;
441 options.in_string = v == "true";
442 }
443 other => {
444 return Err(at(&format!(
445 "unknown option '{other}'; options are open, close, sigil, strict, instring"
446 )));
447 }
448 }
449 }
450 p.expect(&TokenKind::RParen).map_err(|m| at(&m))?;
451 if options.open.is_empty() || options.close.is_empty() || options.sigil.is_empty() {
452 return Err(at("delimiters and sigil must not be empty"));
453 }
454 let tile = match encoding {
455 Some(enc) => super::tile_structural::tile_from_text(&name, &enc, &body, &options, span)?,
456 None => super::tile_structural::tile_from_json_text(&name, &body, &options, span)?,
457 };
458 Ok(Statement::Tile(tile))
459}
460
461fn expect_string(p: &mut Parser, what: &str) -> Result<String, String> {
462 match p.peek().clone() {
463 TokenKind::StringLit(s) => {
464 p.advance();
465 Ok(s)
466 }
467 other => Err(format!(
468 "expected a string for {what}, got {other:?} at line {}, col {}",
469 p.span().line,
470 p.span().col
471 )),
472 }
473}
474
475pub fn for_source_from_text(
477 text: &str,
478 span: Span,
479 allow_producer: bool,
480) -> Result<ForSource, String> {
481 if text.is_empty() {
482 return Err(format!(
483 "`for` at line {}, col {} has no comprehension",
484 span.line, span.col
485 ));
486 }
487 let is_ident = text.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
488 && text
489 .chars()
490 .next()
491 .is_some_and(|c| c.is_ascii_alphabetic() || c == '_');
492 if is_ident {
493 if allow_producer {
494 return Ok(ForSource::producer(text, span));
495 }
496 return Err(format!(
497 "`for {text}` at line {}, col {}: a producer expression needs comprehension text such as `k in 1..10`, \
498 or a derivation such as `{text} where {{k}} > 1` or `{text} order halton/5`",
499 span.line, span.col
500 ));
501 }
502 {
505 use crate::comprehension::parse::{split_at_order, split_at_where};
506 let (head, order) = split_at_order(text);
507 let (base, filter) = split_at_where(&head);
508 let base = base.trim();
509 let base_is_ident = !base.is_empty()
510 && base.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
511 && base
512 .chars()
513 .next()
514 .is_some_and(|c| c.is_ascii_alphabetic() || c == '_');
515 if base_is_ident && (filter.is_some() || order.is_some()) {
516 return Ok(ForSource::derived(base, filter, order, span));
517 }
518 }
519 let algebra = crate::comprehension::spec::parse_comprehension_algebra(text)
520 .map_err(|e| format!("`for {text}` at line {}, col {}: {e}", span.line, span.col))?;
521 ForSource::comprehension(algebra, span).ok_or_else(|| {
527 format!(
528 "`for {text}` at line {}, col {}: parsed, but the comprehension it denotes has no text \
529 form, so it could not be written back; this is a gap between the comprehension \
530 parser and its renderer, not an error in the program",
531 span.line, span.col
532 )
533 })
534}
535
536fn parse_pragma(p: &mut Parser) -> Result<Statement, String> {
537 let span = p.span();
538 p.expect(&TokenKind::Pragma)?;
539 let name = p.expect_ident()?;
540 Ok(Statement::Pragma { name, span })
541}
542
543fn is_module_def(p: &Parser) -> bool {
545 if p.pos + 4 >= p.tokens.len() {
550 return false;
551 }
552 let third_is_param_name = matches!(
553 &p.tokens[p.pos + 2].kind,
554 TokenKind::Ident(_) | TokenKind::Input,
555 );
556 matches!(&p.tokens[p.pos].kind, TokenKind::Ident(_))
557 && matches!(&p.tokens[p.pos + 1].kind, TokenKind::LParen)
558 && third_is_param_name
559 && matches!(&p.tokens[p.pos + 3].kind, TokenKind::Colon)
560}
561
562fn parse_module_def(p: &mut Parser) -> Result<Statement, String> {
564 let span = p.span();
565 let name = p.expect_ident()?;
566
567 p.expect(&TokenKind::LParen)?;
569 let mut params = Vec::new();
570 while !matches!(p.peek(), TokenKind::RParen) {
571 let pname = p.expect_ident()?;
572 p.expect(&TokenKind::Colon)?;
573 let ptype = p.expect_ident()?;
574 params.push(TypedParam {
575 name: pname,
576 typ: ptype,
577 });
578 if matches!(p.peek(), TokenKind::Comma) {
579 p.advance();
580 }
581 }
582 p.expect(&TokenKind::RParen)?;
583
584 p.expect(&TokenKind::Arrow)?;
586 p.expect(&TokenKind::LParen)?;
587 let mut outputs = Vec::new();
588 while !matches!(p.peek(), TokenKind::RParen) {
589 let oname = p.expect_ident()?;
590 p.expect(&TokenKind::Colon)?;
591 let otype = p.expect_ident()?;
592 outputs.push(TypedParam {
593 name: oname,
594 typ: otype,
595 });
596 if matches!(p.peek(), TokenKind::Comma) {
597 p.advance();
598 }
599 }
600 p.expect(&TokenKind::RParen)?;
601
602 p.expect(&TokenKind::ColonEq)?;
604 p.expect(&TokenKind::LBrace)?;
605
606 let mut body = Vec::new();
607 while !matches!(p.peek(), TokenKind::RBrace | TokenKind::Eof) {
608 parse_statement_into(p, &mut body)?;
609 }
610 p.expect(&TokenKind::RBrace)?;
611
612 Ok(Statement::ModuleDef(ModuleDef {
613 name,
614 params,
615 outputs,
616 body,
617 span,
618 }))
619}
620
621fn parse_extern_port(p: &mut Parser) -> Result<Statement, String> {
623 let span = p.span();
624 p.advance(); let name = p.expect_ident()?;
627 p.expect(&TokenKind::Colon)?;
628 let typ = p.expect_ident()?;
629
630 let default = if matches!(p.peek(), TokenKind::Eq) {
632 p.advance(); Some(parse_expr(p)?)
634 } else {
635 None
636 };
637
638 Ok(Statement::ExternPort(ExternPort {
639 name,
640 typ,
641 default,
642 span,
643 }))
644}
645
646fn parse_input_decl(p: &mut Parser, out: &mut Vec<Statement>) -> Result<(), String> {
657 let keyword_span = p.span();
658 p.advance(); if matches!(p.peek(), TokenKind::LParen) {
661 p.advance(); if matches!(p.peek(), TokenKind::RParen) {
664 return Err(format!(
665 "`input ()` is empty; omit the line entirely to declare zero inputs \
666 (at line {}, col {})",
667 keyword_span.line, keyword_span.col,
668 ));
669 }
670 loop {
671 let span = p.span();
672 let name = p.expect_ident()?;
673 let ty = if matches!(p.peek(), TokenKind::Colon) {
674 p.advance();
675 Some(p.expect_ident()?)
676 } else {
677 None
678 };
679 out.push(Statement::InputDecl(InputDecl { name, ty, span }));
680 if matches!(p.peek(), TokenKind::Comma) {
681 p.advance();
682 } else {
683 break;
684 }
685 }
686 p.expect(&TokenKind::RParen)?;
687 } else {
688 let span = p.span();
690 let name = p.expect_ident()?;
691 let ty = if matches!(p.peek(), TokenKind::Colon) {
692 p.advance();
693 Some(p.expect_ident()?)
694 } else {
695 None
696 };
697 out.push(Statement::InputDecl(InputDecl { name, ty, span }));
698 }
699 Ok(())
700}
701
702fn parse_cursor_decl(p: &mut Parser) -> Result<Statement, String> {
707 let span = p.span();
708 p.advance(); let name = p.expect_ident()?;
710 p.expect(&TokenKind::Eq)?;
711 let constructor = parse_expr(p)?;
712 let over = if matches!(p.peek(), TokenKind::Over) {
714 p.advance();
715 Some(parse_expr(p)?)
716 } else {
717 None
718 };
719 Ok(Statement::Cursor(CursorDecl {
720 name,
721 constructor,
722 over,
723 span,
724 }))
725}
726
727fn parse_modified_binding(p: &mut Parser) -> Result<Statement, String> {
732 let start_span = p.span();
733 let mut collected: Vec<WireModifier> = Vec::new();
734
735 loop {
736 let m = match p.peek() {
737 TokenKind::Const => WireModifier::Const,
738 TokenKind::Shared => WireModifier::Shared,
739 TokenKind::Volatile => WireModifier::Volatile,
740 _ => break,
741 };
742 if collected.contains(&m) {
743 return Err(format!(
744 "duplicate `{m:?}` modifier at line {}, col {}",
745 p.span().line,
746 p.span().col,
747 ));
748 }
749 collected.push(m);
750 p.advance();
751 }
752
753 let modifier = BindingModifier::try_from_iter(collected)
754 .map_err(|e| format!("{e} at line {}, col {}", start_span.line, start_span.col,))?;
755
756 match p.peek() {
757 TokenKind::Ident(_) | TokenKind::Input | TokenKind::Cursor | TokenKind::Over => {
763 parse_cycle_binding_with_modifier(p, modifier)
764 }
765 _ => Err(format!(
766 "expected binding name after modifiers at line {}, col {}",
767 p.span().line,
768 p.span().col
769 )),
770 }
771}
772
773fn parse_cycle_binding(p: &mut Parser) -> Result<Statement, String> {
775 parse_cycle_binding_with_modifier(p, BindingModifier::NONE)
776}
777
778fn parse_cycle_binding_with_modifier(
779 p: &mut Parser,
780 modifier: BindingModifier,
781) -> Result<Statement, String> {
782 let span = p.span();
783 let name = p.expect_ident()?;
784 let type_annotation = if matches!(p.peek(), TokenKind::Colon) {
790 p.advance(); let typ = p.expect_ident()?;
792 if !modifier.is_shared() {
793 return Err(format!(
794 "type annotation `{name}: {typ}` is only supported on `shared` bindings (it pins the shared cell's type). For a plain typed slot use `extern {name}: {typ} = …` at line {}, col {}",
795 span.line, span.col,
796 ));
797 }
798 Some(typ)
799 } else {
800 None
801 };
802 p.expect(&TokenKind::ColonEq)?;
803 let value = parse_expr(p)?;
804
805 Ok(Statement::Binding(Binding {
806 targets: vec![name],
807 value,
808 modifier,
809 type_annotation,
810 span,
811 }))
812}
813
814fn parse_destructuring_binding(p: &mut Parser) -> Result<Statement, String> {
816 let span = p.span();
817 p.advance(); let mut targets = Vec::new();
819 loop {
820 targets.push(p.expect_ident()?);
821 if matches!(p.peek(), TokenKind::Comma) {
822 p.advance();
823 } else {
824 break;
825 }
826 }
827 p.expect(&TokenKind::RParen)?;
828 p.expect(&TokenKind::ColonEq)?;
829 let value = parse_expr(p)?;
830
831 Ok(Statement::Binding(Binding {
832 targets,
833 value,
834 modifier: BindingModifier::NONE,
835 type_annotation: None,
836 span,
837 }))
838}
839
840fn parse_expr(p: &mut Parser) -> Result<Expr, String> {
846 parse_expr_bp(p, 0)
847}
848
849fn parse_expr_bp(p: &mut Parser, min_bp: u8) -> Result<Expr, String> {
872 let mut lhs = parse_atom(p)?;
873 lhs = parse_postfix_as(p, lhs)?;
877
878 loop {
879 let op = match p.peek() {
880 TokenKind::PipePipe => Some((BinOpKind::Or, 1, 2)),
881 TokenKind::AmpAmp => Some((BinOpKind::And, 3, 4)),
882 TokenKind::EqEq => Some((BinOpKind::Eq, 5, 6)),
883 TokenKind::BangEq => Some((BinOpKind::Ne, 5, 6)),
884 TokenKind::Lt => Some((BinOpKind::Lt, 7, 8)),
885 TokenKind::Gt => Some((BinOpKind::Gt, 7, 8)),
886 TokenKind::LtEq => Some((BinOpKind::Le, 7, 8)),
887 TokenKind::GtEq => Some((BinOpKind::Ge, 7, 8)),
888 TokenKind::Pipe => Some((BinOpKind::BitOr, 9, 10)),
889 TokenKind::Caret => Some((BinOpKind::BitXor, 11, 12)),
890 TokenKind::Ampersand => Some((BinOpKind::BitAnd, 13, 14)),
891 TokenKind::ShiftLeft => Some((BinOpKind::Shl, 15, 16)),
892 TokenKind::ShiftRight => Some((BinOpKind::Shr, 15, 16)),
893 TokenKind::Plus => Some((BinOpKind::Add, 17, 18)),
894 TokenKind::Minus => Some((BinOpKind::Sub, 17, 18)),
895 TokenKind::Star => Some((BinOpKind::Mul, 19, 20)),
896 TokenKind::Slash => Some((BinOpKind::Div, 19, 20)),
897 TokenKind::Percent => Some((BinOpKind::Mod, 19, 20)),
898 TokenKind::StarStar => Some((BinOpKind::Pow, 22, 21)), _ => None,
900 };
901
902 let Some((op_kind, l_bp, r_bp)) = op else {
903 break;
904 };
905 if l_bp < min_bp {
906 break;
907 }
908
909 p.advance(); let rhs = parse_expr_bp(p, r_bp)?;
911 lhs = Expr::BinOp(Box::new(lhs), op_kind, Box::new(rhs));
912 }
913
914 Ok(lhs)
915}
916
917fn parse_postfix_as(p: &mut Parser, mut expr: Expr) -> Result<Expr, String> {
921 while matches!(p.peek(), TokenKind::Ident(s) if s.as_str() == "as") {
922 let span = p.span();
923 p.advance(); let ty_name = match p.peek() {
925 TokenKind::Ident(name) => name.clone(),
926 other => return Err(format!("expected a type name after `as`, found {other:?}")),
927 };
928 p.advance(); let port_type = crate::PortType::from_keyword(&ty_name)
930 .ok_or_else(|| format!("unknown type `{ty_name}` in `... as {ty_name}` cast"))?;
931 expr = Expr::Cast(Box::new(expr), port_type, span);
932 }
933 Ok(expr)
934}
935
936fn parse_if_block(p: &mut Parser, span: Span) -> Result<Expr, String> {
960 let cond = parse_expr(p)?;
961
962 if !matches!(p.peek(), TokenKind::LBrace) {
963 return Err(format!(
964 "expected `{{` to open the then-branch of an `if` expression, got {:?} at line {}, col {}. \
965 Block form is `if <cond> {{ <then> }} else {{ <else> }}`; the call form `if(cond, a, b)` \
966 is also accepted.",
967 p.peek(),
968 p.span().line,
969 p.span().col
970 ));
971 }
972 p.advance();
973 let then_expr = parse_expr(p)?;
974 p.expect(&TokenKind::RBrace)?;
975
976 match p.peek().clone() {
977 TokenKind::Ident(word) if word == "else" => {
978 p.advance();
979 }
980 other => {
981 return Err(format!(
982 "expected `else` after the then-branch of an `if` expression, got {:?} at line {}, col {}. \
983 `else` is required: a Polydat expression always produces a value, so there is no \
984 result for the false path without it.",
985 other,
986 p.span().line,
987 p.span().col
988 ));
989 }
990 }
991
992 let else_expr = match p.peek().clone() {
994 TokenKind::Ident(word) if word == "if" => {
995 let else_span = p.span();
996 p.advance();
997 parse_if_block(p, else_span)?
998 }
999 TokenKind::LBrace => {
1000 p.advance();
1001 let e = parse_expr(p)?;
1002 p.expect(&TokenKind::RBrace)?;
1003 e
1004 }
1005 other => {
1006 return Err(format!(
1007 "expected `{{` or `if` after `else`, got {:?} at line {}, col {}",
1008 other,
1009 p.span().line,
1010 p.span().col
1011 ));
1012 }
1013 };
1014
1015 Ok(Expr::Call(CallExpr {
1017 func: "if".into(),
1018 args: vec![
1019 Arg::Positional(cond),
1020 Arg::Positional(then_expr),
1021 Arg::Positional(else_expr),
1022 ],
1023 span,
1024 }))
1025}
1026
1027fn parse_atom(p: &mut Parser) -> Result<Expr, String> {
1028 let span = p.span();
1029
1030 match p.peek().clone() {
1031 TokenKind::Minus => {
1032 p.advance();
1034 let inner = parse_atom(p)?;
1035 Ok(Expr::UnaryNeg(Box::new(inner), span))
1036 }
1037 TokenKind::Bang => {
1038 p.advance();
1040 let inner = parse_atom(p)?;
1041 Ok(Expr::UnaryBitNot(Box::new(inner), span))
1042 }
1043 TokenKind::LParen => {
1044 p.advance(); let inner = parse_expr(p)?;
1048 p.expect(&TokenKind::RParen)?;
1049 Ok(inner)
1050 }
1051 TokenKind::StringLit(s) => {
1052 p.advance();
1053 Ok(parse_interpolated_string(s, span))
1054 }
1055 TokenKind::IntLit(v) => {
1056 p.advance();
1057 Ok(Expr::IntLit(v, span))
1058 }
1059 TokenKind::FloatLit(v) => {
1060 p.advance();
1061 Ok(Expr::FloatLit(v, span))
1062 }
1063 TokenKind::LBracket => parse_array_lit(p),
1064 TokenKind::For(text) => {
1065 p.advance();
1066 let source = for_source_from_text(&text, span, false)?;
1067 Ok(Expr::For(Box::new(source)))
1068 }
1069 TokenKind::Ident(name) => {
1070 p.advance();
1071 if name == "if" && !matches!(p.peek(), TokenKind::LParen) {
1077 parse_if_block(p, span)
1078 } else if matches!(p.peek(), TokenKind::LParen) {
1079 parse_call(p, name, span)
1081 } else if matches!(p.peek(), TokenKind::Dot) {
1082 parse_field_chain(p, name, span)
1083 } else {
1084 Ok(Expr::Ident(name, span))
1085 }
1086 }
1087 TokenKind::Input => {
1091 p.advance();
1092 let name = "input".to_string();
1093 if matches!(p.peek(), TokenKind::Dot) {
1094 parse_field_chain(p, name, span)
1095 } else {
1096 Ok(Expr::Ident(name, span))
1097 }
1098 }
1099 TokenKind::Cursor => {
1105 p.advance();
1106 let name = "cursor".to_string();
1107 if matches!(p.peek(), TokenKind::Dot) {
1108 parse_field_chain(p, name, span)
1109 } else {
1110 Ok(Expr::Ident(name, span))
1111 }
1112 }
1113 TokenKind::Over => {
1118 p.advance();
1119 let name = "over".to_string();
1120 if matches!(p.peek(), TokenKind::Dot) {
1121 parse_field_chain(p, name, span)
1122 } else {
1123 Ok(Expr::Ident(name, span))
1124 }
1125 }
1126 _ => Err(format!(
1127 "expected expression, got {:?} at line {}, col {}",
1128 p.peek(),
1129 span.line,
1130 span.col
1131 )),
1132 }
1133}
1134
1135fn parse_interpolated_string(s: String, span: Span) -> Expr {
1170 let segments = match scan_interpolation_segments(&s) {
1171 Some(segs) => segs,
1172 None => return Expr::StringLit(s, span), };
1174
1175 if !segments
1176 .iter()
1177 .any(|seg| matches!(seg, Segment::Placeholder(_)))
1178 {
1179 return Expr::StringLit(s, span);
1180 }
1181
1182 let mut format_str = String::with_capacity(s.len());
1187 let mut placeholder_exprs: Vec<Expr> = Vec::new();
1188 for seg in segments {
1189 match seg {
1190 Segment::Literal(text) => format_str.push_str(&text),
1191 Segment::Placeholder(body) => {
1192 let expr = match parse_placeholder_body(&body, span) {
1193 Ok(e) => e,
1194 Err(_) => return Expr::StringLit(s, span),
1199 };
1200 placeholder_exprs.push(expr);
1201 format_str.push_str("{}");
1202 }
1203 }
1204 }
1205
1206 let mut args: Vec<Arg> = Vec::with_capacity(placeholder_exprs.len() + 1);
1207 args.push(Arg::Positional(Expr::StringLit(format_str, span)));
1208 for e in placeholder_exprs {
1209 args.push(Arg::Positional(e));
1210 }
1211 Expr::Call(CallExpr {
1212 func: "printf".into(),
1213 args,
1214 span,
1215 })
1216}
1217
1218enum Segment {
1220 Literal(String),
1224 Placeholder(String),
1227}
1228
1229fn scan_interpolation_segments(s: &str) -> Option<Vec<Segment>> {
1238 let chars: Vec<char> = s.chars().collect();
1239 let mut segments: Vec<Segment> = Vec::new();
1240 let mut literal = String::new();
1241 let mut i = 0;
1242 while i < chars.len() {
1243 let c = chars[i];
1244 if c == '{' && i + 1 < chars.len() && chars[i + 1] == '{' {
1247 literal.push_str("{{");
1248 i += 2;
1249 continue;
1250 }
1251 if c == '}' && i + 1 < chars.len() && chars[i + 1] == '}' {
1252 literal.push_str("}}");
1253 i += 2;
1254 continue;
1255 }
1256 if c == '{' {
1257 if !literal.is_empty() {
1258 segments.push(Segment::Literal(std::mem::take(&mut literal)));
1259 }
1260 let body_start = i + 1;
1261 let body_end = find_placeholder_end(&chars, body_start)?;
1262 let body: String = chars[body_start..body_end].iter().collect();
1263 segments.push(Segment::Placeholder(body));
1264 i = body_end + 1; continue;
1266 }
1267 literal.push(c);
1268 i += 1;
1269 }
1270 if !literal.is_empty() {
1271 segments.push(Segment::Literal(literal));
1272 }
1273 Some(segments)
1274}
1275
1276fn find_placeholder_end(chars: &[char], start: usize) -> Option<usize> {
1281 let mut depth: i32 = 0;
1282 let mut in_string = false;
1283 let mut i = start;
1284 while i < chars.len() {
1285 let c = chars[i];
1286 if in_string {
1287 if c == '\\' && i + 1 < chars.len() {
1288 i += 2;
1293 continue;
1294 }
1295 if c == '"' {
1296 in_string = false;
1297 }
1298 i += 1;
1299 continue;
1300 }
1301 match c {
1302 '"' => in_string = true,
1303 '(' | '[' | '{' => depth += 1,
1304 ')' | ']' => depth -= 1,
1305 '}' => {
1306 if depth == 0 {
1307 return Some(i);
1308 }
1309 depth -= 1;
1310 }
1311 _ => {}
1312 }
1313 i += 1;
1314 }
1315 None
1316}
1317
1318fn parse_placeholder_body(body: &str, _span: Span) -> Result<Expr, String> {
1320 let body = body.trim();
1321 if body.is_empty() {
1322 return Err("empty placeholder".into());
1323 }
1324 let tokens = crate::lexer::lex(body)?;
1325 parse_expression(tokens)
1326}
1327
1328fn parse_field_chain(p: &mut Parser, base: String, span: Span) -> Result<Expr, String> {
1336 p.advance(); let mut source = base;
1338 let mut field = p.expect_ident()?;
1339 while matches!(p.peek(), TokenKind::Dot) {
1340 p.advance();
1341 source = format!("{source}__{field}");
1342 field = p.expect_ident()?;
1343 }
1344 Ok(Expr::FieldAccess {
1345 source,
1346 field,
1347 span,
1348 })
1349}
1350
1351fn parse_call(p: &mut Parser, func: String, span: Span) -> Result<Expr, String> {
1353 p.advance(); let mut args = Vec::new();
1355
1356 if !matches!(p.peek(), TokenKind::RParen) {
1357 loop {
1358 args.push(parse_arg(p)?);
1359 if matches!(p.peek(), TokenKind::Comma) {
1360 p.advance();
1361 } else {
1362 break;
1363 }
1364 }
1365 }
1366
1367 p.expect(&TokenKind::RParen)?;
1368 Ok(Expr::Call(CallExpr { func, args, span }))
1369}
1370
1371fn parse_arg(p: &mut Parser) -> Result<Arg, String> {
1377 let arg_name: Option<String> = match p.peek() {
1378 TokenKind::Ident(name) => Some(name.clone()),
1379 TokenKind::Input => Some("input".to_string()),
1380 _ => None,
1381 };
1382 if let Some(name) = arg_name
1383 && p.pos + 1 < p.tokens.len()
1384 && matches!(p.tokens[p.pos + 1].kind, TokenKind::Colon)
1385 {
1386 p.advance(); p.advance(); let value = parse_expr(p)?;
1389 return Ok(Arg::Named(name, value));
1390 }
1391 let expr = parse_expr(p)?;
1392 Ok(Arg::Positional(expr))
1393}
1394
1395fn parse_array_lit(p: &mut Parser) -> Result<Expr, String> {
1397 let span = p.span();
1398 p.advance(); let mut elements = Vec::new();
1400
1401 if !matches!(p.peek(), TokenKind::RBracket) {
1402 loop {
1403 elements.push(parse_expr(p)?);
1404 if matches!(p.peek(), TokenKind::Comma) {
1405 p.advance();
1406 } else {
1407 break;
1408 }
1409 }
1410 }
1411
1412 p.expect(&TokenKind::RBracket)?;
1413 Ok(Expr::ArrayLit(elements, span))
1414}
1415
1416#[cfg(test)]
1417mod tests {
1418 use super::*;
1419 use crate::lexer::lex;
1420
1421 fn parse_str(s: &str) -> PolydatFile {
1422 let tokens = lex(s).unwrap();
1423 parse(tokens).unwrap()
1424 }
1425
1426 fn parse_str_err(s: &str) -> String {
1427 let tokens = lex(s).unwrap();
1428 match parse(tokens) {
1429 Ok(_) => panic!("expected parse error from: {s:?}"),
1430 Err(e) => e,
1431 }
1432 }
1433
1434 fn cycle_modifier_of(f: &PolydatFile) -> BindingModifier {
1435 match &f.statements[0] {
1436 Statement::Binding(b) => b.modifier,
1437 other => panic!("expected cycle binding, got {other:?}"),
1438 }
1439 }
1440
1441 #[test]
1442 fn parse_volatile_modifier() {
1443 let f = parse_str("volatile x := 42");
1444 let m = cycle_modifier_of(&f);
1445 assert!(m.is_volatile() && !m.is_const() && !m.is_shared());
1446 }
1447
1448 #[test]
1449 fn parse_modifiers_in_any_order_yields_same_set() {
1450 let m1 = cycle_modifier_of(&parse_str("const shared x := 42"));
1451 let m2 = cycle_modifier_of(&parse_str("shared const x := 42"));
1452 assert_eq!(
1453 m1, m2,
1454 "ordering shouldn't matter: `const shared` and `shared const` collapse to the same set"
1455 );
1456 assert!(m1.is_const() && m1.is_shared());
1457 }
1458
1459 #[test]
1460 fn parse_shared_volatile_combination() {
1461 let m = cycle_modifier_of(&parse_str("shared volatile x := 42"));
1462 assert!(m.is_shared() && m.is_volatile() && !m.is_const());
1463 }
1464
1465 #[test]
1466 fn parse_rejects_const_volatile_combo() {
1467 let err = parse_str_err("const volatile x := 42");
1468 assert!(
1469 err.contains("const") && err.contains("volatile"),
1470 "error should name the conflicting keywords: {err}"
1471 );
1472 }
1473
1474 #[test]
1475 fn parse_rejects_volatile_const_combo_same_as_const_volatile() {
1476 let err = parse_str_err("volatile const x := 42");
1478 assert!(err.contains("const") && err.contains("volatile"));
1479 }
1480
1481 #[test]
1482 fn parse_rejects_duplicate_modifier() {
1483 let err = parse_str_err("const const x := 42");
1484 assert!(
1485 err.contains("duplicate"),
1486 "error should call out duplicate: {err}"
1487 );
1488 }
1489
1490 #[test]
1491 fn parse_volatile_const_binding() {
1492 let f = parse_str("volatile x := 42");
1499 match &f.statements[0] {
1500 Statement::Binding(b) => {
1501 assert!(b.modifier.is_volatile());
1502 assert!(!b.modifier.is_const());
1503 }
1504 other => panic!("expected binding, got {other:?}"),
1505 }
1506 }
1507
1508 #[test]
1509 fn parse_input_bare() {
1510 let f = parse_str("input cycle: u64");
1511 assert_eq!(f.statements.len(), 1);
1512 match &f.statements[0] {
1513 Statement::InputDecl(d) => {
1514 assert_eq!(d.name, "cycle");
1515 assert_eq!(d.ty.as_deref(), Some("u64"));
1516 }
1517 other => panic!("expected InputDecl, got {other:?}"),
1518 }
1519 }
1520
1521 #[test]
1522 fn parse_input_bare_untyped() {
1523 let f = parse_str("input cycle");
1524 match &f.statements[0] {
1525 Statement::InputDecl(d) => {
1526 assert_eq!(d.name, "cycle");
1527 assert!(d.ty.is_none(), "no type annotation");
1528 }
1529 other => panic!("expected InputDecl, got {other:?}"),
1530 }
1531 }
1532
1533 #[test]
1534 fn parse_input_tuple_form() {
1535 let f = parse_str("input (cycle: u64, q: f64)");
1538 assert_eq!(f.statements.len(), 2);
1539 match &f.statements[0] {
1540 Statement::InputDecl(d) => {
1541 assert_eq!(d.name, "cycle");
1542 assert_eq!(d.ty.as_deref(), Some("u64"));
1543 }
1544 other => panic!("expected InputDecl, got {other:?}"),
1545 }
1546 match &f.statements[1] {
1547 Statement::InputDecl(d) => {
1548 assert_eq!(d.name, "q");
1549 assert_eq!(d.ty.as_deref(), Some("f64"));
1550 }
1551 other => panic!("expected InputDecl, got {other:?}"),
1552 }
1553 }
1554
1555 #[test]
1556 fn parse_input_tuple_empty_rejected() {
1557 let tokens = crate::lexer::lex("input ()").unwrap();
1559 let err = parse(tokens).unwrap_err();
1560 assert!(
1561 err.contains("empty"),
1562 "error should mention empty tuple: {err}"
1563 );
1564 }
1565
1566 #[test]
1567 fn parse_const_binding() {
1568 let f = parse_str("const lut := dist_normal(72.0, 5.0)");
1569 assert_eq!(f.statements.len(), 1);
1570 match &f.statements[0] {
1571 Statement::Binding(b) => {
1572 assert_eq!(b.targets, vec!["lut"]);
1573 assert!(b.modifier.is_const());
1574 match &b.value {
1575 Expr::Call(c) => {
1576 assert_eq!(c.func, "dist_normal");
1577 assert_eq!(c.args.len(), 2);
1578 }
1579 _ => panic!("expected call"),
1580 }
1581 }
1582 _ => panic!("expected const binding"),
1583 }
1584 }
1585
1586 #[test]
1587 fn parse_cycle_binding() {
1588 let f = parse_str("seed := hash(cycle)");
1589 match &f.statements[0] {
1590 Statement::Binding(b) => {
1591 assert_eq!(b.targets, vec!["seed"]);
1592 match &b.value {
1593 Expr::Call(c) => {
1594 assert_eq!(c.func, "hash");
1595 assert_eq!(c.args.len(), 1);
1596 }
1597 _ => panic!("expected call"),
1598 }
1599 }
1600 _ => panic!("expected cycle binding"),
1601 }
1602 }
1603
1604 #[test]
1605 fn parse_destructuring() {
1606 let f = parse_str("(tenant, device, reading) := mixed_radix(cycle, 100, 1000, 0)");
1607 match &f.statements[0] {
1608 Statement::Binding(b) => {
1609 assert_eq!(b.targets, vec!["tenant", "device", "reading"]);
1610 match &b.value {
1611 Expr::Call(c) => {
1612 assert_eq!(c.func, "mixed_radix");
1613 assert_eq!(c.args.len(), 4);
1614 }
1615 _ => panic!("expected call"),
1616 }
1617 }
1618 _ => panic!("expected cycle binding"),
1619 }
1620 }
1621
1622 #[test]
1623 fn parse_named_args() {
1624 let f = parse_str("const lut := dist_normal(mean: 72.0, stddev: 5.0)");
1625 match &f.statements[0] {
1626 Statement::Binding(b) => match &b.value {
1627 Expr::Call(c) => {
1628 assert!(matches!(&c.args[0], Arg::Named(n, _) if n == "mean"));
1629 assert!(matches!(&c.args[1], Arg::Named(n, _) if n == "stddev"));
1630 }
1631 _ => panic!("expected call"),
1632 },
1633 _ => panic!("expected const binding"),
1634 }
1635 }
1636
1637 #[test]
1638 fn parse_string_lit_plain() {
1639 let f = parse_str(r#"id := "static text""#);
1642 match &f.statements[0] {
1643 Statement::Binding(b) => match &b.value {
1644 Expr::StringLit(s, _) => assert_eq!(s, "static text"),
1645 _ => panic!("expected string lit"),
1646 },
1647 _ => panic!("expected binding"),
1648 }
1649 }
1650
1651 #[test]
1652 fn parse_string_lit_interpolated() {
1653 let f = parse_str(r#"id := "{code}-{seq}""#);
1657 match &f.statements[0] {
1658 Statement::Binding(b) => match &b.value {
1659 Expr::Call(c) => {
1660 assert_eq!(c.func, "printf");
1661 assert_eq!(c.args.len(), 3);
1662 match &c.args[0] {
1663 Arg::Positional(Expr::StringLit(s, _)) => assert_eq!(s, "{}-{}"),
1664 _ => panic!("expected format string as first arg"),
1665 }
1666 match &c.args[1] {
1667 Arg::Positional(Expr::Ident(n, _)) => assert_eq!(n, "code"),
1668 _ => panic!("expected ident `code`"),
1669 }
1670 match &c.args[2] {
1671 Arg::Positional(Expr::Ident(n, _)) => assert_eq!(n, "seq"),
1672 _ => panic!("expected ident `seq`"),
1673 }
1674 }
1675 other => panic!("expected printf call, got {other:?}"),
1676 },
1677 _ => panic!("expected binding"),
1678 }
1679 }
1680
1681 #[test]
1682 fn parse_string_lit_format_spec_left_alone() {
1683 let f = parse_str(r#"id := "x={:05}""#);
1688 match &f.statements[0] {
1689 Statement::Binding(b) => match &b.value {
1690 Expr::StringLit(s, _) => assert_eq!(s, "x={:05}"),
1691 _ => panic!("expected literal"),
1692 },
1693 _ => panic!("expected binding"),
1694 }
1695 }
1696
1697 #[test]
1698 fn parse_string_lit_nested_call() {
1699 let f = parse_str(r#"email := "{format_u64(hash(cycle), 10)}@example.com""#);
1702 let call = match &f.statements[0] {
1703 Statement::Binding(b) => match &b.value {
1704 Expr::Call(c) => c,
1705 other => panic!("expected printf call, got {other:?}"),
1706 },
1707 _ => panic!("expected binding"),
1708 };
1709 assert_eq!(call.func, "printf");
1710 assert_eq!(call.args.len(), 2);
1711 match &call.args[0] {
1712 Arg::Positional(Expr::StringLit(s, _)) => assert_eq!(s, "{}@example.com"),
1713 other => panic!("expected format string, got {other:?}"),
1714 }
1715 match &call.args[1] {
1716 Arg::Positional(Expr::Call(inner)) => {
1717 assert_eq!(inner.func, "format_u64");
1718 assert_eq!(inner.args.len(), 2);
1719 match &inner.args[0] {
1720 Arg::Positional(Expr::Call(h)) => assert_eq!(h.func, "hash"),
1721 other => panic!("expected hash(...) call, got {other:?}"),
1722 }
1723 match &inner.args[1] {
1724 Arg::Positional(Expr::IntLit(10, _)) => {}
1725 other => panic!("expected literal 10, got {other:?}"),
1726 }
1727 }
1728 other => panic!("expected format_u64 call, got {other:?}"),
1729 }
1730 }
1731
1732 #[test]
1733 fn parse_string_lit_arithmetic_in_placeholder() {
1734 let f = parse_str(r#"id := "x={a + b * 2}""#);
1737 let call = match &f.statements[0] {
1738 Statement::Binding(b) => match &b.value {
1739 Expr::Call(c) => c,
1740 other => panic!("expected call, got {other:?}"),
1741 },
1742 _ => panic!("expected binding"),
1743 };
1744 assert_eq!(call.func, "printf");
1745 match &call.args[1] {
1746 Arg::Positional(Expr::BinOp(_, BinOpKind::Add, _)) => {}
1747 other => panic!("expected addition, got {other:?}"),
1748 }
1749 }
1750
1751 #[test]
1752 fn parse_string_lit_field_access() {
1753 let f = parse_str(r#"k := "row {row.id}""#);
1755 let call = match &f.statements[0] {
1756 Statement::Binding(b) => match &b.value {
1757 Expr::Call(c) => c,
1758 other => panic!("expected call, got {other:?}"),
1759 },
1760 _ => panic!("expected binding"),
1761 };
1762 assert_eq!(call.func, "printf");
1763 match &call.args[1] {
1764 Arg::Positional(Expr::FieldAccess { source, field, .. }) => {
1765 assert_eq!(source, "row");
1766 assert_eq!(field, "id");
1767 }
1768 other => panic!("expected field access, got {other:?}"),
1769 }
1770 }
1771
1772 #[test]
1773 fn parse_string_lit_escaped_braces() {
1774 let f = parse_str(r#"k := "{{not a placeholder}} but {real}""#);
1778 let call = match &f.statements[0] {
1779 Statement::Binding(b) => match &b.value {
1780 Expr::Call(c) => c,
1781 other => panic!("expected call, got {other:?}"),
1782 },
1783 _ => panic!("expected binding"),
1784 };
1785 match &call.args[0] {
1786 Arg::Positional(Expr::StringLit(s, _)) => {
1787 assert_eq!(s, "{{not a placeholder}} but {}");
1788 }
1789 other => panic!("expected fmt string, got {other:?}"),
1790 }
1791 match &call.args[1] {
1792 Arg::Positional(Expr::Ident(n, _)) => assert_eq!(n, "real"),
1793 other => panic!("expected ident `real`, got {other:?}"),
1794 }
1795 }
1796
1797 #[test]
1798 fn parse_string_lit_unterminated_falls_back() {
1799 let f = parse_str(r#"k := "missing close {abc""#);
1801 match &f.statements[0] {
1802 Statement::Binding(b) => match &b.value {
1803 Expr::StringLit(s, _) => assert_eq!(s, "missing close {abc"),
1804 other => panic!("expected literal, got {other:?}"),
1805 },
1806 _ => panic!("expected binding"),
1807 }
1808 }
1809
1810 #[test]
1811 fn parse_string_lit_parens_in_placeholder() {
1812 let f = parse_str(r#"k := "{abs(x - y)}""#);
1816 let call = match &f.statements[0] {
1817 Statement::Binding(b) => match &b.value {
1818 Expr::Call(c) => c,
1819 other => panic!("expected call, got {other:?}"),
1820 },
1821 _ => panic!("expected binding"),
1822 };
1823 assert_eq!(call.func, "printf");
1824 match &call.args[1] {
1825 Arg::Positional(Expr::Call(inner)) => assert_eq!(inner.func, "abs"),
1826 other => panic!("expected abs call, got {other:?}"),
1827 }
1828 }
1829
1830 #[test]
1831 fn parse_array_lit() {
1832 let f = parse_str("const weights := [60.0, 20.0, 15.0, 5.0]");
1833 match &f.statements[0] {
1834 Statement::Binding(b) => match &b.value {
1835 Expr::ArrayLit(elems, _) => assert_eq!(elems.len(), 4),
1836 _ => panic!("expected array lit"),
1837 },
1838 _ => panic!("expected const binding"),
1839 }
1840 }
1841
1842 #[test]
1843 fn parse_nested_call() {
1844 let f = parse_str("x := hash(interleave(a, b))");
1845 match &f.statements[0] {
1846 Statement::Binding(b) => match &b.value {
1847 Expr::Call(c) => {
1848 assert_eq!(c.func, "hash");
1849 assert_eq!(c.args.len(), 1);
1850 match &c.args[0] {
1851 Arg::Positional(Expr::Call(inner)) => {
1852 assert_eq!(inner.func, "interleave");
1853 assert_eq!(inner.args.len(), 2);
1854 }
1855 _ => panic!("expected nested call"),
1856 }
1857 }
1858 _ => panic!("expected call"),
1859 },
1860 _ => panic!("expected binding"),
1861 }
1862 }
1863
1864 #[test]
1865 fn parse_full_program() {
1866 let src = r#"
1867 // Const bindings (compile-time fold or scope-init pull)
1868 const temp_lut := dist_normal(mean: 72.0, stddev: 5.0)
1869 const weights := [60.0, 20.0, 15.0]
1870
1871 // Cycle bindings (per-cycle eval)
1872 input cycle: u64
1873 (tenant, device) := mixed_radix(cycle, 100, 0)
1874 tenant_h := hash(tenant)
1875 code := mod(tenant_h, 10000)
1876 device_id := "{code}-{seq}"
1877 "#;
1878 let f = parse_str(src);
1879 assert_eq!(f.statements.len(), 7);
1880 }
1881
1882 #[test]
1883 fn parse_mixed_positional_named() {
1884 let f = parse_str("const lut := dist_normal(72.0, 5.0, resolution: 2000)");
1885 match &f.statements[0] {
1886 Statement::Binding(b) => match &b.value {
1887 Expr::Call(c) => {
1888 assert!(matches!(&c.args[0], Arg::Positional(_)));
1889 assert!(matches!(&c.args[1], Arg::Positional(_)));
1890 assert!(matches!(&c.args[2], Arg::Named(n, _) if n == "resolution"));
1891 }
1892 _ => panic!("expected call"),
1893 },
1894 _ => panic!("expected const binding"),
1895 }
1896 }
1897
1898 #[test]
1899 fn parse_simple_addition() {
1900 let f = parse_str("y := a + b");
1901 match &f.statements[0] {
1902 Statement::Binding(b) => match &b.value {
1903 Expr::BinOp(lhs, BinOpKind::Add, rhs) => {
1904 assert!(matches!(**lhs, Expr::Ident(ref s, _) if s == "a"));
1905 assert!(matches!(**rhs, Expr::Ident(ref s, _) if s == "b"));
1906 }
1907 _ => panic!("expected BinOp Add, got {:?}", b.value),
1908 },
1909 _ => panic!("expected cycle binding"),
1910 }
1911 }
1912
1913 #[test]
1914 fn parse_precedence_mul_over_add() {
1915 let f = parse_str("y := a + b * c");
1917 match &f.statements[0] {
1918 Statement::Binding(b) => match &b.value {
1919 Expr::BinOp(lhs, BinOpKind::Add, rhs) => {
1920 assert!(matches!(**lhs, Expr::Ident(ref s, _) if s == "a"));
1921 match &**rhs {
1922 Expr::BinOp(rl, BinOpKind::Mul, rr) => {
1923 assert!(matches!(**rl, Expr::Ident(ref s, _) if s == "b"));
1924 assert!(matches!(**rr, Expr::Ident(ref s, _) if s == "c"));
1925 }
1926 _ => panic!("expected inner Mul"),
1927 }
1928 }
1929 _ => panic!("expected outer Add"),
1930 },
1931 _ => panic!("expected cycle binding"),
1932 }
1933 }
1934
1935 #[test]
1936 fn parse_parenthesized_grouping() {
1937 let f = parse_str("y := (a + b) * c");
1939 match &f.statements[0] {
1940 Statement::Binding(b) => {
1941 match &b.value {
1942 Expr::BinOp(lhs, BinOpKind::Mul, rhs) => {
1943 match &**lhs {
1944 Expr::BinOp(_, BinOpKind::Add, _) => {} _ => panic!("expected inner Add in lhs"),
1946 }
1947 assert!(matches!(**rhs, Expr::Ident(ref s, _) if s == "c"));
1948 }
1949 _ => panic!("expected outer Mul"),
1950 }
1951 }
1952 _ => panic!("expected cycle binding"),
1953 }
1954 }
1955
1956 fn if_call(src: &str) -> CallExpr {
1958 let f = parse_str(src);
1959 match &f.statements[0] {
1960 Statement::Binding(b) => match &b.value {
1961 Expr::Call(c) => {
1962 assert_eq!(
1963 c.func, "if",
1964 "block form must desugar to the `if` intrinsic"
1965 );
1966 assert_eq!(c.args.len(), 3, "if intrinsic takes (cond, then, else)");
1967 c.clone()
1968 }
1969 other => panic!("expected Call, got {:?}", other),
1970 },
1971 _ => panic!("expected binding"),
1972 }
1973 }
1974
1975 #[test]
1976 fn if_block_desugars_to_the_call_intrinsic() {
1977 let block = if_call("y := if c { a } else { b }");
1981 let call = if_call("y := if(c, a, b)");
1982 for (i, (bl, ca)) in block.args.iter().zip(call.args.iter()).enumerate() {
1983 match (bl, ca) {
1984 (Arg::Positional(Expr::Ident(x, _)), Arg::Positional(Expr::Ident(y, _))) => {
1985 assert_eq!(x, y, "arg {} differs between block and call form", i);
1986 }
1987 _ => panic!("expected plain idents in both forms"),
1988 }
1989 }
1990 }
1991
1992 #[test]
1993 fn if_block_accepts_expressions_in_condition_and_branches() {
1994 let c = if_call("y := if segments > 0 { total / segments } else { 0 }");
1995 assert!(
1996 matches!(
1997 &c.args[0],
1998 Arg::Positional(Expr::BinOp(_, BinOpKind::Gt, _))
1999 ),
2000 "condition should parse as a full expression"
2001 );
2002 assert!(
2003 matches!(
2004 &c.args[1],
2005 Arg::Positional(Expr::BinOp(_, BinOpKind::Div, _))
2006 ),
2007 "then-branch should parse as a full expression"
2008 );
2009 }
2010
2011 #[test]
2012 fn if_block_chains_else_if() {
2013 let c = if_call("y := if a { 1 } else if b { 2 } else { 3 }");
2015 match &c.args[2] {
2016 Arg::Positional(Expr::Call(inner)) => {
2017 assert_eq!(inner.func, "if");
2018 assert!(matches!(
2019 &inner.args[1],
2020 Arg::Positional(Expr::IntLit(2, _))
2021 ));
2022 assert!(matches!(
2023 &inner.args[2],
2024 Arg::Positional(Expr::IntLit(3, _))
2025 ));
2026 }
2027 other => panic!("expected nested if in else position, got {:?}", other),
2028 }
2029 }
2030
2031 #[test]
2032 fn if_block_nests_inside_other_expressions() {
2033 let f = parse_str("y := 1 + if c { 2 } else { 3 }");
2035 match &f.statements[0] {
2036 Statement::Binding(b) => match &b.value {
2037 Expr::BinOp(_, BinOpKind::Add, rhs) => {
2038 assert!(matches!(**rhs, Expr::Call(ref c) if c.func == "if"));
2039 }
2040 other => panic!("expected Add with an if on the rhs, got {:?}", other),
2041 },
2042 _ => panic!("expected binding"),
2043 }
2044 }
2045
2046 #[test]
2047 fn if_call_form_still_parses_as_a_call() {
2048 let c = if_call("y := if(c, a, b)");
2050 assert_eq!(c.args.len(), 3);
2051 }
2052
2053 #[test]
2054 fn if_block_requires_else() {
2055 let err = parse_str_err("y := if c { a }");
2058 assert!(
2059 err.contains("else"),
2060 "error should name the missing else: {}",
2061 err
2062 );
2063 }
2064
2065 #[test]
2066 fn if_block_reports_a_missing_brace_helpfully() {
2067 let err = parse_str_err("y := if c a else b");
2068 assert!(
2069 err.contains("if <cond>"),
2070 "error should show the block form: {}",
2071 err
2072 );
2073 }
2074
2075 #[test]
2076 fn parse_unary_negation() {
2077 let f = parse_str("y := -x");
2078 match &f.statements[0] {
2079 Statement::Binding(b) => match &b.value {
2080 Expr::UnaryNeg(inner, _) => {
2081 assert!(matches!(**inner, Expr::Ident(ref s, _) if s == "x"));
2082 }
2083 _ => panic!("expected UnaryNeg"),
2084 },
2085 _ => panic!("expected cycle binding"),
2086 }
2087 }
2088
2089 #[test]
2090 fn parse_func_call_with_infix_arg() {
2091 let f = parse_str("y := sin(cycle * 0.25)");
2093 match &f.statements[0] {
2094 Statement::Binding(b) => match &b.value {
2095 Expr::Call(c) => {
2096 assert_eq!(c.func, "sin");
2097 assert_eq!(c.args.len(), 1);
2098 match &c.args[0] {
2099 Arg::Positional(Expr::BinOp(_, BinOpKind::Mul, _)) => {}
2100 _ => panic!("expected Mul inside sin() arg"),
2101 }
2102 }
2103 _ => panic!("expected call"),
2104 },
2105 _ => panic!("expected cycle binding"),
2106 }
2107 }
2108
2109 #[test]
2110 fn parse_power_right_associative() {
2111 let f = parse_str("y := a ** b ** c");
2113 match &f.statements[0] {
2114 Statement::Binding(b) => match &b.value {
2115 Expr::BinOp(lhs, BinOpKind::Pow, rhs) => {
2116 assert!(matches!(**lhs, Expr::Ident(ref s, _) if s == "a"));
2117 match &**rhs {
2118 Expr::BinOp(rl, BinOpKind::Pow, rr) => {
2119 assert!(matches!(**rl, Expr::Ident(ref s, _) if s == "b"));
2120 assert!(matches!(**rr, Expr::Ident(ref s, _) if s == "c"));
2121 }
2122 _ => panic!("expected inner Pow"),
2123 }
2124 }
2125 _ => panic!("expected outer Pow"),
2126 },
2127 _ => panic!("expected cycle binding"),
2128 }
2129 }
2130
2131 #[test]
2132 fn parse_negate_function_call() {
2133 let f = parse_str("y := -sin(x)");
2135 match &f.statements[0] {
2136 Statement::Binding(b) => match &b.value {
2137 Expr::UnaryNeg(inner, _) => match &**inner {
2138 Expr::Call(c) => assert_eq!(c.func, "sin"),
2139 _ => panic!("expected Call inside UnaryNeg"),
2140 },
2141 _ => panic!("expected UnaryNeg"),
2142 },
2143 _ => panic!("expected cycle binding"),
2144 }
2145 }
2146
2147 #[test]
2148 fn parse_all_operators() {
2149 let f = parse_str("y := a + b - c * d / e % f ** g");
2151 match &f.statements[0] {
2152 Statement::Binding(_) => {} _ => panic!("expected cycle binding"),
2154 }
2155 }
2156
2157 #[test]
2158 fn parse_star_star_power() {
2159 let f = parse_str("y := x ** 2.0");
2161 match &f.statements[0] {
2162 Statement::Binding(b) => match &b.value {
2163 Expr::BinOp(lhs, BinOpKind::Pow, rhs) => {
2164 assert!(matches!(**lhs, Expr::Ident(ref s, _) if s == "x"));
2165 assert!(matches!(**rhs, Expr::FloatLit(v, _) if v == 2.0));
2166 }
2167 _ => panic!("expected BinOp Pow, got {:?}", b.value),
2168 },
2169 _ => panic!("expected cycle binding"),
2170 }
2171 }
2172
2173 #[test]
2174 fn parse_caret_is_xor() {
2175 let f = parse_str("y := a ^ b");
2177 match &f.statements[0] {
2178 Statement::Binding(b) => match &b.value {
2179 Expr::BinOp(lhs, BinOpKind::BitXor, rhs) => {
2180 assert!(matches!(**lhs, Expr::Ident(ref s, _) if s == "a"));
2181 assert!(matches!(**rhs, Expr::Ident(ref s, _) if s == "b"));
2182 }
2183 _ => panic!("expected BinOp BitXor, got {:?}", b.value),
2184 },
2185 _ => panic!("expected cycle binding"),
2186 }
2187 }
2188
2189 #[test]
2190 fn parse_bitand_binds_tighter_than_bitor() {
2191 let f = parse_str("y := a & b | c");
2193 match &f.statements[0] {
2194 Statement::Binding(b) => {
2195 match &b.value {
2196 Expr::BinOp(lhs, BinOpKind::BitOr, rhs) => {
2197 match &**lhs {
2198 Expr::BinOp(_, BinOpKind::BitAnd, _) => {} _ => panic!("expected inner BitAnd in lhs"),
2200 }
2201 assert!(matches!(**rhs, Expr::Ident(ref s, _) if s == "c"));
2202 }
2203 _ => panic!("expected outer BitOr"),
2204 }
2205 }
2206 _ => panic!("expected cycle binding"),
2207 }
2208 }
2209
2210 #[test]
2211 fn parse_shift_left() {
2212 let f = parse_str("y := a << 4");
2214 match &f.statements[0] {
2215 Statement::Binding(b) => match &b.value {
2216 Expr::BinOp(lhs, BinOpKind::Shl, rhs) => {
2217 assert!(matches!(**lhs, Expr::Ident(ref s, _) if s == "a"));
2218 assert!(matches!(**rhs, Expr::IntLit(4, _)));
2219 }
2220 _ => panic!("expected BinOp Shl, got {:?}", b.value),
2221 },
2222 _ => panic!("expected cycle binding"),
2223 }
2224 }
2225
2226 #[test]
2227 fn parse_cursor_without_over_clause() {
2228 let f = parse_str("cursor q = range(0, 100)");
2229 match &f.statements[0] {
2230 Statement::Cursor(c) => {
2231 assert_eq!(c.name, "q");
2232 assert!(c.over.is_none(), "no `over` → over is None");
2233 }
2234 other => panic!("expected Cursor, got {other:?}"),
2235 }
2236 }
2237
2238 #[test]
2239 fn parse_cursor_with_over_iter_var() {
2240 let f = parse_str("cursor q = range(0, 100) over p");
2241 match &f.statements[0] {
2242 Statement::Cursor(c) => {
2243 assert_eq!(c.name, "q");
2244 match &c.over {
2245 Some(Expr::Ident(name, _)) => assert_eq!(name, "p"),
2246 other => panic!("expected Some(Ident('p')), got {other:?}"),
2247 }
2248 }
2249 other => panic!("expected Cursor, got {other:?}"),
2250 }
2251 }
2252
2253 #[test]
2254 fn parse_cursor_with_over_dotted_param_projection() {
2255 let f = parse_str("cursor q = range(0, 100) over cursor.partitions");
2256 match &f.statements[0] {
2257 Statement::Cursor(c) => {
2258 assert!(c.over.is_some(), "should have over clause");
2259 match &c.over {
2261 Some(Expr::FieldAccess { .. }) => {} Some(other) => panic!("expected FieldAccess, got {other:?}"),
2263 None => panic!("expected Some"),
2264 }
2265 }
2266 other => panic!("expected Cursor, got {other:?}"),
2267 }
2268 }
2269
2270 #[test]
2271 fn parse_cursor_over_does_not_swallow_following_statement() {
2272 let f = parse_str("cursor q = range(0, 100) over p\nother := 42");
2273 assert_eq!(f.statements.len(), 2);
2274 }
2275
2276 #[test]
2277 fn parse_chained_field_access_flattens_intermediate_levels() {
2278 let f = parse_str("i := q.cursor.idx");
2283 match &f.statements[0] {
2284 Statement::Binding(b) => match &b.value {
2285 Expr::FieldAccess { source, field, .. } => {
2286 assert_eq!(source, "q__cursor");
2287 assert_eq!(field, "idx");
2288 }
2289 other => panic!("expected FieldAccess, got {other:?}"),
2290 },
2291 other => panic!("expected binding, got {other:?}"),
2292 }
2293 let f = parse_str("x := a.b.c.d");
2295 match &f.statements[0] {
2296 Statement::Binding(b) => match &b.value {
2297 Expr::FieldAccess { source, field, .. } => {
2298 assert_eq!(source, "a__b__c");
2299 assert_eq!(field, "d");
2300 }
2301 other => panic!("expected FieldAccess, got {other:?}"),
2302 },
2303 other => panic!("expected binding, got {other:?}"),
2304 }
2305 }
2306
2307 #[test]
2308 fn parse_unary_bitnot() {
2309 let f = parse_str("y := !x");
2311 match &f.statements[0] {
2312 Statement::Binding(b) => match &b.value {
2313 Expr::UnaryBitNot(inner, _) => {
2314 assert!(matches!(**inner, Expr::Ident(ref s, _) if s == "x"));
2315 }
2316 _ => panic!("expected UnaryBitNot, got {:?}", b.value),
2317 },
2318 _ => panic!("expected cycle binding"),
2319 }
2320 }
2321}