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