Synthesize subroutine Function nodes from REM-labelled sections that end with RETURN. MS BASIC 2.0 has no formal subroutine syntax. We detect patterns like: ```basic 100 REM LOG A MESSAGE 110 REM PARAMS: ... 200 PRINT ... 210 RETURN ``` A REM line (or consecutive REM lines) followed by code ending with RETURN is treated as a subroutine. The REM text becomes the docstring, and a function name is d
(state: &mut ExtractionState, lines: &[BasicLine<'_>])
| 312 | /// is treated as a subroutine. The REM text becomes the docstring, and a |
| 313 | /// function name is derived from the first REM text. |
| 314 | fn extract_subroutines(state: &mut ExtractionState, lines: &[BasicLine<'_>]) { |
| 315 | let mut i = 0; |
| 316 | while i < lines.len() { |
| 317 | // Look for a REM line that starts a potential subroutine. |
| 318 | if lines[i].statement_kind == "comment" { |
| 319 | // Gather consecutive REM lines. |
| 320 | let rem_start = i; |
| 321 | let mut rem_comments: Vec<String> = Vec::new(); |
| 322 | while i < lines.len() && lines[i].statement_kind == "comment" { |
| 323 | if let Some(ref text) = lines[i].comment_text { |
| 324 | rem_comments.push(text.clone()); |
| 325 | } |
| 326 | i += 1; |
| 327 | } |
| 328 | |
| 329 | // Check if the lines following the REM block end with RETURN. |
| 330 | let body_start = i; |
| 331 | let mut body_end = i; |
| 332 | let mut has_return = false; |
| 333 | while body_end < lines.len() { |
| 334 | if lines[body_end].statement_kind == "return_statement" { |
| 335 | has_return = true; |
| 336 | body_end += 1; |
| 337 | break; |
| 338 | } |
| 339 | // Stop at the next REM block (which would be the start of another subroutine). |
| 340 | if lines[body_end].statement_kind == "comment" { |
| 341 | break; |
| 342 | } |
| 343 | body_end += 1; |
| 344 | } |
| 345 | |
| 346 | if has_return && body_start < body_end { |
| 347 | // Derive a function name from the first REM comment. |
| 348 | let fn_name = derive_function_name(&rem_comments); |
| 349 | let docstring = if rem_comments.is_empty() { |
| 350 | None |
| 351 | } else { |
| 352 | Some(rem_comments.join("\n")) |
| 353 | }; |
| 354 | |
| 355 | // The subroutine spans from the first REM line to the RETURN line. |
| 356 | let first_node = lines[rem_start].node; |
| 357 | let last_node = lines[body_end - 1].node; |
| 358 | let start_line = first_node.start_position().row as u32; |
| 359 | let end_line = last_node.end_position().row as u32; |
| 360 | let start_column = first_node.start_position().column as u32; |
| 361 | let end_column = last_node.end_position().column as u32; |
| 362 | let qualified_name = format!("{}::{}", state.qualified_prefix(), fn_name); |
| 363 | let fn_id = generate_node_id( |
| 364 | &state.file_path, |
| 365 | &NodeKind::Function, |
| 366 | &fn_name, |
| 367 | start_line, |
| 368 | ); |
| 369 | |
| 370 | // Count complexity by walking body lines' AST nodes. |
| 371 | let mut branches: u32 = 0; |
nothing calls this directly
no test coverage detected