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