phase 3 #4
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!4
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "phase3"
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 3 PR Review — REXX Subroutines & Functions
I've reviewed the implementation of CALL/RETURN, function calls, PROCEDURE/EXPOSE, ARG, and DROP instructions. Overall, the implementation is solid and handles the core REXX semantics correctly. Below are specific issues that need attention:
🔴 Critical Issues
1. exec_from runs to end of program after routine completes (src/eval.rs:85-95)
Severity: High — Incorrect semantics
The
exec_frommethod executes fromstartto the end of the program, not just the routine body. After a routine returns, execution continues through all subsequent clauses in the program until EOF.Current code:
Problem: When calling
routine1at line 50 in a 100-line program,exec_from(51)will execute clauses 51-100 instead of stopping at the next label or program end.Expected REXX semantics: Routine execution should stop at:
Test that should fail but might pass:
Should only print "in sub", but will also print "after sub" because
exec_fromruns past the RETURN.Fix: Track routine boundaries. Either:
2. ARG parsing doesn't handle missing arguments correctly (src/eval.rs:245-264)
Severity: Medium — Incorrect REXX semantics
Current code:
Problem: Missing arguments receive empty string
"", but per REXX semantics, they should remain unset (so they return their uppercase name when referenced).Expected REXX behavior:
Fix: Don't call
self.env.set()for missing arguments — leave them unset.3. ARG parser accepts variables without commas between them (src/parser.rs:791-812)
Severity: Medium — Incorrect parsing
Current code:
Problem: This accepts
ARG x y zas equivalent toARG x, y, z. In REXX, comma-separated args map to different argument positions. Without commas,ARG x yshould parsexfrom arg 1, then try to parse patterny(a literal string pattern, Phase 4 feature).Impact: Currently harmless since patterns are ignored, but creates wrong AST structure. Will break when Phase 4 implements pattern matching.
Expected parsing:
ARG a, b, c→ 3 positional args from positions 0, 1, 2ARG a b c→ parse from position 0, split on pattern 'b', assign remainder to 'c' (Phase 4)Fix for Phase 3: Insert a comma after each variable if the next token is also a variable, or require commas between positional arguments.
⚠️ Moderate Issues
4. exec_arg doesn't increment arg_idx for missing commas (src/eval.rs:257)
Severity: Medium — Logic error
With the current parser accepting
ARG x y z, the evaluator correctly incrementsarg_idxonly on commas. However, if we fix issue #3, this becomes correct. If we don't fix #3, then:Current code:
Without commas in the element list,
arg_idxnever increments, soARG x y zwould try to assign args[0] to x, args[0] to y, args[0] to z.This is consistent with the broken parser, but both need fixing together.
5. EXPOSE doesn't search outer scopes for unset variables (src/env.rs:118-139)
Severity: Low — Edge case
Current code:
Problem: Only copies variables from the immediate caller scope. REXX
EXPOSEshould search all outer scopes (like a closure capture).Example:
Expected:
level2should findx='global'in the outermost scope.Current behavior:
xis unset inlevel1's scope, solevel2gets no value → prints 'X'.REXX semantics: EXPOSE searches the caller's scope chain up to the global scope. This is complex because REXX has dynamic scope, not lexical scope.
Fix: In
push_procedure_expose, searchself.scopes.iter().rev()(all scopes from caller back to global), not just the last scope.6. Missing bounds check in call_routine (src/eval.rs:191)
Severity: Low — Safety
Current code:
Good: Uses
.get()for bounds checking.Potential issue: If a label is the last clause in the program (no instructions after it),
start_idxis out of bounds.Current behavior:
get()returnsNone,has_procedure = false, thenexec_from(out_of_bounds)creates an empty slice → returnsNormalimmediately → correct behavior by accident.Recommendation: Add explicit check and return error for label-without-body:
📊 Test Coverage Observations
The test suite is comprehensive and covers:
Missing test cases:
call sub; exit; sub: return; say 'oops'test: arg a,b,c; say b; return; call test 1a. = 'default'; a.1 = 'x'; drop a.call HELLOvshello: return✅ Strengths
📝 Recommendations
Priority 1 (must fix):
exec_fromto respect routine boundariesPriority 2 (should fix):
Priority 3 (nice to have):
Overall assessment: Strong implementation of REXX subroutine semantics with correct error handling. The critical issues (#1, #2) will cause incorrect behavior on valid REXX programs and should be fixed before merge. Issues #3-6 are edge cases that could be addressed in follow-up PRs.
Great work on the pending_exit mechanism and EXPOSE write-back logic! 🚀
Phase 3 PR Review: Subroutines & Procedure Scoping
Summary
✅ Overall: Strong implementation with correct REXX semantics
All tests pass. The implementation correctly handles subroutine calls, PROCEDURE scoping, PROCEDURE EXPOSE, ARG instruction, and DROP instruction. Error numbers are properly mapped per ANSI X3.274-1996.
Critical Issues Found
1. Execution fall-through bug (src/eval.rs:185-219)
Severity: HIGH — Subroutines that reach EOF continue executing subsequent clauses
Location:
call_routinemethod, line 211Issue: After calling
exec_from(exec_start), execution continues from that point until EOF or an explicit RETURN/EXIT. If a routine doesn't contain a RETURN statement, it will fall through and execute all subsequent clauses in the program (including other label definitions, main code after EXIT, etc.).REXX Spec: Per ANSI X3.274-1996 §7.3.1, routine execution should stop when reaching:
Current behavior:
This will execute
other_labeland return "oops" instead of returning the variable name "X".Fix: Track routine boundaries by stopping execution at the next label:
Test to add:
2. EXIT within routines doesn't propagate correctly (src/eval.rs:615-618)
Severity: MEDIUM — Function calls set pending_exit but don't check it before returning
Location:
eval_exprFunctionCall arm, line 608-620Issue: When EXIT is executed inside a function call during expression evaluation, the pending exit is set but the function returns an empty string. The caller continues evaluating the expression instead of immediately propagating the EXIT signal.
Example:
Current behavior: Prints "before after" and exits with 99 (empty string from cleanup concatenates).
Expected behavior: Should exit immediately with code 99 without printing "after".
Root cause: Line 604-606 checks
pending_exitduring argument evaluation but doesn't check it aftercall_routinereturns on line 608.Fix:
Actually, the current code does handle this at line 615-617 by setting pending_exit. The real issue is that expression evaluation doesn't check pending_exit after each subexpression in binary operations. Consider:
If func1() triggers EXIT, func2() should never be called.
Better fix: Check pending_exit in
eval_binopafter evaluating left operand:Medium Issues
3. PROCEDURE EXPOSE writes back even on error (src/env.rs:143-159)
Severity: MEDIUM — Partial state modification on error
Issue: If an error occurs during routine execution after modifying exposed variables,
pop_procedurestill writes the modified values back to the parent scope. REXX semantics aren't crystal clear here, but traditional implementations tend to leave parent variables unchanged on error.Recommendation: Consider wrapping exposed variable writeback in the
call_routinemethod to only write back on successful return:However, this may not match all REXX implementations. Needs spec clarification.
4. ARG instruction uppercases values, not just variable names (src/eval.rs:252)
Severity: MEDIUM — Incorrect REXX semantics
Location:
exec_arg, line 252Code:
Issue: The ARG instruction should uppercase the variable name (which is already done by the parser), but it should not uppercase the argument value. The current code uppercases the value itself.
REXX Spec: Per ANSI X3.274-1996 §7.4.1, ARG is shorthand for "PARSE UPPER ARG", which means:
Actually, I was wrong: The current implementation is correct. ARG is defined as PARSE UPPER ARG, so values should be uppercased. The test at line 142-146 confirms this:
This is correct per ANSI spec. Ignore this issue.
Minor Issues / Observations
5. Missing bounds check on label_idx + 1 (src/eval.rs:191-198)
Severity: LOW — Edge case safety
Code:
Issue: If a label is the last clause in the program (
label_idx == program.clauses.len() - 1), thenstart_idxwill be out of bounds. Thegetcall will return None, which is safe, but it means a label at EOF will successfully "call" and immediately return None (no PROCEDURE, empty body).Expected behavior: Calling a label at EOF should either:
Current behavior is reasonable but consider a comment explaining this edge case.
6. Potential infinite recursion without stack limit (src/eval.rs:185)
Severity: LOW — Missing safeguard
Issue: Recursive calls have no depth limit. Deep recursion (e.g.,
fact(10000)) will overflow the Rust call stack.Recommendation: Add a recursion depth counter:
Not critical for Phase 3, but important for production.
7. LEAVE/ITERATE from within functions silently ignored (src/eval.rs:241, 619)
Severity: LOW — Spec compliance edge case
Code:
Issue: If a function executes LEAVE or ITERATE (e.g., nested inside a DO loop that then calls a function), the signal is silently converted to Normal. Per REXX spec, LEAVE/ITERATE should propagate to the enclosing loop, not be swallowed.
Example:
This is correct — LEAVE is in the main loop, not the function.
But consider:
Per ANSI spec: LEAVE without a name targets the innermost loop. If executed in a PROCEDURE (isolated scope), there's no enclosing loop, so this should raise Error 28 (Invalid LEAVE or ITERATE).
Current behavior: Silently converts to Normal.
Fix: In
call_routine, check for LEAVE/ITERATE in isolated procedures:For non-PROCEDURE routines (dynamic scope), LEAVE/ITERATE should propagate.
Code Quality Observations
Positive
✅ Clean separation of concerns (parser, evaluator, environment)
✅ Proper use of REXX error numbers per ANSI spec
✅ Good test coverage for core functionality
✅ Correct handling of PROCEDURE EXPOSE writeback semantics
✅ PendingExit pattern correctly handles deferred EXIT propagation
Suggestions
Test Coverage Gaps
The existing tests are good but missing:
call foo 1,,3)Correctness per ANSI X3.274-1996
Recommended Fixes (Priority Order)
Conclusion
Strong implementation overall. The critical routine fall-through bug must be fixed before merge. The LEAVE/ITERATE issue is a spec compliance gap but less urgent. Consider adding the suggested tests to prevent regressions.
Great work on Phase 3! 🎉