1use crate::ast::*;
44use crate::lexer::{Span, Token, TokenKind};
45
46struct Parser {
48 tokens: Vec<Token>,
49 pos: usize,
50}
51
52impl Parser {
53 fn new(tokens: Vec<Token>) -> Self {
54 Self { tokens, pos: 0 }
55 }
56
57 fn peek(&self) -> &TokenKind {
58 &self.tokens[self.pos].kind
59 }
60
61 fn span(&self) -> Span {
62 self.tokens[self.pos].span
63 }
64
65 fn advance(&mut self) -> &Token {
66 let tok = &self.tokens[self.pos];
67 if self.pos < self.tokens.len() - 1 {
68 self.pos += 1;
69 }
70 tok
71 }
72
73 fn expect(&mut self, expected: &TokenKind) -> Result<&Token, String> {
74 if self.peek() == expected {
75 Ok(self.advance())
76 } else {
77 Err(format!(
78 "expected {:?}, got {:?} at line {}, col {}",
79 expected,
80 self.peek(),
81 self.span().line,
82 self.span().col
83 ))
84 }
85 }
86
87 fn expect_ident(&mut self) -> Result<String, String> {
88 match self.peek().clone() {
89 TokenKind::Ident(name) => {
90 self.advance();
91 Ok(name)
92 }
93 TokenKind::Input => {
100 self.advance();
101 Ok("input".to_string())
102 }
103 TokenKind::Cursor => {
108 self.advance();
109 Ok("cursor".to_string())
110 }
111 TokenKind::Over => {
116 self.advance();
117 Ok("over".to_string())
118 }
119 _ => Err(format!(
120 "expected identifier, got {:?} at line {}, col {}",
121 self.peek(),
122 self.span().line,
123 self.span().col
124 )),
125 }
126 }
127
128 fn at_eof(&self) -> bool {
129 matches!(self.peek(), TokenKind::Eof)
130 }
131}
132
133pub fn parse(tokens: Vec<Token>) -> Result<PolydatFile, String> {
135 let mut parser = Parser::new(tokens);
136 let mut statements = Vec::new();
137
138 while !parser.at_eof() {
139 parse_statement_into(&mut parser, &mut statements)?;
140 }
141
142 Ok(PolydatFile { statements })
143}
144
145pub fn parse_expression(tokens: Vec<Token>) -> Result<Expr, String> {
163 let mut parser = Parser::new(tokens);
164 let expr = parse_expr(&mut parser)?;
165 if !parser.at_eof() {
166 let span = parser.span();
167 return Err(format!(
168 "expected end of expression at line {}, col {}, got {:?}",
169 span.line,
170 span.col,
171 parser.peek()
172 ));
173 }
174 Ok(expr)
175}
176
177fn parse_statement_into(p: &mut Parser, out: &mut Vec<Statement>) -> Result<(), String> {
183 match p.peek() {
184 TokenKind::Pragma => out.push(parse_pragma(p)?),
185 TokenKind::Input => parse_input_decl(p, out)?,
186 TokenKind::Extern => out.push(parse_extern_port(p)?),
187 TokenKind::Cursor => out.push(parse_cursor_decl(p)?),
188 TokenKind::For(_) => out.push(parse_for_statement(p)?),
189 TokenKind::Tile => out.push(parse_tile(p)?),
190 TokenKind::Const | TokenKind::Shared | TokenKind::Volatile => {
191 out.push(parse_modified_binding(p)?);
192 }
193 TokenKind::LParen => out.push(parse_destructuring_binding(p)?),
194 TokenKind::Ident(_) => {
195 if is_module_def(p) {
199 out.push(parse_module_def(p)?);
200 } else if is_polytile_binding(p) {
201 out.push(parse_polytile_binding(p)?);
202 } else {
203 out.push(parse_cycle_binding(p)?);
204 }
205 }
206 _ => {
207 return Err(format!(
208 "unexpected token {:?} at line {}, col {}",
209 p.peek(),
210 p.span().line,
211 p.span().col
212 ));
213 }
214 }
215 Ok(())
216}
217
218fn parse_for_statement(p: &mut Parser) -> Result<Statement, String> {
228 let span = p.span();
229 let text = match p.peek().clone() {
230 TokenKind::For(text) => text,
231 other => {
232 return Err(format!(
233 "expected `for`, got {other:?} at line {}, col {}",
234 span.line, span.col
235 ));
236 }
237 };
238 p.advance();
239 let source = for_source_from_text(&text, span, true)?;
240 if !matches!(p.peek(), TokenKind::LBrace) {
241 return Err(format!(
242 "`for {text}` at line {}, col {} needs a `{{` block on the same line; \
243 to bind a producer instead, write `name := for {text}`",
244 span.line, span.col
245 ));
246 }
247 p.advance();
248 let mut body = Vec::new();
249 while !matches!(p.peek(), TokenKind::RBrace | TokenKind::Eof) {
250 parse_statement_into(p, &mut body)?;
251 }
252 p.expect(&TokenKind::RBrace)?;
253 Ok(Statement::For(ForStmt { source, body, span }))
254}
255
256fn parse_tile(p: &mut Parser) -> Result<Statement, String> {
261 let span = p.span();
262 p.expect(&TokenKind::Tile)?;
263 let name = p.expect_ident()?;
264 let encoding = if matches!(p.peek(), TokenKind::Colon) {
265 p.advance();
266 Some(p.expect_ident()?)
267 } else {
268 None
269 };
270 let mut options = TileOptions::default();
271 if matches!(p.peek(), TokenKind::LParen) {
272 p.advance();
273 while !matches!(p.peek(), TokenKind::RParen | TokenKind::Eof) {
274 let key = p.expect_ident()?;
275 match key.as_str() {
276 "delims" => {
277 options.open = expect_string(p, "delims open")?;
278 options.close = expect_string(p, "delims close")?;
279 if options.open.is_empty() || options.close.is_empty() {
280 return Err(format!(
281 "tile '{name}' at line {}, col {}: delimiters must not be empty",
282 span.line, span.col
283 ));
284 }
285 }
286 "sigil" => {
287 options.sigil = expect_string(p, "sigil")?;
288 if options.sigil.is_empty() {
289 return Err(format!(
290 "tile '{name}' at line {}, col {}: sigil must not be empty",
291 span.line, span.col
292 ));
293 }
294 }
295 "strict" => options.strict = true,
296 "instring" => options.in_string = true,
297 other => {
298 return Err(format!(
299 "tile '{name}' at line {}, col {}: unknown option '{other}'; options are delims, sigil, strict, instring",
300 span.line, span.col
301 ));
302 }
303 }
304 if matches!(p.peek(), TokenKind::Comma) {
305 p.advance();
306 }
307 }
308 p.expect(&TokenKind::RParen)?;
309 }
310 if !matches!(p.peek(), TokenKind::ColonEq) {
313 return Err(format!(
314 "tile '{name}' at line {}, col {}: expected `:=` before the body; a tile binds a wire, \
315 as in `tile {name} : json := {{ ... }}` or `tile {name} := \"...\"`, got {:?}",
316 span.line,
317 span.col,
318 p.peek()
319 ));
320 }
321 p.advance();
322 let (body_kind, body) = match p.peek().clone() {
323 TokenKind::TileBody(text, kind) => {
324 p.advance();
325 (kind, text)
326 }
327 TokenKind::StringLit(s) => {
328 p.advance();
329 (TileBodyKind::Literal, s)
330 }
331 other => {
332 return Err(format!(
333 "tile '{name}' at line {}, col {}: expected a body (a `{{ }}` or `[ ]` block, `<<< >>>` heredoc, or string), got {other:?}",
334 span.line, span.col
335 ));
336 }
337 };
338 if let Some(enc) = &encoding
339 && !matches!(enc.as_str(), "json" | "text" | "csv")
340 {
341 return Err(format!(
342 "tile '{name}' at line {}, col {}: unknown encoding '{enc}'; encodings are json, text, csv",
343 span.line, span.col
344 ));
345 }
346 let pieces = super::tile::parse_template(&body, &options, span)
347 .map_err(|e| format!("tile '{name}': {e}"))?;
348 Ok(Statement::Tile(TileDef {
349 name,
350 encoding,
351 options,
352 body_kind,
353 body,
354 pieces,
355 span,
356 }))
357}
358
359fn is_polytile_binding(p: &Parser) -> bool {
362 p.pos + 3 < p.tokens.len()
363 && matches!(&p.tokens[p.pos].kind, TokenKind::Ident(_))
364 && matches!(&p.tokens[p.pos + 1].kind, TokenKind::ColonEq)
365 && matches!(&p.tokens[p.pos + 2].kind, TokenKind::Ident(f) if f == "polytile" || f == "polytile_json")
366 && matches!(&p.tokens[p.pos + 3].kind, TokenKind::LParen)
367}
368
369fn parse_polytile_binding(p: &mut Parser) -> Result<Statement, String> {
377 let span = p.span();
378 let name = p.expect_ident()?;
379 p.expect(&TokenKind::ColonEq)?;
380 let func = p.expect_ident()?;
381 p.expect(&TokenKind::LParen)?;
382 let at = |what: &str| {
383 format!(
384 "{func} for '{name}' at line {}, col {}: {what}",
385 span.line, span.col
386 )
387 };
388 let encoding = if func == "polytile" {
389 let e = expect_string(p, "the encoding").map_err(|m| at(&m))?;
390 if !matches!(p.peek(), TokenKind::Comma) {
391 return Err(at("expected `,` and then the template"));
392 }
393 p.advance();
394 Some(e)
395 } else {
396 None
397 };
398 let body = expect_string(p, "the template body (a string or a `<<< >>>` heredoc)")
399 .map_err(|m| at(&m))?;
400 let mut options = TileOptions::default();
401 while matches!(p.peek(), TokenKind::Comma) {
402 p.advance();
403 if matches!(p.peek(), TokenKind::RParen) {
404 break;
405 }
406 let key = p.expect_ident()?;
407 p.expect(&TokenKind::Colon)
408 .map_err(|_| at(&format!("option `{key}` needs `: \"value\"`")))?;
409 match key.as_str() {
410 "open" => options.open = expect_string(p, "open").map_err(|m| at(&m))?,
411 "close" => options.close = expect_string(p, "close").map_err(|m| at(&m))?,
412 "sigil" => options.sigil = expect_string(p, "sigil").map_err(|m| at(&m))?,
413 "strict" => {
414 let v = p.expect_ident().map_err(|m| at(&m))?;
415 options.strict = v == "true";
416 }
417 "instring" => {
418 let v = p.expect_ident().map_err(|m| at(&m))?;
419 options.in_string = v == "true";
420 }
421 other => {
422 return Err(at(&format!(
423 "unknown option '{other}'; options are open, close, sigil, strict, instring"
424 )));
425 }
426 }
427 }
428 p.expect(&TokenKind::RParen).map_err(|m| at(&m))?;
429 if options.open.is_empty() || options.close.is_empty() || options.sigil.is_empty() {
430 return Err(at("delimiters and sigil must not be empty"));
431 }
432 let tile = match encoding {
433 Some(enc) => super::tile_structural::tile_from_text(&name, &enc, &body, &options, span)?,
434 None => super::tile_structural::tile_from_json_text(&name, &body, &options, span)?,
435 };
436 Ok(Statement::Tile(tile))
437}
438
439fn expect_string(p: &mut Parser, what: &str) -> Result<String, String> {
440 match p.peek().clone() {
441 TokenKind::StringLit(s) => {
442 p.advance();
443 Ok(s)
444 }
445 other => Err(format!(
446 "expected a string for {what}, got {other:?} at line {}, col {}",
447 p.span().line,
448 p.span().col
449 )),
450 }
451}
452
453pub fn for_source_from_text(
455 text: &str,
456 span: Span,
457 allow_producer: bool,
458) -> Result<ForSource, String> {
459 if text.is_empty() {
460 return Err(format!(
461 "`for` at line {}, col {} has no comprehension",
462 span.line, span.col
463 ));
464 }
465 let is_ident = text.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
466 && text
467 .chars()
468 .next()
469 .is_some_and(|c| c.is_ascii_alphabetic() || c == '_');
470 if is_ident {
471 if allow_producer {
472 return Ok(ForSource {
473 text: text.to_string(),
474 kind: ForSourceKind::Producer(text.to_string()),
475 span,
476 });
477 }
478 return Err(format!(
479 "`for {text}` at line {}, col {}: a producer expression needs comprehension text such as `k in 1..10`, \
480 or a derivation such as `{text} where {{k}} > 1` or `{text} order halton/5`",
481 span.line, span.col
482 ));
483 }
484 {
487 use crate::comprehension::parse::{split_at_order, split_at_where};
488 let (head, order) = split_at_order(text);
489 let (base, filter) = split_at_where(&head);
490 let base = base.trim();
491 let base_is_ident = !base.is_empty()
492 && base.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
493 && base
494 .chars()
495 .next()
496 .is_some_and(|c| c.is_ascii_alphabetic() || c == '_');
497 if base_is_ident && (filter.is_some() || order.is_some()) {
498 return Ok(ForSource {
499 text: text.to_string(),
500 kind: ForSourceKind::Derived {
501 base: base.to_string(),
502 filter,
503 order,
504 },
505 span,
506 });
507 }
508 }
509 let legacy = crate::comprehension::parse::parse_comprehension_text(text)
510 .map_err(|e| format!("`for {text}` at line {}, col {}: {e}", span.line, span.col))?;
511 let algebra = crate::comprehension::spec::legacy_to_algebra(&legacy)
512 .map_err(|e| format!("`for {text}` at line {}, col {}: {e}", span.line, span.col))?;
513 Ok(ForSource {
514 text: text.to_string(),
515 kind: ForSourceKind::Comprehension(algebra),
516 span,
517 })
518}
519
520fn parse_pragma(p: &mut Parser) -> Result<Statement, String> {
521 let span = p.span();
522 p.expect(&TokenKind::Pragma)?;
523 let name = p.expect_ident()?;
524 Ok(Statement::Pragma { name, span })
525}
526
527fn is_module_def(p: &Parser) -> bool {
529 if p.pos + 4 >= p.tokens.len() {
534 return false;
535 }
536 let third_is_param_name = matches!(
537 &p.tokens[p.pos + 2].kind,
538 TokenKind::Ident(_) | TokenKind::Input,
539 );
540 matches!(&p.tokens[p.pos].kind, TokenKind::Ident(_))
541 && matches!(&p.tokens[p.pos + 1].kind, TokenKind::LParen)
542 && third_is_param_name
543 && matches!(&p.tokens[p.pos + 3].kind, TokenKind::Colon)
544}
545
546fn parse_module_def(p: &mut Parser) -> Result<Statement, String> {
548 let span = p.span();
549 let name = p.expect_ident()?;
550
551 p.expect(&TokenKind::LParen)?;
553 let mut params = Vec::new();
554 while !matches!(p.peek(), TokenKind::RParen) {
555 let pname = p.expect_ident()?;
556 p.expect(&TokenKind::Colon)?;
557 let ptype = p.expect_ident()?;
558 params.push(TypedParam {
559 name: pname,
560 typ: ptype,
561 });
562 if matches!(p.peek(), TokenKind::Comma) {
563 p.advance();
564 }
565 }
566 p.expect(&TokenKind::RParen)?;
567
568 p.expect(&TokenKind::Arrow)?;
570 p.expect(&TokenKind::LParen)?;
571 let mut outputs = Vec::new();
572 while !matches!(p.peek(), TokenKind::RParen) {
573 let oname = p.expect_ident()?;
574 p.expect(&TokenKind::Colon)?;
575 let otype = p.expect_ident()?;
576 outputs.push(TypedParam {
577 name: oname,
578 typ: otype,
579 });
580 if matches!(p.peek(), TokenKind::Comma) {
581 p.advance();
582 }
583 }
584 p.expect(&TokenKind::RParen)?;
585
586 p.expect(&TokenKind::ColonEq)?;
588 p.expect(&TokenKind::LBrace)?;
589
590 let mut body = Vec::new();
591 while !matches!(p.peek(), TokenKind::RBrace | TokenKind::Eof) {
592 parse_statement_into(p, &mut body)?;
593 }
594 p.expect(&TokenKind::RBrace)?;
595
596 Ok(Statement::ModuleDef(ModuleDef {
597 name,
598 params,
599 outputs,
600 body,
601 span,
602 }))
603}
604
605fn parse_extern_port(p: &mut Parser) -> Result<Statement, String> {
607 let span = p.span();
608 p.advance(); let name = p.expect_ident()?;
611 p.expect(&TokenKind::Colon)?;
612 let typ = p.expect_ident()?;
613
614 let default = if matches!(p.peek(), TokenKind::Eq) {
616 p.advance(); Some(parse_expr(p)?)
618 } else {
619 None
620 };
621
622 Ok(Statement::ExternPort(ExternPort {
623 name,
624 typ,
625 default,
626 span,
627 }))
628}
629
630fn parse_input_decl(p: &mut Parser, out: &mut Vec<Statement>) -> Result<(), String> {
641 let keyword_span = p.span();
642 p.advance(); if matches!(p.peek(), TokenKind::LParen) {
645 p.advance(); if matches!(p.peek(), TokenKind::RParen) {
648 return Err(format!(
649 "`input ()` is empty; omit the line entirely to declare zero inputs \
650 (at line {}, col {})",
651 keyword_span.line, keyword_span.col,
652 ));
653 }
654 loop {
655 let span = p.span();
656 let name = p.expect_ident()?;
657 let ty = if matches!(p.peek(), TokenKind::Colon) {
658 p.advance();
659 Some(p.expect_ident()?)
660 } else {
661 None
662 };
663 out.push(Statement::InputDecl(InputDecl { name, ty, span }));
664 if matches!(p.peek(), TokenKind::Comma) {
665 p.advance();
666 } else {
667 break;
668 }
669 }
670 p.expect(&TokenKind::RParen)?;
671 } else {
672 let span = p.span();
674 let name = p.expect_ident()?;
675 let ty = if matches!(p.peek(), TokenKind::Colon) {
676 p.advance();
677 Some(p.expect_ident()?)
678 } else {
679 None
680 };
681 out.push(Statement::InputDecl(InputDecl { name, ty, span }));
682 }
683 Ok(())
684}
685
686fn parse_cursor_decl(p: &mut Parser) -> Result<Statement, String> {
691 let span = p.span();
692 p.advance(); let name = p.expect_ident()?;
694 p.expect(&TokenKind::Eq)?;
695 let constructor = parse_expr(p)?;
696 let over = if matches!(p.peek(), TokenKind::Over) {
698 p.advance();
699 Some(parse_expr(p)?)
700 } else {
701 None
702 };
703 Ok(Statement::Cursor(CursorDecl {
704 name,
705 constructor,
706 over,
707 span,
708 }))
709}
710
711fn parse_modified_binding(p: &mut Parser) -> Result<Statement, String> {
716 let start_span = p.span();
717 let mut collected: Vec<WireModifier> = Vec::new();
718
719 loop {
720 let m = match p.peek() {
721 TokenKind::Const => WireModifier::Const,
722 TokenKind::Shared => WireModifier::Shared,
723 TokenKind::Volatile => WireModifier::Volatile,
724 _ => break,
725 };
726 if collected.contains(&m) {
727 return Err(format!(
728 "duplicate `{m:?}` modifier at line {}, col {}",
729 p.span().line,
730 p.span().col,
731 ));
732 }
733 collected.push(m);
734 p.advance();
735 }
736
737 let modifier = BindingModifier::try_from_iter(collected)
738 .map_err(|e| format!("{e} at line {}, col {}", start_span.line, start_span.col,))?;
739
740 match p.peek() {
741 TokenKind::Ident(_) | TokenKind::Input | TokenKind::Cursor | TokenKind::Over => {
747 parse_cycle_binding_with_modifier(p, modifier)
748 }
749 _ => Err(format!(
750 "expected binding name after modifiers at line {}, col {}",
751 p.span().line,
752 p.span().col
753 )),
754 }
755}
756
757fn parse_cycle_binding(p: &mut Parser) -> Result<Statement, String> {
759 parse_cycle_binding_with_modifier(p, BindingModifier::NONE)
760}
761
762fn parse_cycle_binding_with_modifier(
763 p: &mut Parser,
764 modifier: BindingModifier,
765) -> Result<Statement, String> {
766 let span = p.span();
767 let name = p.expect_ident()?;
768 let type_annotation = if matches!(p.peek(), TokenKind::Colon) {
774 p.advance(); let typ = p.expect_ident()?;
776 if !modifier.is_shared() {
777 return Err(format!(
778 "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 {}",
779 span.line, span.col,
780 ));
781 }
782 Some(typ)
783 } else {
784 None
785 };
786 p.expect(&TokenKind::ColonEq)?;
787 let value = parse_expr(p)?;
788
789 Ok(Statement::Binding(Binding {
790 targets: vec![name],
791 value,
792 modifier,
793 type_annotation,
794 span,
795 }))
796}
797
798fn parse_destructuring_binding(p: &mut Parser) -> Result<Statement, String> {
800 let span = p.span();
801 p.advance(); let mut targets = Vec::new();
803 loop {
804 targets.push(p.expect_ident()?);
805 if matches!(p.peek(), TokenKind::Comma) {
806 p.advance();
807 } else {
808 break;
809 }
810 }
811 p.expect(&TokenKind::RParen)?;
812 p.expect(&TokenKind::ColonEq)?;
813 let value = parse_expr(p)?;
814
815 Ok(Statement::Binding(Binding {
816 targets,
817 value,
818 modifier: BindingModifier::NONE,
819 type_annotation: None,
820 span,
821 }))
822}
823
824fn parse_expr(p: &mut Parser) -> Result<Expr, String> {
830 parse_expr_bp(p, 0)
831}
832
833fn parse_expr_bp(p: &mut Parser, min_bp: u8) -> Result<Expr, String> {
856 let mut lhs = parse_atom(p)?;
857 lhs = parse_postfix_as(p, lhs)?;
861
862 loop {
863 let op = match p.peek() {
864 TokenKind::PipePipe => Some((BinOpKind::Or, 1, 2)),
865 TokenKind::AmpAmp => Some((BinOpKind::And, 3, 4)),
866 TokenKind::EqEq => Some((BinOpKind::Eq, 5, 6)),
867 TokenKind::BangEq => Some((BinOpKind::Ne, 5, 6)),
868 TokenKind::Lt => Some((BinOpKind::Lt, 7, 8)),
869 TokenKind::Gt => Some((BinOpKind::Gt, 7, 8)),
870 TokenKind::LtEq => Some((BinOpKind::Le, 7, 8)),
871 TokenKind::GtEq => Some((BinOpKind::Ge, 7, 8)),
872 TokenKind::Pipe => Some((BinOpKind::BitOr, 9, 10)),
873 TokenKind::Caret => Some((BinOpKind::BitXor, 11, 12)),
874 TokenKind::Ampersand => Some((BinOpKind::BitAnd, 13, 14)),
875 TokenKind::ShiftLeft => Some((BinOpKind::Shl, 15, 16)),
876 TokenKind::ShiftRight => Some((BinOpKind::Shr, 15, 16)),
877 TokenKind::Plus => Some((BinOpKind::Add, 17, 18)),
878 TokenKind::Minus => Some((BinOpKind::Sub, 17, 18)),
879 TokenKind::Star => Some((BinOpKind::Mul, 19, 20)),
880 TokenKind::Slash => Some((BinOpKind::Div, 19, 20)),
881 TokenKind::Percent => Some((BinOpKind::Mod, 19, 20)),
882 TokenKind::StarStar => Some((BinOpKind::Pow, 22, 21)), _ => None,
884 };
885
886 let Some((op_kind, l_bp, r_bp)) = op else {
887 break;
888 };
889 if l_bp < min_bp {
890 break;
891 }
892
893 p.advance(); let rhs = parse_expr_bp(p, r_bp)?;
895 lhs = Expr::BinOp(Box::new(lhs), op_kind, Box::new(rhs));
896 }
897
898 Ok(lhs)
899}
900
901fn parse_postfix_as(p: &mut Parser, mut expr: Expr) -> Result<Expr, String> {
905 while matches!(p.peek(), TokenKind::Ident(s) if s.as_str() == "as") {
906 let span = p.span();
907 p.advance(); let ty_name = match p.peek() {
909 TokenKind::Ident(name) => name.clone(),
910 other => return Err(format!("expected a type name after `as`, found {other:?}")),
911 };
912 p.advance(); let port_type = crate::PortType::from_keyword(&ty_name)
914 .ok_or_else(|| format!("unknown type `{ty_name}` in `... as {ty_name}` cast"))?;
915 expr = Expr::Cast(Box::new(expr), port_type, span);
916 }
917 Ok(expr)
918}
919
920fn parse_if_block(p: &mut Parser, span: Span) -> Result<Expr, String> {
944 let cond = parse_expr(p)?;
945
946 if !matches!(p.peek(), TokenKind::LBrace) {
947 return Err(format!(
948 "expected `{{` to open the then-branch of an `if` expression, got {:?} at line {}, col {}. \
949 Block form is `if <cond> {{ <then> }} else {{ <else> }}`; the call form `if(cond, a, b)` \
950 is also accepted.",
951 p.peek(),
952 p.span().line,
953 p.span().col
954 ));
955 }
956 p.advance();
957 let then_expr = parse_expr(p)?;
958 p.expect(&TokenKind::RBrace)?;
959
960 match p.peek().clone() {
961 TokenKind::Ident(word) if word == "else" => {
962 p.advance();
963 }
964 other => {
965 return Err(format!(
966 "expected `else` after the then-branch of an `if` expression, got {:?} at line {}, col {}. \
967 `else` is required: a Polydat expression always produces a value, so there is no \
968 result for the false path without it.",
969 other,
970 p.span().line,
971 p.span().col
972 ));
973 }
974 }
975
976 let else_expr = match p.peek().clone() {
978 TokenKind::Ident(word) if word == "if" => {
979 let else_span = p.span();
980 p.advance();
981 parse_if_block(p, else_span)?
982 }
983 TokenKind::LBrace => {
984 p.advance();
985 let e = parse_expr(p)?;
986 p.expect(&TokenKind::RBrace)?;
987 e
988 }
989 other => {
990 return Err(format!(
991 "expected `{{` or `if` after `else`, got {:?} at line {}, col {}",
992 other,
993 p.span().line,
994 p.span().col
995 ));
996 }
997 };
998
999 Ok(Expr::Call(CallExpr {
1001 func: "if".into(),
1002 args: vec![
1003 Arg::Positional(cond),
1004 Arg::Positional(then_expr),
1005 Arg::Positional(else_expr),
1006 ],
1007 span,
1008 }))
1009}
1010
1011fn parse_atom(p: &mut Parser) -> Result<Expr, String> {
1012 let span = p.span();
1013
1014 match p.peek().clone() {
1015 TokenKind::Minus => {
1016 p.advance();
1018 let inner = parse_atom(p)?;
1019 Ok(Expr::UnaryNeg(Box::new(inner), span))
1020 }
1021 TokenKind::Bang => {
1022 p.advance();
1024 let inner = parse_atom(p)?;
1025 Ok(Expr::UnaryBitNot(Box::new(inner), span))
1026 }
1027 TokenKind::LParen => {
1028 p.advance(); let inner = parse_expr(p)?;
1032 p.expect(&TokenKind::RParen)?;
1033 Ok(inner)
1034 }
1035 TokenKind::StringLit(s) => {
1036 p.advance();
1037 Ok(parse_interpolated_string(s, span))
1038 }
1039 TokenKind::IntLit(v) => {
1040 p.advance();
1041 Ok(Expr::IntLit(v, span))
1042 }
1043 TokenKind::FloatLit(v) => {
1044 p.advance();
1045 Ok(Expr::FloatLit(v, span))
1046 }
1047 TokenKind::LBracket => parse_array_lit(p),
1048 TokenKind::For(text) => {
1049 p.advance();
1050 let source = for_source_from_text(&text, span, false)?;
1051 Ok(Expr::For(Box::new(source)))
1052 }
1053 TokenKind::Ident(name) => {
1054 p.advance();
1055 if name == "if" && !matches!(p.peek(), TokenKind::LParen) {
1061 parse_if_block(p, span)
1062 } else if matches!(p.peek(), TokenKind::LParen) {
1063 parse_call(p, name, span)
1065 } else if matches!(p.peek(), TokenKind::Dot) {
1066 parse_field_chain(p, name, span)
1067 } else {
1068 Ok(Expr::Ident(name, span))
1069 }
1070 }
1071 TokenKind::Input => {
1075 p.advance();
1076 let name = "input".to_string();
1077 if matches!(p.peek(), TokenKind::Dot) {
1078 parse_field_chain(p, name, span)
1079 } else {
1080 Ok(Expr::Ident(name, span))
1081 }
1082 }
1083 TokenKind::Cursor => {
1089 p.advance();
1090 let name = "cursor".to_string();
1091 if matches!(p.peek(), TokenKind::Dot) {
1092 parse_field_chain(p, name, span)
1093 } else {
1094 Ok(Expr::Ident(name, span))
1095 }
1096 }
1097 TokenKind::Over => {
1102 p.advance();
1103 let name = "over".to_string();
1104 if matches!(p.peek(), TokenKind::Dot) {
1105 parse_field_chain(p, name, span)
1106 } else {
1107 Ok(Expr::Ident(name, span))
1108 }
1109 }
1110 _ => Err(format!(
1111 "expected expression, got {:?} at line {}, col {}",
1112 p.peek(),
1113 span.line,
1114 span.col
1115 )),
1116 }
1117}
1118
1119fn parse_interpolated_string(s: String, span: Span) -> Expr {
1154 let segments = match scan_interpolation_segments(&s) {
1155 Some(segs) => segs,
1156 None => return Expr::StringLit(s, span), };
1158
1159 if !segments
1160 .iter()
1161 .any(|seg| matches!(seg, Segment::Placeholder(_)))
1162 {
1163 return Expr::StringLit(s, span);
1164 }
1165
1166 let mut format_str = String::with_capacity(s.len());
1171 let mut placeholder_exprs: Vec<Expr> = Vec::new();
1172 for seg in segments {
1173 match seg {
1174 Segment::Literal(text) => format_str.push_str(&text),
1175 Segment::Placeholder(body) => {
1176 let expr = match parse_placeholder_body(&body, span) {
1177 Ok(e) => e,
1178 Err(_) => return Expr::StringLit(s, span),
1183 };
1184 placeholder_exprs.push(expr);
1185 format_str.push_str("{}");
1186 }
1187 }
1188 }
1189
1190 let mut args: Vec<Arg> = Vec::with_capacity(placeholder_exprs.len() + 1);
1191 args.push(Arg::Positional(Expr::StringLit(format_str, span)));
1192 for e in placeholder_exprs {
1193 args.push(Arg::Positional(e));
1194 }
1195 Expr::Call(CallExpr {
1196 func: "printf".into(),
1197 args,
1198 span,
1199 })
1200}
1201
1202enum Segment {
1204 Literal(String),
1208 Placeholder(String),
1211}
1212
1213fn scan_interpolation_segments(s: &str) -> Option<Vec<Segment>> {
1222 let chars: Vec<char> = s.chars().collect();
1223 let mut segments: Vec<Segment> = Vec::new();
1224 let mut literal = String::new();
1225 let mut i = 0;
1226 while i < chars.len() {
1227 let c = chars[i];
1228 if c == '{' && i + 1 < chars.len() && chars[i + 1] == '{' {
1231 literal.push_str("{{");
1232 i += 2;
1233 continue;
1234 }
1235 if c == '}' && i + 1 < chars.len() && chars[i + 1] == '}' {
1236 literal.push_str("}}");
1237 i += 2;
1238 continue;
1239 }
1240 if c == '{' {
1241 if !literal.is_empty() {
1242 segments.push(Segment::Literal(std::mem::take(&mut literal)));
1243 }
1244 let body_start = i + 1;
1245 let body_end = find_placeholder_end(&chars, body_start)?;
1246 let body: String = chars[body_start..body_end].iter().collect();
1247 segments.push(Segment::Placeholder(body));
1248 i = body_end + 1; continue;
1250 }
1251 literal.push(c);
1252 i += 1;
1253 }
1254 if !literal.is_empty() {
1255 segments.push(Segment::Literal(literal));
1256 }
1257 Some(segments)
1258}
1259
1260fn find_placeholder_end(chars: &[char], start: usize) -> Option<usize> {
1265 let mut depth: i32 = 0;
1266 let mut in_string = false;
1267 let mut i = start;
1268 while i < chars.len() {
1269 let c = chars[i];
1270 if in_string {
1271 if c == '\\' && i + 1 < chars.len() {
1272 i += 2;
1277 continue;
1278 }
1279 if c == '"' {
1280 in_string = false;
1281 }
1282 i += 1;
1283 continue;
1284 }
1285 match c {
1286 '"' => in_string = true,
1287 '(' | '[' | '{' => depth += 1,
1288 ')' | ']' => depth -= 1,
1289 '}' => {
1290 if depth == 0 {
1291 return Some(i);
1292 }
1293 depth -= 1;
1294 }
1295 _ => {}
1296 }
1297 i += 1;
1298 }
1299 None
1300}
1301
1302fn parse_placeholder_body(body: &str, _span: Span) -> Result<Expr, String> {
1304 let body = body.trim();
1305 if body.is_empty() {
1306 return Err("empty placeholder".into());
1307 }
1308 let tokens = crate::lexer::lex(body)?;
1309 parse_expression(tokens)
1310}
1311
1312fn parse_field_chain(p: &mut Parser, base: String, span: Span) -> Result<Expr, String> {
1320 p.advance(); let mut source = base;
1322 let mut field = p.expect_ident()?;
1323 while matches!(p.peek(), TokenKind::Dot) {
1324 p.advance();
1325 source = format!("{source}__{field}");
1326 field = p.expect_ident()?;
1327 }
1328 Ok(Expr::FieldAccess {
1329 source,
1330 field,
1331 span,
1332 })
1333}
1334
1335fn parse_call(p: &mut Parser, func: String, span: Span) -> Result<Expr, String> {
1337 p.advance(); let mut args = Vec::new();
1339
1340 if !matches!(p.peek(), TokenKind::RParen) {
1341 loop {
1342 args.push(parse_arg(p)?);
1343 if matches!(p.peek(), TokenKind::Comma) {
1344 p.advance();
1345 } else {
1346 break;
1347 }
1348 }
1349 }
1350
1351 p.expect(&TokenKind::RParen)?;
1352 Ok(Expr::Call(CallExpr { func, args, span }))
1353}
1354
1355fn parse_arg(p: &mut Parser) -> Result<Arg, String> {
1361 let arg_name: Option<String> = match p.peek() {
1362 TokenKind::Ident(name) => Some(name.clone()),
1363 TokenKind::Input => Some("input".to_string()),
1364 _ => None,
1365 };
1366 if let Some(name) = arg_name
1367 && p.pos + 1 < p.tokens.len()
1368 && matches!(p.tokens[p.pos + 1].kind, TokenKind::Colon)
1369 {
1370 p.advance(); p.advance(); let value = parse_expr(p)?;
1373 return Ok(Arg::Named(name, value));
1374 }
1375 let expr = parse_expr(p)?;
1376 Ok(Arg::Positional(expr))
1377}
1378
1379fn parse_array_lit(p: &mut Parser) -> Result<Expr, String> {
1381 let span = p.span();
1382 p.advance(); let mut elements = Vec::new();
1384
1385 if !matches!(p.peek(), TokenKind::RBracket) {
1386 loop {
1387 elements.push(parse_expr(p)?);
1388 if matches!(p.peek(), TokenKind::Comma) {
1389 p.advance();
1390 } else {
1391 break;
1392 }
1393 }
1394 }
1395
1396 p.expect(&TokenKind::RBracket)?;
1397 Ok(Expr::ArrayLit(elements, span))
1398}
1399
1400#[cfg(test)]
1401mod tests {
1402 use super::*;
1403 use crate::lexer::lex;
1404
1405 fn parse_str(s: &str) -> PolydatFile {
1406 let tokens = lex(s).unwrap();
1407 parse(tokens).unwrap()
1408 }
1409
1410 fn parse_str_err(s: &str) -> String {
1411 let tokens = lex(s).unwrap();
1412 match parse(tokens) {
1413 Ok(_) => panic!("expected parse error from: {s:?}"),
1414 Err(e) => e,
1415 }
1416 }
1417
1418 fn cycle_modifier_of(f: &PolydatFile) -> BindingModifier {
1419 match &f.statements[0] {
1420 Statement::Binding(b) => b.modifier,
1421 other => panic!("expected cycle binding, got {other:?}"),
1422 }
1423 }
1424
1425 #[test]
1426 fn parse_volatile_modifier() {
1427 let f = parse_str("volatile x := 42");
1428 let m = cycle_modifier_of(&f);
1429 assert!(m.is_volatile() && !m.is_const() && !m.is_shared());
1430 }
1431
1432 #[test]
1433 fn parse_modifiers_in_any_order_yields_same_set() {
1434 let m1 = cycle_modifier_of(&parse_str("const shared x := 42"));
1435 let m2 = cycle_modifier_of(&parse_str("shared const x := 42"));
1436 assert_eq!(
1437 m1, m2,
1438 "ordering shouldn't matter: `const shared` and `shared const` collapse to the same set"
1439 );
1440 assert!(m1.is_const() && m1.is_shared());
1441 }
1442
1443 #[test]
1444 fn parse_shared_volatile_combination() {
1445 let m = cycle_modifier_of(&parse_str("shared volatile x := 42"));
1446 assert!(m.is_shared() && m.is_volatile() && !m.is_const());
1447 }
1448
1449 #[test]
1450 fn parse_rejects_const_volatile_combo() {
1451 let err = parse_str_err("const volatile x := 42");
1452 assert!(
1453 err.contains("const") && err.contains("volatile"),
1454 "error should name the conflicting keywords: {err}"
1455 );
1456 }
1457
1458 #[test]
1459 fn parse_rejects_volatile_const_combo_same_as_const_volatile() {
1460 let err = parse_str_err("volatile const x := 42");
1462 assert!(err.contains("const") && err.contains("volatile"));
1463 }
1464
1465 #[test]
1466 fn parse_rejects_duplicate_modifier() {
1467 let err = parse_str_err("const const x := 42");
1468 assert!(
1469 err.contains("duplicate"),
1470 "error should call out duplicate: {err}"
1471 );
1472 }
1473
1474 #[test]
1475 fn parse_volatile_const_binding() {
1476 let f = parse_str("volatile x := 42");
1483 match &f.statements[0] {
1484 Statement::Binding(b) => {
1485 assert!(b.modifier.is_volatile());
1486 assert!(!b.modifier.is_const());
1487 }
1488 other => panic!("expected binding, got {other:?}"),
1489 }
1490 }
1491
1492 #[test]
1493 fn parse_input_bare() {
1494 let f = parse_str("input cycle: u64");
1495 assert_eq!(f.statements.len(), 1);
1496 match &f.statements[0] {
1497 Statement::InputDecl(d) => {
1498 assert_eq!(d.name, "cycle");
1499 assert_eq!(d.ty.as_deref(), Some("u64"));
1500 }
1501 other => panic!("expected InputDecl, got {other:?}"),
1502 }
1503 }
1504
1505 #[test]
1506 fn parse_input_bare_untyped() {
1507 let f = parse_str("input cycle");
1508 match &f.statements[0] {
1509 Statement::InputDecl(d) => {
1510 assert_eq!(d.name, "cycle");
1511 assert!(d.ty.is_none(), "no type annotation");
1512 }
1513 other => panic!("expected InputDecl, got {other:?}"),
1514 }
1515 }
1516
1517 #[test]
1518 fn parse_input_tuple_form() {
1519 let f = parse_str("input (cycle: u64, q: f64)");
1522 assert_eq!(f.statements.len(), 2);
1523 match &f.statements[0] {
1524 Statement::InputDecl(d) => {
1525 assert_eq!(d.name, "cycle");
1526 assert_eq!(d.ty.as_deref(), Some("u64"));
1527 }
1528 other => panic!("expected InputDecl, got {other:?}"),
1529 }
1530 match &f.statements[1] {
1531 Statement::InputDecl(d) => {
1532 assert_eq!(d.name, "q");
1533 assert_eq!(d.ty.as_deref(), Some("f64"));
1534 }
1535 other => panic!("expected InputDecl, got {other:?}"),
1536 }
1537 }
1538
1539 #[test]
1540 fn parse_input_tuple_empty_rejected() {
1541 let tokens = crate::lexer::lex("input ()").unwrap();
1543 let err = parse(tokens).unwrap_err();
1544 assert!(
1545 err.contains("empty"),
1546 "error should mention empty tuple: {err}"
1547 );
1548 }
1549
1550 #[test]
1551 fn parse_const_binding() {
1552 let f = parse_str("const lut := dist_normal(72.0, 5.0)");
1553 assert_eq!(f.statements.len(), 1);
1554 match &f.statements[0] {
1555 Statement::Binding(b) => {
1556 assert_eq!(b.targets, vec!["lut"]);
1557 assert!(b.modifier.is_const());
1558 match &b.value {
1559 Expr::Call(c) => {
1560 assert_eq!(c.func, "dist_normal");
1561 assert_eq!(c.args.len(), 2);
1562 }
1563 _ => panic!("expected call"),
1564 }
1565 }
1566 _ => panic!("expected const binding"),
1567 }
1568 }
1569
1570 #[test]
1571 fn parse_cycle_binding() {
1572 let f = parse_str("seed := hash(cycle)");
1573 match &f.statements[0] {
1574 Statement::Binding(b) => {
1575 assert_eq!(b.targets, vec!["seed"]);
1576 match &b.value {
1577 Expr::Call(c) => {
1578 assert_eq!(c.func, "hash");
1579 assert_eq!(c.args.len(), 1);
1580 }
1581 _ => panic!("expected call"),
1582 }
1583 }
1584 _ => panic!("expected cycle binding"),
1585 }
1586 }
1587
1588 #[test]
1589 fn parse_destructuring() {
1590 let f = parse_str("(tenant, device, reading) := mixed_radix(cycle, 100, 1000, 0)");
1591 match &f.statements[0] {
1592 Statement::Binding(b) => {
1593 assert_eq!(b.targets, vec!["tenant", "device", "reading"]);
1594 match &b.value {
1595 Expr::Call(c) => {
1596 assert_eq!(c.func, "mixed_radix");
1597 assert_eq!(c.args.len(), 4);
1598 }
1599 _ => panic!("expected call"),
1600 }
1601 }
1602 _ => panic!("expected cycle binding"),
1603 }
1604 }
1605
1606 #[test]
1607 fn parse_named_args() {
1608 let f = parse_str("const lut := dist_normal(mean: 72.0, stddev: 5.0)");
1609 match &f.statements[0] {
1610 Statement::Binding(b) => match &b.value {
1611 Expr::Call(c) => {
1612 assert!(matches!(&c.args[0], Arg::Named(n, _) if n == "mean"));
1613 assert!(matches!(&c.args[1], Arg::Named(n, _) if n == "stddev"));
1614 }
1615 _ => panic!("expected call"),
1616 },
1617 _ => panic!("expected const binding"),
1618 }
1619 }
1620
1621 #[test]
1622 fn parse_string_lit_plain() {
1623 let f = parse_str(r#"id := "static text""#);
1626 match &f.statements[0] {
1627 Statement::Binding(b) => match &b.value {
1628 Expr::StringLit(s, _) => assert_eq!(s, "static text"),
1629 _ => panic!("expected string lit"),
1630 },
1631 _ => panic!("expected binding"),
1632 }
1633 }
1634
1635 #[test]
1636 fn parse_string_lit_interpolated() {
1637 let f = parse_str(r#"id := "{code}-{seq}""#);
1641 match &f.statements[0] {
1642 Statement::Binding(b) => match &b.value {
1643 Expr::Call(c) => {
1644 assert_eq!(c.func, "printf");
1645 assert_eq!(c.args.len(), 3);
1646 match &c.args[0] {
1647 Arg::Positional(Expr::StringLit(s, _)) => assert_eq!(s, "{}-{}"),
1648 _ => panic!("expected format string as first arg"),
1649 }
1650 match &c.args[1] {
1651 Arg::Positional(Expr::Ident(n, _)) => assert_eq!(n, "code"),
1652 _ => panic!("expected ident `code`"),
1653 }
1654 match &c.args[2] {
1655 Arg::Positional(Expr::Ident(n, _)) => assert_eq!(n, "seq"),
1656 _ => panic!("expected ident `seq`"),
1657 }
1658 }
1659 other => panic!("expected printf call, got {other:?}"),
1660 },
1661 _ => panic!("expected binding"),
1662 }
1663 }
1664
1665 #[test]
1666 fn parse_string_lit_format_spec_left_alone() {
1667 let f = parse_str(r#"id := "x={:05}""#);
1672 match &f.statements[0] {
1673 Statement::Binding(b) => match &b.value {
1674 Expr::StringLit(s, _) => assert_eq!(s, "x={:05}"),
1675 _ => panic!("expected literal"),
1676 },
1677 _ => panic!("expected binding"),
1678 }
1679 }
1680
1681 #[test]
1682 fn parse_string_lit_nested_call() {
1683 let f = parse_str(r#"email := "{format_u64(hash(cycle), 10)}@example.com""#);
1686 let call = match &f.statements[0] {
1687 Statement::Binding(b) => match &b.value {
1688 Expr::Call(c) => c,
1689 other => panic!("expected printf call, got {other:?}"),
1690 },
1691 _ => panic!("expected binding"),
1692 };
1693 assert_eq!(call.func, "printf");
1694 assert_eq!(call.args.len(), 2);
1695 match &call.args[0] {
1696 Arg::Positional(Expr::StringLit(s, _)) => assert_eq!(s, "{}@example.com"),
1697 other => panic!("expected format string, got {other:?}"),
1698 }
1699 match &call.args[1] {
1700 Arg::Positional(Expr::Call(inner)) => {
1701 assert_eq!(inner.func, "format_u64");
1702 assert_eq!(inner.args.len(), 2);
1703 match &inner.args[0] {
1704 Arg::Positional(Expr::Call(h)) => assert_eq!(h.func, "hash"),
1705 other => panic!("expected hash(...) call, got {other:?}"),
1706 }
1707 match &inner.args[1] {
1708 Arg::Positional(Expr::IntLit(10, _)) => {}
1709 other => panic!("expected literal 10, got {other:?}"),
1710 }
1711 }
1712 other => panic!("expected format_u64 call, got {other:?}"),
1713 }
1714 }
1715
1716 #[test]
1717 fn parse_string_lit_arithmetic_in_placeholder() {
1718 let f = parse_str(r#"id := "x={a + b * 2}""#);
1721 let call = match &f.statements[0] {
1722 Statement::Binding(b) => match &b.value {
1723 Expr::Call(c) => c,
1724 other => panic!("expected call, got {other:?}"),
1725 },
1726 _ => panic!("expected binding"),
1727 };
1728 assert_eq!(call.func, "printf");
1729 match &call.args[1] {
1730 Arg::Positional(Expr::BinOp(_, BinOpKind::Add, _)) => {}
1731 other => panic!("expected addition, got {other:?}"),
1732 }
1733 }
1734
1735 #[test]
1736 fn parse_string_lit_field_access() {
1737 let f = parse_str(r#"k := "row {row.id}""#);
1739 let call = match &f.statements[0] {
1740 Statement::Binding(b) => match &b.value {
1741 Expr::Call(c) => c,
1742 other => panic!("expected call, got {other:?}"),
1743 },
1744 _ => panic!("expected binding"),
1745 };
1746 assert_eq!(call.func, "printf");
1747 match &call.args[1] {
1748 Arg::Positional(Expr::FieldAccess { source, field, .. }) => {
1749 assert_eq!(source, "row");
1750 assert_eq!(field, "id");
1751 }
1752 other => panic!("expected field access, got {other:?}"),
1753 }
1754 }
1755
1756 #[test]
1757 fn parse_string_lit_escaped_braces() {
1758 let f = parse_str(r#"k := "{{not a placeholder}} but {real}""#);
1762 let call = match &f.statements[0] {
1763 Statement::Binding(b) => match &b.value {
1764 Expr::Call(c) => c,
1765 other => panic!("expected call, got {other:?}"),
1766 },
1767 _ => panic!("expected binding"),
1768 };
1769 match &call.args[0] {
1770 Arg::Positional(Expr::StringLit(s, _)) => {
1771 assert_eq!(s, "{{not a placeholder}} but {}");
1772 }
1773 other => panic!("expected fmt string, got {other:?}"),
1774 }
1775 match &call.args[1] {
1776 Arg::Positional(Expr::Ident(n, _)) => assert_eq!(n, "real"),
1777 other => panic!("expected ident `real`, got {other:?}"),
1778 }
1779 }
1780
1781 #[test]
1782 fn parse_string_lit_unterminated_falls_back() {
1783 let f = parse_str(r#"k := "missing close {abc""#);
1785 match &f.statements[0] {
1786 Statement::Binding(b) => match &b.value {
1787 Expr::StringLit(s, _) => assert_eq!(s, "missing close {abc"),
1788 other => panic!("expected literal, got {other:?}"),
1789 },
1790 _ => panic!("expected binding"),
1791 }
1792 }
1793
1794 #[test]
1795 fn parse_string_lit_parens_in_placeholder() {
1796 let f = parse_str(r#"k := "{abs(x - y)}""#);
1800 let call = match &f.statements[0] {
1801 Statement::Binding(b) => match &b.value {
1802 Expr::Call(c) => c,
1803 other => panic!("expected call, got {other:?}"),
1804 },
1805 _ => panic!("expected binding"),
1806 };
1807 assert_eq!(call.func, "printf");
1808 match &call.args[1] {
1809 Arg::Positional(Expr::Call(inner)) => assert_eq!(inner.func, "abs"),
1810 other => panic!("expected abs call, got {other:?}"),
1811 }
1812 }
1813
1814 #[test]
1815 fn parse_array_lit() {
1816 let f = parse_str("const weights := [60.0, 20.0, 15.0, 5.0]");
1817 match &f.statements[0] {
1818 Statement::Binding(b) => match &b.value {
1819 Expr::ArrayLit(elems, _) => assert_eq!(elems.len(), 4),
1820 _ => panic!("expected array lit"),
1821 },
1822 _ => panic!("expected const binding"),
1823 }
1824 }
1825
1826 #[test]
1827 fn parse_nested_call() {
1828 let f = parse_str("x := hash(interleave(a, b))");
1829 match &f.statements[0] {
1830 Statement::Binding(b) => match &b.value {
1831 Expr::Call(c) => {
1832 assert_eq!(c.func, "hash");
1833 assert_eq!(c.args.len(), 1);
1834 match &c.args[0] {
1835 Arg::Positional(Expr::Call(inner)) => {
1836 assert_eq!(inner.func, "interleave");
1837 assert_eq!(inner.args.len(), 2);
1838 }
1839 _ => panic!("expected nested call"),
1840 }
1841 }
1842 _ => panic!("expected call"),
1843 },
1844 _ => panic!("expected binding"),
1845 }
1846 }
1847
1848 #[test]
1849 fn parse_full_program() {
1850 let src = r#"
1851 // Const bindings (compile-time fold or scope-init pull)
1852 const temp_lut := dist_normal(mean: 72.0, stddev: 5.0)
1853 const weights := [60.0, 20.0, 15.0]
1854
1855 // Cycle bindings (per-cycle eval)
1856 input cycle: u64
1857 (tenant, device) := mixed_radix(cycle, 100, 0)
1858 tenant_h := hash(tenant)
1859 code := mod(tenant_h, 10000)
1860 device_id := "{code}-{seq}"
1861 "#;
1862 let f = parse_str(src);
1863 assert_eq!(f.statements.len(), 7);
1864 }
1865
1866 #[test]
1867 fn parse_mixed_positional_named() {
1868 let f = parse_str("const lut := dist_normal(72.0, 5.0, resolution: 2000)");
1869 match &f.statements[0] {
1870 Statement::Binding(b) => match &b.value {
1871 Expr::Call(c) => {
1872 assert!(matches!(&c.args[0], Arg::Positional(_)));
1873 assert!(matches!(&c.args[1], Arg::Positional(_)));
1874 assert!(matches!(&c.args[2], Arg::Named(n, _) if n == "resolution"));
1875 }
1876 _ => panic!("expected call"),
1877 },
1878 _ => panic!("expected const binding"),
1879 }
1880 }
1881
1882 #[test]
1883 fn parse_simple_addition() {
1884 let f = parse_str("y := a + b");
1885 match &f.statements[0] {
1886 Statement::Binding(b) => match &b.value {
1887 Expr::BinOp(lhs, BinOpKind::Add, rhs) => {
1888 assert!(matches!(**lhs, Expr::Ident(ref s, _) if s == "a"));
1889 assert!(matches!(**rhs, Expr::Ident(ref s, _) if s == "b"));
1890 }
1891 _ => panic!("expected BinOp Add, got {:?}", b.value),
1892 },
1893 _ => panic!("expected cycle binding"),
1894 }
1895 }
1896
1897 #[test]
1898 fn parse_precedence_mul_over_add() {
1899 let f = parse_str("y := a + b * c");
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 match &**rhs {
1906 Expr::BinOp(rl, BinOpKind::Mul, rr) => {
1907 assert!(matches!(**rl, Expr::Ident(ref s, _) if s == "b"));
1908 assert!(matches!(**rr, Expr::Ident(ref s, _) if s == "c"));
1909 }
1910 _ => panic!("expected inner Mul"),
1911 }
1912 }
1913 _ => panic!("expected outer Add"),
1914 },
1915 _ => panic!("expected cycle binding"),
1916 }
1917 }
1918
1919 #[test]
1920 fn parse_parenthesized_grouping() {
1921 let f = parse_str("y := (a + b) * c");
1923 match &f.statements[0] {
1924 Statement::Binding(b) => {
1925 match &b.value {
1926 Expr::BinOp(lhs, BinOpKind::Mul, rhs) => {
1927 match &**lhs {
1928 Expr::BinOp(_, BinOpKind::Add, _) => {} _ => panic!("expected inner Add in lhs"),
1930 }
1931 assert!(matches!(**rhs, Expr::Ident(ref s, _) if s == "c"));
1932 }
1933 _ => panic!("expected outer Mul"),
1934 }
1935 }
1936 _ => panic!("expected cycle binding"),
1937 }
1938 }
1939
1940 fn if_call(src: &str) -> CallExpr {
1942 let f = parse_str(src);
1943 match &f.statements[0] {
1944 Statement::Binding(b) => match &b.value {
1945 Expr::Call(c) => {
1946 assert_eq!(
1947 c.func, "if",
1948 "block form must desugar to the `if` intrinsic"
1949 );
1950 assert_eq!(c.args.len(), 3, "if intrinsic takes (cond, then, else)");
1951 c.clone()
1952 }
1953 other => panic!("expected Call, got {:?}", other),
1954 },
1955 _ => panic!("expected binding"),
1956 }
1957 }
1958
1959 #[test]
1960 fn if_block_desugars_to_the_call_intrinsic() {
1961 let block = if_call("y := if c { a } else { b }");
1965 let call = if_call("y := if(c, a, b)");
1966 for (i, (bl, ca)) in block.args.iter().zip(call.args.iter()).enumerate() {
1967 match (bl, ca) {
1968 (Arg::Positional(Expr::Ident(x, _)), Arg::Positional(Expr::Ident(y, _))) => {
1969 assert_eq!(x, y, "arg {} differs between block and call form", i);
1970 }
1971 _ => panic!("expected plain idents in both forms"),
1972 }
1973 }
1974 }
1975
1976 #[test]
1977 fn if_block_accepts_expressions_in_condition_and_branches() {
1978 let c = if_call("y := if segments > 0 { total / segments } else { 0 }");
1979 assert!(
1980 matches!(
1981 &c.args[0],
1982 Arg::Positional(Expr::BinOp(_, BinOpKind::Gt, _))
1983 ),
1984 "condition should parse as a full expression"
1985 );
1986 assert!(
1987 matches!(
1988 &c.args[1],
1989 Arg::Positional(Expr::BinOp(_, BinOpKind::Div, _))
1990 ),
1991 "then-branch should parse as a full expression"
1992 );
1993 }
1994
1995 #[test]
1996 fn if_block_chains_else_if() {
1997 let c = if_call("y := if a { 1 } else if b { 2 } else { 3 }");
1999 match &c.args[2] {
2000 Arg::Positional(Expr::Call(inner)) => {
2001 assert_eq!(inner.func, "if");
2002 assert!(matches!(
2003 &inner.args[1],
2004 Arg::Positional(Expr::IntLit(2, _))
2005 ));
2006 assert!(matches!(
2007 &inner.args[2],
2008 Arg::Positional(Expr::IntLit(3, _))
2009 ));
2010 }
2011 other => panic!("expected nested if in else position, got {:?}", other),
2012 }
2013 }
2014
2015 #[test]
2016 fn if_block_nests_inside_other_expressions() {
2017 let f = parse_str("y := 1 + if c { 2 } else { 3 }");
2019 match &f.statements[0] {
2020 Statement::Binding(b) => match &b.value {
2021 Expr::BinOp(_, BinOpKind::Add, rhs) => {
2022 assert!(matches!(**rhs, Expr::Call(ref c) if c.func == "if"));
2023 }
2024 other => panic!("expected Add with an if on the rhs, got {:?}", other),
2025 },
2026 _ => panic!("expected binding"),
2027 }
2028 }
2029
2030 #[test]
2031 fn if_call_form_still_parses_as_a_call() {
2032 let c = if_call("y := if(c, a, b)");
2034 assert_eq!(c.args.len(), 3);
2035 }
2036
2037 #[test]
2038 fn if_block_requires_else() {
2039 let err = parse_str_err("y := if c { a }");
2042 assert!(
2043 err.contains("else"),
2044 "error should name the missing else: {}",
2045 err
2046 );
2047 }
2048
2049 #[test]
2050 fn if_block_reports_a_missing_brace_helpfully() {
2051 let err = parse_str_err("y := if c a else b");
2052 assert!(
2053 err.contains("if <cond>"),
2054 "error should show the block form: {}",
2055 err
2056 );
2057 }
2058
2059 #[test]
2060 fn parse_unary_negation() {
2061 let f = parse_str("y := -x");
2062 match &f.statements[0] {
2063 Statement::Binding(b) => match &b.value {
2064 Expr::UnaryNeg(inner, _) => {
2065 assert!(matches!(**inner, Expr::Ident(ref s, _) if s == "x"));
2066 }
2067 _ => panic!("expected UnaryNeg"),
2068 },
2069 _ => panic!("expected cycle binding"),
2070 }
2071 }
2072
2073 #[test]
2074 fn parse_func_call_with_infix_arg() {
2075 let f = parse_str("y := sin(cycle * 0.25)");
2077 match &f.statements[0] {
2078 Statement::Binding(b) => match &b.value {
2079 Expr::Call(c) => {
2080 assert_eq!(c.func, "sin");
2081 assert_eq!(c.args.len(), 1);
2082 match &c.args[0] {
2083 Arg::Positional(Expr::BinOp(_, BinOpKind::Mul, _)) => {}
2084 _ => panic!("expected Mul inside sin() arg"),
2085 }
2086 }
2087 _ => panic!("expected call"),
2088 },
2089 _ => panic!("expected cycle binding"),
2090 }
2091 }
2092
2093 #[test]
2094 fn parse_power_right_associative() {
2095 let f = parse_str("y := a ** b ** c");
2097 match &f.statements[0] {
2098 Statement::Binding(b) => match &b.value {
2099 Expr::BinOp(lhs, BinOpKind::Pow, rhs) => {
2100 assert!(matches!(**lhs, Expr::Ident(ref s, _) if s == "a"));
2101 match &**rhs {
2102 Expr::BinOp(rl, BinOpKind::Pow, rr) => {
2103 assert!(matches!(**rl, Expr::Ident(ref s, _) if s == "b"));
2104 assert!(matches!(**rr, Expr::Ident(ref s, _) if s == "c"));
2105 }
2106 _ => panic!("expected inner Pow"),
2107 }
2108 }
2109 _ => panic!("expected outer Pow"),
2110 },
2111 _ => panic!("expected cycle binding"),
2112 }
2113 }
2114
2115 #[test]
2116 fn parse_negate_function_call() {
2117 let f = parse_str("y := -sin(x)");
2119 match &f.statements[0] {
2120 Statement::Binding(b) => match &b.value {
2121 Expr::UnaryNeg(inner, _) => match &**inner {
2122 Expr::Call(c) => assert_eq!(c.func, "sin"),
2123 _ => panic!("expected Call inside UnaryNeg"),
2124 },
2125 _ => panic!("expected UnaryNeg"),
2126 },
2127 _ => panic!("expected cycle binding"),
2128 }
2129 }
2130
2131 #[test]
2132 fn parse_all_operators() {
2133 let f = parse_str("y := a + b - c * d / e % f ** g");
2135 match &f.statements[0] {
2136 Statement::Binding(_) => {} _ => panic!("expected cycle binding"),
2138 }
2139 }
2140
2141 #[test]
2142 fn parse_star_star_power() {
2143 let f = parse_str("y := x ** 2.0");
2145 match &f.statements[0] {
2146 Statement::Binding(b) => match &b.value {
2147 Expr::BinOp(lhs, BinOpKind::Pow, rhs) => {
2148 assert!(matches!(**lhs, Expr::Ident(ref s, _) if s == "x"));
2149 assert!(matches!(**rhs, Expr::FloatLit(v, _) if v == 2.0));
2150 }
2151 _ => panic!("expected BinOp Pow, got {:?}", b.value),
2152 },
2153 _ => panic!("expected cycle binding"),
2154 }
2155 }
2156
2157 #[test]
2158 fn parse_caret_is_xor() {
2159 let f = parse_str("y := a ^ b");
2161 match &f.statements[0] {
2162 Statement::Binding(b) => match &b.value {
2163 Expr::BinOp(lhs, BinOpKind::BitXor, rhs) => {
2164 assert!(matches!(**lhs, Expr::Ident(ref s, _) if s == "a"));
2165 assert!(matches!(**rhs, Expr::Ident(ref s, _) if s == "b"));
2166 }
2167 _ => panic!("expected BinOp BitXor, got {:?}", b.value),
2168 },
2169 _ => panic!("expected cycle binding"),
2170 }
2171 }
2172
2173 #[test]
2174 fn parse_bitand_binds_tighter_than_bitor() {
2175 let f = parse_str("y := a & b | c");
2177 match &f.statements[0] {
2178 Statement::Binding(b) => {
2179 match &b.value {
2180 Expr::BinOp(lhs, BinOpKind::BitOr, rhs) => {
2181 match &**lhs {
2182 Expr::BinOp(_, BinOpKind::BitAnd, _) => {} _ => panic!("expected inner BitAnd in lhs"),
2184 }
2185 assert!(matches!(**rhs, Expr::Ident(ref s, _) if s == "c"));
2186 }
2187 _ => panic!("expected outer BitOr"),
2188 }
2189 }
2190 _ => panic!("expected cycle binding"),
2191 }
2192 }
2193
2194 #[test]
2195 fn parse_shift_left() {
2196 let f = parse_str("y := a << 4");
2198 match &f.statements[0] {
2199 Statement::Binding(b) => match &b.value {
2200 Expr::BinOp(lhs, BinOpKind::Shl, rhs) => {
2201 assert!(matches!(**lhs, Expr::Ident(ref s, _) if s == "a"));
2202 assert!(matches!(**rhs, Expr::IntLit(4, _)));
2203 }
2204 _ => panic!("expected BinOp Shl, got {:?}", b.value),
2205 },
2206 _ => panic!("expected cycle binding"),
2207 }
2208 }
2209
2210 #[test]
2211 fn parse_cursor_without_over_clause() {
2212 let f = parse_str("cursor q = range(0, 100)");
2213 match &f.statements[0] {
2214 Statement::Cursor(c) => {
2215 assert_eq!(c.name, "q");
2216 assert!(c.over.is_none(), "no `over` → over is None");
2217 }
2218 other => panic!("expected Cursor, got {other:?}"),
2219 }
2220 }
2221
2222 #[test]
2223 fn parse_cursor_with_over_iter_var() {
2224 let f = parse_str("cursor q = range(0, 100) over p");
2225 match &f.statements[0] {
2226 Statement::Cursor(c) => {
2227 assert_eq!(c.name, "q");
2228 match &c.over {
2229 Some(Expr::Ident(name, _)) => assert_eq!(name, "p"),
2230 other => panic!("expected Some(Ident('p')), got {other:?}"),
2231 }
2232 }
2233 other => panic!("expected Cursor, got {other:?}"),
2234 }
2235 }
2236
2237 #[test]
2238 fn parse_cursor_with_over_dotted_param_projection() {
2239 let f = parse_str("cursor q = range(0, 100) over cursor.partitions");
2240 match &f.statements[0] {
2241 Statement::Cursor(c) => {
2242 assert!(c.over.is_some(), "should have over clause");
2243 match &c.over {
2245 Some(Expr::FieldAccess { .. }) => {} Some(other) => panic!("expected FieldAccess, got {other:?}"),
2247 None => panic!("expected Some"),
2248 }
2249 }
2250 other => panic!("expected Cursor, got {other:?}"),
2251 }
2252 }
2253
2254 #[test]
2255 fn parse_cursor_over_does_not_swallow_following_statement() {
2256 let f = parse_str("cursor q = range(0, 100) over p\nother := 42");
2257 assert_eq!(f.statements.len(), 2);
2258 }
2259
2260 #[test]
2261 fn parse_chained_field_access_flattens_intermediate_levels() {
2262 let f = parse_str("i := q.cursor.idx");
2267 match &f.statements[0] {
2268 Statement::Binding(b) => match &b.value {
2269 Expr::FieldAccess { source, field, .. } => {
2270 assert_eq!(source, "q__cursor");
2271 assert_eq!(field, "idx");
2272 }
2273 other => panic!("expected FieldAccess, got {other:?}"),
2274 },
2275 other => panic!("expected binding, got {other:?}"),
2276 }
2277 let f = parse_str("x := a.b.c.d");
2279 match &f.statements[0] {
2280 Statement::Binding(b) => match &b.value {
2281 Expr::FieldAccess { source, field, .. } => {
2282 assert_eq!(source, "a__b__c");
2283 assert_eq!(field, "d");
2284 }
2285 other => panic!("expected FieldAccess, got {other:?}"),
2286 },
2287 other => panic!("expected binding, got {other:?}"),
2288 }
2289 }
2290
2291 #[test]
2292 fn parse_unary_bitnot() {
2293 let f = parse_str("y := !x");
2295 match &f.statements[0] {
2296 Statement::Binding(b) => match &b.value {
2297 Expr::UnaryBitNot(inner, _) => {
2298 assert!(matches!(**inner, Expr::Ident(ref s, _) if s == "x"));
2299 }
2300 _ => panic!("expected UnaryBitNot, got {:?}", b.value),
2301 },
2302 _ => panic!("expected cycle binding"),
2303 }
2304 }
2305}