⏺ Phase 11 is complete. Here's the summary: #14
No reviewers
Labels
No labels
bug
dependencies
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
rust
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
navicore/patch-rexx!14
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "phase11"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Phase 11: External Function Libraries — Done
Files Created
┌──────────────────┬───────┬────────────────────────────────────────────────────────────────────────┐
│ File │ Lines │ Purpose │
├──────────────────┼───────┼────────────────────────────────────────────────────────────────────────┤
│ src/external.rs │ ~75 │ External function resolution — search path, file candidates, lex+parse │
├──────────────────┼───────┼────────────────────────────────────────────────────────────────────────┤
│ tests/phase11.rs │ ~225 │ 12 integration tests │
└──────────────────┴───────┴────────────────────────────────────────────────────────────────────────┘
Files Modified
File: src/env.rs
Change: Added source_path: Option field + set_source_path(), source_path(), source_dir() accessors
────────────────────────────────────────
File: src/main.rs
Change: Set source_path on environment when executing a file
────────────────────────────────────────
File: src/lib.rs
Change: Added pub mod external;
────────────────────────────────────────
File: src/eval.rs
Change: Added external_depth field, try_call_external() method, modified exec_call() and eval_expr() dispatch to try external before error, fixed PARSE
SOURCE to return actual filename
Test Results
What the 12 Tests Verify
┌─────────────────────────────┬─────────────────────────────────────────────────────┐
│ Test │ Verifies │
├─────────────────────────────┼─────────────────────────────────────────────────────┤
│ external_call_basic │ CALL greet 'World' loads greet.rexx, RESULT set │
├─────────────────────────────┼─────────────────────────────────────────────────────┤
│ external_function_in_expr │ say add(3, 4) loads add.rexx, returns value │
├─────────────────────────────┼─────────────────────────────────────────────────────┤
│ external_not_found │ CALL nosuch → Error 43 │
├─────────────────────────────┼─────────────────────────────────────────────────────┤
│ internal_label_priority │ Internal label chosen over same-named .rexx file │
├─────────────────────────────┼─────────────────────────────────────────────────────┤
│ bif_priority │ length("abc") returns 3, not external length.rexx │
├─────────────────────────────┼─────────────────────────────────────────────────────┤
│ rexxpath_resolution │ External found via REXXPATH env var │
├─────────────────────────────┼─────────────────────────────────────────────────────┤
│ external_scope_isolation │ Caller's variables not visible to external function │
├─────────────────────────────┼─────────────────────────────────────────────────────┤
│ external_calls_external │ Chained external function calls work │
├─────────────────────────────┼─────────────────────────────────────────────────────┤
│ parse_source_filename │ PARSE SOURCE returns actual filename │
├─────────────────────────────┼─────────────────────────────────────────────────────┤
│ case_insensitive_resolution │ CALL MYUTIL finds myutil.rexx │
├─────────────────────────────┼─────────────────────────────────────────────────────┤
│ external_no_return_call │ CALL context with no RETURN → RESULT dropped │
├─────────────────────────────┼─────────────────────────────────────────────────────┤
│ external_no_return_expr │ Expression context with no RETURN → Error 44 │
└─────────────────────────────┴─────────────────────────────────────────────────────┘
PR Review: Phase 11 External Function Libraries
I've reviewed the external function implementation against REXX semantics and found one critical bug that breaks external-to-external calls. The rest of the implementation is solid.
🔴 Critical Issue:
source_pathNot Updated for External CallsLocation:
src/eval.rs:722-758(try_call_external)Problem: When an external function is loaded and executed, the
Environment'ssource_pathis never updated to point to the new external file. This breaks the search path for any subsequent external calls made from within that external function.Impact: Test
external_calls_external(phase11.rs:138-148) passes only by accident —quadruple.rexxcan finddouble.rexxbecause both files are in the same directory (the temp dir), which is also the CWD. Ifdouble.rexxwere in a subdirectory relative toquadruple.rexx, the call would fail with Error 43.Example failure case:
When
helper.rexxexecutes,source_pathstill points tomain.rexx, so the search forutil.rexxstarts frommain.rexx's directory instead ofhelper.rexx's directory. Per ANSI X3.274-1996, the calling script's directory should be the first search location.Fix:
Recommended test addition:
✅ Correctness Review
Parser/Lexer Safety: ✓ No issues
Error Handling: ✓ Correct REXX error numbers
REXX Semantics: ✓ Compliant
push_procedure()is correct (eval.rs:745)Performance: ✓ No issues
resolve_externaldoes filesystem I/O per call, which is unavoidableSafety: ✓ No panics
📋 Test Coverage
Good coverage of:
Missing edge cases:
.rexextension (tests only use.rexx)🔍 Minor Observations
REXXPATH separator hardcoded (external.rs:44)
:separator, which is Unix-only;Unused binding (eval.rs:731)
_pathis bound but never usedpathis needed forset_source_pathSummary
Fix the
source_pathupdate bug intry_call_externalbefore merging. The rest of the implementation is well-designed and correctly handles REXX semantics, error propagation, and safety concerns.Pull Request #14 Review: External Function Libraries (Phase 11)
Summary
This PR implements external function resolution for REXX, allowing the interpreter to search the filesystem for
.rexx/.rexfiles when a function call cannot resolve to an internal label or built-in function. The implementation is solid and well-tested, with all 16 new tests passing.Critical Issues
1. Platform-Specific Path Separator in REXXPATH ⚠️
File:
src/external.rs:44Issue: Hardcoded colon (
:) separator for REXXPATH parsing is Unix-specific and will break on Windows.REXX Standard: Classic REXX implementations use platform-specific path separators (
;on Windows,:on Unix).Impact: External function resolution via REXXPATH will fail on Windows platforms.
Fix Required: Use
std::env::split_paths()for cross-platform path list parsing:Correctness Issues
2. Missing source_path Restoration on Early Error 🔴
File:
src/eval.rs:748-763Issue: If
exec_interpret_body()returns an error at line 753, thesource_pathis not restored because cleanup code (lines 756-758) only runs afterresult?unwrapping at line 763.Impact: If an external function throws an error (syntax error, runtime error), subsequent external function calls in the same stack frame will resolve relative to the wrong source file.
Test Gap: None of the 16 tests verify error propagation from external functions with proper cleanup.
Fix Required: Swap cleanup order to ensure source_path is restored before the
?operator:REXX Semantics
3. Resolution Order Correctly Implemented ✅
The implementation correctly follows REXX semantics:
self.labels.contains_key(name))crate::builtins::call_builtin)try_call_external)Verified by tests:
internal_label_priority(line 74-86 in tests/phase11.rs)bif_priority(line 90-98)4. Scope Isolation Correctly Implemented ✅
External functions run in isolated scope via
env.push_procedure()(eval.rs:744), correctly preventing caller variable leakage.Verified by test
external_scope_isolation(tests/phase11.rs:119-133).5. PARSE SOURCE Filename Handling ✅
The fix to return actual filename instead of hardcoded "patch-rexx" is correct (eval.rs:904-912).
Verified by test
parse_source_filename(tests/phase11.rs:149-160).Error Handling
6. Error Numbers Are Correct ✅
All match ANSI X3.274-1996 spec.
7. Recursion Limit Properly Enforced ✅
100-level external recursion depth limit prevents stack overflow (eval.rs:737-741).
However, combined INTERPRET + external depth could theoretically reach 200 levels total. This is acceptable for REXX's typical use cases.
Safety
8. No Panics on Malformed External Files ✅
Parse errors from external files propagate as
RexxDiagnostic(external.rs:64-66), not panics.Verified by test
external_parse_error(tests/phase11.rs:243-251).9. Filesystem Race Conditions Handled ✅
The
is_file()check (external.rs:58) followed byread_to_string()(external.rs:59) has a TOCTOU gap, but the error is properly caught and returned as Error 48. This is acceptable.10. Symlink Canonicalization Safe ✅
Both uses of
canonicalize()have safe fallbacks:main.rs:39:unwrap_or_else(|_| path.clone())external.rs:67:unwrap_or(full)No panic risk from symlink resolution failures.
Performance
11. Unnecessary PathBuf Clone in try_call_external ⚠️
File:
src/eval.rs:730This clones the path unnecessarily.
resolve_external()acceptsOption<&Path>, so we can pass the reference directly:Impact: Minor — one allocation per external function call. Not on hot path.
12. Filesystem Probing Is Sequential ℹ️
The search algorithm (external.rs:55-71) probes directories sequentially, potentially checking up to
dirs × 4file candidates for each external call.Example: With REXXPATH containing 5 directories, a failed lookup tests up to 20 files (5 dirs × 4 candidates).
Mitigation: This is standard REXX behavior and unavoidable without caching. The
is_file()check is fast, and successful resolutions stop early.Recommendation: Consider adding an LRU cache for resolved external functions in future phases (not required for this PR).
Test Coverage
13. Comprehensive Test Suite ✅
The 16 tests cover:
.rexxand.rexextensionsGap: Missing test for source_path restoration on error (see Issue #2).
Minor Observations (Not Issues)
14. Case-Insensitive Resolution Implementation
The implementation tries both lowercase and uppercase filenames (external.rs:29-35). This is correct for REXX semantics, where function names are case-insensitive.
Note: If name is "foo", candidates are
["foo.rexx", "foo.rex", "FOO.rexx", "FOO.rex"]. This handles all common REXX file naming conventions.15. External Depth Counter
The
external_depthfield (eval.rs:180) is separate frominterpret_depth, allowing 100 external calls + 100 INTERPRET levels = 200 total nesting. This is intentional and acceptable.Summary of Required Fixes
src/external.rs:44std::env::split_paths()src/eval.rs:753-763result?src/eval.rs:730&PathdirectlyRecommendation
Approve with required fixes. The implementation is well-architected and correct, but has two critical cross-platform and error-handling issues that must be resolved before merge.
The test suite is excellent and should be augmented with one additional test verifying source_path restoration on error.
Additional Notes
Code Review: Phase 11 - External Function Libraries
Reviewed the implementation of external function resolution for the REXX interpreter. Overall, the implementation is solid with correct REXX semantics and good test coverage. However, I found one critical bug that needs to be fixed.
Critical Issue: Source Path Not Restored When Original Was None
File:
src/eval.rs:754-757Problem: When
old_source_pathisNone(e.g., REPL mode or initial script with no parent), the code skips restoration:This leaves
source_pathset to the external function's path instead of restoring it toNone. Subsequent external calls from the REPL or top-level context will incorrectly resolve relative to the last external function's directory.Fix: Always restore the source path:
Or add a
clear_source_path()method toEnvironmentand call it whenold_source_path.is_none().Test case to add: Call an external function from REPL context (no source file), then call another external function and verify it doesn't resolve relative to the first external's directory.
What Works Well
✅ Correct REXX semantics:
foo.rexxandFOO.rexx)push_procedure()- external functions don't see caller variables✅ Safety:
result?on line 762)external_parse_error)Ok(None)gracefully✅ Test coverage:
source_path_restored_after_error) verifies cleanup on trapped errors.rexextension, REXXPATH with multiple entries, subdirectoriesMinor Observations (Not Issues)
Performance (external.rs:29-36): The candidate file generation allocates 2-4 strings on every external call:
This is fine for typical use cases (external calls aren't in tight loops), but if profiling shows this as a hotspot, consider using stack-allocated path construction or an iterator-based approach. No action needed unless benchmarks indicate a problem.
Allocation (external.rs:39-51):
search_dirsallocates aVecon every lookup. Again, this is not a problem for typical REXX programs. The code is clear and correct.Summary
The PR is very close to merge-ready once the source path restoration is fixed.
Code Review - Phase 11: External Function Libraries
Summary
This PR implements external function resolution for the REXX interpreter, enabling calls to external
.rexx/.rexfiles. The implementation follows ANSI X3.274-1996 semantics with proper resolution order, scope isolation, and comprehensive error handling.Overall Assessment: APPROVED ✅
All 447 tests pass (87 unit + 348 integration + 12 new phase11). Clippy clean.
Issues Identified
1. MEDIUM: Magic number for recursion limit (src/eval.rs:736)
The external function recursion limit is hardcoded:
While this matches
MAX_INTERPRET_DEPTH, it should be its own constant for maintainability:Impact: Code maintainability - if limits need to be adjusted independently, having separate constants makes this clearer.
2. LOW: Potential path canonicalization issues (src/external.rs:66, src/main.rs:39)
Both locations use:
On some platforms,
canonicalize()can fail for valid paths (symlink loops, permission issues). The fallback prevents crashes, but meanssource_pathmight not be canonical. This could theoretically cause issues if the same file is accessed via different paths (e.g., via symlink vs direct path).Impact: Low - most systems canonicalize successfully, and the fallback is safe. Edge case only.
3. LOW: Search path deduplication (src/external.rs:38-52)
The search directory list isn't deduplicated. If
source_dir == cwdor REXXPATH contains duplicates, directories will be searched multiple times:Impact: Negligible performance cost, no correctness issue.
Correctness Verification
✅ Error handling: Correct error numbers (43 = RoutineNotFound, 44 = NoReturnData)
✅ Resolution order: Internal labels → BIFs → external → error (per ANSI spec)
✅ Scope isolation: External functions get isolated scopes via
push_procedure()✅ Recursion guard: 100-level limit prevents stack overflow
✅ Cleanup order: Resources properly cleaned up in reverse order, even on error (src/eval.rs:755-763)
✅ Case-insensitive: Tries lowercase and uppercase filename variants
✅ REXX semantics:
Test Coverage Analysis
The 18 new tests comprehensively cover:
Missing edge case tests (not critical, but worth considering):
Positive Findings
Recommendations
MAX_EXTERNAL_DEPTHconstant (medium priority)Verdict: APPROVED - This is well-designed, thoroughly tested code that correctly implements REXX external function semantics. The identified issues are minor and don't affect correctness.