Skip to main content

patch_rexx/
external.rs

1//! External function resolution — search filesystem for `.rexx`/`.rex` files.
2//!
3//! Per ANSI X3.274-1996, when a function or subroutine call cannot be resolved
4//! to an internal label or built-in function, the interpreter searches for an
5//! external program file. This module handles that search.
6
7use std::path::{Path, PathBuf};
8
9use crate::ast::Program;
10use crate::error::{RexxDiagnostic, RexxError, RexxResult};
11use crate::lexer::Lexer;
12use crate::parser::Parser;
13
14/// Search for an external REXX program matching `name`.
15///
16/// Search path (in order):
17/// 1. Directory of the calling script (`source_dir`)
18/// 2. Entries from `REXXPATH` environment variable (platform path separator)
19/// 3. Current working directory
20///
21/// Candidate filenames for name `FOO`: `foo.rexx`, `foo.rex`, `FOO.rexx`, `FOO.rex`
22/// (uppercase candidates only tried if different from lowercase).
23///
24/// Returns `Ok(Some((program, path)))` on first match, `Ok(None)` if not found.
25pub fn resolve_external(
26    name: &str,
27    source_dir: Option<&Path>,
28) -> RexxResult<Option<(Program, PathBuf)>> {
29    let lower = name.to_lowercase();
30    let upper = name.to_uppercase();
31
32    let mut candidates: Vec<String> = vec![format!("{lower}.rexx"), format!("{lower}.rex")];
33    if upper != lower {
34        candidates.push(format!("{upper}.rexx"));
35        candidates.push(format!("{upper}.rex"));
36    }
37
38    // Build search directories
39    let mut search_dirs: Vec<PathBuf> = Vec::new();
40    if let Some(dir) = source_dir {
41        search_dirs.push(dir.to_path_buf());
42    }
43    if let Ok(rexxpath) = std::env::var("REXXPATH") {
44        for entry in std::env::split_paths(&rexxpath) {
45            if entry.is_dir() {
46                search_dirs.push(entry);
47            }
48        }
49    }
50    if let Ok(cwd) = std::env::current_dir() {
51        search_dirs.push(cwd);
52    }
53
54    for dir in &search_dirs {
55        for candidate in &candidates {
56            let full = dir.join(candidate);
57            if full.is_file() {
58                let source = std::fs::read_to_string(&full).map_err(|e| {
59                    RexxDiagnostic::new(RexxError::SystemFailure)
60                        .with_detail(format!("cannot read '{}': {e}", full.display()))
61                })?;
62                let mut lexer = Lexer::new(&source);
63                let tokens = lexer.tokenize()?;
64                let mut parser = Parser::new(tokens);
65                let program = parser.parse()?;
66                let canonical = full.canonicalize().unwrap_or(full);
67                return Ok(Some((program, canonical)));
68            }
69        }
70    }
71
72    Ok(None)
73}