Rust · 89535 bytes Raw Blame History
1 //! Statement parser.
2 //!
3 //! Parses executable statements: assignments, IF, DO, SELECT CASE,
4 //! WHERE, FORALL, BLOCK, ASSOCIATE, EXIT, CYCLE, STOP, RETURN, GOTO,
5 //! CALL, PRINT, and legacy control flow.
6
7 use super::expr::span_from_to;
8 use super::{ParseError, Parser};
9 use crate::ast::expr::{Expr, SpannedExpr};
10 use crate::ast::stmt::*;
11 use crate::ast::Spanned;
12 use crate::lexer::TokenKind;
13
14 impl<'a> Parser<'a> {
15 /// Parse a single statement.
16 pub fn parse_stmt(&mut self) -> Result<SpannedStmt, ParseError> {
17 self.skip_newlines();
18 let start = self.current_span();
19
20 // Check for statement label: a decimal integer at statement start.
21 // In Fortran, any statement can be prefixed by a label (e.g. `10 i = i + 1`).
22 // Disambiguate from arithmetic IF branch targets (which appear after a `)`) by
23 // checking that the integer is genuinely the first token of the statement.
24 if self.peek() == &TokenKind::IntegerLiteral {
25 let next_pos = self.pos + 1;
26 if next_pos < self.tokens.len() {
27 let next_kind = &self.tokens[next_pos].kind;
28 // Only treat as a label if the following token starts a statement.
29 // Reject if followed by a comma (e.g. computed-GOTO label list handled elsewhere).
30 if matches!(
31 next_kind,
32 TokenKind::Identifier | TokenKind::IntegerLiteral | TokenKind::LParen
33 ) {
34 let label_text = self.advance().clone().text;
35 let label: u64 = label_text.parse().unwrap_or(0);
36 let inner = self.parse_stmt()?;
37 let span = span_from_to(start, self.prev_span());
38 return Ok(Spanned::new(
39 Stmt::Labeled {
40 label,
41 stmt: Box::new(inner),
42 },
43 span,
44 ));
45 }
46 }
47 }
48
49 // Check for named construct: name: if/do/select/...
50 if self.peek() == &TokenKind::Identifier {
51 let next_pos = self.pos + 1;
52 if next_pos < self.tokens.len() && self.tokens[next_pos].kind == TokenKind::Colon {
53 let name = self.advance().clone().text;
54 self.advance(); // consume :
55 return self.parse_named_construct(start, name);
56 }
57 }
58
59 let text = self.peek_text().to_lowercase();
60
61 // FORMAT statement: skip over the (...) format spec. Format
62 // strings as labeled FORMAT are largely informational — modern
63 // Fortran uses character format strings inline. We swallow the
64 // whole statement and emit a Continue placeholder so labels
65 // can still target it.
66 if text == "format" {
67 self.advance(); // consume 'format'
68 if self.peek() == &TokenKind::LParen {
69 let mut depth = 0;
70 while self.peek() != &TokenKind::Eof {
71 match self.peek() {
72 TokenKind::LParen => {
73 self.advance();
74 depth += 1;
75 }
76 TokenKind::RParen => {
77 self.advance();
78 depth -= 1;
79 if depth == 0 {
80 break;
81 }
82 }
83 _ => {
84 self.advance();
85 }
86 }
87 }
88 }
89 let span = span_from_to(start, self.prev_span());
90 return Ok(Spanned::new(Stmt::Continue { label: None }, span));
91 }
92
93 match text.as_str() {
94 "if" => self.parse_if(start),
95 "do" => self.parse_do(start),
96 // SELECT is not a reserved word — F2008 §3.2.5. LAPACK
97 // routines use `select` as a logical-array dummy
98 // argument, then assign `select(k) = .false.`. Disambiguate
99 // by requiring `case`, `type`, or `rank` to follow; else
100 // treat as an identifier.
101 "select" => {
102 let next_text = self
103 .tokens
104 .get(self.pos + 1)
105 .map(|t| t.text.to_lowercase())
106 .unwrap_or_default();
107 if matches!(next_text.as_str(), "case" | "type" | "rank") {
108 self.parse_select(start)
109 } else {
110 self.parse_assignment_or_call(start)
111 }
112 }
113 "where" => self.parse_where_construct(start),
114 "forall" => self.parse_forall_construct(start),
115 "block" => {
116 if self.at_stmt_end_after(1) {
117 self.parse_block_construct(start)
118 } else {
119 self.parse_assignment_or_call(start)
120 }
121 }
122 "associate" => self.parse_associate(start),
123 "exit" => {
124 self.advance();
125 self.parse_exit(start)
126 }
127 "cycle" => {
128 self.advance();
129 self.parse_cycle(start)
130 }
131 "stop" => {
132 self.advance();
133 self.parse_stop(start, false)
134 }
135 "error" => {
136 if self.tokens.get(self.pos + 1).is_some_and(|tok| {
137 tok.kind == TokenKind::Identifier && tok.text.eq_ignore_ascii_case("stop")
138 }) {
139 self.advance();
140 self.advance();
141 self.parse_stop(start, true)
142 } else {
143 self.parse_assignment_or_call(start)
144 }
145 }
146 "entry" => {
147 let looks_like_entry_stmt = matches!(
148 self.tokens.get(self.pos + 1).map(|t| &t.kind),
149 Some(TokenKind::Identifier)
150 );
151 if looks_like_entry_stmt {
152 Err(self
153 .error("ENTRY statements are recognized but not yet implemented".into()))
154 } else {
155 self.parse_assignment_or_call(start)
156 }
157 }
158 "return" => {
159 self.advance();
160 self.parse_return(start)
161 }
162 "goto" | "go" => self.parse_goto(start),
163 "call" => {
164 self.advance();
165 self.parse_call(start)
166 }
167 "print" => {
168 self.advance();
169 self.parse_print(start)
170 }
171 "write" => {
172 self.advance();
173 self.parse_write(start)
174 }
175 "read" => {
176 self.advance();
177 self.parse_read(start)
178 }
179 // I/O and memory keywords double as legal identifiers. The
180 // statement form always opens with `(`; anything else (`=`,
181 // `==`, end of line, etc.) means the lexeme is being used as
182 // a variable name and we redirect to assignment/call.
183 "open" if self.peek_kind_at(1) == Some(&TokenKind::LParen) => {
184 self.advance();
185 self.parse_io_paren_stmt(start, "open")
186 }
187 "close" if self.peek_kind_at(1) == Some(&TokenKind::LParen) => {
188 self.advance();
189 self.parse_io_paren_stmt(start, "close")
190 }
191 "inquire" if self.peek_kind_at(1) == Some(&TokenKind::LParen) => {
192 self.advance();
193 self.parse_inquire(start)
194 }
195 "rewind" if self.peek_kind_at(1) == Some(&TokenKind::LParen) => {
196 self.advance();
197 self.parse_io_paren_stmt(start, "rewind")
198 }
199 "backspace" if self.peek_kind_at(1) == Some(&TokenKind::LParen) => {
200 self.advance();
201 self.parse_io_paren_stmt(start, "backspace")
202 }
203 "endfile" if self.peek_kind_at(1) == Some(&TokenKind::LParen) => {
204 self.advance();
205 self.parse_io_paren_stmt(start, "endfile")
206 }
207 "flush" if self.peek_kind_at(1) == Some(&TokenKind::LParen) => {
208 self.advance();
209 self.parse_io_paren_stmt(start, "flush")
210 }
211 "wait" if self.peek_kind_at(1) == Some(&TokenKind::LParen) => {
212 self.advance();
213 self.parse_io_paren_stmt(start, "wait")
214 }
215 "allocate" if self.peek_kind_at(1) == Some(&TokenKind::LParen) => {
216 self.advance();
217 self.parse_allocate(start, false)
218 }
219 "deallocate" if self.peek_kind_at(1) == Some(&TokenKind::LParen) => {
220 self.advance();
221 self.parse_allocate(start, true)
222 }
223 "nullify" if self.peek_kind_at(1) == Some(&TokenKind::LParen) => {
224 self.advance();
225 self.parse_nullify(start)
226 }
227 "namelist" => {
228 self.advance();
229 self.parse_namelist(start)
230 }
231 "continue" => {
232 self.advance();
233 let span = span_from_to(start, self.prev_span());
234 Ok(Spanned::new(Stmt::Continue { label: None }, span))
235 }
236 "sync" => {
237 let looks_like_sync_stmt = self.tokens.get(self.pos + 1).is_some_and(|tok| {
238 tok.kind == TokenKind::Identifier
239 && matches!(
240 tok.text.to_ascii_lowercase().as_str(),
241 "all" | "images" | "memory" | "team"
242 )
243 });
244 if looks_like_sync_stmt {
245 Err(self.error(
246 "coarray SYNC statements are recognized but not yet implemented".into(),
247 ))
248 } else {
249 self.parse_assignment_or_call(start)
250 }
251 }
252 _ => self.parse_assignment_or_call(start),
253 }
254 }
255
256 /// Parse a block of statements until a terminating keyword.
257 pub fn parse_stmt_block(
258 &mut self,
259 terminators: &[&str],
260 ) -> Result<Vec<SpannedStmt>, ParseError> {
261 let mut stmts = Vec::new();
262 loop {
263 self.skip_newlines();
264 if self.peek() == &TokenKind::Eof {
265 break;
266 }
267 let text = self.peek_text().to_lowercase();
268
269 // Check for combined end-keyword: "endif", "enddo", "endselect", etc.
270 if terminators.iter().any(|t| text == format!("end{}", t)) {
271 break;
272 }
273 // Check for "end" followed by a terminator keyword: "end if", "end do", etc.
274 if text == "end" {
275 let next = if self.pos + 1 < self.tokens.len() {
276 self.tokens[self.pos + 1].text.to_lowercase()
277 } else {
278 String::new()
279 };
280 if terminators.iter().any(|t| next == *t) || next.is_empty() {
281 break;
282 }
283 }
284 // Check for "else", "elsewhere", "case", "contains" which terminate inner blocks.
285 if matches!(
286 text.as_str(),
287 "else" | "elseif" | "elsewhere" | "case" | "contains" | "default"
288 ) {
289 break;
290 }
291 stmts.push(self.parse_stmt()?);
292 self.skip_newlines();
293 }
294 Ok(stmts)
295 }
296
297 // ---- IF ----
298
299 fn parse_if(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
300 self.advance(); // consume 'if'
301 self.expect(&TokenKind::LParen)?;
302 let condition = self.parse_expr()?;
303 self.expect(&TokenKind::RParen)?;
304
305 // Check for THEN → block IF construct.
306 if self.peek_text().eq_ignore_ascii_case("then") {
307 self.advance();
308 return self.parse_if_construct(start, None, condition);
309 }
310
311 // Arithmetic IF: if (expr) label, label, label
312 if self.peek() == &TokenKind::IntegerLiteral {
313 let neg: u64 = self.advance().clone().text.parse().unwrap_or(0);
314 self.expect(&TokenKind::Comma)?;
315 let zero: u64 = self.advance().clone().text.parse().unwrap_or(0);
316 self.expect(&TokenKind::Comma)?;
317 let pos: u64 = self.advance().clone().text.parse().unwrap_or(0);
318 let span = span_from_to(start, self.prev_span());
319 return Ok(Spanned::new(
320 Stmt::ArithmeticIf {
321 expr: condition,
322 neg,
323 zero,
324 pos,
325 },
326 span,
327 ));
328 }
329
330 // Single-line IF: if (cond) action
331 let action = self.parse_stmt()?;
332 let span = span_from_to(start, self.prev_span());
333 Ok(Spanned::new(
334 Stmt::IfStmt {
335 condition,
336 action: Box::new(action),
337 },
338 span,
339 ))
340 }
341
342 fn parse_if_construct(
343 &mut self,
344 start: crate::lexer::Span,
345 name: Option<String>,
346 condition: SpannedExpr,
347 ) -> Result<SpannedStmt, ParseError> {
348 let then_body = self.parse_stmt_block(&["if"])?;
349 let mut else_ifs = Vec::new();
350 let mut else_body = None;
351
352 loop {
353 self.skip_newlines();
354 let text = self.peek_text().to_lowercase();
355
356 if text == "elseif" || text == "else" {
357 if text == "elseif"
358 || (text == "else" && {
359 let next = if self.pos + 1 < self.tokens.len() {
360 self.tokens[self.pos + 1].text.to_lowercase()
361 } else {
362 String::new()
363 };
364 next == "if"
365 })
366 {
367 // ELSE IF
368 self.advance(); // else
369 if self.peek_text().eq_ignore_ascii_case("if") {
370 self.advance(); // if
371 }
372 self.expect(&TokenKind::LParen)?;
373 let ei_cond = self.parse_expr()?;
374 self.expect(&TokenKind::RParen)?;
375 if self.peek_text().eq_ignore_ascii_case("then") {
376 self.advance();
377 }
378 let ei_body = self.parse_stmt_block(&["if"])?;
379 else_ifs.push((ei_cond, ei_body));
380 continue;
381 }
382
383 // ELSE (no IF)
384 self.advance(); // else
385 let eb = self.parse_stmt_block(&["if"])?;
386 else_body = Some(eb);
387 continue;
388 }
389
390 break;
391 }
392
393 // Consume END IF / ENDIF
394 self.consume_end("if")?;
395
396 let span = span_from_to(start, self.prev_span());
397 Ok(Spanned::new(
398 Stmt::IfConstruct {
399 name,
400 condition,
401 then_body,
402 else_ifs,
403 else_body,
404 },
405 span,
406 ))
407 }
408
409 // ---- DO ----
410
411 fn parse_do(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
412 self.advance(); // consume 'do'
413 self.parse_do_body(start)
414 }
415
416 // ---- SELECT CASE ----
417
418 fn parse_select(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
419 self.advance(); // consume 'select'
420 let keyword = self.peek_text().to_lowercase();
421 if keyword == "type" {
422 return self.parse_select_type(start);
423 }
424 if keyword == "rank" {
425 return self.parse_select_rank(start);
426 }
427 self.eat_ident("case");
428 self.expect(&TokenKind::LParen)?;
429 let selector = self.parse_expr()?;
430 self.expect(&TokenKind::RParen)?;
431
432 let mut cases = Vec::new();
433 loop {
434 self.skip_newlines();
435 let text = self.peek_text().to_lowercase();
436 if text == "case" {
437 self.advance();
438 let selectors = self.parse_case_selectors()?;
439 let body = self.parse_stmt_block(&["select"])?;
440 cases.push(CaseBlock { selectors, body });
441 } else {
442 break;
443 }
444 }
445 self.consume_end("select")?;
446 let span = span_from_to(start, self.prev_span());
447 Ok(Spanned::new(
448 Stmt::SelectCase {
449 name: None,
450 selector,
451 cases,
452 },
453 span,
454 ))
455 }
456
457 fn parse_select_type(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
458 self.advance(); // consume 'type'
459 self.expect(&TokenKind::LParen)?;
460
461 // Check for association: SELECT TYPE (assoc => expr)
462 let (assoc_name, selector) = {
463 let expr = self.parse_expr()?;
464 if self.eat(&TokenKind::Arrow) {
465 // assoc => expr
466 let assoc = if let Expr::Name { name } = &expr.node {
467 Some(name.clone())
468 } else {
469 None
470 };
471 let sel = self.parse_expr()?;
472 (assoc, sel)
473 } else {
474 (None, expr)
475 }
476 };
477 self.expect(&TokenKind::RParen)?;
478
479 let mut guards = Vec::new();
480 loop {
481 self.skip_newlines();
482 let text = self.peek_text().to_lowercase();
483 if text == "type" {
484 self.advance(); // consume 'type'
485 self.eat_ident("is");
486 self.expect(&TokenKind::LParen)?;
487 let type_name = self.parse_select_type_spec()?;
488 self.expect(&TokenKind::RParen)?;
489 let body = self.parse_select_type_body()?;
490 guards.push(TypeGuard::TypeIs { type_name, body });
491 } else if text == "class" {
492 self.advance(); // consume 'class'
493 let next = self.peek_text().to_lowercase();
494 if next == "is" {
495 self.advance(); // consume 'is'
496 self.expect(&TokenKind::LParen)?;
497 let type_name = self.parse_select_type_spec()?;
498 self.expect(&TokenKind::RParen)?;
499 let body = self.parse_select_type_body()?;
500 guards.push(TypeGuard::ClassIs { type_name, body });
501 } else if next == "default" {
502 self.advance(); // consume 'default'
503 let body = self.parse_select_type_body()?;
504 guards.push(TypeGuard::ClassDefault { body });
505 } else {
506 break;
507 }
508 } else {
509 break;
510 }
511 }
512 self.consume_end("select")?;
513 let span = span_from_to(start, self.prev_span());
514 Ok(Spanned::new(
515 Stmt::SelectType {
516 name: None,
517 selector,
518 assoc_name,
519 guards,
520 },
521 span,
522 ))
523 }
524
525 /// Parse the body of a SELECT TYPE guard — stops at TYPE IS, CLASS IS, CLASS DEFAULT, or END SELECT.
526 /// Parse a type spec inside SELECT TYPE / CLASS IS guards.
527 /// Accepts derived names like `foo`, intrinsic types with optional
528 /// kind/length like `real(sp)`, `integer(int8)`, `character(len=*)`.
529 fn parse_select_type_spec(&mut self) -> Result<String, ParseError> {
530 let base = self.advance().clone().text;
531 let base_lc = base.to_lowercase();
532 let is_intrinsic = matches!(
533 base_lc.as_str(),
534 "integer" | "real" | "double" | "complex" | "logical" | "character"
535 );
536 if is_intrinsic && self.peek() == &TokenKind::LParen {
537 // Skip over the kind/length spec without storing it.
538 // We just need to consume balanced parens.
539 self.advance(); // consume '('
540 let mut depth = 1;
541 while depth > 0 && self.peek() != &TokenKind::Eof {
542 match self.peek() {
543 TokenKind::LParen => {
544 self.advance();
545 depth += 1;
546 }
547 TokenKind::RParen => {
548 self.advance();
549 depth -= 1;
550 }
551 _ => {
552 self.advance();
553 }
554 }
555 }
556 }
557 Ok(base)
558 }
559
560 fn parse_select_type_body(&mut self) -> Result<Vec<SpannedStmt>, ParseError> {
561 let mut stmts = Vec::new();
562 loop {
563 self.skip_newlines();
564 if self.peek() == &TokenKind::Eof {
565 break;
566 }
567 let text = self.peek_text().to_lowercase();
568 // Break on guard keywords or end.
569 if text == "type" || text == "class" || text == "endselect" {
570 break;
571 }
572 if text == "end" {
573 let next = if self.pos + 1 < self.tokens.len() {
574 self.tokens[self.pos + 1].text.to_lowercase()
575 } else {
576 String::new()
577 };
578 if next == "select" || next.is_empty() {
579 break;
580 }
581 }
582 stmts.push(self.parse_stmt()?);
583 }
584 Ok(stmts)
585 }
586
587 fn parse_select_rank(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
588 self.advance(); // consume 'rank'
589 self.expect(&TokenKind::LParen)?;
590
591 let (assoc_name, selector) = {
592 let expr = self.parse_expr()?;
593 if self.eat(&TokenKind::Arrow) {
594 let assoc = if let Expr::Name { name } = &expr.node {
595 Some(name.clone())
596 } else {
597 None
598 };
599 let sel = self.parse_expr()?;
600 (assoc, sel)
601 } else {
602 (None, expr)
603 }
604 };
605 self.expect(&TokenKind::RParen)?;
606
607 let mut guards = Vec::new();
608 loop {
609 self.skip_newlines();
610 let text = self.peek_text().to_lowercase();
611 if text != "rank" {
612 break;
613 }
614 self.advance(); // consume 'rank'
615 let next_text = self.peek_text().to_lowercase();
616 if next_text == "default" {
617 self.advance(); // consume 'default'
618 let body = self.parse_select_rank_body()?;
619 guards.push(RankGuard::RankDefault { body });
620 } else {
621 self.expect(&TokenKind::LParen)?;
622 if self.peek_text() == "*" {
623 self.advance(); // consume '*'
624 self.expect(&TokenKind::RParen)?;
625 let body = self.parse_select_rank_body()?;
626 guards.push(RankGuard::RankStar { body });
627 } else {
628 let rank_expr = self.parse_expr()?;
629 let rank_val = match &rank_expr.node {
630 Expr::IntegerLiteral { text, .. } => text.parse::<i64>().unwrap_or(0),
631 Expr::UnaryOp {
632 op: crate::ast::expr::UnaryOp::Minus,
633 operand,
634 } => {
635 if let Expr::IntegerLiteral { text, .. } = &operand.node {
636 -text.parse::<i64>().unwrap_or(0)
637 } else {
638 0
639 }
640 }
641 _ => 0,
642 };
643 self.expect(&TokenKind::RParen)?;
644 let body = self.parse_select_rank_body()?;
645 guards.push(RankGuard::Rank {
646 rank: rank_val,
647 body,
648 });
649 }
650 }
651 }
652 self.consume_end("select")?;
653 let span = span_from_to(start, self.prev_span());
654 Ok(Spanned::new(
655 Stmt::SelectRank {
656 name: None,
657 selector,
658 assoc_name,
659 guards,
660 },
661 span,
662 ))
663 }
664
665 fn parse_select_rank_body(&mut self) -> Result<Vec<SpannedStmt>, ParseError> {
666 let mut stmts = Vec::new();
667 loop {
668 self.skip_newlines();
669 if self.peek() == &TokenKind::Eof {
670 break;
671 }
672 let text = self.peek_text().to_lowercase();
673 if text == "rank" || text == "endselect" {
674 break;
675 }
676 if text == "end" {
677 let next = if self.pos + 1 < self.tokens.len() {
678 self.tokens[self.pos + 1].text.to_lowercase()
679 } else {
680 String::new()
681 };
682 if next == "select" || next.is_empty() {
683 break;
684 }
685 }
686 stmts.push(self.parse_stmt()?);
687 }
688 Ok(stmts)
689 }
690
691 /// Parse a comma-separated list of names (identifiers).
692 fn parse_name_list(&mut self) -> Result<Vec<String>, ParseError> {
693 let mut names = Vec::new();
694 loop {
695 if self.peek() == &TokenKind::Identifier {
696 names.push(self.advance().clone().text);
697 } else {
698 break;
699 }
700 if !self.eat(&TokenKind::Comma) {
701 break;
702 }
703 }
704 Ok(names)
705 }
706
707 fn parse_case_selectors(&mut self) -> Result<Vec<CaseSelector>, ParseError> {
708 if self.peek_text().eq_ignore_ascii_case("default") {
709 self.advance();
710 return Ok(vec![CaseSelector::Default]);
711 }
712 self.expect(&TokenKind::LParen)?;
713 let mut selectors = Vec::new();
714 loop {
715 // Check for range: low:high, :high, low:
716 if self.peek() == &TokenKind::Colon {
717 self.advance();
718 let high = self.parse_expr()?;
719 selectors.push(CaseSelector::Range {
720 low: None,
721 high: Some(high),
722 });
723 } else {
724 let val = self.parse_expr()?;
725 if self.eat(&TokenKind::Colon) {
726 if matches!(self.peek(), TokenKind::Comma | TokenKind::RParen) {
727 selectors.push(CaseSelector::Range {
728 low: Some(val),
729 high: None,
730 });
731 } else {
732 let high = self.parse_expr()?;
733 selectors.push(CaseSelector::Range {
734 low: Some(val),
735 high: Some(high),
736 });
737 }
738 } else {
739 selectors.push(CaseSelector::Value(val));
740 }
741 }
742 if !self.eat(&TokenKind::Comma) {
743 break;
744 }
745 }
746 self.expect(&TokenKind::RParen)?;
747 Ok(selectors)
748 }
749
750 // ---- Simple statements ----
751
752 fn parse_exit(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
753 let name = if self.peek() == &TokenKind::Identifier && !self.at_stmt_end() {
754 Some(self.advance().clone().text)
755 } else {
756 None
757 };
758 let span = span_from_to(start, self.prev_span());
759 Ok(Spanned::new(Stmt::Exit { name }, span))
760 }
761
762 fn parse_cycle(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
763 let name = if self.peek() == &TokenKind::Identifier && !self.at_stmt_end() {
764 Some(self.advance().clone().text)
765 } else {
766 None
767 };
768 let span = span_from_to(start, self.prev_span());
769 Ok(Spanned::new(Stmt::Cycle { name }, span))
770 }
771
772 fn parse_stop(
773 &mut self,
774 start: crate::lexer::Span,
775 is_error: bool,
776 ) -> Result<SpannedStmt, ParseError> {
777 let code = if !self.at_stmt_end() && !self.peek_text().eq_ignore_ascii_case("quiet") {
778 Some(self.parse_expr()?)
779 } else {
780 None
781 };
782
783 // Check for QUIET= specifier.
784 let mut quiet = false;
785 let _ = self.eat(&TokenKind::Comma); // optional comma before QUIET=
786 if self.peek_text().eq_ignore_ascii_case("quiet") {
787 self.advance();
788 self.expect(&TokenKind::Assign)?;
789 let val_text = self.peek_text().to_lowercase();
790 if val_text == ".true." || val_text == ".t." {
791 quiet = true;
792 }
793 self.advance(); // consume the logical literal
794 }
795
796 let span = span_from_to(start, self.prev_span());
797 if is_error {
798 Ok(Spanned::new(Stmt::ErrorStop { code, quiet }, span))
799 } else {
800 Ok(Spanned::new(Stmt::Stop { code, quiet }, span))
801 }
802 }
803
804 fn parse_return(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
805 let value = if !self.at_stmt_end() {
806 Some(self.parse_expr()?)
807 } else {
808 None
809 };
810 let span = span_from_to(start, self.prev_span());
811 Ok(Spanned::new(Stmt::Return { value }, span))
812 }
813
814 fn parse_goto(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
815 self.advance(); // consume 'goto' or 'go'
816 if self.peek_text().eq_ignore_ascii_case("to") {
817 self.advance();
818 }
819 // Plain GOTO label.
820 if self.peek() == &TokenKind::IntegerLiteral {
821 let label: u64 = self.advance().clone().text.parse().unwrap_or(0);
822 let span = span_from_to(start, self.prev_span());
823 return Ok(Spanned::new(Stmt::Goto { label }, span));
824 }
825 // Computed GOTO: (label-list), selector
826 if self.peek() == &TokenKind::LParen {
827 self.advance();
828 let mut labels = Vec::new();
829 loop {
830 let l: u64 = self.advance().clone().text.parse().unwrap_or(0);
831 labels.push(l);
832 if !self.eat(&TokenKind::Comma) {
833 break;
834 }
835 }
836 self.expect(&TokenKind::RParen)?;
837 self.eat(&TokenKind::Comma);
838 let selector = self.parse_expr()?;
839 let span = span_from_to(start, self.prev_span());
840 return Ok(Spanned::new(Stmt::ComputedGoto { labels, selector }, span));
841 }
842 Err(self.error("expected label or (label-list) after GOTO".into()))
843 }
844
845 fn parse_call(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
846 let callee = self.parse_expr()?;
847 let span = span_from_to(start, self.prev_span());
848 // The expression parser handles the (args) part as FunctionCall.
849 // Extract the args from the FunctionCall if present.
850 if let Expr::FunctionCall {
851 callee: inner,
852 args,
853 } = callee.node
854 {
855 Ok(Spanned::new(
856 Stmt::Call {
857 callee: *inner,
858 args,
859 },
860 span,
861 ))
862 } else {
863 // Call with no arguments: call sub
864 Ok(Spanned::new(
865 Stmt::Call {
866 callee,
867 args: Vec::new(),
868 },
869 span,
870 ))
871 }
872 }
873
874 fn parse_print(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
875 // Format can be * (list-directed), a label, or a format string.
876 let format = if self.peek() == &TokenKind::Star {
877 let tok = self.advance().clone();
878 Spanned::new(Expr::Name { name: "*".into() }, tok.span)
879 } else {
880 self.parse_expr()?
881 };
882 let mut items = Vec::new();
883 if self.eat(&TokenKind::Comma) {
884 loop {
885 items.push(self.parse_expr()?);
886 if !self.eat(&TokenKind::Comma) {
887 break;
888 }
889 }
890 }
891 let span = span_from_to(start, self.prev_span());
892 Ok(Spanned::new(Stmt::Print { format, items }, span))
893 }
894
895 fn parse_assignment_or_call(
896 &mut self,
897 start: crate::lexer::Span,
898 ) -> Result<SpannedStmt, ParseError> {
899 let target = self.parse_expr()?;
900
901 if self.eat(&TokenKind::Assign) {
902 let value = self.parse_expr()?;
903 let span = span_from_to(start, self.prev_span());
904 return Ok(Spanned::new(Stmt::Assignment { target, value }, span));
905 }
906
907 if self.eat(&TokenKind::Arrow) {
908 let value = self.parse_expr()?;
909 let span = span_from_to(start, self.prev_span());
910 return Ok(Spanned::new(
911 Stmt::PointerAssignment { target, value },
912 span,
913 ));
914 }
915
916 Err(ParseError {
917 span: target.span,
918 msg: "unexpected expression statement; expected assignment (=) or pointer assignment (=>); subroutine calls require CALL".into(),
919 })
920 }
921
922 // ---- WHERE / FORALL / BLOCK / ASSOCIATE stubs ----
923
924 fn parse_where_construct(
925 &mut self,
926 start: crate::lexer::Span,
927 ) -> Result<SpannedStmt, ParseError> {
928 self.advance(); // consume 'where'
929 self.expect(&TokenKind::LParen)?;
930 let mask = self.parse_expr()?;
931 self.expect(&TokenKind::RParen)?;
932
933 // Single-line WHERE: where (mask) stmt
934 if !self.at_stmt_end() && !self.peek_text().eq_ignore_ascii_case("then") {
935 // Check if this looks like a statement, not a newline.
936 let action = self.parse_stmt()?;
937 let span = span_from_to(start, self.prev_span());
938 return Ok(Spanned::new(
939 Stmt::WhereStmt {
940 mask,
941 stmt: Box::new(action),
942 },
943 span,
944 ));
945 }
946
947 let body = self.parse_stmt_block(&["where"])?;
948 let mut elsewhere = Vec::new();
949 while self.peek_text().eq_ignore_ascii_case("elsewhere") {
950 self.advance();
951 let ew_mask = if self.peek() == &TokenKind::LParen {
952 self.advance();
953 let m = self.parse_expr()?;
954 self.expect(&TokenKind::RParen)?;
955 Some(m)
956 } else {
957 None
958 };
959 let ew_body = self.parse_stmt_block(&["where"])?;
960 elsewhere.push((ew_mask, ew_body));
961 }
962 self.consume_end("where")?;
963 let span = span_from_to(start, self.prev_span());
964 Ok(Spanned::new(
965 Stmt::WhereConstruct {
966 name: None,
967 mask,
968 body,
969 elsewhere,
970 },
971 span,
972 ))
973 }
974
975 fn parse_forall_construct(
976 &mut self,
977 start: crate::lexer::Span,
978 ) -> Result<SpannedStmt, ParseError> {
979 self.advance(); // consume 'forall'
980 self.expect(&TokenKind::LParen)?;
981 let mut specs = Vec::new();
982 loop {
983 let var = self.advance().clone().text;
984 self.expect(&TokenKind::Assign)?;
985 let fs_start = self.parse_expr()?;
986 self.expect(&TokenKind::Colon)?;
987 let end = self.parse_expr()?;
988 let step = if self.eat(&TokenKind::Colon) {
989 Some(self.parse_expr()?)
990 } else {
991 None
992 };
993 specs.push(ForallSpec {
994 var,
995 start: fs_start,
996 end,
997 step,
998 });
999 if !self.eat(&TokenKind::Comma) {
1000 break;
1001 }
1002 // Check if next is a control or mask.
1003 if self.peek() != &TokenKind::Identifier || {
1004 let np = self.pos + 1;
1005 np >= self.tokens.len() || self.tokens[np].kind != TokenKind::Assign
1006 } {
1007 break;
1008 }
1009 }
1010 let mask = if self.peek() != &TokenKind::RParen {
1011 Some(self.parse_expr()?)
1012 } else {
1013 None
1014 };
1015 self.expect(&TokenKind::RParen)?;
1016
1017 // Single-line FORALL or block.
1018 if !self.at_stmt_end() {
1019 let action = self.parse_stmt()?;
1020 let span = span_from_to(start, self.prev_span());
1021 return Ok(Spanned::new(
1022 Stmt::ForallStmt {
1023 specs,
1024 mask,
1025 stmt: Box::new(action),
1026 },
1027 span,
1028 ));
1029 }
1030
1031 let body = self.parse_stmt_block(&["forall"])?;
1032 self.consume_end("forall")?;
1033 let span = span_from_to(start, self.prev_span());
1034 Ok(Spanned::new(
1035 Stmt::ForallConstruct {
1036 name: None,
1037 specs,
1038 mask,
1039 body,
1040 },
1041 span,
1042 ))
1043 }
1044
1045 fn parse_block_construct(
1046 &mut self,
1047 start: crate::lexer::Span,
1048 ) -> Result<SpannedStmt, ParseError> {
1049 self.advance(); // consume 'block'
1050 // F2008: a BLOCK construct can have a specification part
1051 // (declarations) before its execution part (statements).
1052 // Reuse parse_unit_body which already handles the full
1053 // interleaving of type-decls, PARAMETER, COMMON, DATA,
1054 // derived-type defs, and executable statements.
1055 let (uses, _imports, implicit, decls, body, ifaces) = self.parse_unit_body(&["block"])?;
1056 self.consume_end("block")?;
1057 let span = span_from_to(start, self.prev_span());
1058 Ok(Spanned::new(
1059 Stmt::Block {
1060 name: None,
1061 uses,
1062 ifaces,
1063 implicit,
1064 decls,
1065 body,
1066 },
1067 span,
1068 ))
1069 }
1070
1071 fn parse_associate(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
1072 self.advance(); // consume 'associate'
1073 self.expect(&TokenKind::LParen)?;
1074 let mut assocs = Vec::new();
1075 loop {
1076 let name = self.advance().clone().text;
1077 self.expect(&TokenKind::Arrow)?;
1078 let expr = self.parse_expr()?;
1079 assocs.push((name, expr));
1080 if !self.eat(&TokenKind::Comma) {
1081 break;
1082 }
1083 }
1084 self.expect(&TokenKind::RParen)?;
1085 let body = self.parse_stmt_block(&["associate"])?;
1086 self.consume_end("associate")?;
1087 let span = span_from_to(start, self.prev_span());
1088 Ok(Spanned::new(
1089 Stmt::Associate {
1090 name: None,
1091 assocs,
1092 body,
1093 },
1094 span,
1095 ))
1096 }
1097
1098 // ---- Helpers ----
1099
1100 fn parse_named_construct(
1101 &mut self,
1102 start: crate::lexer::Span,
1103 name: String,
1104 ) -> Result<SpannedStmt, ParseError> {
1105 let text = self.peek_text().to_lowercase();
1106 match text.as_str() {
1107 "if" => {
1108 self.advance();
1109 self.expect(&TokenKind::LParen)?;
1110 let condition = self.parse_expr()?;
1111 self.expect(&TokenKind::RParen)?;
1112 if self.peek_text().eq_ignore_ascii_case("then") {
1113 self.advance();
1114 }
1115 self.parse_if_construct(start, Some(name), condition)
1116 }
1117 "do" => {
1118 self.advance();
1119 // Reuse DO parsing but inject the name.
1120 let mut stmt = self.parse_do_body(start)?;
1121 // Inject name into the statement.
1122 match &mut stmt.node {
1123 Stmt::DoLoop { name: n, .. }
1124 | Stmt::DoWhile { name: n, .. }
1125 | Stmt::DoConcurrent { name: n, .. } => *n = Some(name),
1126 _ => {}
1127 }
1128 Ok(stmt)
1129 }
1130 "select" => {
1131 self.advance();
1132 self.eat_ident("case");
1133 self.expect(&TokenKind::LParen)?;
1134 let selector = self.parse_expr()?;
1135 self.expect(&TokenKind::RParen)?;
1136 let mut cases = Vec::new();
1137 loop {
1138 self.skip_newlines();
1139 if self.peek_text().eq_ignore_ascii_case("case") {
1140 self.advance();
1141 let selectors = self.parse_case_selectors()?;
1142 let body = self.parse_stmt_block(&["select"])?;
1143 cases.push(CaseBlock { selectors, body });
1144 } else {
1145 break;
1146 }
1147 }
1148 self.consume_end("select")?;
1149 let span = span_from_to(start, self.prev_span());
1150 Ok(Spanned::new(
1151 Stmt::SelectCase {
1152 name: Some(name),
1153 selector,
1154 cases,
1155 },
1156 span,
1157 ))
1158 }
1159 "where" => {
1160 let mut s = self.parse_where_construct(start)?;
1161 if let Stmt::WhereConstruct { name: n, .. } = &mut s.node {
1162 *n = Some(name);
1163 }
1164 Ok(s)
1165 }
1166 "forall" => {
1167 let mut s = self.parse_forall_construct(start)?;
1168 if let Stmt::ForallConstruct { name: n, .. } = &mut s.node {
1169 *n = Some(name);
1170 }
1171 Ok(s)
1172 }
1173 "block" => {
1174 let mut s = self.parse_block_construct(start)?;
1175 if let Stmt::Block { name: n, .. } = &mut s.node {
1176 *n = Some(name);
1177 }
1178 Ok(s)
1179 }
1180 "associate" => {
1181 let mut s = self.parse_associate(start)?;
1182 if let Stmt::Associate { name: n, .. } = &mut s.node {
1183 *n = Some(name);
1184 }
1185 Ok(s)
1186 }
1187 _ => Err(self.error(format!(
1188 "expected construct keyword after '{}:', got '{}'",
1189 name, text
1190 ))),
1191 }
1192 }
1193
1194 /// Parse the body of a DO statement (after 'do' keyword has been consumed).
1195 /// Factored out so named constructs can reuse it.
1196 fn parse_do_body(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
1197 // DO WHILE
1198 if self.peek_text().eq_ignore_ascii_case("while") {
1199 self.advance();
1200 self.expect(&TokenKind::LParen)?;
1201 let condition = self.parse_expr()?;
1202 self.expect(&TokenKind::RParen)?;
1203 let body = self.parse_stmt_block(&["do"])?;
1204 self.consume_end("do")?;
1205 let span = span_from_to(start, self.prev_span());
1206 return Ok(Spanned::new(
1207 Stmt::DoWhile {
1208 name: None,
1209 condition,
1210 body,
1211 },
1212 span,
1213 ));
1214 }
1215
1216 // DO CONCURRENT
1217 if self.peek_text().eq_ignore_ascii_case("concurrent") {
1218 self.advance();
1219 self.expect(&TokenKind::LParen)?;
1220 let mut controls = Vec::new();
1221 loop {
1222 let var = self.advance().clone().text;
1223 self.expect(&TokenKind::Assign)?;
1224 let ctrl_start = self.parse_expr()?;
1225 self.expect(&TokenKind::Colon)?;
1226 let end = self.parse_expr()?;
1227 let step = if self.eat(&TokenKind::Colon) {
1228 Some(self.parse_expr()?)
1229 } else {
1230 None
1231 };
1232 controls.push(ConcurrentControl {
1233 var,
1234 start: ctrl_start,
1235 end,
1236 step,
1237 });
1238 if !self.eat(&TokenKind::Comma) {
1239 break;
1240 }
1241 if self.peek() != &TokenKind::Identifier || {
1242 let np = self.pos + 1;
1243 np >= self.tokens.len() || self.tokens[np].kind != TokenKind::Assign
1244 } {
1245 break;
1246 }
1247 }
1248 let mask = if self.peek() != &TokenKind::RParen {
1249 Some(self.parse_expr()?)
1250 } else {
1251 None
1252 };
1253 self.expect(&TokenKind::RParen)?;
1254
1255 // Parse optional locality specs: LOCAL(...), SHARED(...), DEFAULT(NONE), REDUCE(op:...)
1256 let mut locality = Vec::new();
1257 loop {
1258 let kw = self.peek_text().to_lowercase();
1259 match kw.as_str() {
1260 "local" => {
1261 self.advance();
1262 if self.peek_text().eq_ignore_ascii_case("_init")
1263 || self.peek_text().eq_ignore_ascii_case("init")
1264 {
1265 // Check for LOCAL_INIT — might be lexed as LOCAL followed by _INIT
1266 // or as LOCAL_INIT as one token.
1267 let next = self.peek_text().to_lowercase();
1268 if next == "_init" {
1269 self.advance();
1270 self.expect(&TokenKind::LParen)?;
1271 let vars = self.parse_name_list()?;
1272 self.expect(&TokenKind::RParen)?;
1273 locality.push(LocalitySpec::LocalInit(vars));
1274 continue;
1275 }
1276 }
1277 self.expect(&TokenKind::LParen)?;
1278 let vars = self.parse_name_list()?;
1279 self.expect(&TokenKind::RParen)?;
1280 locality.push(LocalitySpec::Local(vars));
1281 }
1282 "local_init" => {
1283 self.advance();
1284 self.expect(&TokenKind::LParen)?;
1285 let vars = self.parse_name_list()?;
1286 self.expect(&TokenKind::RParen)?;
1287 locality.push(LocalitySpec::LocalInit(vars));
1288 }
1289 "shared" => {
1290 self.advance();
1291 self.expect(&TokenKind::LParen)?;
1292 let vars = self.parse_name_list()?;
1293 self.expect(&TokenKind::RParen)?;
1294 locality.push(LocalitySpec::Shared(vars));
1295 }
1296 "default" => {
1297 self.advance();
1298 self.expect(&TokenKind::LParen)?;
1299 self.eat_ident("none");
1300 self.expect(&TokenKind::RParen)?;
1301 locality.push(LocalitySpec::DefaultNone);
1302 }
1303 "reduce" => {
1304 self.advance();
1305 self.expect(&TokenKind::LParen)?;
1306 let op = self.advance().clone().text;
1307 self.expect(&TokenKind::Colon)?;
1308 let vars = self.parse_name_list()?;
1309 self.expect(&TokenKind::RParen)?;
1310 locality.push(LocalitySpec::Reduce { op, vars });
1311 }
1312 _ => break,
1313 }
1314 }
1315
1316 let body = self.parse_stmt_block(&["do"])?;
1317 self.consume_end("do")?;
1318 let span = span_from_to(start, self.prev_span());
1319 return Ok(Spanned::new(
1320 Stmt::DoConcurrent {
1321 name: None,
1322 controls,
1323 mask,
1324 locality,
1325 body,
1326 },
1327 span,
1328 ));
1329 }
1330
1331 // Infinite DO
1332 if self.at_stmt_end() {
1333 let body = self.parse_stmt_block(&["do"])?;
1334 self.consume_end("do")?;
1335 let span = span_from_to(start, self.prev_span());
1336 return Ok(Spanned::new(
1337 Stmt::DoLoop {
1338 name: None,
1339 var: None,
1340 start: None,
1341 end: None,
1342 step: None,
1343 body,
1344 },
1345 span,
1346 ));
1347 }
1348
1349 // Classic labeled DO (F77): DO 10 I = 1, N
1350 let terminating_label = if self.peek() == &TokenKind::IntegerLiteral {
1351 Some(self.advance().clone().text.parse().unwrap_or(0))
1352 } else {
1353 None
1354 };
1355
1356 // Counted DO
1357 let var = self.advance().clone().text;
1358 self.expect(&TokenKind::Assign)?;
1359 let do_start = self.parse_expr()?;
1360 self.expect(&TokenKind::Comma)?;
1361 let do_end = self.parse_expr()?;
1362 let step = if self.eat(&TokenKind::Comma) {
1363 Some(self.parse_expr()?)
1364 } else {
1365 None
1366 };
1367 let body = if let Some(label) = terminating_label {
1368 self.parse_labeled_do_body(label)?
1369 } else {
1370 let body = self.parse_stmt_block(&["do"])?;
1371 self.consume_end("do")?;
1372 body
1373 };
1374 let span = span_from_to(start, self.prev_span());
1375 Ok(Spanned::new(
1376 Stmt::DoLoop {
1377 name: None,
1378 var: Some(var),
1379 start: Some(do_start),
1380 end: Some(do_end),
1381 step,
1382 body,
1383 },
1384 span,
1385 ))
1386 }
1387
1388 fn parse_labeled_do_body(&mut self, label: u64) -> Result<Vec<SpannedStmt>, ParseError> {
1389 let mut body = Vec::new();
1390 loop {
1391 self.skip_newlines();
1392 if self.peek() == &TokenKind::Eof {
1393 return Err(self.error(format!(
1394 "expected statement with terminating label {}",
1395 label
1396 )));
1397 }
1398
1399 let is_terminator = self.peek() == &TokenKind::IntegerLiteral
1400 && self.tokens[self.pos]
1401 .text
1402 .parse::<u64>()
1403 .ok()
1404 .map(|current| current == label)
1405 .unwrap_or(false);
1406
1407 let stmt = self.parse_stmt()?;
1408 body.push(stmt);
1409 if is_terminator {
1410 break;
1411 }
1412 }
1413 Ok(body)
1414 }
1415
1416 // ---- I/O statements ----
1417
1418 fn parse_write(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
1419 self.expect(&TokenKind::LParen)?;
1420 let controls = self.parse_io_control_list()?;
1421 self.expect(&TokenKind::RParen)?;
1422 let items = self.parse_io_item_list()?;
1423 let span = span_from_to(start, self.prev_span());
1424 Ok(Spanned::new(Stmt::Write { controls, items }, span))
1425 }
1426
1427 fn parse_read(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
1428 // Read has two forms:
1429 // 1. read(unit, fmt, ...) items — parenthesized control list
1430 // 2. read *, items — simple form (format + items)
1431 // 3. read fmt, items — simple form with format label
1432 if self.peek() == &TokenKind::LParen {
1433 self.advance();
1434 let controls = self.parse_io_control_list()?;
1435 self.expect(&TokenKind::RParen)?;
1436 let items = self.parse_io_item_list()?;
1437 let span = span_from_to(start, self.prev_span());
1438 Ok(Spanned::new(Stmt::Read { controls, items }, span))
1439 } else {
1440 // Simple form: read *, x, y or read fmt, x, y
1441 let format = if self.peek() == &TokenKind::Star {
1442 let tok = self.advance().clone();
1443 Spanned::new(Expr::Name { name: "*".into() }, tok.span)
1444 } else {
1445 self.parse_expr()?
1446 };
1447 let controls = vec![IoControl {
1448 keyword: None,
1449 value: format,
1450 }];
1451 let items = if self.eat(&TokenKind::Comma) {
1452 self.parse_io_expr_list()?
1453 } else {
1454 Vec::new()
1455 };
1456 let span = span_from_to(start, self.prev_span());
1457 Ok(Spanned::new(Stmt::Read { controls, items }, span))
1458 }
1459 }
1460
1461 fn parse_inquire(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
1462 self.expect(&TokenKind::LParen)?;
1463 // Check for IOLENGTH form: inquire(iolength=var) items
1464 let specs = self.parse_io_control_list()?;
1465 self.expect(&TokenKind::RParen)?;
1466 let items = self.parse_io_item_list()?;
1467 let span = span_from_to(start, self.prev_span());
1468 Ok(Spanned::new(Stmt::Inquire { specs, items }, span))
1469 }
1470
1471 /// Parse a generic I/O statement with parenthesized specifiers:
1472 /// OPEN/CLOSE/REWIND/BACKSPACE/ENDFILE/FLUSH
1473 fn parse_io_paren_stmt(
1474 &mut self,
1475 start: crate::lexer::Span,
1476 kind: &str,
1477 ) -> Result<SpannedStmt, ParseError> {
1478 self.expect(&TokenKind::LParen)?;
1479 let specs = self.parse_io_control_list()?;
1480 self.expect(&TokenKind::RParen)?;
1481 let span = span_from_to(start, self.prev_span());
1482 Ok(Spanned::new(
1483 match kind {
1484 "open" => Stmt::Open { specs },
1485 "close" => Stmt::Close { specs },
1486 "rewind" => Stmt::Rewind { specs },
1487 "backspace" => Stmt::Backspace { specs },
1488 "endfile" => Stmt::Endfile { specs },
1489 "flush" => Stmt::Flush { specs },
1490 "wait" => Stmt::Wait { specs },
1491 _ => unreachable!(),
1492 },
1493 span,
1494 ))
1495 }
1496
1497 /// Parse a comma-separated list of keyword=value or positional I/O control specifiers.
1498 fn parse_io_control_list(&mut self) -> Result<Vec<IoControl>, ParseError> {
1499 let mut controls = Vec::new();
1500 if self.peek() == &TokenKind::RParen {
1501 return Ok(controls);
1502 }
1503 loop {
1504 // Check for keyword=value.
1505 if self.peek() == &TokenKind::Identifier {
1506 let next_pos = self.pos + 1;
1507 if next_pos < self.tokens.len() && self.tokens[next_pos].kind == TokenKind::Assign {
1508 let kw = self.advance().clone().text;
1509 self.advance(); // =
1510 let val = if self.peek() == &TokenKind::Star {
1511 let tok = self.advance().clone();
1512 Spanned::new(Expr::Name { name: "*".into() }, tok.span)
1513 } else {
1514 self.parse_expr()?
1515 };
1516 controls.push(IoControl {
1517 keyword: Some(kw),
1518 value: val,
1519 });
1520 if !self.eat(&TokenKind::Comma) {
1521 break;
1522 }
1523 continue;
1524 }
1525 }
1526 // Positional: could be * (format), integer (unit/label), or expression.
1527 let val = if self.peek() == &TokenKind::Star {
1528 let tok = self.advance().clone();
1529 Spanned::new(Expr::Name { name: "*".into() }, tok.span)
1530 } else {
1531 self.parse_expr()?
1532 };
1533 controls.push(IoControl {
1534 keyword: None,
1535 value: val,
1536 });
1537 if !self.eat(&TokenKind::Comma) {
1538 break;
1539 }
1540 }
1541 Ok(controls)
1542 }
1543
1544 /// Parse I/O item list after the control list (for WRITE/READ/INQUIRE).
1545 fn parse_io_item_list(&mut self) -> Result<Vec<SpannedExpr>, ParseError> {
1546 if self.at_stmt_end() {
1547 return Ok(Vec::new());
1548 }
1549 self.parse_io_expr_list()
1550 }
1551
1552 fn parse_io_expr_list(&mut self) -> Result<Vec<SpannedExpr>, ParseError> {
1553 let mut items = Vec::new();
1554 loop {
1555 // Check for implied-DO: (expr-list, var=start,end[,step])
1556 if self.peek() == &TokenKind::LParen {
1557 let save = self.pos;
1558 if let Ok(implied) = self.try_parse_io_implied_do() {
1559 items.push(implied);
1560 if !self.eat(&TokenKind::Comma) {
1561 break;
1562 }
1563 continue;
1564 }
1565 self.pos = save;
1566 }
1567 items.push(self.parse_expr()?);
1568 if !self.eat(&TokenKind::Comma) {
1569 break;
1570 }
1571 }
1572 Ok(items)
1573 }
1574
1575 fn try_parse_io_implied_do(&mut self) -> Result<SpannedExpr, ParseError> {
1576 let start = self.current_span();
1577 self.expect(&TokenKind::LParen)?;
1578
1579 // Parse items until we find var=start,end pattern.
1580 let mut inner_items = vec![self.parse_expr()?];
1581 while self.eat(&TokenKind::Comma) {
1582 // Check for var=start pattern (identifier followed by =).
1583 if self.peek() == &TokenKind::Identifier {
1584 let next_pos = self.pos + 1;
1585 if next_pos < self.tokens.len() && self.tokens[next_pos].kind == TokenKind::Assign {
1586 let var = self.advance().clone().text;
1587 self.advance(); // =
1588 let loop_start = self.parse_expr()?;
1589 self.expect(&TokenKind::Comma)?;
1590 let end = self.parse_expr()?;
1591 let step = if self.eat(&TokenKind::Comma) {
1592 Some(Box::new(self.parse_expr()?))
1593 } else {
1594 None
1595 };
1596 self.expect(&TokenKind::RParen)?;
1597
1598 // Build as a synthetic expression — a FunctionCall-like node
1599 // that sema can recognize as an I/O implied-do.
1600 let span = span_from_to(start, self.prev_span());
1601 use crate::ast::expr::{AcValue, Expr, ImpliedDoLoop};
1602 let values: Vec<AcValue> = inner_items.into_iter().map(AcValue::Expr).collect();
1603 return Ok(Spanned::new(
1604 Expr::ArrayConstructor {
1605 type_spec: None,
1606 values: vec![AcValue::ImpliedDo(Box::new(ImpliedDoLoop {
1607 values,
1608 var,
1609 start: loop_start,
1610 end,
1611 step: step.map(|s| *s),
1612 }))],
1613 },
1614 span,
1615 ));
1616 }
1617 }
1618 inner_items.push(self.parse_expr()?);
1619 }
1620 // If we got here without finding var=, it's not an implied-do.
1621 Err(self.error("expected implied-do variable assignment".into()))
1622 }
1623
1624 // ---- ALLOCATE / DEALLOCATE ----
1625
1626 fn parse_allocate(
1627 &mut self,
1628 start: crate::lexer::Span,
1629 is_dealloc: bool,
1630 ) -> Result<SpannedStmt, ParseError> {
1631 self.expect(&TokenKind::LParen)?;
1632 let mut items = Vec::new();
1633 let mut opts = Vec::new();
1634 let mut type_spec = None;
1635
1636 // Check for typed allocation: allocate(type-spec :: items)
1637 // E.g., allocate(integer :: x), allocate(base_type :: poly_var)
1638 if !is_dealloc {
1639 let save = self.pos;
1640 if let Some(ts_result) = self.try_parse_type_spec() {
1641 if ts_result.is_ok() && self.peek() == &TokenKind::ColonColon {
1642 type_spec = ts_result.ok();
1643 self.advance(); // consume ::
1644 // Continue to parse items normally below.
1645 } else {
1646 // Not a typed allocate — restore.
1647 self.pos = save;
1648 }
1649 } else if self.peek() == &TokenKind::Identifier {
1650 let type_name = self.advance().clone().text;
1651 if self.peek() == &TokenKind::ColonColon {
1652 type_spec = Some(crate::ast::decl::TypeSpec::Type(type_name));
1653 self.advance(); // consume ::
1654 } else {
1655 self.pos = save;
1656 }
1657 }
1658 }
1659
1660 loop {
1661 // Check for keyword=value (stat=, errmsg=, source=, mold=).
1662 if self.peek() == &TokenKind::Identifier {
1663 let text = self.peek_text().to_lowercase();
1664 if matches!(text.as_str(), "stat" | "errmsg" | "source" | "mold") {
1665 let next_pos = self.pos + 1;
1666 if next_pos < self.tokens.len()
1667 && self.tokens[next_pos].kind == TokenKind::Assign
1668 {
1669 let kw = self.advance().clone().text;
1670 self.advance(); // =
1671 let val = self.parse_expr()?;
1672 opts.push(IoControl {
1673 keyword: Some(kw),
1674 value: val,
1675 });
1676 if !self.eat(&TokenKind::Comma) {
1677 break;
1678 }
1679 continue;
1680 }
1681 }
1682 }
1683 items.push(self.parse_expr()?);
1684 if !self.eat(&TokenKind::Comma) {
1685 break;
1686 }
1687 }
1688
1689 self.expect(&TokenKind::RParen)?;
1690 let span = span_from_to(start, self.prev_span());
1691 if is_dealloc {
1692 Ok(Spanned::new(Stmt::Deallocate { items, opts }, span))
1693 } else {
1694 Ok(Spanned::new(
1695 Stmt::Allocate {
1696 type_spec,
1697 items,
1698 opts,
1699 },
1700 span,
1701 ))
1702 }
1703 }
1704
1705 // ---- NULLIFY ----
1706
1707 fn parse_nullify(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
1708 self.expect(&TokenKind::LParen)?;
1709 let mut items = Vec::new();
1710 loop {
1711 items.push(self.parse_expr()?);
1712 if !self.eat(&TokenKind::Comma) {
1713 break;
1714 }
1715 }
1716 self.expect(&TokenKind::RParen)?;
1717 let span = span_from_to(start, self.prev_span());
1718 Ok(Spanned::new(Stmt::Nullify { items }, span))
1719 }
1720
1721 // ---- NAMELIST ----
1722
1723 fn parse_namelist(&mut self, start: crate::lexer::Span) -> Result<SpannedStmt, ParseError> {
1724 let mut groups = Vec::new();
1725 loop {
1726 self.expect(&TokenKind::Slash)?;
1727 let name = self.advance().clone().text;
1728 self.expect(&TokenKind::Slash)?;
1729 let mut vars = Vec::new();
1730 loop {
1731 vars.push(self.advance().clone().text);
1732 if !self.eat(&TokenKind::Comma) {
1733 break;
1734 }
1735 // Check for next group starting with /
1736 if self.peek() == &TokenKind::Slash {
1737 break;
1738 }
1739 }
1740 groups.push((name, vars));
1741 if self.at_stmt_end() || self.peek() != &TokenKind::Slash {
1742 break;
1743 }
1744 }
1745 let span = span_from_to(start, self.prev_span());
1746 Ok(Spanned::new(Stmt::Namelist { groups }, span))
1747 }
1748
1749 // ---- Helpers ----
1750
1751 pub(crate) fn consume_end(&mut self, keyword: &str) -> Result<(), ParseError> {
1752 self.skip_newlines();
1753 let text = self.peek_text().to_lowercase();
1754 let combined = format!("end{}", keyword);
1755 if text == combined {
1756 self.advance();
1757 } else if text == "end" {
1758 self.advance();
1759 self.eat_ident(keyword);
1760 } else {
1761 return Err(self.error(format!(
1762 "expected 'end {}' or 'end{}', got '{}'",
1763 keyword, keyword, text
1764 )));
1765 }
1766 // Skip optional construct name after end, but only on the same line.
1767 // For END INTERFACE, Fortran also permits a trailing generic-spec such
1768 // as `operator(+)` or `assignment(=)`.
1769 if self.peek() == &TokenKind::Identifier && !self.at_stmt_end() {
1770 let trailing = self.advance().clone().text;
1771 if matches!(keyword, "interface")
1772 && (trailing.eq_ignore_ascii_case("operator")
1773 || trailing.eq_ignore_ascii_case("assignment"))
1774 && self.eat(&TokenKind::LParen)
1775 {
1776 while self.peek() != &TokenKind::RParen && !self.at_stmt_end() {
1777 self.advance();
1778 }
1779 self.expect(&TokenKind::RParen)?;
1780 }
1781 }
1782 Ok(())
1783 }
1784 }
1785
1786 #[cfg(test)]
1787 mod tests {
1788 use super::*;
1789 use crate::lexer::Lexer;
1790
1791 fn parse_one(src: &str) -> SpannedStmt {
1792 let tokens = Lexer::tokenize(src, 0).unwrap();
1793 let mut parser = Parser::new(&tokens);
1794 parser.parse_stmt().unwrap()
1795 }
1796
1797 // ---- Assignment ----
1798
1799 #[test]
1800 fn assignment() {
1801 let s = parse_one("x = 42\n");
1802 assert!(matches!(s.node, Stmt::Assignment { .. }));
1803 }
1804
1805 #[test]
1806 fn pointer_assignment() {
1807 let s = parse_one("ptr => target\n");
1808 assert!(matches!(s.node, Stmt::PointerAssignment { .. }));
1809 }
1810
1811 // ---- IF ----
1812
1813 #[test]
1814 fn if_single_line() {
1815 let s = parse_one("if (x > 0) y = 1\n");
1816 assert!(matches!(s.node, Stmt::IfStmt { .. }));
1817 }
1818
1819 #[test]
1820 fn if_construct() {
1821 let s = parse_one("if (x > 0) then\n y = 1\nend if\n");
1822 if let Stmt::IfConstruct {
1823 then_body,
1824 else_ifs,
1825 else_body,
1826 ..
1827 } = &s.node
1828 {
1829 assert_eq!(then_body.len(), 1);
1830 assert!(else_ifs.is_empty());
1831 assert!(else_body.is_none());
1832 } else {
1833 panic!("not IfConstruct");
1834 }
1835 }
1836
1837 #[test]
1838 fn if_else() {
1839 let s = parse_one("if (x > 0) then\n y = 1\nelse\n y = 2\nend if\n");
1840 if let Stmt::IfConstruct { else_body, .. } = &s.node {
1841 assert!(else_body.is_some());
1842 } else {
1843 panic!("not IfConstruct");
1844 }
1845 }
1846
1847 #[test]
1848 fn if_elseif() {
1849 let s = parse_one(
1850 "if (x > 0) then\n y = 1\nelse if (x < 0) then\n y = 2\nelse\n y = 0\nend if\n",
1851 );
1852 if let Stmt::IfConstruct {
1853 else_ifs,
1854 else_body,
1855 ..
1856 } = &s.node
1857 {
1858 assert_eq!(else_ifs.len(), 1);
1859 assert!(else_body.is_some());
1860 } else {
1861 panic!("not IfConstruct");
1862 }
1863 }
1864
1865 // ---- DO ----
1866
1867 #[test]
1868 fn do_counted() {
1869 let s = parse_one("do i = 1, 10\n x = i\nend do\n");
1870 if let Stmt::DoLoop {
1871 var,
1872 start,
1873 end,
1874 step,
1875 body,
1876 ..
1877 } = &s.node
1878 {
1879 assert_eq!(var.as_deref(), Some("i"));
1880 assert!(start.is_some());
1881 assert!(end.is_some());
1882 assert!(step.is_none());
1883 assert_eq!(body.len(), 1);
1884 } else {
1885 panic!("not DoLoop");
1886 }
1887 }
1888
1889 #[test]
1890 fn do_with_step() {
1891 let s = parse_one("do i = 10, 1, -1\n x = i\nend do\n");
1892 if let Stmt::DoLoop { step, .. } = &s.node {
1893 assert!(step.is_some());
1894 } else {
1895 panic!("not DoLoop");
1896 }
1897 }
1898
1899 #[test]
1900 fn do_with_terminating_label() {
1901 let s = parse_one("do 10 i = 1, 3\n x = i\n10 continue\n");
1902 if let Stmt::DoLoop { var, body, .. } = &s.node {
1903 assert_eq!(var.as_deref(), Some("i"));
1904 assert_eq!(body.len(), 2);
1905 assert!(matches!(body[1].node, Stmt::Labeled { label: 10, .. }));
1906 } else {
1907 panic!("not DoLoop");
1908 }
1909 }
1910
1911 #[test]
1912 fn do_while() {
1913 let s = parse_one("do while (x > 0)\n x = x - 1\nend do\n");
1914 assert!(matches!(s.node, Stmt::DoWhile { .. }));
1915 }
1916
1917 #[test]
1918 fn do_infinite() {
1919 let s = parse_one("do\n if (done) exit\nend do\n");
1920 if let Stmt::DoLoop { var, .. } = &s.node {
1921 assert!(var.is_none());
1922 } else {
1923 panic!("not DoLoop");
1924 }
1925 }
1926
1927 // ---- SELECT CASE ----
1928
1929 #[test]
1930 fn select_case() {
1931 let s = parse_one("select case (x)\ncase (1)\n y = 1\ncase (2)\n y = 2\ncase default\n y = 0\nend select\n");
1932 if let Stmt::SelectCase { cases, .. } = &s.node {
1933 assert_eq!(cases.len(), 3);
1934 assert!(matches!(cases[2].selectors[0], CaseSelector::Default));
1935 } else {
1936 panic!("not SelectCase");
1937 }
1938 }
1939
1940 #[test]
1941 fn select_case_range() {
1942 let s = parse_one("select case (x)\ncase (1:10)\n y = 1\nend select\n");
1943 if let Stmt::SelectCase { cases, .. } = &s.node {
1944 assert!(matches!(cases[0].selectors[0], CaseSelector::Range { .. }));
1945 } else {
1946 panic!("not SelectCase");
1947 }
1948 }
1949
1950 // ---- Simple statements ----
1951
1952 #[test]
1953 fn exit_stmt() {
1954 let s = parse_one("exit\n");
1955 assert!(matches!(s.node, Stmt::Exit { name: None }));
1956 }
1957
1958 #[test]
1959 fn exit_named() {
1960 let s = parse_one("exit outer\n");
1961 if let Stmt::Exit { name } = &s.node {
1962 assert_eq!(name.as_deref(), Some("outer"));
1963 } else {
1964 panic!("not Exit");
1965 }
1966 }
1967
1968 #[test]
1969 fn cycle_stmt() {
1970 let s = parse_one("cycle\n");
1971 assert!(matches!(s.node, Stmt::Cycle { name: None }));
1972 }
1973
1974 #[test]
1975 fn stop_stmt() {
1976 let s = parse_one("stop\n");
1977 assert!(matches!(s.node, Stmt::Stop { code: None, .. }));
1978 }
1979
1980 #[test]
1981 fn stop_with_code() {
1982 let s = parse_one("stop 1\n");
1983 assert!(matches!(s.node, Stmt::Stop { code: Some(_), .. }));
1984 }
1985
1986 #[test]
1987 fn error_stop() {
1988 let s = parse_one("error stop\n");
1989 assert!(matches!(s.node, Stmt::ErrorStop { .. }));
1990 }
1991
1992 #[test]
1993 fn error_name_can_start_assignment() {
1994 let s = parse_one("error = 1\n");
1995 assert!(matches!(s.node, Stmt::Assignment { .. }));
1996 }
1997
1998 #[test]
1999 fn error_name_can_start_component_assignment() {
2000 let s = parse_one("error%has_error = parser%has_error\n");
2001 assert!(matches!(s.node, Stmt::Assignment { .. }));
2002 }
2003
2004 #[test]
2005 fn return_stmt() {
2006 let s = parse_one("return\n");
2007 assert!(matches!(s.node, Stmt::Return { value: None }));
2008 }
2009
2010 #[test]
2011 fn goto_stmt() {
2012 let s = parse_one("goto 100\n");
2013 if let Stmt::Goto { label } = &s.node {
2014 assert_eq!(*label, 100);
2015 } else {
2016 panic!("not Goto");
2017 }
2018 }
2019
2020 #[test]
2021 fn call_stmt() {
2022 let s = parse_one("call sub(a, b)\n");
2023 assert!(matches!(s.node, Stmt::Call { .. }));
2024 }
2025
2026 #[test]
2027 fn print_stmt() {
2028 let s = parse_one("print *, x, y\n");
2029 if let Stmt::Print { items, .. } = &s.node {
2030 assert_eq!(items.len(), 2);
2031 } else {
2032 panic!("not Print");
2033 }
2034 }
2035
2036 #[test]
2037 fn continue_stmt() {
2038 let s = parse_one("continue\n");
2039 assert!(matches!(s.node, Stmt::Continue { .. }));
2040 }
2041
2042 // ---- Arithmetic IF ----
2043
2044 #[test]
2045 fn arithmetic_if() {
2046 let s = parse_one("if (x) 10, 20, 30\n");
2047 if let Stmt::ArithmeticIf { neg, zero, pos, .. } = &s.node {
2048 assert_eq!(*neg, 10);
2049 assert_eq!(*zero, 20);
2050 assert_eq!(*pos, 30);
2051 } else {
2052 panic!("not ArithmeticIf, got {:?}", s.node);
2053 }
2054 }
2055
2056 // ---- Named constructs ----
2057
2058 #[test]
2059 fn named_do() {
2060 let s = parse_one("outer: do i = 1, 10\n x = i\nend do outer\n");
2061 if let Stmt::DoLoop { name, var, .. } = &s.node {
2062 assert_eq!(name.as_deref(), Some("outer"));
2063 assert_eq!(var.as_deref(), Some("i"));
2064 } else {
2065 panic!("not DoLoop, got {:?}", s.node);
2066 }
2067 }
2068
2069 #[test]
2070 fn named_if() {
2071 let s = parse_one("check: if (x > 0) then\n y = 1\nend if check\n");
2072 if let Stmt::IfConstruct { name, .. } = &s.node {
2073 assert_eq!(name.as_deref(), Some("check"));
2074 } else {
2075 panic!("not IfConstruct, got {:?}", s.node);
2076 }
2077 }
2078
2079 // ---- Nesting ----
2080
2081 #[test]
2082 fn deeply_nested() {
2083 let src = "\
2084 if (a > 0) then
2085 do i = 1, n
2086 select case (x)
2087 case (1)
2088 do while (cond)
2089 if (done) exit
2090 end do
2091 end select
2092 end do
2093 end if
2094 ";
2095 let s = parse_one(src);
2096 if let Stmt::IfConstruct { then_body, .. } = &s.node {
2097 assert!(!then_body.is_empty());
2098 // DO inside IF.
2099 assert!(matches!(then_body[0].node, Stmt::DoLoop { .. }));
2100 } else {
2101 panic!("not IfConstruct");
2102 }
2103 }
2104
2105 // ---- Additional construct tests ----
2106
2107 #[test]
2108 fn where_construct() {
2109 let s = parse_one("where (a > 0)\n b = 1\nelsewhere\n b = 0\nend where\n");
2110 if let Stmt::WhereConstruct { elsewhere, .. } = &s.node {
2111 assert_eq!(elsewhere.len(), 1);
2112 } else {
2113 panic!("not WhereConstruct");
2114 }
2115 }
2116
2117 #[test]
2118 fn computed_goto() {
2119 let s = parse_one("go to (10, 20, 30), i\n");
2120 if let Stmt::ComputedGoto { labels, .. } = &s.node {
2121 assert_eq!(labels, &[10, 20, 30]);
2122 } else {
2123 panic!("not ComputedGoto");
2124 }
2125 }
2126
2127 #[test]
2128 fn block_construct() {
2129 let s = parse_one("block\n x = 1\nend block\n");
2130 assert!(matches!(s.node, Stmt::Block { .. }));
2131 }
2132
2133 #[test]
2134 fn associate_construct() {
2135 let s = parse_one("associate (n => size(a))\n x = n\nend associate\n");
2136 if let Stmt::Associate { assocs, .. } = &s.node {
2137 assert_eq!(assocs.len(), 1);
2138 assert_eq!(assocs[0].0, "n");
2139 } else {
2140 panic!("not Associate");
2141 }
2142 }
2143
2144 // ---- Missing test coverage from audit ----
2145
2146 #[test]
2147 fn do_concurrent() {
2148 let s = parse_one("do concurrent (i = 1:n)\n a(i) = 0\nend do\n");
2149 assert!(matches!(s.node, Stmt::DoConcurrent { .. }));
2150 }
2151
2152 #[test]
2153 fn forall_construct() {
2154 let s = parse_one("forall (i = 1:n)\n a(i) = i\nend forall\n");
2155 assert!(matches!(s.node, Stmt::ForallConstruct { .. }));
2156 }
2157
2158 #[test]
2159 fn forall_single_line() {
2160 let s = parse_one("forall (i = 1:n) a(i) = i\n");
2161 assert!(matches!(s.node, Stmt::ForallStmt { .. }));
2162 }
2163
2164 #[test]
2165 fn where_single_line() {
2166 let s = parse_one("where (a > 0) b = 1\n");
2167 assert!(matches!(s.node, Stmt::WhereStmt { .. }));
2168 }
2169
2170 #[test]
2171 fn goto_two_words() {
2172 let s = parse_one("go to 100\n");
2173 if let Stmt::Goto { label } = &s.node {
2174 assert_eq!(*label, 100);
2175 } else {
2176 panic!("not Goto");
2177 }
2178 }
2179
2180 #[test]
2181 fn case_multiple_selectors() {
2182 let s = parse_one("select case (x)\ncase (1, 2, 3)\n y = 1\nend select\n");
2183 if let Stmt::SelectCase { cases, .. } = &s.node {
2184 assert_eq!(cases[0].selectors.len(), 3);
2185 } else {
2186 panic!("not SelectCase");
2187 }
2188 }
2189
2190 #[test]
2191 fn case_open_range_low() {
2192 let s = parse_one("select case (x)\ncase (:10)\n y = 1\nend select\n");
2193 if let Stmt::SelectCase { cases, .. } = &s.node {
2194 assert!(matches!(
2195 cases[0].selectors[0],
2196 CaseSelector::Range { low: None, .. }
2197 ));
2198 } else {
2199 panic!("not SelectCase");
2200 }
2201 }
2202
2203 #[test]
2204 fn case_open_range_high() {
2205 let s = parse_one("select case (x)\ncase (10:)\n y = 1\nend select\n");
2206 if let Stmt::SelectCase { cases, .. } = &s.node {
2207 assert!(matches!(
2208 cases[0].selectors[0],
2209 CaseSelector::Range { high: None, .. }
2210 ));
2211 } else {
2212 panic!("not SelectCase");
2213 }
2214 }
2215
2216 #[test]
2217 fn stop_with_string() {
2218 let s = parse_one("stop 'error message'\n");
2219 assert!(matches!(s.node, Stmt::Stop { code: Some(_), .. }));
2220 }
2221
2222 #[test]
2223 fn error_stop_with_code() {
2224 let s = parse_one("error stop 1\n");
2225 assert!(matches!(s.node, Stmt::ErrorStop { code: Some(_), .. }));
2226 }
2227
2228 #[test]
2229 fn return_with_value() {
2230 let s = parse_one("return 1\n");
2231 assert!(matches!(s.node, Stmt::Return { value: Some(_) }));
2232 }
2233
2234 #[test]
2235 fn error_missing_end_do() {
2236 let tokens = Lexer::tokenize("do i = 1, 10\n x = i\n", 0).unwrap();
2237 let mut parser = Parser::new(&tokens);
2238 let result = parser.parse_stmt();
2239 assert!(result.is_err(), "missing end do should error");
2240 }
2241
2242 // ---- Same-type nesting (critical regression tests) ----
2243
2244 #[test]
2245 fn if_inside_if() {
2246 let s = parse_one("if (a > 0) then\n if (b > 0) then\n x = 1\n end if\nend if\n");
2247 if let Stmt::IfConstruct { then_body, .. } = &s.node {
2248 assert_eq!(then_body.len(), 1, "outer IF should have 1 stmt in body");
2249 assert!(
2250 matches!(then_body[0].node, Stmt::IfConstruct { .. }),
2251 "inner should be IfConstruct"
2252 );
2253 } else {
2254 panic!("not IfConstruct");
2255 }
2256 }
2257
2258 #[test]
2259 fn do_inside_do() {
2260 let s = parse_one("do i = 1, 10\n do j = 1, 10\n x = i + j\n end do\nend do\n");
2261 if let Stmt::DoLoop { body, .. } = &s.node {
2262 assert_eq!(body.len(), 1, "outer DO should have 1 stmt in body");
2263 assert!(
2264 matches!(body[0].node, Stmt::DoLoop { .. }),
2265 "inner should be DoLoop"
2266 );
2267 } else {
2268 panic!("not DoLoop");
2269 }
2270 }
2271
2272 #[test]
2273 fn select_inside_select() {
2274 let s = parse_one("select case (x)\ncase (1)\n select case (y)\n case (2)\n z = 1\n end select\nend select\n");
2275 if let Stmt::SelectCase { cases, .. } = &s.node {
2276 assert!(!cases.is_empty());
2277 assert!(
2278 matches!(cases[0].body[0].node, Stmt::SelectCase { .. }),
2279 "inner should be SelectCase"
2280 );
2281 } else {
2282 panic!("not SelectCase");
2283 }
2284 }
2285
2286 // ======================================================================
2287 // Sprint 11: I/O, ALLOCATE, NAMELIST, NULLIFY
2288 // ======================================================================
2289
2290 // ---- WRITE ----
2291
2292 #[test]
2293 fn write_simple() {
2294 let s = parse_one("write(*, *) x, y, z\n");
2295 if let Stmt::Write { controls, items } = &s.node {
2296 assert_eq!(controls.len(), 2); // unit=*, fmt=*
2297 assert_eq!(items.len(), 3);
2298 } else {
2299 panic!("not Write, got {:?}", s.node);
2300 }
2301 }
2302
2303 #[test]
2304 fn write_with_keywords() {
2305 let s = parse_one("write(unit=10, fmt='(A)', iostat=ios) msg\n");
2306 if let Stmt::Write { controls, items } = &s.node {
2307 assert!(controls
2308 .iter()
2309 .any(|c| c.keyword.as_deref() == Some("unit")));
2310 assert!(controls.iter().any(|c| c.keyword.as_deref() == Some("fmt")));
2311 assert!(controls
2312 .iter()
2313 .any(|c| c.keyword.as_deref() == Some("iostat")));
2314 assert_eq!(items.len(), 1);
2315 } else {
2316 panic!("not Write");
2317 }
2318 }
2319
2320 // ---- READ ----
2321
2322 #[test]
2323 fn read_paren_form() {
2324 let s = parse_one("read(10, '(A)') line\n");
2325 if let Stmt::Read { controls, items } = &s.node {
2326 assert_eq!(controls.len(), 2);
2327 assert_eq!(items.len(), 1);
2328 } else {
2329 panic!("not Read");
2330 }
2331 }
2332
2333 #[test]
2334 fn read_simple_form() {
2335 let s = parse_one("read *, x, y\n");
2336 if let Stmt::Read { controls, items } = &s.node {
2337 assert_eq!(controls.len(), 1); // format=*
2338 assert_eq!(items.len(), 2);
2339 } else {
2340 panic!("not Read");
2341 }
2342 }
2343
2344 // ---- OPEN ----
2345
2346 #[test]
2347 fn open_stmt() {
2348 let s =
2349 parse_one("open(unit=10, file='data.txt', status='old', action='read', iostat=ios)\n");
2350 if let Stmt::Open { specs } = &s.node {
2351 assert!(specs.len() >= 4);
2352 assert!(specs.iter().any(|s| s.keyword.as_deref() == Some("file")));
2353 } else {
2354 panic!("not Open");
2355 }
2356 }
2357
2358 #[test]
2359 fn open_simple() {
2360 let s = parse_one("open(10, file='data.txt')\n");
2361 if let Stmt::Open { specs } = &s.node {
2362 assert!(specs.len() >= 2);
2363 } else {
2364 panic!("not Open");
2365 }
2366 }
2367
2368 // ---- CLOSE ----
2369
2370 #[test]
2371 fn close_stmt() {
2372 let s = parse_one("close(10)\n");
2373 if let Stmt::Close { specs } = &s.node {
2374 assert_eq!(specs.len(), 1);
2375 } else {
2376 panic!("not Close");
2377 }
2378 }
2379
2380 #[test]
2381 fn close_with_keywords() {
2382 let s = parse_one("close(unit=10, status='delete', iostat=ios)\n");
2383 if let Stmt::Close { specs } = &s.node {
2384 assert!(specs.iter().any(|s| s.keyword.as_deref() == Some("status")));
2385 } else {
2386 panic!("not Close");
2387 }
2388 }
2389
2390 // ---- INQUIRE ----
2391
2392 #[test]
2393 fn inquire_by_file() {
2394 let s = parse_one("inquire(file='test.dat', exist=ex)\n");
2395 assert!(matches!(s.node, Stmt::Inquire { .. }));
2396 }
2397
2398 #[test]
2399 fn inquire_by_unit() {
2400 let s = parse_one("inquire(unit=10, opened=op)\n");
2401 assert!(matches!(s.node, Stmt::Inquire { .. }));
2402 }
2403
2404 // ---- File positioning ----
2405
2406 #[test]
2407 fn rewind_stmt() {
2408 let s = parse_one("rewind(10)\n");
2409 assert!(matches!(s.node, Stmt::Rewind { .. }));
2410 }
2411
2412 #[test]
2413 fn backspace_stmt() {
2414 let s = parse_one("backspace(unit=10, iostat=ios)\n");
2415 assert!(matches!(s.node, Stmt::Backspace { .. }));
2416 }
2417
2418 #[test]
2419 fn flush_stmt() {
2420 let s = parse_one("flush(10)\n");
2421 assert!(matches!(s.node, Stmt::Flush { .. }));
2422 }
2423
2424 // ---- ALLOCATE / DEALLOCATE ----
2425
2426 #[test]
2427 fn allocate_simple() {
2428 let s = parse_one("allocate(a(n), b(m,k))\n");
2429 if let Stmt::Allocate {
2430 type_spec,
2431 items,
2432 opts,
2433 } = &s.node
2434 {
2435 assert!(type_spec.is_none());
2436 assert_eq!(items.len(), 2);
2437 assert!(opts.is_empty());
2438 } else {
2439 panic!("not Allocate");
2440 }
2441 }
2442
2443 #[test]
2444 fn allocate_with_stat() {
2445 let s = parse_one("allocate(x(100), stat=ios, errmsg=msg)\n");
2446 if let Stmt::Allocate { items, opts, .. } = &s.node {
2447 assert_eq!(items.len(), 1);
2448 assert!(opts.iter().any(|o| o.keyword.as_deref() == Some("stat")));
2449 assert!(opts.iter().any(|o| o.keyword.as_deref() == Some("errmsg")));
2450 } else {
2451 panic!("not Allocate");
2452 }
2453 }
2454
2455 #[test]
2456 fn allocate_with_source() {
2457 let s = parse_one("allocate(x, source=template)\n");
2458 if let Stmt::Allocate { opts, .. } = &s.node {
2459 assert!(opts.iter().any(|o| o.keyword.as_deref() == Some("source")));
2460 } else {
2461 panic!("not Allocate");
2462 }
2463 }
2464
2465 #[test]
2466 fn deallocate_stmt() {
2467 let s = parse_one("deallocate(a, b, stat=ios)\n");
2468 if let Stmt::Deallocate { items, opts } = &s.node {
2469 assert_eq!(items.len(), 2);
2470 assert!(opts.iter().any(|o| o.keyword.as_deref() == Some("stat")));
2471 } else {
2472 panic!("not Deallocate");
2473 }
2474 }
2475
2476 // ---- NULLIFY ----
2477
2478 #[test]
2479 fn nullify_stmt() {
2480 let s = parse_one("nullify(ptr1, ptr2)\n");
2481 if let Stmt::Nullify { items } = &s.node {
2482 assert_eq!(items.len(), 2);
2483 } else {
2484 panic!("not Nullify");
2485 }
2486 }
2487
2488 // ---- NAMELIST ----
2489
2490 #[test]
2491 fn namelist_stmt() {
2492 let s = parse_one("namelist /input_data/ x, y, z\n");
2493 if let Stmt::Namelist { groups } = &s.node {
2494 assert_eq!(groups.len(), 1);
2495 assert_eq!(groups[0].0, "input_data");
2496 assert_eq!(groups[0].1.len(), 3);
2497 } else {
2498 panic!("not Namelist");
2499 }
2500 }
2501
2502 #[test]
2503 fn namelist_multiple_groups() {
2504 let s = parse_one("namelist /in/ x, y /out/ z\n");
2505 if let Stmt::Namelist { groups } = &s.node {
2506 assert_eq!(groups.len(), 2);
2507 assert_eq!(groups[0].0, "in");
2508 assert_eq!(groups[1].0, "out");
2509 } else {
2510 panic!("not Namelist");
2511 }
2512 }
2513
2514 // ---- Audit fixes ----
2515
2516 #[test]
2517 fn wait_stmt() {
2518 let s = parse_one("wait(unit=10, iostat=ios)\n");
2519 assert!(matches!(s.node, Stmt::Wait { .. }));
2520 }
2521
2522 #[test]
2523 fn endfile_stmt() {
2524 let s = parse_one("endfile(10)\n");
2525 assert!(matches!(s.node, Stmt::Endfile { .. }));
2526 }
2527
2528 #[test]
2529 fn write_implied_do() {
2530 let s = parse_one("write(*, *) (a(i), i=1,10)\n");
2531 if let Stmt::Write { items, .. } = &s.node {
2532 assert_eq!(items.len(), 1, "implied-do should produce 1 item");
2533 } else {
2534 panic!("not Write");
2535 }
2536 }
2537
2538 #[test]
2539 fn allocate_typed() {
2540 // allocate(integer :: x) — typed allocation.
2541 let s = parse_one("allocate(integer :: x(100))\n");
2542 if let Stmt::Allocate {
2543 type_spec: Some(crate::ast::decl::TypeSpec::Integer(_)),
2544 ..
2545 } = s.node
2546 {
2547 } else {
2548 panic!("typed allocation should preserve the type-spec");
2549 }
2550 }
2551
2552 #[test]
2553 fn allocate_typed_derived_name() {
2554 let s = parse_one("allocate(toml_array :: val)\n");
2555 if let Stmt::Allocate {
2556 type_spec: Some(crate::ast::decl::TypeSpec::Type(name)),
2557 ..
2558 } = s.node
2559 {
2560 assert_eq!(name, "toml_array");
2561 } else {
2562 panic!("derived typed allocation should preserve the bare type-spec");
2563 }
2564 }
2565
2566 #[test]
2567 fn allocate_mold() {
2568 let s = parse_one("allocate(y, mold=template)\n");
2569 if let Stmt::Allocate { opts, .. } = &s.node {
2570 assert!(opts.iter().any(|o| o.keyword.as_deref() == Some("mold")));
2571 } else {
2572 panic!("not Allocate");
2573 }
2574 }
2575
2576 #[test]
2577 fn call_method_syntax() {
2578 let s = parse_one("call obj%method(a, b)\n");
2579 assert!(matches!(s.node, Stmt::Call { .. }));
2580 }
2581
2582 #[test]
2583 fn print_label_format() {
2584 let s = parse_one("print 100, x\n");
2585 assert!(matches!(s.node, Stmt::Print { .. }));
2586 }
2587
2588 #[test]
2589 fn entry_stmt_reports_not_implemented() {
2590 let tokens = Lexer::tokenize("entry g(y)\n", 0).unwrap();
2591 let mut parser = Parser::new(&tokens);
2592 let err = parser.parse_stmt().expect_err("ENTRY should not parse yet");
2593 assert!(err
2594 .msg
2595 .contains("ENTRY statements are recognized but not yet implemented"));
2596 }
2597
2598 #[test]
2599 fn entry_name_can_still_start_assignment() {
2600 let s = parse_one("entry(i:i) = c_entry(i)\n");
2601 assert!(matches!(s.node, Stmt::Assignment { .. }));
2602 }
2603
2604 #[test]
2605 fn block_name_can_still_start_component_assignment() {
2606 let s = parse_one("block%for_count = count\n");
2607 assert!(matches!(s.node, Stmt::Assignment { .. }));
2608 }
2609
2610 #[test]
2611 fn sync_stmt_reports_not_implemented() {
2612 let tokens = Lexer::tokenize("sync all\n", 0).unwrap();
2613 let mut parser = Parser::new(&tokens);
2614 let err = parser.parse_stmt().expect_err("SYNC should not parse yet");
2615 assert!(err
2616 .msg
2617 .contains("coarray SYNC statements are recognized but not yet implemented"));
2618 }
2619
2620 #[test]
2621 fn sync_name_can_start_component_assignment() {
2622 let s = parse_one("sync%version = 0\n");
2623 assert!(matches!(s.node, Stmt::Assignment { .. }));
2624 }
2625 }
2626