Skip to main content

patch_rexx/
eval.rs

1//! REXX tree-walking evaluator — AST + Environment -> execution.
2//!
3//! Walks the AST produced by the parser, evaluating expressions using
4//! `RexxValue` and `BigDecimal` arithmetic, and executing instructions
5//! against an `Environment`.
6
7use bigdecimal::{BigDecimal, Zero};
8use std::collections::{HashMap, VecDeque};
9use std::fmt;
10use std::path::Path;
11use std::str::FromStr;
12
13use crate::ast::{
14    AddressAction, AssignTarget, BinOp, Clause, ClauseKind, Condition, ControlledLoop, DoBlock,
15    DoKind, Expr, NumericFormSetting, NumericSetting, ParseSource, ParseTemplate, Program,
16    SignalAction, TailElement, TemplateElement, UnaryOp,
17};
18use crate::env::{EnvVars, Environment};
19use crate::error::{RexxDiagnostic, RexxError, RexxResult};
20use crate::lexer::Lexer;
21use crate::parser::Parser;
22use crate::value::{NumericSettings, RexxValue};
23
24/// Maximum nesting depth for INTERPRET to prevent stack overflow.
25const MAX_INTERPRET_DEPTH: usize = 100;
26
27/// REXX trace levels, ordered from least to most verbose.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
29enum TraceLevel {
30    Off,
31    Normal,
32    Failure,
33    Errors,
34    Commands,
35    Labels,
36    Results,
37    Intermediates,
38    All,
39}
40
41impl TraceLevel {
42    /// Parse a single-letter or full-word trace level (case-insensitive).
43    fn parse(s: &str) -> Option<Self> {
44        let upper = s.trim().to_uppercase();
45        match upper.as_str() {
46            "O" | "OFF" => Some(Self::Off),
47            "N" | "NORMAL" => Some(Self::Normal),
48            "F" | "FAILURE" => Some(Self::Failure),
49            "E" | "ERRORS" => Some(Self::Errors),
50            "C" | "COMMANDS" => Some(Self::Commands),
51            "L" | "LABELS" => Some(Self::Labels),
52            "R" | "RESULTS" => Some(Self::Results),
53            "I" | "INTERMEDIATES" => Some(Self::Intermediates),
54            "A" | "ALL" => Some(Self::All),
55            _ => None,
56        }
57    }
58
59    /// Return the single-letter representation.
60    fn letter(self) -> char {
61        match self {
62            Self::Off => 'O',
63            Self::Normal => 'N',
64            Self::Failure => 'F',
65            Self::Errors => 'E',
66            Self::Commands => 'C',
67            Self::Labels => 'L',
68            Self::Results => 'R',
69            Self::Intermediates => 'I',
70            Self::All => 'A',
71        }
72    }
73}
74
75/// Combined trace setting: level + interactive flag.
76#[derive(Debug, Clone)]
77struct TraceSetting {
78    level: TraceLevel,
79    interactive: bool,
80}
81
82/// Result of parsing a trace setting string.
83enum TraceAction {
84    /// "?" alone — toggle interactive mode, keep current level.
85    ToggleInteractive,
86    /// A concrete setting (e.g., "R", "?R", "OFF").
87    Set(TraceSetting),
88}
89
90impl TraceAction {
91    /// Parse a trace setting string like "R", "?R", "?", "OFF", "?Results".
92    fn parse(s: &str) -> Option<Self> {
93        let trimmed = s.trim();
94        if trimmed.is_empty() {
95            return None;
96        }
97        if trimmed == "?" {
98            return Some(Self::ToggleInteractive);
99        }
100        if let Some(rest) = trimmed.strip_prefix('?') {
101            let level = TraceLevel::parse(rest)?;
102            Some(Self::Set(TraceSetting {
103                level,
104                interactive: true,
105            }))
106        } else {
107            let level = TraceLevel::parse(trimmed)?;
108            Some(Self::Set(TraceSetting {
109                level,
110                interactive: false,
111            }))
112        }
113    }
114}
115
116impl Default for TraceSetting {
117    fn default() -> Self {
118        Self {
119            level: TraceLevel::Normal,
120            interactive: false,
121        }
122    }
123}
124
125impl fmt::Display for TraceSetting {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        if self.interactive {
128            write!(f, "?{}", self.level.letter())
129        } else {
130            write!(f, "{}", self.level.letter())
131        }
132    }
133}
134
135/// Signal returned by clause/block execution for control flow.
136pub enum ExecSignal {
137    Normal,
138    Leave(Option<String>),
139    Iterate(Option<String>),
140    Exit(Option<RexxValue>),
141    Return(Option<RexxValue>),
142    /// SIGNAL transfers control to a label, abandoning all blocks.
143    Signal(String),
144}
145
146/// Pending EXIT state — distinguishes "no pending exit" from "exit with/without value".
147enum PendingExit {
148    None,
149    WithValue(Option<RexxValue>),
150}
151
152impl PendingExit {
153    /// If an EXIT is pending, take the value and reset to `None`.
154    /// Returns the exit value wrapped in `ExecSignal::Exit` for immediate propagation.
155    fn take_signal(&mut self) -> Option<ExecSignal> {
156        match std::mem::replace(self, PendingExit::None) {
157            PendingExit::None => Option::None,
158            PendingExit::WithValue(v) => Some(ExecSignal::Exit(v)),
159        }
160    }
161
162    const fn is_pending(&self) -> bool {
163        matches!(self, PendingExit::WithValue(_))
164    }
165}
166
167/// A custom command handler for ADDRESS environments.
168///
169/// Receives `(address_environment, command_string)` and returns `Some(rc)` if
170/// it handled the command, or `None` to fall through to default shell execution.
171///
172/// # Panics
173///
174/// The handler must not panic. If it does, the panic will propagate through
175/// the evaluator. Handlers should handle all error cases internally and
176/// return appropriate return codes.
177type CommandHandler = Box<dyn FnMut(&str, &str) -> Option<i32>>;
178
179/// A custom command handler that also receives an [`EnvVars`] handle for
180/// reading and writing REXX variables.
181///
182/// Receives `(address_environment, command_string, vars)` and returns `Some(rc)`
183/// if it handled the command, or `None` to fall through to default shell execution.
184///
185/// This variant allows embedding applications to inspect and update REXX variables
186/// (e.g., refreshing EXTRACT stem variables) during command execution.  The
187/// [`EnvVars`] wrapper intentionally restricts access to variable operations only —
188/// ADDRESS routing and PROCEDURE scoping are not exposed.
189///
190/// # Panics
191///
192/// The handler must not panic. If it does, the panic will propagate through
193/// the evaluator. Handlers should handle all error cases internally and
194/// return appropriate return codes.
195type CommandHandlerWithEnv = Box<dyn FnMut(&str, &str, &mut EnvVars<'_>) -> Option<i32>>;
196
197pub struct Evaluator<'a> {
198    env: &'a mut Environment,
199    program: &'a Program,
200    settings: NumericSettings,
201    labels: HashMap<String, usize>,
202    arg_stack: Vec<Vec<RexxValue>>,
203    pending_exit: PendingExit,
204    /// Active condition traps: condition → target label name.
205    traps: HashMap<Condition, String>,
206    /// Pending signal from a trap (e.g., NOVALUE). Fires after clause completes.
207    pending_signal: Option<String>,
208    /// Current INTERPRET nesting depth (for recursion limit).
209    interpret_depth: usize,
210    /// Current external function call nesting depth (for recursion limit).
211    external_depth: usize,
212    /// Current TRACE setting (level + interactive flag).
213    trace_setting: TraceSetting,
214    /// External data queue for PUSH/QUEUE/PULL.
215    queue: VecDeque<String>,
216    /// Optional custom command handler for ADDRESS environments.
217    /// Called as `handler(address_environment, command_string)` and returns
218    /// `Some(rc)` to handle the command or `None` to fall through to shell execution.
219    command_handler: Option<CommandHandler>,
220    /// Optional custom command handler with `&mut Environment` access.
221    /// Tried before `command_handler`; allows embedding applications to
222    /// inspect and update REXX variables during command execution.
223    command_handler_with_env: Option<CommandHandlerWithEnv>,
224}
225
226impl<'a> Evaluator<'a> {
227    pub fn new(env: &'a mut Environment, program: &'a Program) -> Self {
228        let labels = Self::build_labels(program);
229        Self {
230            env,
231            program,
232            settings: NumericSettings::default(),
233            labels,
234            arg_stack: Vec::new(),
235            pending_exit: PendingExit::None,
236            traps: HashMap::new(),
237            pending_signal: None,
238            interpret_depth: 0,
239            external_depth: 0,
240            trace_setting: TraceSetting::default(),
241            queue: VecDeque::new(),
242            command_handler: None,
243            command_handler_with_env: None,
244        }
245    }
246
247    fn build_labels(program: &Program) -> HashMap<String, usize> {
248        let mut labels = HashMap::new();
249        for (i, clause) in program.clauses.iter().enumerate() {
250            if let ClauseKind::Label(name) = &clause.kind {
251                labels.entry(name.clone()).or_insert(i);
252            }
253        }
254        labels
255    }
256
257    pub fn exec(&mut self) -> RexxResult<ExecSignal> {
258        let mut start = 0;
259        loop {
260            match self.exec_from(start)? {
261                ExecSignal::Signal(label) => {
262                    let &idx = self.labels.get(&label).ok_or_else(|| {
263                        RexxDiagnostic::new(RexxError::LabelNotFound)
264                            .with_detail(format!("label '{label}' not found"))
265                    })?;
266                    // Trace the label clause at Labels or All level per ANSI REXX
267                    if matches!(
268                        self.trace_setting.level,
269                        TraceLevel::Labels | TraceLevel::All
270                    ) {
271                        self.trace_clause(&self.program.clauses[idx]);
272                    }
273                    start = idx + 1;
274                }
275                other => return Ok(other),
276            }
277        }
278    }
279
280    fn exec_from(&mut self, start: usize) -> RexxResult<ExecSignal> {
281        for clause in &self.program.clauses[start..] {
282            let signal = self.exec_clause_outer(clause)?;
283            if let Some(signal) = self.pending_exit.take_signal() {
284                return Ok(signal);
285            }
286            if let Some(label) = self.pending_signal.take() {
287                return Ok(ExecSignal::Signal(label));
288            }
289            if !matches!(signal, ExecSignal::Normal) {
290                return Ok(signal);
291            }
292        }
293        Ok(ExecSignal::Normal)
294    }
295
296    fn exec_body(&mut self, body: &[Clause]) -> RexxResult<ExecSignal> {
297        for clause in body {
298            let signal = self.exec_clause_outer(clause)?;
299            if let Some(signal) = self.pending_exit.take_signal() {
300                return Ok(signal);
301            }
302            if let Some(label) = self.pending_signal.take() {
303                return Ok(ExecSignal::Signal(label));
304            }
305            if !matches!(signal, ExecSignal::Normal) {
306                return Ok(signal);
307            }
308        }
309        Ok(ExecSignal::Normal)
310    }
311
312    /// Outer clause executor: wraps `exec_clause` with SYNTAX trap support and TRACE output.
313    fn exec_clause_outer(&mut self, clause: &Clause) -> RexxResult<ExecSignal> {
314        let trace_level = self.trace_setting.level;
315
316        // Pre-execution trace: source line per ANSI REXX §8.3.36.
317        // Labels: only at Labels or All level.
318        // Commands: at Commands, Results, Intermediates, All.
319        // Other clauses: at Results, Intermediates, All.
320        let should_trace_source = match &clause.kind {
321            ClauseKind::Label(_) => {
322                matches!(trace_level, TraceLevel::Labels | TraceLevel::All)
323            }
324            ClauseKind::Command(_) => matches!(
325                trace_level,
326                TraceLevel::Commands
327                    | TraceLevel::Results
328                    | TraceLevel::Intermediates
329                    | TraceLevel::All
330            ),
331            _ => matches!(
332                trace_level,
333                TraceLevel::Results | TraceLevel::Intermediates | TraceLevel::All
334            ),
335        };
336        if should_trace_source && !matches!(clause.kind, ClauseKind::Nop) {
337            self.trace_clause(clause);
338        }
339
340        match self.exec_clause(clause) {
341            Ok(signal) => {
342                // Post-execution trace for interactive pause
343                if should_trace_source && !matches!(clause.kind, ClauseKind::Nop) {
344                    self.trace_interactive_pause()?;
345                }
346                Ok(signal)
347            }
348            Err(diag) => {
349                if let Some(label) = self.traps.get(&Condition::Syntax).cloned() {
350                    // Set RC to the error number
351                    self.env
352                        .set("RC", RexxValue::new(diag.error.number().to_string()));
353                    // Set condition info
354                    let desc = diag.detail.unwrap_or_default();
355                    self.env.set_condition_info(crate::env::ConditionInfoData {
356                        condition: "SYNTAX".to_string(),
357                        description: desc,
358                        instruction: "SIGNAL".to_string(),
359                        status: "ON".to_string(),
360                    });
361                    // Disable the trap (per REXX: trap fires once)
362                    self.traps.remove(&Condition::Syntax);
363                    Ok(ExecSignal::Signal(label))
364                } else {
365                    Err(diag)
366                }
367            }
368        }
369    }
370
371    #[allow(clippy::too_many_lines)]
372    fn exec_clause(&mut self, clause: &Clause) -> RexxResult<ExecSignal> {
373        match &clause.kind {
374            ClauseKind::Say(expr) => {
375                let val = self.eval_expr(expr)?;
376                if self.pending_exit.is_pending() {
377                    return Ok(ExecSignal::Normal);
378                }
379                if matches!(
380                    self.trace_setting.level,
381                    TraceLevel::Results | TraceLevel::Intermediates | TraceLevel::All
382                ) {
383                    self.trace_tag(">>", val.as_str());
384                }
385                println!("{val}");
386                Ok(ExecSignal::Normal)
387            }
388            ClauseKind::Assignment { target, expr } => {
389                let val = self.eval_expr(expr)?;
390                if matches!(
391                    self.trace_setting.level,
392                    TraceLevel::Results | TraceLevel::Intermediates | TraceLevel::All
393                ) {
394                    self.trace_tag(">>", val.as_str());
395                }
396                match target {
397                    AssignTarget::Simple(name) => {
398                        self.env.set(name, val);
399                    }
400                    AssignTarget::Stem { stem, tail } => {
401                        let resolved_tail = self.resolve_tail(tail);
402                        if resolved_tail.is_empty() {
403                            self.env.set_stem_default(stem, val);
404                        } else {
405                            self.env.set_compound(stem, &resolved_tail, val);
406                        }
407                    }
408                }
409                Ok(ExecSignal::Normal)
410            }
411            ClauseKind::Command(expr) => {
412                let val = self.eval_expr(expr)?;
413                if self.pending_exit.is_pending() || self.pending_signal.is_some() {
414                    return Ok(ExecSignal::Normal);
415                }
416                Ok(self.exec_host_command(val.as_str()))
417            }
418            ClauseKind::If {
419                condition,
420                then_clause,
421                else_clause,
422            } => self.exec_if(condition, then_clause, else_clause.as_deref()),
423            ClauseKind::Do(block) => self.exec_do(block),
424            ClauseKind::Select {
425                when_clauses,
426                otherwise,
427            } => self.exec_select(when_clauses, otherwise.as_ref()),
428            ClauseKind::Leave(name) => Ok(ExecSignal::Leave(name.clone())),
429            ClauseKind::Iterate(name) => Ok(ExecSignal::Iterate(name.clone())),
430            ClauseKind::Exit(expr) => {
431                let val = if let Some(e) = expr {
432                    Some(self.eval_expr(e)?)
433                } else {
434                    None
435                };
436                Ok(ExecSignal::Exit(val))
437            }
438            ClauseKind::Return(expr) => {
439                let val = if let Some(e) = expr {
440                    Some(self.eval_expr(e)?)
441                } else {
442                    None
443                };
444                Ok(ExecSignal::Return(val))
445            }
446            ClauseKind::Call { name, args } => self.exec_call(name, args),
447            ClauseKind::Signal(action) => self.exec_signal(action),
448            ClauseKind::Label(_) | ClauseKind::Nop => Ok(ExecSignal::Normal),
449            ClauseKind::Procedure(_) => Err(RexxDiagnostic::new(RexxError::UnexpectedProcedure)
450                .with_detail("PROCEDURE must be the first instruction in a called routine")),
451            ClauseKind::Drop(names) => {
452                for name in names {
453                    self.env.drop(name);
454                }
455                Ok(ExecSignal::Normal)
456            }
457            ClauseKind::Arg(template) => self.exec_arg(template),
458            ClauseKind::Parse {
459                upper,
460                source,
461                template,
462            } => self.exec_parse(*upper, source, template),
463            ClauseKind::Pull(template_opt) => {
464                let raw = self.pull_from_queue_or_stdin()?;
465                let text = raw.to_uppercase();
466                if let Some(template) = template_opt {
467                    self.apply_template(&text, template)?;
468                }
469                Ok(ExecSignal::Normal)
470            }
471            ClauseKind::Interpret(expr) => self.exec_interpret(expr),
472            ClauseKind::Address(action) => self.exec_address(action),
473            ClauseKind::Trace(expr) => self.exec_trace(expr),
474            ClauseKind::Numeric(setting) => self.exec_numeric(setting),
475            ClauseKind::Push(expr) => self.exec_push(expr.as_ref()),
476            ClauseKind::Queue(expr) => self.exec_queue(expr.as_ref()),
477        }
478    }
479
480    // ── ADDRESS / host command execution ───────────────────────────
481
482    /// Execute an ADDRESS instruction.
483    fn exec_address(&mut self, action: &AddressAction) -> RexxResult<ExecSignal> {
484        match action {
485            AddressAction::SetEnvironment(name) => {
486                if name.is_empty() {
487                    self.env.swap_address();
488                } else {
489                    self.env.set_address(name);
490                }
491                Ok(ExecSignal::Normal)
492            }
493            AddressAction::Value(expr) => {
494                let val = self.eval_expr(expr)?;
495                if self.pending_exit.is_pending() || self.pending_signal.is_some() {
496                    return Ok(ExecSignal::Normal);
497                }
498                let name = val.as_str().to_uppercase();
499                self.env.set_address(&name);
500                Ok(ExecSignal::Normal)
501            }
502            AddressAction::Temporary {
503                environment,
504                command,
505            } => {
506                let cmd_val = self.eval_expr(command)?;
507                if self.pending_exit.is_pending() || self.pending_signal.is_some() {
508                    return Ok(ExecSignal::Normal);
509                }
510                let cmd_str = cmd_val.as_str().to_string();
511                // Temporarily switch environment, run command, then restore.
512                let saved_default = self.env.address().to_string();
513                self.env.set_address(environment);
514                let signal = self.exec_host_command(&cmd_str);
515                // Restore: set_address saves current as previous, which is
516                // what we want — the temporary env becomes previous per REXX.
517                self.env.set_address(&saved_default);
518                Ok(signal)
519            }
520        }
521    }
522
523    /// Execute a host command: try custom handler first, then `sh -c`.
524    /// Sets RC and fires ERROR/FAILURE conditions as appropriate.
525    fn exec_host_command(&mut self, command: &str) -> ExecSignal {
526        // Try the env-aware handler first.
527        // We use take/put-back to split the borrow: `handler` is moved out of
528        // `self` so we can pass `&mut self.env` (wrapped in EnvVars) without
529        // conflicting with the `&mut self` borrow.  If the handler panics the
530        // slot stays empty, but the panic propagates immediately — the empty
531        // slot only matters if the caller catches the unwind.
532        let mut custom_rc = None;
533        if let Some(mut handler) = self.command_handler_with_env.take() {
534            let addr = self.env.address().to_string();
535            let mut vars = EnvVars::new(self.env);
536            custom_rc = handler(&addr, command, &mut vars);
537            self.command_handler_with_env = Some(handler);
538        }
539        // Fall through to the basic handler if the env-aware handler didn't handle it
540        if custom_rc.is_none()
541            && let Some(ref mut handler) = self.command_handler
542        {
543            let addr = self.env.address().to_string();
544            custom_rc = handler(&addr, command);
545        }
546
547        let rc = if let Some(rc) = custom_rc {
548            // Custom handler handled it
549            self.env.set("RC", RexxValue::new(rc.to_string()));
550            rc
551        } else {
552            // Fall through to shell execution
553            let result = std::process::Command::new("sh")
554                .arg("-c")
555                .arg(command)
556                .status();
557            if let Ok(status) = result {
558                let code = status.code().unwrap_or(-1);
559                self.env.set("RC", RexxValue::new(code.to_string()));
560                code
561            } else {
562                self.env.set("RC", RexxValue::new("-1"));
563                return self.fire_failure_trap(command);
564            }
565        };
566
567        // ANSI X3.274-1996 §7.3.3: negative rc → FAILURE, positive rc → ERROR
568        if rc < 0 {
569            return self.fire_failure_trap(command);
570        }
571        if rc > 0
572            && let Some(label) = self.traps.get(&Condition::Error).cloned()
573        {
574            self.env.set_condition_info(crate::env::ConditionInfoData {
575                condition: "ERROR".to_string(),
576                description: command.to_string(),
577                instruction: "SIGNAL".to_string(),
578                status: "ON".to_string(),
579            });
580            self.traps.remove(&Condition::Error);
581            return ExecSignal::Signal(label);
582        }
583        ExecSignal::Normal
584    }
585
586    fn fire_failure_trap(&mut self, command: &str) -> ExecSignal {
587        if let Some(label) = self.traps.get(&Condition::Failure).cloned() {
588            self.env.set_condition_info(crate::env::ConditionInfoData {
589                condition: "FAILURE".to_string(),
590                description: command.to_string(),
591                instruction: "SIGNAL".to_string(),
592                status: "ON".to_string(),
593            });
594            self.traps.remove(&Condition::Failure);
595            ExecSignal::Signal(label)
596        } else {
597            ExecSignal::Normal
598        }
599    }
600
601    // ── TRACE execution ────────────────────────────────────────────
602
603    /// Apply a trace setting string, returning the previous setting as a string.
604    /// Shared by TRACE instruction, `TRACE()` BIF, and CALL TRACE.
605    fn apply_trace_setting(&mut self, s: &str) -> RexxResult<String> {
606        let old = self.trace_setting.to_string();
607        let action = TraceAction::parse(s).ok_or_else(|| {
608            RexxDiagnostic::new(RexxError::InvalidTrace)
609                .with_detail(format!("invalid trace setting '{s}'"))
610        })?;
611        match action {
612            TraceAction::ToggleInteractive => {
613                self.trace_setting.interactive = !self.trace_setting.interactive;
614            }
615            TraceAction::Set(new_setting) => {
616                self.trace_setting = new_setting;
617            }
618        }
619        Ok(old)
620    }
621
622    /// Execute a TRACE instruction: evaluate setting, update trace state.
623    fn exec_trace(&mut self, expr: &Expr) -> RexxResult<ExecSignal> {
624        let val = self.eval_expr(expr)?;
625        if self.pending_exit.is_pending() || self.pending_signal.is_some() {
626            return Ok(ExecSignal::Normal);
627        }
628        self.apply_trace_setting(val.as_str())?;
629        Ok(ExecSignal::Normal)
630    }
631
632    /// Print a trace source line: "     3 *-* say 'hello'" to stderr.
633    #[allow(clippy::unused_self)]
634    fn trace_clause(&self, clause: &Clause) {
635        let line_num = clause.loc.line;
636        let source = clause.loc.source_line.as_deref().unwrap_or("(unknown)");
637        eprintln!("{line_num:>6} *-* {source}");
638    }
639
640    /// Print a trace tag line: "       >>> \"value\"" to stderr.
641    #[allow(clippy::unused_self)]
642    fn trace_tag(&self, tag: &str, value: &str) {
643        eprintln!("       >{tag}> \"{value}\"");
644    }
645
646    /// Conditionally trace an intermediate value (only at Intermediates or All level).
647    fn trace_intermediates(&self, tag: &str, value: &str) {
648        if matches!(
649            self.trace_setting.level,
650            TraceLevel::Intermediates | TraceLevel::All
651        ) {
652            self.trace_tag(tag, value);
653        }
654    }
655
656    /// Handle interactive pause: read stdin lines and execute via INTERPRET.
657    /// Per ANSI REXX, only a null line (no characters at all, not even spaces)
658    /// continues execution. Any other input is executed as REXX code.
659    fn trace_interactive_pause(&mut self) -> RexxResult<()> {
660        if !self.trace_setting.interactive {
661            return Ok(());
662        }
663        loop {
664            let mut line = String::new();
665            match std::io::stdin().read_line(&mut line) {
666                Ok(0) | Err(_) => break, // EOF or I/O error
667                Ok(_) => {}
668            }
669            // Strip trailing newline/carriage return only (preserve interior whitespace)
670            let content = line.trim_end_matches(['\n', '\r']);
671            // Null line (empty after stripping newline) → continue execution
672            if content.is_empty() {
673                break;
674            }
675            // Execute the input as REXX via the existing interpret machinery
676            let source = content.trim().to_string();
677            let mut lexer = Lexer::new(&source);
678            let tokens = lexer.tokenize()?;
679            let mut parser = Parser::new(tokens);
680            let program = parser.parse()?;
681            let labels = HashMap::new();
682            self.exec_interpret_body(&program.clauses, &labels)?;
683        }
684        Ok(())
685    }
686
687    // ── INTERPRET execution ────────────────────────────────────────
688
689    /// Execute an INTERPRET instruction: evaluate expr to string, lex, parse, execute.
690    fn exec_interpret(&mut self, expr: &Expr) -> RexxResult<ExecSignal> {
691        let val = self.eval_expr(expr)?;
692        if self.pending_exit.is_pending() {
693            return Ok(ExecSignal::Normal);
694        }
695        if self.pending_signal.is_some() {
696            return Ok(ExecSignal::Normal);
697        }
698
699        let source = val.as_str().to_string();
700        if source.is_empty() {
701            return Ok(ExecSignal::Normal);
702        }
703
704        // Depth guard
705        if self.interpret_depth >= MAX_INTERPRET_DEPTH {
706            return Err(RexxDiagnostic::new(RexxError::ResourceExhausted)
707                .with_detail("INTERPRET recursion depth limit exceeded"));
708        }
709
710        // Lex and parse the interpreted string
711        let mut lexer = Lexer::new(&source);
712        let tokens = lexer.tokenize()?;
713        let mut parser = Parser::new(tokens);
714        let program = parser.parse()?;
715
716        // Build label map for the interpreted code
717        let mut labels = HashMap::new();
718        for (i, clause) in program.clauses.iter().enumerate() {
719            if let ClauseKind::Label(name) = &clause.kind {
720                labels.entry(name.clone()).or_insert(i);
721            }
722        }
723
724        self.interpret_depth += 1;
725        let result = self.exec_interpret_body(&program.clauses, &labels);
726        self.interpret_depth -= 1;
727
728        result
729    }
730
731    /// Execute interpreted clauses with a restart loop for local SIGNAL targets.
732    fn exec_interpret_body(
733        &mut self,
734        clauses: &[Clause],
735        labels: &HashMap<String, usize>,
736    ) -> RexxResult<ExecSignal> {
737        let mut start = 0;
738        loop {
739            match self.exec_interpret_from(clauses, start)? {
740                ExecSignal::Signal(ref label) => {
741                    if let Some(&idx) = labels.get(label) {
742                        start = idx + 1; // restart locally
743                    } else {
744                        return Ok(ExecSignal::Signal(label.clone()));
745                    }
746                }
747                other => return Ok(other),
748            }
749        }
750    }
751
752    /// Execute interpreted clauses from a given index (mirrors `exec_from` for interpreted code).
753    fn exec_interpret_from(&mut self, clauses: &[Clause], start: usize) -> RexxResult<ExecSignal> {
754        for clause in &clauses[start..] {
755            let signal = self.exec_clause_outer(clause)?;
756            if let Some(signal) = self.pending_exit.take_signal() {
757                return Ok(signal);
758            }
759            if let Some(label) = self.pending_signal.take() {
760                return Ok(ExecSignal::Signal(label));
761            }
762            if !matches!(signal, ExecSignal::Normal) {
763                return Ok(signal);
764            }
765        }
766        Ok(ExecSignal::Normal)
767    }
768
769    fn call_routine(&mut self, name: &str, args: Vec<RexxValue>) -> RexxResult<ExecSignal> {
770        let &label_idx = self.labels.get(name).ok_or_else(|| {
771            RexxDiagnostic::new(RexxError::RoutineNotFound)
772                .with_detail(format!("routine '{name}' not found"))
773        })?;
774
775        let start_idx = label_idx + 1; // skip the label clause itself
776        self.arg_stack.push(args);
777
778        // Check if first clause after label is PROCEDURE — consume it before executing body
779        let has_procedure = matches!(
780            self.program.clauses.get(start_idx).map(|c| &c.kind),
781            Some(ClauseKind::Procedure(_))
782        );
783
784        let exec_start = if has_procedure {
785            match &self.program.clauses[start_idx].kind {
786                ClauseKind::Procedure(Some(names)) => self.env.push_procedure_expose(names),
787                ClauseKind::Procedure(None) => self.env.push_procedure(),
788                _ => unreachable!(),
789            }
790            start_idx + 1
791        } else {
792            start_idx
793        };
794
795        let result = self.exec_from(exec_start);
796
797        if has_procedure {
798            self.env.pop_procedure();
799        }
800        self.arg_stack.pop();
801
802        result
803    }
804
805    /// Try to call an external function by searching the filesystem for a `.rexx`/`.rex` file.
806    /// Returns `Ok(None)` if no external file was found, `Ok(Some(signal))` if executed.
807    fn try_call_external(
808        &mut self,
809        name: &str,
810        args: Vec<RexxValue>,
811    ) -> RexxResult<Option<ExecSignal>> {
812        // 1. Resolve external file
813        let source_dir = self.env.source_dir();
814        let Some((program, path)) = crate::external::resolve_external(name, source_dir)? else {
815            return Ok(None);
816        };
817
818        // 2. Recursion guard
819        if self.external_depth >= 100 {
820            return Err(RexxDiagnostic::new(RexxError::ResourceExhausted)
821                .with_detail("external function call recursion depth limit exceeded"));
822        }
823        self.external_depth += 1;
824
825        // 3. Push isolated scope, set args
826        self.env.push_procedure();
827        self.arg_stack.push(args);
828
829        // 4. Update source_path so nested external calls resolve relative to this file
830        let old_source_path = self.env.source_path().map(Path::to_path_buf);
831        self.env.set_source_path(path);
832
833        // 5. Build labels for external program, execute via exec_interpret_body
834        let ext_labels = Self::build_labels(&program);
835        let result = self.exec_interpret_body(&program.clauses, &ext_labels);
836
837        // 6. Restore source_path, clean up scope
838        match old_source_path {
839            Some(old) => self.env.set_source_path(old),
840            None => self.env.clear_source_path(),
841        }
842        self.arg_stack.pop();
843        self.env.pop_procedure();
844        self.external_depth -= 1;
845
846        Ok(Some(result?))
847    }
848
849    fn exec_call(&mut self, name: &str, arg_exprs: &[Expr]) -> RexxResult<ExecSignal> {
850        let mut args = Vec::with_capacity(arg_exprs.len());
851        for expr in arg_exprs {
852            args.push(self.eval_expr(expr)?);
853            if let Some(signal) = self.pending_exit.take_signal() {
854                return Ok(signal);
855            }
856        }
857
858        // CALL TRACE — handle before normal dispatch
859        if name.eq_ignore_ascii_case("TRACE") {
860            if args.len() == 1 {
861                let old = self.apply_trace_setting(args[0].as_str())?;
862                self.env.set("RESULT", RexxValue::new(old));
863            } else if args.is_empty() {
864                let old = self.trace_setting.to_string();
865                self.env.set("RESULT", RexxValue::new(old));
866            } else {
867                return Err(RexxDiagnostic::new(RexxError::IncorrectCall)
868                    .with_detail("TRACE expects 0 or 1 arguments"));
869            }
870            return Ok(ExecSignal::Normal);
871        }
872
873        // Resolution order: 1) internal labels, 2) built-in functions, 3) external, 4) error
874        if self.labels.contains_key(name) {
875            let signal = self.call_routine(name, args)?;
876            match signal {
877                ExecSignal::Return(Some(val)) => {
878                    self.env.set("RESULT", val);
879                    Ok(ExecSignal::Normal)
880                }
881                ExecSignal::Return(None) | ExecSignal::Normal => {
882                    self.env.drop("RESULT");
883                    Ok(ExecSignal::Normal)
884                }
885                ExecSignal::Exit(_) | ExecSignal::Signal(_) => Ok(signal),
886                ExecSignal::Leave(_) | ExecSignal::Iterate(_) => Ok(ExecSignal::Normal),
887            }
888        } else if let Some(result) =
889            crate::builtins::call_builtin(name, &args, &self.settings, self.env, self.queue.len())
890        {
891            let val = result?;
892            self.env.set("RESULT", val);
893            Ok(ExecSignal::Normal)
894        } else {
895            // Step 3: external function search
896            match self.try_call_external(name, args)? {
897                Some(signal) => match signal {
898                    ExecSignal::Return(Some(val)) => {
899                        self.env.set("RESULT", val);
900                        Ok(ExecSignal::Normal)
901                    }
902                    ExecSignal::Return(None) | ExecSignal::Normal => {
903                        self.env.drop("RESULT");
904                        Ok(ExecSignal::Normal)
905                    }
906                    ExecSignal::Exit(_) | ExecSignal::Signal(_) => Ok(signal),
907                    ExecSignal::Leave(_) | ExecSignal::Iterate(_) => Ok(ExecSignal::Normal),
908                },
909                None => Err(RexxDiagnostic::new(RexxError::RoutineNotFound)
910                    .with_detail(format!("routine '{name}' not found"))),
911            }
912        }
913    }
914
915    /// Execute a SIGNAL instruction.
916    fn exec_signal(&mut self, action: &SignalAction) -> RexxResult<ExecSignal> {
917        match action {
918            SignalAction::Label(label) => Ok(ExecSignal::Signal(label.clone())),
919            SignalAction::Value(expr) => {
920                let val = self.eval_expr(expr)?;
921                let label = val.as_str().to_uppercase();
922                Ok(ExecSignal::Signal(label))
923            }
924            SignalAction::On { condition, name } => {
925                let label = name
926                    .clone()
927                    .unwrap_or_else(|| Self::condition_default_label(condition));
928                self.traps.insert(condition.clone(), label);
929                Ok(ExecSignal::Normal)
930            }
931            SignalAction::Off(condition) => {
932                self.traps.remove(condition);
933                Ok(ExecSignal::Normal)
934            }
935        }
936    }
937
938    /// Default label name for a condition (the condition name itself, uppercased).
939    fn condition_default_label(condition: &Condition) -> String {
940        match condition {
941            Condition::Error => "ERROR".to_string(),
942            Condition::Failure => "FAILURE".to_string(),
943            Condition::Halt => "HALT".to_string(),
944            Condition::NoValue => "NOVALUE".to_string(),
945            Condition::NotReady => "NOTREADY".to_string(),
946            Condition::Syntax => "SYNTAX".to_string(),
947            Condition::LostDigits => "LOSTDIGITS".to_string(),
948        }
949    }
950
951    /// ARG is shorthand for PARSE UPPER ARG.
952    fn exec_arg(&mut self, template: &ParseTemplate) -> RexxResult<ExecSignal> {
953        self.exec_parse(true, &ParseSource::Arg, template)
954    }
955
956    /// Public setter so main.rs can push CLI arguments for the main program.
957    pub fn set_main_args(&mut self, args: Vec<RexxValue>) {
958        self.arg_stack.push(args);
959    }
960
961    /// Set a custom command handler for ADDRESS environments.
962    ///
963    /// The handler receives `(address_environment, command_string)` and returns:
964    /// - `Some(rc)` if it handled the command (rc is the return code)
965    /// - `None` if the command should fall through to default shell execution
966    ///
967    /// This allows embedding applications (like XEDIT) to intercept commands
968    /// sent to custom ADDRESS environments.
969    pub fn set_command_handler(&mut self, handler: CommandHandler) {
970        self.command_handler = Some(handler);
971    }
972
973    /// Set a custom command handler that receives an [`EnvVars`] handle for
974    /// reading and writing REXX variables.
975    ///
976    /// The handler receives `(address_environment, command_string, vars)` and returns:
977    /// - `Some(rc)` if it handled the command (rc is the return code)
978    /// - `None` if the command should fall through to the next handler or default shell execution
979    ///
980    /// This handler is tried before the basic `command_handler`. It allows embedding
981    /// applications (like XEDIT) to inspect and update REXX variables during command
982    /// execution — for example, refreshing EXTRACT stem variables after state-changing
983    /// commands.  The [`EnvVars`] wrapper restricts access to variable operations only,
984    /// preventing handlers from mutating ADDRESS routing or PROCEDURE scoping.
985    ///
986    /// # Panics
987    ///
988    /// Handlers **must not** panic. If a handler panics the panic propagates through
989    /// the evaluator; if the caller catches the unwind, the handler slot is left empty
990    /// and subsequent commands fall through to `command_handler` or shell execution.
991    pub fn set_command_handler_with_env(&mut self, handler: CommandHandlerWithEnv) {
992        self.command_handler_with_env = Some(handler);
993    }
994
995    // ── PARSE template engine ──────────────────────────────────────
996
997    /// Execute a PARSE instruction: resolve source, split at commas, apply templates.
998    fn exec_parse(
999        &mut self,
1000        upper: bool,
1001        source: &ParseSource,
1002        template: &ParseTemplate,
1003    ) -> RexxResult<ExecSignal> {
1004        let sub_templates = Self::split_template_at_commas(template);
1005
1006        if let ParseSource::Arg = source {
1007            let args = self.arg_stack.last().cloned().unwrap_or_default();
1008            for (i, sub_t) in sub_templates.iter().enumerate() {
1009                let raw = args
1010                    .get(i)
1011                    .map(|v| v.as_str().to_string())
1012                    .unwrap_or_default();
1013                let text = if upper { raw.to_uppercase() } else { raw };
1014                self.apply_template(&text, sub_t)?;
1015            }
1016        } else {
1017            let raw = match source {
1018                ParseSource::Var(name) => self.env.get(name).as_str().to_string(),
1019                ParseSource::Value(expr) => self.eval_expr(expr)?.as_str().to_string(),
1020                ParseSource::Pull => self.pull_from_queue_or_stdin()?,
1021                ParseSource::LineIn => self.read_stdin_line()?,
1022                ParseSource::Source => {
1023                    let filename = self
1024                        .env
1025                        .source_path()
1026                        .and_then(|p| p.file_name())
1027                        .map_or_else(|| "rexx".to_string(), |f| f.to_string_lossy().into_owned());
1028                    format!("UNIX COMMAND {filename}")
1029                }
1030                ParseSource::Version => {
1031                    format!("REXX-patch-rexx {} 8 Feb 2026", env!("CARGO_PKG_VERSION"))
1032                }
1033                ParseSource::Arg => unreachable!(),
1034            };
1035            let text = if upper { raw.to_uppercase() } else { raw };
1036            for (i, sub_t) in sub_templates.iter().enumerate() {
1037                let s = if i == 0 { &text } else { "" };
1038                self.apply_template(s, sub_t)?;
1039            }
1040        }
1041
1042        Ok(ExecSignal::Normal)
1043    }
1044
1045    /// Apply a single PARSE template to a source string.
1046    fn apply_template(&mut self, source: &str, template: &ParseTemplate) -> RexxResult<()> {
1047        let elements = &template.elements;
1048        let len = elements.len();
1049        let mut cursor: usize = 0;
1050        let mut i: usize = 0;
1051
1052        while i < len {
1053            // Collect consecutive Variable/Dot targets.
1054            let mut targets: Vec<&TemplateElement> = Vec::new();
1055            while i < len {
1056                match &elements[i] {
1057                    e @ (TemplateElement::Variable(_) | TemplateElement::Dot) => {
1058                        targets.push(e);
1059                        i += 1;
1060                    }
1061                    _ => break,
1062                }
1063            }
1064
1065            // Determine the section based on next element.
1066            if i >= len {
1067                // End of template: section = cursor..end
1068                let section = if cursor < source.len() {
1069                    &source[cursor..]
1070                } else {
1071                    ""
1072                };
1073                self.assign_section(section, &targets);
1074                break;
1075            }
1076
1077            match &elements[i] {
1078                TemplateElement::Literal(pat) => {
1079                    cursor = self.match_pattern(source, cursor, pat, &targets);
1080                    i += 1;
1081                }
1082                TemplateElement::AbsolutePos(expr) => {
1083                    let pos_val = self.eval_expr(expr)?;
1084                    let pos = self.to_position_value(&pos_val)?;
1085                    #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
1086                    let char_pos = if pos > 0 { (pos - 1) as usize } else { 0 };
1087                    let target = Self::char_pos_to_byte_offset(source, char_pos);
1088                    let section = if target > cursor {
1089                        &source[cursor..target]
1090                    } else {
1091                        ""
1092                    };
1093                    self.assign_section(section, &targets);
1094                    cursor = target;
1095                    i += 1;
1096                }
1097                TemplateElement::RelativePos(offset) => {
1098                    #[allow(clippy::cast_sign_loss)]
1099                    let target = if *offset >= 0 {
1100                        Self::advance_chars(source, cursor, *offset as usize)
1101                    } else {
1102                        Self::retreat_chars(source, cursor, offset.unsigned_abs() as usize)
1103                    };
1104                    let section = if target > cursor {
1105                        &source[cursor..target]
1106                    } else {
1107                        ""
1108                    };
1109                    self.assign_section(section, &targets);
1110                    cursor = target;
1111                    i += 1;
1112                }
1113                TemplateElement::VariablePattern(name) => {
1114                    let pat = self.env.get(name).as_str().to_string();
1115                    cursor = self.match_pattern(source, cursor, &pat, &targets);
1116                    i += 1;
1117                }
1118                _ => {
1119                    i += 1;
1120                }
1121            }
1122        }
1123        Ok(())
1124    }
1125
1126    /// Assign a section of text to target variables using REXX word-parsing rules.
1127    /// Per ANSI REXX, "blanks" include space (0x20) and horizontal tab (0x09).
1128    fn assign_section(&mut self, section: &str, targets: &[&TemplateElement]) {
1129        match targets.len() {
1130            0 => {} // no targets — just repositioning cursor
1131            1 => {
1132                // Single target gets entire section verbatim
1133                self.assign_target(targets[0], section);
1134            }
1135            _ => {
1136                // Multiple targets: word-parse
1137                let mut remaining = section;
1138                for (j, target) in targets.iter().enumerate() {
1139                    if j == targets.len() - 1 {
1140                        // Last target: strip leading blanks, take rest
1141                        let trimmed = remaining.trim_start_matches([' ', '\t']);
1142                        self.assign_target(target, trimmed);
1143                    } else {
1144                        // Non-last: strip leading blanks, take one word
1145                        let trimmed = remaining.trim_start_matches([' ', '\t']);
1146                        if let Some(blank_pos) = trimmed.find([' ', '\t']) {
1147                            let word = &trimmed[..blank_pos];
1148                            self.assign_target(target, word);
1149                            remaining = &trimmed[blank_pos..];
1150                        } else {
1151                            // No more words
1152                            self.assign_target(target, trimmed);
1153                            remaining = "";
1154                        }
1155                    }
1156                }
1157            }
1158        }
1159    }
1160
1161    /// Search for a literal pattern in source from cursor. Assigns the section
1162    /// before the match (or the rest if not found) to targets. Returns new cursor.
1163    /// Empty patterns are treated as not found per REXX semantics.
1164    fn match_pattern(
1165        &mut self,
1166        source: &str,
1167        cursor: usize,
1168        pat: &str,
1169        targets: &[&TemplateElement],
1170    ) -> usize {
1171        if !pat.is_empty()
1172            && let Some(found) = source[cursor..].find(pat)
1173        {
1174            let abs = cursor + found;
1175            self.assign_section(&source[cursor..abs], targets);
1176            abs + pat.len()
1177        } else {
1178            let section = if cursor < source.len() {
1179                &source[cursor..]
1180            } else {
1181                ""
1182            };
1183            self.assign_section(section, targets);
1184            source.len()
1185        }
1186    }
1187
1188    /// Assign a value to a single template target (Variable or Dot).
1189    fn assign_target(&mut self, target: &TemplateElement, value: &str) {
1190        if let TemplateElement::Variable(name) = target {
1191            self.env.set(name, RexxValue::new(value));
1192        }
1193        // Dot and other elements are discarded
1194    }
1195
1196    /// Split a template at Comma elements into sub-templates.
1197    /// Fast path: if no commas, return the template as-is without cloning.
1198    fn split_template_at_commas(template: &ParseTemplate) -> Vec<ParseTemplate> {
1199        if !template
1200            .elements
1201            .iter()
1202            .any(|e| matches!(e, TemplateElement::Comma))
1203        {
1204            return vec![template.clone()];
1205        }
1206        let mut result = Vec::new();
1207        let mut current = Vec::new();
1208        for elem in &template.elements {
1209            if matches!(elem, TemplateElement::Comma) {
1210                result.push(ParseTemplate {
1211                    elements: std::mem::take(&mut current),
1212                });
1213            } else {
1214                current.push(elem.clone());
1215            }
1216        }
1217        result.push(ParseTemplate { elements: current });
1218        result
1219    }
1220
1221    // ── NUMERIC execution ─────────────────────────────────────────
1222
1223    /// Execute a NUMERIC instruction: update self.settings.
1224    fn exec_numeric(&mut self, setting: &NumericSetting) -> RexxResult<ExecSignal> {
1225        match setting {
1226            NumericSetting::Digits(expr) => {
1227                let digits = if let Some(e) = expr {
1228                    let val = self.eval_expr(e)?;
1229                    if self.pending_exit.is_pending() || self.pending_signal.is_some() {
1230                        return Ok(ExecSignal::Normal);
1231                    }
1232                    let n = self.to_integer(&val)?;
1233                    if n < 1 {
1234                        return Err(RexxDiagnostic::new(RexxError::InvalidWholeNumber)
1235                            .with_detail("NUMERIC DIGITS value must be positive"));
1236                    }
1237                    if n > i64::from(u32::MAX) {
1238                        return Err(RexxDiagnostic::new(RexxError::InvalidWholeNumber)
1239                            .with_detail("NUMERIC DIGITS value too large"));
1240                    }
1241                    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
1242                    {
1243                        n as u32
1244                    }
1245                } else {
1246                    9 // default
1247                };
1248                self.settings.digits = digits;
1249            }
1250            NumericSetting::Form(form_setting) => {
1251                let form = match form_setting {
1252                    NumericFormSetting::Scientific => crate::value::NumericForm::Scientific,
1253                    NumericFormSetting::Engineering => crate::value::NumericForm::Engineering,
1254                    NumericFormSetting::Value(expr) => {
1255                        let val = self.eval_expr(expr)?;
1256                        if self.pending_exit.is_pending() || self.pending_signal.is_some() {
1257                            return Ok(ExecSignal::Normal);
1258                        }
1259                        let s = val.as_str().to_uppercase();
1260                        match s.as_str() {
1261                            "SCIENTIFIC" => crate::value::NumericForm::Scientific,
1262                            "ENGINEERING" => crate::value::NumericForm::Engineering,
1263                            _ => {
1264                                return Err(RexxDiagnostic::new(RexxError::InvalidSubKeyword)
1265                                    .with_detail(format!(
1266                                        "NUMERIC FORM value must be SCIENTIFIC or ENGINEERING; got '{s}'"
1267                                    )));
1268                            }
1269                        }
1270                    }
1271                };
1272                self.settings.form = form;
1273            }
1274            NumericSetting::Fuzz(expr) => {
1275                let fuzz = if let Some(e) = expr {
1276                    let val = self.eval_expr(e)?;
1277                    if self.pending_exit.is_pending() || self.pending_signal.is_some() {
1278                        return Ok(ExecSignal::Normal);
1279                    }
1280                    let n = self.to_integer(&val)?;
1281                    if n < 0 {
1282                        return Err(RexxDiagnostic::new(RexxError::InvalidWholeNumber)
1283                            .with_detail("NUMERIC FUZZ value must not be negative"));
1284                    }
1285                    if n > i64::from(u32::MAX) {
1286                        return Err(RexxDiagnostic::new(RexxError::InvalidWholeNumber)
1287                            .with_detail("NUMERIC FUZZ value too large"));
1288                    }
1289                    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
1290                    {
1291                        n as u32
1292                    }
1293                } else {
1294                    0 // default
1295                };
1296                if fuzz >= self.settings.digits {
1297                    return Err(RexxDiagnostic::new(RexxError::InvalidWholeNumber)
1298                        .with_detail("NUMERIC FUZZ must be less than NUMERIC DIGITS"));
1299                }
1300                self.settings.fuzz = fuzz;
1301            }
1302        }
1303        Ok(ExecSignal::Normal)
1304    }
1305
1306    // ── PUSH / QUEUE / PULL execution ────────────────────────────────
1307
1308    /// Execute PUSH: evaluate expr, add to front of queue (LIFO).
1309    fn exec_push(&mut self, expr: Option<&Expr>) -> RexxResult<ExecSignal> {
1310        let val = if let Some(e) = expr {
1311            let v = self.eval_expr(e)?;
1312            if self.pending_exit.is_pending() || self.pending_signal.is_some() {
1313                return Ok(ExecSignal::Normal);
1314            }
1315            v.as_str().to_string()
1316        } else {
1317            String::new()
1318        };
1319        self.queue.push_front(val);
1320        Ok(ExecSignal::Normal)
1321    }
1322
1323    /// Execute QUEUE: evaluate expr, add to back of queue (FIFO).
1324    fn exec_queue(&mut self, expr: Option<&Expr>) -> RexxResult<ExecSignal> {
1325        let val = if let Some(e) = expr {
1326            let v = self.eval_expr(e)?;
1327            if self.pending_exit.is_pending() || self.pending_signal.is_some() {
1328                return Ok(ExecSignal::Normal);
1329            }
1330            v.as_str().to_string()
1331        } else {
1332            String::new()
1333        };
1334        self.queue.push_back(val);
1335        Ok(ExecSignal::Normal)
1336    }
1337
1338    /// Pull from the external data queue; if empty, read from stdin.
1339    fn pull_from_queue_or_stdin(&mut self) -> RexxResult<String> {
1340        if let Some(line) = self.queue.pop_front() {
1341            Ok(line)
1342        } else {
1343            self.read_stdin_line()
1344        }
1345    }
1346
1347    /// Return the current queue length (for `QUEUED()` BIF).
1348    pub fn queue_len(&self) -> usize {
1349        self.queue.len()
1350    }
1351
1352    /// Read one line from stdin, stripping the trailing newline.
1353    #[allow(clippy::unused_self)]
1354    fn read_stdin_line(&self) -> RexxResult<String> {
1355        let mut line = String::new();
1356        std::io::stdin().read_line(&mut line).map_err(|e| {
1357            RexxDiagnostic::new(RexxError::SystemFailure)
1358                .with_detail(format!("failed to read stdin: {e}"))
1359        })?;
1360        if line.ends_with('\n') {
1361            line.pop();
1362            if line.ends_with('\r') {
1363                line.pop();
1364            }
1365        }
1366        Ok(line)
1367    }
1368
1369    /// Convert a 0-based character position to a byte offset in a UTF-8 string.
1370    /// Clamps to `source.len()` if the character position exceeds the string length.
1371    fn char_pos_to_byte_offset(source: &str, char_pos: usize) -> usize {
1372        source
1373            .char_indices()
1374            .nth(char_pos)
1375            .map_or(source.len(), |(byte_offset, _)| byte_offset)
1376    }
1377
1378    /// Advance `n` characters forward from `byte_cursor` and return the new byte offset.
1379    fn advance_chars(source: &str, byte_cursor: usize, n: usize) -> usize {
1380        let clamped = byte_cursor.min(source.len());
1381        source[clamped..]
1382            .char_indices()
1383            .nth(n)
1384            .map_or(source.len(), |(offset, _)| clamped + offset)
1385    }
1386
1387    /// Retreat `n` characters backward from `byte_cursor` and return the new byte offset.
1388    fn retreat_chars(source: &str, byte_cursor: usize, n: usize) -> usize {
1389        if n == 0 {
1390            return byte_cursor.min(source.len());
1391        }
1392        let clamped = byte_cursor.min(source.len());
1393        source[..clamped]
1394            .char_indices()
1395            .map(|(i, _)| i)
1396            .rev()
1397            .nth(n - 1)
1398            .unwrap_or(0)
1399    }
1400
1401    /// Convert a value to a position (integer) for PARSE template positioning.
1402    fn to_position_value(&self, val: &RexxValue) -> RexxResult<i64> {
1403        let d = self.to_number(val)?;
1404        let rounded = d.round(0);
1405        if d != rounded {
1406            return Err(RexxDiagnostic::new(RexxError::InvalidWholeNumber)
1407                .with_detail(format!("'{val}' is not a whole number")));
1408        }
1409        let s = rounded.to_string();
1410        s.parse::<i64>().map_err(|_| {
1411            RexxDiagnostic::new(RexxError::InvalidWholeNumber)
1412                .with_detail(format!("'{val}' is not a valid position"))
1413        })
1414    }
1415
1416    fn exec_if(
1417        &mut self,
1418        condition: &Expr,
1419        then_clause: &Clause,
1420        else_clause: Option<&Clause>,
1421    ) -> RexxResult<ExecSignal> {
1422        let cond_val = self.eval_expr(condition)?;
1423        let b = to_logical(&cond_val)?;
1424        if b {
1425            self.exec_clause(then_clause)
1426        } else if let Some(else_c) = else_clause {
1427            self.exec_clause(else_c)
1428        } else {
1429            Ok(ExecSignal::Normal)
1430        }
1431    }
1432
1433    fn exec_do(&mut self, block: &DoBlock) -> RexxResult<ExecSignal> {
1434        match &block.kind {
1435            DoKind::Simple => {
1436                let signal = self.exec_body(&block.body)?;
1437                Ok(signal)
1438            }
1439            DoKind::Forever => self.exec_do_forever(block),
1440            DoKind::Count(expr) => self.exec_do_count(expr, block),
1441            DoKind::While(expr) => self.exec_do_while(expr, block),
1442            DoKind::Until(expr) => self.exec_do_until(expr, block),
1443            DoKind::Controlled(ctrl) => self.exec_do_controlled(ctrl, block),
1444        }
1445    }
1446
1447    fn exec_do_forever(&mut self, block: &DoBlock) -> RexxResult<ExecSignal> {
1448        loop {
1449            let signal = self.exec_body(&block.body)?;
1450            match signal {
1451                ExecSignal::Normal => {}
1452                ExecSignal::Leave(ref name) => {
1453                    if Self::signal_matches(name.as_ref(), block.name.as_ref()) {
1454                        return Ok(ExecSignal::Normal);
1455                    }
1456                    return Ok(signal);
1457                }
1458                ExecSignal::Iterate(ref name) => {
1459                    if Self::signal_matches(name.as_ref(), block.name.as_ref()) {
1460                        continue;
1461                    }
1462                    return Ok(signal);
1463                }
1464                ExecSignal::Exit(_) | ExecSignal::Return(_) | ExecSignal::Signal(_) => {
1465                    return Ok(signal);
1466                }
1467            }
1468        }
1469    }
1470
1471    fn exec_do_count(&mut self, count_expr: &Expr, block: &DoBlock) -> RexxResult<ExecSignal> {
1472        let count_val = self.eval_expr(count_expr)?;
1473        let count = self.to_integer(&count_val)?;
1474        for _ in 0..count {
1475            let signal = self.exec_body(&block.body)?;
1476            match signal {
1477                ExecSignal::Normal => {}
1478                ExecSignal::Leave(ref name) => {
1479                    if Self::signal_matches(name.as_ref(), block.name.as_ref()) {
1480                        return Ok(ExecSignal::Normal);
1481                    }
1482                    return Ok(signal);
1483                }
1484                ExecSignal::Iterate(ref name) => {
1485                    if Self::signal_matches(name.as_ref(), block.name.as_ref()) {
1486                        continue;
1487                    }
1488                    return Ok(signal);
1489                }
1490                ExecSignal::Exit(_) | ExecSignal::Return(_) | ExecSignal::Signal(_) => {
1491                    return Ok(signal);
1492                }
1493            }
1494        }
1495        Ok(ExecSignal::Normal)
1496    }
1497
1498    fn exec_do_while(&mut self, cond_expr: &Expr, block: &DoBlock) -> RexxResult<ExecSignal> {
1499        loop {
1500            let cond_val = self.eval_expr(cond_expr)?;
1501            if !to_logical(&cond_val)? {
1502                break;
1503            }
1504            let signal = self.exec_body(&block.body)?;
1505            match signal {
1506                ExecSignal::Normal => {}
1507                ExecSignal::Leave(ref name) => {
1508                    if Self::signal_matches(name.as_ref(), block.name.as_ref()) {
1509                        return Ok(ExecSignal::Normal);
1510                    }
1511                    return Ok(signal);
1512                }
1513                ExecSignal::Iterate(ref name) => {
1514                    if Self::signal_matches(name.as_ref(), block.name.as_ref()) {
1515                        continue;
1516                    }
1517                    return Ok(signal);
1518                }
1519                ExecSignal::Exit(_) | ExecSignal::Return(_) | ExecSignal::Signal(_) => {
1520                    return Ok(signal);
1521                }
1522            }
1523        }
1524        Ok(ExecSignal::Normal)
1525    }
1526
1527    fn exec_do_until(&mut self, cond_expr: &Expr, block: &DoBlock) -> RexxResult<ExecSignal> {
1528        loop {
1529            let signal = self.exec_body(&block.body)?;
1530            match signal {
1531                ExecSignal::Normal => {}
1532                ExecSignal::Leave(ref name) => {
1533                    if Self::signal_matches(name.as_ref(), block.name.as_ref()) {
1534                        return Ok(ExecSignal::Normal);
1535                    }
1536                    return Ok(signal);
1537                }
1538                ExecSignal::Iterate(ref name) => {
1539                    if !Self::signal_matches(name.as_ref(), block.name.as_ref()) {
1540                        return Ok(signal);
1541                    }
1542                    // ITERATE matched: continue to UNTIL check
1543                }
1544                ExecSignal::Exit(_) | ExecSignal::Return(_) | ExecSignal::Signal(_) => {
1545                    return Ok(signal);
1546                }
1547            }
1548            let cond_val = self.eval_expr(cond_expr)?;
1549            if to_logical(&cond_val)? {
1550                break;
1551            }
1552        }
1553        Ok(ExecSignal::Normal)
1554    }
1555
1556    #[allow(clippy::too_many_lines)]
1557    fn exec_do_controlled(
1558        &mut self,
1559        ctrl: &ControlledLoop,
1560        block: &DoBlock,
1561    ) -> RexxResult<ExecSignal> {
1562        // Evaluate start value
1563        let start_val = self.eval_expr(&ctrl.start)?;
1564        let start_num = self.to_number(&start_val)?;
1565
1566        // Evaluate TO limit
1567        let to_num = if let Some(ref to_expr) = ctrl.to {
1568            let v = self.eval_expr(to_expr)?;
1569            Some(self.to_number(&v)?)
1570        } else {
1571            None
1572        };
1573
1574        // Evaluate BY step (default 1)
1575        let by_num = if let Some(ref by_expr) = ctrl.by {
1576            let v = self.eval_expr(by_expr)?;
1577            self.to_number(&v)?
1578        } else {
1579            BigDecimal::from(1)
1580        };
1581
1582        // REXX requires BY to be non-zero
1583        if by_num.is_zero() {
1584            return Err(RexxDiagnostic::new(RexxError::InvalidWholeNumber)
1585                .with_detail("BY value in DO loop must not be zero"));
1586        }
1587
1588        // Evaluate FOR count
1589        let for_count = if let Some(ref for_expr) = ctrl.r#for {
1590            let v = self.eval_expr(for_expr)?;
1591            Some(self.to_integer(&v)?)
1592        } else {
1593            None
1594        };
1595
1596        // Set the control variable
1597        let mut current = start_num;
1598        let mut iterations: i64 = 0;
1599
1600        loop {
1601            // Check TO limit before executing body.
1602            // BY is guaranteed non-zero (checked earlier).
1603            if let Some(ref limit) = to_num {
1604                let positive_step = by_num.sign() == bigdecimal::num_bigint::Sign::Plus;
1605                if positive_step {
1606                    if current > *limit {
1607                        break;
1608                    }
1609                } else if current < *limit {
1610                    break;
1611                }
1612            }
1613
1614            // Check FOR count
1615            if let Some(max) = for_count
1616                && iterations >= max
1617            {
1618                break;
1619            }
1620
1621            // Set control variable
1622            self.env.set(
1623                &ctrl.var,
1624                RexxValue::from_decimal(&current, self.settings.digits, self.settings.form),
1625            );
1626
1627            // Check WHILE condition
1628            if let Some(ref while_expr) = ctrl.while_cond {
1629                let v = self.eval_expr(while_expr)?;
1630                if !to_logical(&v)? {
1631                    break;
1632                }
1633            }
1634
1635            // Execute body
1636            let signal = self.exec_body(&block.body)?;
1637            match signal {
1638                ExecSignal::Normal => {}
1639                ExecSignal::Leave(ref name) => {
1640                    if Self::signal_matches(name.as_ref(), block.name.as_ref()) {
1641                        return Ok(ExecSignal::Normal);
1642                    }
1643                    return Ok(signal);
1644                }
1645                ExecSignal::Iterate(ref name) => {
1646                    if !Self::signal_matches(name.as_ref(), block.name.as_ref()) {
1647                        return Ok(signal);
1648                    }
1649                    // ITERATE matched: fall through to increment
1650                }
1651                ExecSignal::Exit(_) | ExecSignal::Return(_) | ExecSignal::Signal(_) => {
1652                    return Ok(signal);
1653                }
1654            }
1655
1656            // Check UNTIL condition (after body, before increment).
1657            // Per ANSI REXX §7.3.5, when UNTIL is satisfied the loop
1658            // terminates immediately — the control variable retains its
1659            // current value (the increment below is skipped via break).
1660            if let Some(ref until_expr) = ctrl.until_cond {
1661                let v = self.eval_expr(until_expr)?;
1662                if to_logical(&v)? {
1663                    break;
1664                }
1665            }
1666
1667            // Increment (only reached if UNTIL was false or absent)
1668            current += &by_num;
1669            iterations = iterations.saturating_add(1);
1670        }
1671
1672        // Set final value of control variable (may be one-past-limit
1673        // for TO termination, or last body value for UNTIL termination).
1674        self.env.set(
1675            &ctrl.var,
1676            RexxValue::from_decimal(&current, self.settings.digits, self.settings.form),
1677        );
1678
1679        Ok(ExecSignal::Normal)
1680    }
1681
1682    fn exec_select(
1683        &mut self,
1684        when_clauses: &[(Expr, Vec<Clause>)],
1685        otherwise: Option<&Vec<Clause>>,
1686    ) -> RexxResult<ExecSignal> {
1687        for (condition, body) in when_clauses {
1688            let val = self.eval_expr(condition)?;
1689            if to_logical(&val)? {
1690                return self.exec_body(body);
1691            }
1692        }
1693        if let Some(body) = otherwise {
1694            return self.exec_body(body);
1695        }
1696        Err(RexxDiagnostic::new(RexxError::ExpectedWhenOtherwise)
1697            .with_detail("no WHEN matched and no OTHERWISE in SELECT"))
1698    }
1699
1700    /// Check if a LEAVE or ITERATE signal name matches this loop's name.
1701    /// Unnamed signals (None) match any loop; named signals match only
1702    /// if the loop has the same name.
1703    fn signal_matches(signal_name: Option<&String>, loop_name: Option<&String>) -> bool {
1704        match signal_name {
1705            None => true,
1706            Some(name) => loop_name.is_some_and(|ln| ln == name),
1707        }
1708    }
1709
1710    /// Convert a value to a non-negative whole number for loop counts.
1711    /// Per ANSI REXX, loop counts and FOR values must be whole numbers.
1712    fn to_integer(&self, val: &RexxValue) -> RexxResult<i64> {
1713        let d = self.to_number(val)?;
1714        let rounded = d.round(0);
1715        if d != rounded {
1716            return Err(RexxDiagnostic::new(RexxError::InvalidWholeNumber)
1717                .with_detail("loop count must be a whole number"));
1718        }
1719        let s = rounded.to_string();
1720        let n = s.parse::<i64>().map_err(|_| {
1721            RexxDiagnostic::new(RexxError::ArithmeticOverflow)
1722                .with_detail(format!("'{rounded}' is too large for a loop count"))
1723        })?;
1724        if n < 0 {
1725            return Err(RexxDiagnostic::new(RexxError::InvalidWholeNumber)
1726                .with_detail(format!("loop count must not be negative (got {n})")));
1727        }
1728        Ok(n)
1729    }
1730
1731    fn resolve_tail(&self, tail: &[TailElement]) -> String {
1732        tail.iter()
1733            .map(|elem| match elem {
1734                TailElement::Const(c) => c.clone(),
1735                TailElement::Var(v) => self.env.get(v).into_string(),
1736            })
1737            .collect::<Vec<_>>()
1738            .join(".")
1739    }
1740
1741    #[allow(clippy::too_many_lines)]
1742    fn eval_expr(&mut self, expr: &Expr) -> RexxResult<RexxValue> {
1743        match expr {
1744            Expr::StringLit(s) => {
1745                let val = RexxValue::new(s.clone());
1746                self.trace_intermediates("L", val.as_str());
1747                Ok(val)
1748            }
1749            Expr::Number(n) => {
1750                let val = RexxValue::new(n.clone());
1751                self.trace_intermediates("L", val.as_str());
1752                Ok(val)
1753            }
1754            Expr::Symbol(name) => {
1755                if !self.env.is_set(name)
1756                    && let Some(label) = self.traps.get(&Condition::NoValue).cloned()
1757                {
1758                    // Set condition info before firing
1759                    self.env.set_condition_info(crate::env::ConditionInfoData {
1760                        condition: "NOVALUE".to_string(),
1761                        description: name.clone(),
1762                        instruction: "SIGNAL".to_string(),
1763                        status: "ON".to_string(),
1764                    });
1765                    // Disable the trap (fires once per REXX spec)
1766                    self.traps.remove(&Condition::NoValue);
1767                    self.pending_signal = Some(label);
1768                }
1769                let val = self.env.get(name);
1770                self.trace_intermediates("V", val.as_str());
1771                Ok(val)
1772            }
1773            Expr::Compound { stem, tail } => {
1774                let resolved = self.resolve_tail(tail);
1775                if !self.env.is_compound_set(stem, &resolved)
1776                    && let Some(label) = self.traps.get(&Condition::NoValue).cloned()
1777                {
1778                    let compound_name = format!("{}.{}", stem.to_uppercase(), resolved);
1779                    self.env.set_condition_info(crate::env::ConditionInfoData {
1780                        condition: "NOVALUE".to_string(),
1781                        description: compound_name,
1782                        instruction: "SIGNAL".to_string(),
1783                        status: "ON".to_string(),
1784                    });
1785                    self.traps.remove(&Condition::NoValue);
1786                    self.pending_signal = Some(label);
1787                }
1788                let val = self.env.get_compound(stem, &resolved);
1789                self.trace_intermediates("C", val.as_str());
1790                Ok(val)
1791            }
1792            Expr::Paren(inner) => self.eval_expr(inner),
1793            Expr::UnaryOp { op, operand } => {
1794                let val = self.eval_expr(operand)?;
1795                if self.pending_signal.is_some() {
1796                    return Ok(val);
1797                }
1798                let result = self.eval_unary(*op, &val)?;
1799                self.trace_intermediates("P", result.as_str());
1800                Ok(result)
1801            }
1802            Expr::BinOp { left, op, right } => {
1803                let lval = self.eval_expr(left)?;
1804                if self.pending_signal.is_some() {
1805                    return Ok(lval);
1806                }
1807                let rval = self.eval_expr(right)?;
1808                if self.pending_signal.is_some() {
1809                    return Ok(rval);
1810                }
1811                let result = self.eval_binop(*op, &lval, &rval)?;
1812                self.trace_intermediates("O", result.as_str());
1813                Ok(result)
1814            }
1815            Expr::FunctionCall { name, args } => {
1816                let mut evaluated_args = Vec::with_capacity(args.len());
1817                for arg_expr in args {
1818                    evaluated_args.push(self.eval_expr(arg_expr)?);
1819                    if self.pending_exit.is_pending() || self.pending_signal.is_some() {
1820                        return Ok(RexxValue::new(""));
1821                    }
1822                }
1823                // TRACE() BIF — needs evaluator state, handle before normal dispatch
1824                if name.eq_ignore_ascii_case("TRACE") {
1825                    if evaluated_args.len() == 1 {
1826                        let old = self.apply_trace_setting(evaluated_args[0].as_str())?;
1827                        return Ok(RexxValue::new(old));
1828                    } else if !evaluated_args.is_empty() {
1829                        return Err(RexxDiagnostic::new(RexxError::IncorrectCall)
1830                            .with_detail("TRACE expects 0 or 1 arguments"));
1831                    }
1832                    return Ok(RexxValue::new(self.trace_setting.to_string()));
1833                }
1834
1835                // Resolution order: 1) internal labels, 2) built-in functions, 3) external, 4) error
1836                if self.labels.contains_key(name.as_str()) {
1837                    let signal = self.call_routine(name, evaluated_args)?;
1838                    match signal {
1839                        ExecSignal::Return(Some(val)) => {
1840                            self.trace_intermediates("F", val.as_str());
1841                            Ok(val)
1842                        }
1843                        ExecSignal::Return(None) | ExecSignal::Normal => {
1844                            Err(RexxDiagnostic::new(RexxError::NoReturnData)
1845                                .with_detail(format!("function '{name}' did not return data")))
1846                        }
1847                        ExecSignal::Exit(val) => {
1848                            self.pending_exit = PendingExit::WithValue(val);
1849                            Ok(RexxValue::new(""))
1850                        }
1851                        ExecSignal::Signal(_) => {
1852                            // Propagate signal as pending — we can't return ExecSignal from eval_expr
1853                            if let ExecSignal::Signal(label) = signal {
1854                                self.pending_signal = Some(label);
1855                            }
1856                            Ok(RexxValue::new(""))
1857                        }
1858                        ExecSignal::Leave(_) | ExecSignal::Iterate(_) => Ok(RexxValue::new("")),
1859                    }
1860                } else if let Some(result) = crate::builtins::call_builtin(
1861                    name,
1862                    &evaluated_args,
1863                    &self.settings,
1864                    self.env,
1865                    self.queue.len(),
1866                ) {
1867                    let val = result?;
1868                    self.trace_intermediates("F", val.as_str());
1869                    Ok(val)
1870                } else {
1871                    // Step 3: external function search
1872                    match self.try_call_external(name, evaluated_args)? {
1873                        Some(signal) => match signal {
1874                            ExecSignal::Return(Some(val)) => {
1875                                self.trace_intermediates("F", val.as_str());
1876                                Ok(val)
1877                            }
1878                            ExecSignal::Return(None) | ExecSignal::Normal => {
1879                                Err(RexxDiagnostic::new(RexxError::NoReturnData)
1880                                    .with_detail(format!("function '{name}' did not return data")))
1881                            }
1882                            ExecSignal::Exit(val) => {
1883                                self.pending_exit = PendingExit::WithValue(val);
1884                                Ok(RexxValue::new(""))
1885                            }
1886                            ExecSignal::Signal(_) => {
1887                                if let ExecSignal::Signal(label) = signal {
1888                                    self.pending_signal = Some(label);
1889                                }
1890                                Ok(RexxValue::new(""))
1891                            }
1892                            ExecSignal::Leave(_) | ExecSignal::Iterate(_) => Ok(RexxValue::new("")),
1893                        },
1894                        None => Err(RexxDiagnostic::new(RexxError::RoutineNotFound)
1895                            .with_detail(format!("routine '{name}' not found"))),
1896                    }
1897                }
1898            }
1899        }
1900    }
1901
1902    fn eval_unary(&self, op: UnaryOp, val: &RexxValue) -> RexxResult<RexxValue> {
1903        match op {
1904            UnaryOp::Plus => {
1905                // Force numeric validation
1906                let d = val.to_decimal().ok_or_else(|| {
1907                    RexxDiagnostic::new(RexxError::BadArithmetic)
1908                        .with_detail(format!("'{}' is not a number", val.as_str()))
1909                })?;
1910                Ok(RexxValue::from_decimal(
1911                    &d,
1912                    self.settings.digits,
1913                    self.settings.form,
1914                ))
1915            }
1916            UnaryOp::Minus => {
1917                let d = val.to_decimal().ok_or_else(|| {
1918                    RexxDiagnostic::new(RexxError::BadArithmetic)
1919                        .with_detail(format!("'{}' is not a number", val.as_str()))
1920                })?;
1921                let neg = -d;
1922                Ok(RexxValue::from_decimal(
1923                    &neg,
1924                    self.settings.digits,
1925                    self.settings.form,
1926                ))
1927            }
1928            UnaryOp::Not => {
1929                let s = val.as_str().trim();
1930                match s {
1931                    "0" => Ok(RexxValue::new("1")),
1932                    "1" => Ok(RexxValue::new("0")),
1933                    _ => Err(RexxDiagnostic::new(RexxError::InvalidLogicalValue)
1934                        .with_detail(format!("'{}' is not 0 or 1", val.as_str()))),
1935                }
1936            }
1937        }
1938    }
1939
1940    #[allow(clippy::too_many_lines)]
1941    fn eval_binop(&self, op: BinOp, left: &RexxValue, right: &RexxValue) -> RexxResult<RexxValue> {
1942        match op {
1943            // Arithmetic
1944            BinOp::Add => self.arithmetic(left, right, |a, b| a + b),
1945            BinOp::Sub => self.arithmetic(left, right, |a, b| a - b),
1946            BinOp::Mul => self.arithmetic(left, right, |a, b| a * b),
1947            BinOp::Div => {
1948                let b = right.to_decimal().ok_or_else(|| {
1949                    RexxDiagnostic::new(RexxError::BadArithmetic)
1950                        .with_detail(format!("'{}' is not a number", right.as_str()))
1951                })?;
1952                if b.is_zero() {
1953                    return Err(RexxDiagnostic::new(RexxError::ArithmeticOverflow)
1954                        .with_detail("division by zero"));
1955                }
1956                let a = left.to_decimal().ok_or_else(|| {
1957                    RexxDiagnostic::new(RexxError::BadArithmetic)
1958                        .with_detail(format!("'{}' is not a number", left.as_str()))
1959                })?;
1960                let result = a / b;
1961                Ok(RexxValue::from_decimal(
1962                    &result,
1963                    self.settings.digits,
1964                    self.settings.form,
1965                ))
1966            }
1967            BinOp::IntDiv => {
1968                let a = self.to_number(left)?;
1969                let b = self.to_number(right)?;
1970                if b.is_zero() {
1971                    return Err(RexxDiagnostic::new(RexxError::ArithmeticOverflow)
1972                        .with_detail("division by zero"));
1973                }
1974                // REXX integer division truncates toward zero
1975                let result = trunc_div(&a, &b);
1976                Ok(RexxValue::from_decimal(
1977                    &result,
1978                    self.settings.digits,
1979                    self.settings.form,
1980                ))
1981            }
1982            BinOp::Remainder => {
1983                let a = self.to_number(left)?;
1984                let b = self.to_number(right)?;
1985                if b.is_zero() {
1986                    return Err(RexxDiagnostic::new(RexxError::ArithmeticOverflow)
1987                        .with_detail("division by zero"));
1988                }
1989                // REXX remainder: a - (a%b)*b where % truncates toward zero
1990                let int_div = trunc_div(&a, &b);
1991                let result = &a - &int_div * &b;
1992                Ok(RexxValue::from_decimal(
1993                    &result,
1994                    self.settings.digits,
1995                    self.settings.form,
1996                ))
1997            }
1998            BinOp::Power => {
1999                let base = self.to_number(left)?;
2000                let exp_val = self.to_number(right)?;
2001                // REXX requires whole-number exponent
2002                let exp_rounded = exp_val.round(0);
2003                if exp_val != exp_rounded {
2004                    return Err(RexxDiagnostic::new(RexxError::InvalidWholeNumber)
2005                        .with_detail("exponent must be a whole number"));
2006                }
2007                let exp_i64: i64 = exp_rounded.to_string().parse().map_err(|_| {
2008                    RexxDiagnostic::new(RexxError::ArithmeticOverflow)
2009                        .with_detail("exponent too large")
2010                })?;
2011                // Limit exponent to prevent OOM from massive intermediate values.
2012                // 1_000_000 is generous for practical REXX use cases.
2013                if exp_i64.abs() > 1_000_000 {
2014                    return Err(RexxDiagnostic::new(RexxError::ArithmeticOverflow)
2015                        .with_detail("exponent exceeds limits"));
2016                }
2017                if base.is_zero() && exp_i64 < 0 {
2018                    return Err(RexxDiagnostic::new(RexxError::ArithmeticOverflow)
2019                        .with_detail("zero raised to a negative power"));
2020                }
2021                let result = pow_bigdecimal(&base, exp_i64);
2022                Ok(RexxValue::from_decimal(
2023                    &result,
2024                    self.settings.digits,
2025                    self.settings.form,
2026                ))
2027            }
2028
2029            // Concatenation
2030            BinOp::Concat => {
2031                let s = format!("{}{}", left.as_str(), right.as_str());
2032                Ok(RexxValue::new(s))
2033            }
2034            BinOp::ConcatBlank => {
2035                let s = format!("{} {}", left.as_str(), right.as_str());
2036                Ok(RexxValue::new(s))
2037            }
2038
2039            // Normal comparison
2040            BinOp::Eq => Ok(bool_val(
2041                normal_compare(left, right) == std::cmp::Ordering::Equal,
2042            )),
2043            BinOp::NotEq => Ok(bool_val(
2044                normal_compare(left, right) != std::cmp::Ordering::Equal,
2045            )),
2046            BinOp::Gt => Ok(bool_val(
2047                normal_compare(left, right) == std::cmp::Ordering::Greater,
2048            )),
2049            BinOp::Lt => Ok(bool_val(
2050                normal_compare(left, right) == std::cmp::Ordering::Less,
2051            )),
2052            BinOp::GtEq => Ok(bool_val(
2053                normal_compare(left, right) != std::cmp::Ordering::Less,
2054            )),
2055            BinOp::LtEq => Ok(bool_val(
2056                normal_compare(left, right) != std::cmp::Ordering::Greater,
2057            )),
2058
2059            // Strict comparison
2060            BinOp::StrictEq => Ok(bool_val(left.as_str() == right.as_str())),
2061            BinOp::StrictNotEq => Ok(bool_val(left.as_str() != right.as_str())),
2062            BinOp::StrictGt => Ok(bool_val(left.as_str() > right.as_str())),
2063            BinOp::StrictLt => Ok(bool_val(left.as_str() < right.as_str())),
2064            BinOp::StrictGtEq => Ok(bool_val(left.as_str() >= right.as_str())),
2065            BinOp::StrictLtEq => Ok(bool_val(left.as_str() <= right.as_str())),
2066
2067            // Logical
2068            BinOp::And => {
2069                let l = to_logical(left)?;
2070                let r = to_logical(right)?;
2071                Ok(bool_val(l && r))
2072            }
2073            BinOp::Or => {
2074                let l = to_logical(left)?;
2075                let r = to_logical(right)?;
2076                Ok(bool_val(l || r))
2077            }
2078            BinOp::Xor => {
2079                let l = to_logical(left)?;
2080                let r = to_logical(right)?;
2081                Ok(bool_val(l ^ r))
2082            }
2083        }
2084    }
2085
2086    fn arithmetic(
2087        &self,
2088        left: &RexxValue,
2089        right: &RexxValue,
2090        f: impl FnOnce(BigDecimal, BigDecimal) -> BigDecimal,
2091    ) -> RexxResult<RexxValue> {
2092        let a = self.to_number(left)?;
2093        let b = self.to_number(right)?;
2094        let result = f(a, b);
2095        Ok(RexxValue::from_decimal(
2096            &result,
2097            self.settings.digits,
2098            self.settings.form,
2099        ))
2100    }
2101
2102    #[allow(clippy::unused_self)]
2103    fn to_number(&self, val: &RexxValue) -> RexxResult<BigDecimal> {
2104        val.to_decimal().ok_or_else(|| {
2105            RexxDiagnostic::new(RexxError::BadArithmetic)
2106                .with_detail(format!("'{}' is not a number", val.as_str()))
2107        })
2108    }
2109}
2110
2111/// Convert a `RexxValue` to a boolean, requiring it to be "0" or "1".
2112fn to_logical(val: &RexxValue) -> RexxResult<bool> {
2113    match val.as_str().trim() {
2114        "0" => Ok(false),
2115        "1" => Ok(true),
2116        _ => Err(RexxDiagnostic::new(RexxError::InvalidLogicalValue)
2117            .with_detail(format!("'{}' is not 0 or 1", val.as_str()))),
2118    }
2119}
2120
2121/// Produce a REXX boolean value: "1" for true, "0" for false.
2122fn bool_val(b: bool) -> RexxValue {
2123    RexxValue::new(if b { "1" } else { "0" })
2124}
2125
2126/// REXX normal comparison: strip leading/trailing blanks from both sides,
2127/// then if both are valid numbers, compare numerically;
2128/// otherwise pad the shorter with blanks and compare character-by-character.
2129fn normal_compare(left: &RexxValue, right: &RexxValue) -> std::cmp::Ordering {
2130    let ls = left.as_str().trim();
2131    let rs = right.as_str().trim();
2132
2133    // Try numeric comparison first
2134    if let (Some(ld), Some(rd)) = (BigDecimal::from_str(ls).ok(), BigDecimal::from_str(rs).ok()) {
2135        return ld.cmp(&rd);
2136    }
2137
2138    // String comparison: pad shorter with trailing blanks
2139    let max_len = ls.len().max(rs.len());
2140    let lp: String = format!("{ls:<max_len$}");
2141    let rp: String = format!("{rs:<max_len$}");
2142    lp.cmp(&rp)
2143}
2144
2145/// REXX integer division: divide and truncate toward zero.
2146fn trunc_div(a: &BigDecimal, b: &BigDecimal) -> BigDecimal {
2147    let quotient = a / b;
2148    // RoundingMode::Down truncates toward zero (not toward negative infinity).
2149    // Using with_scale_round avoids string-based truncation that breaks on
2150    // scientific notation from BigDecimal::to_string().
2151    quotient.with_scale_round(0, bigdecimal::RoundingMode::Down)
2152}
2153
2154/// Compute base ** exp for `BigDecimal` with integer exponent.
2155fn pow_bigdecimal(base: &BigDecimal, exp: i64) -> BigDecimal {
2156    if exp == 0 {
2157        return BigDecimal::from(1);
2158    }
2159    if exp < 0 {
2160        let pos_result = pow_bigdecimal(base, -exp);
2161        return BigDecimal::from(1) / pos_result;
2162    }
2163    let mut result = BigDecimal::from(1);
2164    let mut b = base.clone();
2165    let mut e = exp;
2166    // Exponentiation by squaring
2167    while e > 0 {
2168        if e & 1 == 1 {
2169            result *= &b;
2170        }
2171        b = &b * &b;
2172        e >>= 1;
2173    }
2174    result
2175}
2176
2177#[cfg(test)]
2178mod tests {
2179    use super::*;
2180    use crate::lexer::Lexer;
2181    use crate::parser::Parser;
2182
2183    fn eval_expr(src: &str) -> RexxValue {
2184        let mut env = Environment::new();
2185        let mut lexer = Lexer::new(src);
2186        let tokens = lexer.tokenize().unwrap();
2187        let mut parser = Parser::new(tokens);
2188        let program = parser.parse().unwrap();
2189        let mut eval = Evaluator::new(&mut env, &program);
2190        // Evaluate as command and extract the value
2191        match &program.clauses[0].kind {
2192            ClauseKind::Command(expr) => eval.eval_expr(expr).unwrap(),
2193            _ => panic!("expected command clause"),
2194        }
2195    }
2196
2197    #[test]
2198    fn eval_addition() {
2199        let val = eval_expr("2 + 3");
2200        assert_eq!(val.as_str(), "5");
2201    }
2202
2203    #[test]
2204    fn eval_subtraction() {
2205        let val = eval_expr("10 - 4");
2206        assert_eq!(val.as_str(), "6");
2207    }
2208
2209    #[test]
2210    fn eval_multiplication() {
2211        let val = eval_expr("3 * 7");
2212        assert_eq!(val.as_str(), "21");
2213    }
2214
2215    #[test]
2216    fn eval_division() {
2217        let val = eval_expr("10 / 4");
2218        assert_eq!(val.as_str(), "2.5");
2219    }
2220
2221    #[test]
2222    fn eval_precedence() {
2223        let val = eval_expr("2 + 3 * 4");
2224        assert_eq!(val.as_str(), "14");
2225    }
2226
2227    #[test]
2228    fn eval_division_by_zero() {
2229        let mut env = Environment::new();
2230        let mut lexer = Lexer::new("1 / 0");
2231        let tokens = lexer.tokenize().unwrap();
2232        let mut parser = Parser::new(tokens);
2233        let program = parser.parse().unwrap();
2234        let mut eval = Evaluator::new(&mut env, &program);
2235        match &program.clauses[0].kind {
2236            ClauseKind::Command(expr) => {
2237                let result = eval.eval_expr(expr);
2238                assert!(result.is_err());
2239                assert_eq!(result.unwrap_err().error, RexxError::ArithmeticOverflow);
2240            }
2241            _ => panic!("expected command clause"),
2242        }
2243    }
2244
2245    #[test]
2246    fn eval_power() {
2247        let val = eval_expr("2 ** 10");
2248        assert_eq!(val.as_str(), "1024");
2249    }
2250
2251    #[test]
2252    fn eval_string_concat_blank() {
2253        let val = eval_expr("'hello' 'world'");
2254        assert_eq!(val.as_str(), "hello world");
2255    }
2256
2257    #[test]
2258    fn eval_string_concat_abuttal() {
2259        let val = eval_expr("'hello'||'world'");
2260        assert_eq!(val.as_str(), "helloworld");
2261    }
2262
2263    #[test]
2264    fn eval_comparison_numeric() {
2265        let val = eval_expr("3 > 2");
2266        assert_eq!(val.as_str(), "1");
2267    }
2268
2269    #[test]
2270    fn eval_comparison_equal() {
2271        let val = eval_expr("5 = 5");
2272        assert_eq!(val.as_str(), "1");
2273    }
2274
2275    #[test]
2276    fn eval_comparison_string() {
2277        let val = eval_expr("'abc' = 'abc'");
2278        assert_eq!(val.as_str(), "1");
2279    }
2280
2281    #[test]
2282    fn eval_strict_comparison() {
2283        let val = eval_expr("' abc' == 'abc'");
2284        assert_eq!(val.as_str(), "0");
2285    }
2286
2287    #[test]
2288    fn eval_logical_and() {
2289        let val = eval_expr("1 & 1");
2290        assert_eq!(val.as_str(), "1");
2291        let val = eval_expr("1 & 0");
2292        assert_eq!(val.as_str(), "0");
2293    }
2294
2295    #[test]
2296    fn eval_logical_or() {
2297        let val = eval_expr("0 | 1");
2298        assert_eq!(val.as_str(), "1");
2299    }
2300
2301    #[test]
2302    fn eval_logical_not() {
2303        let val = eval_expr("\\0");
2304        assert_eq!(val.as_str(), "1");
2305    }
2306
2307    #[test]
2308    fn eval_variable_assignment_and_lookup() {
2309        let mut env = Environment::new();
2310        let mut lexer = Lexer::new("x = 42; x + 1");
2311        let tokens = lexer.tokenize().unwrap();
2312        let mut parser = Parser::new(tokens);
2313        let program = parser.parse().unwrap();
2314        let mut eval = Evaluator::new(&mut env, &program);
2315        let signal = eval.exec().unwrap();
2316        assert!(matches!(signal, ExecSignal::Normal));
2317        // After execution, x should be "42" and the command clause evaluated "43"
2318        assert_eq!(env.get("X").as_str(), "42");
2319    }
2320
2321    #[test]
2322    fn eval_unset_variable_returns_name() {
2323        let val = eval_expr("foo");
2324        assert_eq!(val.as_str(), "FOO");
2325    }
2326
2327    #[test]
2328    fn eval_say_runs() {
2329        // Smoke test — just ensure it doesn't panic
2330        let mut env = Environment::new();
2331        let mut lexer = Lexer::new("say 2 + 3");
2332        let tokens = lexer.tokenize().unwrap();
2333        let mut parser = Parser::new(tokens);
2334        let program = parser.parse().unwrap();
2335        let mut eval = Evaluator::new(&mut env, &program);
2336        let signal = eval.exec().unwrap();
2337        assert!(matches!(signal, ExecSignal::Normal));
2338    }
2339
2340    #[test]
2341    fn eval_negative_power() {
2342        let val = eval_expr("2 ** -1");
2343        assert_eq!(val.as_str(), "0.5");
2344    }
2345
2346    #[test]
2347    fn eval_unary_minus() {
2348        let val = eval_expr("-5 + 3");
2349        assert_eq!(val.as_str(), "-2");
2350    }
2351
2352    #[test]
2353    fn eval_remainder() {
2354        let val = eval_expr("17 // 5");
2355        assert_eq!(val.as_str(), "2");
2356    }
2357
2358    #[test]
2359    fn eval_integer_division() {
2360        let val = eval_expr("17 % 5");
2361        assert_eq!(val.as_str(), "3");
2362    }
2363
2364    #[test]
2365    fn command_handler_with_env_sets_compound() {
2366        let mut env = Environment::new();
2367        let src = "address XEDIT; 'EXTRACT /CURLINE/'; say CURLINE.1";
2368        let mut lexer = Lexer::new(src);
2369        let tokens = lexer.tokenize().unwrap();
2370        let mut parser = Parser::new(tokens);
2371        let program = parser.parse().unwrap();
2372        let mut eval = Evaluator::new(&mut env, &program);
2373        eval.set_command_handler_with_env(Box::new(|_addr, _cmd, vars| {
2374            vars.set_compound("CURLINE", "1", RexxValue::new("Hello from XEDIT"));
2375            Some(0)
2376        }));
2377        let signal = eval.exec().unwrap();
2378        assert!(matches!(signal, ExecSignal::Normal));
2379        assert_eq!(env.get("RC").as_str(), "0");
2380        assert_eq!(
2381            env.get_compound("CURLINE", "1").as_str(),
2382            "Hello from XEDIT"
2383        );
2384    }
2385
2386    #[test]
2387    fn command_handler_with_env_none_falls_through_to_basic() {
2388        let mut env = Environment::new();
2389        // The env-aware handler returns None, so the basic handler should be consulted
2390        let src = "'some command'";
2391        let mut lexer = Lexer::new(src);
2392        let tokens = lexer.tokenize().unwrap();
2393        let mut parser = Parser::new(tokens);
2394        let program = parser.parse().unwrap();
2395        let mut eval = Evaluator::new(&mut env, &program);
2396        eval.set_command_handler_with_env(Box::new(|_addr, _cmd, _vars| None));
2397        eval.set_command_handler(Box::new(|_addr, _cmd| Some(7)));
2398        let signal = eval.exec().unwrap();
2399        assert!(matches!(signal, ExecSignal::Normal));
2400        assert_eq!(env.get("RC").as_str(), "7");
2401    }
2402
2403    #[test]
2404    fn command_handler_with_env_some_skips_basic() {
2405        let mut env = Environment::new();
2406        let src = "'some command'";
2407        let mut lexer = Lexer::new(src);
2408        let tokens = lexer.tokenize().unwrap();
2409        let mut parser = Parser::new(tokens);
2410        let program = parser.parse().unwrap();
2411        let mut eval = Evaluator::new(&mut env, &program);
2412        eval.set_command_handler_with_env(Box::new(|_addr, _cmd, _vars| Some(0)));
2413        eval.set_command_handler(Box::new(|_addr, _cmd| Some(99)));
2414        let signal = eval.exec().unwrap();
2415        assert!(matches!(signal, ExecSignal::Normal));
2416        // The env-aware handler handled it; basic handler should NOT be called
2417        assert_eq!(env.get("RC").as_str(), "0");
2418    }
2419
2420    #[test]
2421    fn command_handler_with_env_error_trap_fires() {
2422        let mut env = Environment::new();
2423        // SIGNAL ON ERROR NAME OOPS routes to the OOPS label when a command returns positive rc
2424        let src = "signal on error name oops; 'fail'; say 'NOT REACHED'; exit 0\noops: exit RC";
2425        let mut lexer = Lexer::new(src);
2426        let tokens = lexer.tokenize().unwrap();
2427        let mut parser = Parser::new(tokens);
2428        let program = parser.parse().unwrap();
2429        let mut eval = Evaluator::new(&mut env, &program);
2430        eval.set_command_handler_with_env(Box::new(|_addr, _cmd, _vars| Some(42)));
2431        let signal = eval.exec().unwrap();
2432        // The ERROR trap should fire, jumping to OOPS which exits with RC=42
2433        match &signal {
2434            ExecSignal::Exit(Some(val)) => assert_eq!(val.as_str(), "42"),
2435            _ => panic!("expected Exit(Some(42))"),
2436        }
2437        assert_eq!(env.get("RC").as_str(), "42");
2438    }
2439
2440    #[test]
2441    fn command_handler_with_env_failure_trap_fires_on_negative_rc() {
2442        let mut env = Environment::new();
2443        // SIGNAL ON FAILURE NAME BAD routes to BAD when a command returns negative rc
2444        let src = "signal on failure name bad; 'notfound'; say 'NOT REACHED'; exit 0\nbad: exit RC";
2445        let mut lexer = Lexer::new(src);
2446        let tokens = lexer.tokenize().unwrap();
2447        let mut parser = Parser::new(tokens);
2448        let program = parser.parse().unwrap();
2449        let mut eval = Evaluator::new(&mut env, &program);
2450        eval.set_command_handler_with_env(Box::new(|_addr, _cmd, _vars| Some(-3)));
2451        let signal = eval.exec().unwrap();
2452        // The FAILURE trap should fire (negative rc), jumping to BAD which exits with RC=-3
2453        match &signal {
2454            ExecSignal::Exit(Some(val)) => assert_eq!(val.as_str(), "-3"),
2455            _ => panic!("expected Exit(Some(-3))"),
2456        }
2457        assert_eq!(env.get("RC").as_str(), "-3");
2458    }
2459}