use crate::keywords::{is_function_start, is_procedure_start}; use crate::models::{ParsedCall, SourceRange}; use crate::parser::{extract_name_from_header, routine_ended}; pub fn extract_calls(source: &str) -> Vec { let mut result = Vec::new(); let mut current_routine: Option = None; for (index, line) in source.lines().enumerate() { let line_no = index + 1; let trimmed = strip_inline_comment(line).trim(); if is_procedure_start(trimmed) || is_function_start(trimmed) { current_routine = Some(extract_name_from_header(trimmed)); continue; } if routine_ended(trimmed) { current_routine = None; continue; } let Some(caller) = current_routine.as_ref() else { continue; }; if let Some(callee) = simple_call_name(trimmed).or_else(|| condition_call_name(trimmed)) { result.push(ParsedCall { caller: caller.clone(), callee, source_range: SourceRange { line_start: line_no, line_end: line_no, column_start: 1, column_end: line.len() + 1, }, }); } } result } fn simple_call_name(line: &str) -> Option { if !line.ends_with(';') || !line.contains('(') || !line.contains(')') || line.contains('.') { return None; } let call_expr = line.split('=').next_back().unwrap_or(line).trim(); let name = call_expr.split('(').next()?.trim(); if name.is_empty() { return None; } Some(name.to_string()) } fn condition_call_name(line: &str) -> Option { let lowered = line.to_lowercase(); let is_condition = (lowered.starts_with("если ") && lowered.contains(" тогда")) || (lowered.starts_with("if ") && lowered.contains(" then")); if !is_condition || line.contains('.') { return None; } let before_then = if let Some(index) = lowered.find(" тогда") { &line[..index] } else if let Some(index) = lowered.find(" then") { &line[..index] } else { line }; let name_part = before_then.split('(').next()?.split_whitespace().last()?; if name_part.eq_ignore_ascii_case("Если") || name_part.eq_ignore_ascii_case("If") { return None; } Some(name_part.to_string()) } fn strip_inline_comment(line: &str) -> &str { line.split("//").next().unwrap_or(line) }