Stack prompt cleanup and REPL error rollback fixes #269

Closed
opened 2026-01-17 16:42:41 +00:00 by navicore · 1 comment
navicore commented 2026-01-17 16:42:41 +00:00 (Migrated from github.com)

Summary

Two improvements to the REPL experience:

  1. Cleaner stack prompt: Change stack: to » for less visual noise
  2. Error rollback: REPL should rollback session file when expressions fail at runtime

Changes Required

1. crates/core/src/stack.rs (around line 1073-1077)

Find:

    if depth == 0 {
        println!("stack:");
    } else {
        use std::io::Write;
        print!("stack: ");

Replace with:

    if depth == 0 {
        println!("»");
    } else {
        use std::io::Write;
        print!("» ");

2. crates/repl/src/app.rs (around line 547-576)

Find:

                    Ok(result) => {
                        let stdout = String::from_utf8_lossy(&result.stdout);
                        let stderr = String::from_utf8_lossy(&result.stderr);

                        // Update IR from the session file
                        self.update_ir_from_session(expr);

                        if result.status.success() {
                            let output_text = stdout.trim();
                            if output_text.is_empty() {
                                self.repl_state
                                    .add_entry(HistoryEntry::new(expr).with_output("ok"));
                            } else {
                                self.repl_state
                                    .add_entry(HistoryEntry::new(expr).with_output(output_text));
                            }
                        } else {
                            let err = if stderr.is_empty() {
                                format!("exit: {:?}", result.status.code())
                            } else {
                                stderr.trim().to_string()
                            };
                            self.repl_state
                                .add_entry(HistoryEntry::new(expr).with_error(&err));
                        }
                    }
                    Err(e) => {
                        self.add_error_entry(expr, &format!("Run error: {}", e));
                    }

Replace with:

                    Ok(result) => {
                        let stdout = String::from_utf8_lossy(&result.stdout);
                        let stderr = String::from_utf8_lossy(&result.stderr);

                        if result.status.success() {
                            // Update IR from the session file - only on success
                            self.update_ir_from_session(expr);

                            let output_text = stdout.trim();
                            if output_text.is_empty() {
                                self.repl_state
                                    .add_entry(HistoryEntry::new(expr).with_output("ok"));
                            } else {
                                self.repl_state
                                    .add_entry(HistoryEntry::new(expr).with_output(output_text));
                            }
                        } else {
                            // Rollback on runtime error - don't keep failed expression in session
                            if let Err(rollback_err) = fs::write(&self.session_path, &original) {
                                self.status_message = Some(format!(
                                    "Warning: Could not rollback session file: {}",
                                    rollback_err
                                ));
                            }
                            let err = if stderr.is_empty() {
                                format!("exit: {:?}", result.status.code())
                            } else {
                                stderr.trim().to_string()
                            };
                            self.repl_state
                                .add_entry(HistoryEntry::new(expr).with_error(&err));
                        }
                    }
                    Err(e) => {
                        // Rollback on run error - don't keep failed expression in session
                        if let Err(rollback_err) = fs::write(&self.session_path, &original) {
                            self.status_message = Some(format!(
                                "Warning: Could not rollback session file: {}",
                                rollback_err
                            ));
                        }
                        self.add_error_entry(expr, &format!("Run error: {}", e));
                    }

Rationale

Stack prompt

  • The » character is visually distinct, won't be confused with input
  • Reduces noise since context is already clear in the REPL
  • Widely supported Unicode (Latin-1, U+00BB)

Error rollback

Previously, if an expression like 5 roll compiled but failed at runtime (e.g., insufficient stack depth), the expression stayed in the session file, breaking all subsequent evaluations. Now:

  1. Runtime errors (non-zero exit): Session rolled back
  2. Run errors (binary can't execute): Session rolled back
  3. IR update: Only happens on success, keeping IR in sync with session

This gives the REPL proper error recovery behavior.

## Summary Two improvements to the REPL experience: 1. **Cleaner stack prompt**: Change `stack:` to `»` for less visual noise 2. **Error rollback**: REPL should rollback session file when expressions fail at runtime ## Changes Required ### 1. `crates/core/src/stack.rs` (around line 1073-1077) **Find:** ```rust if depth == 0 { println!("stack:"); } else { use std::io::Write; print!("stack: "); ``` **Replace with:** ```rust if depth == 0 { println!("»"); } else { use std::io::Write; print!("» "); ``` ### 2. `crates/repl/src/app.rs` (around line 547-576) **Find:** ```rust Ok(result) => { let stdout = String::from_utf8_lossy(&result.stdout); let stderr = String::from_utf8_lossy(&result.stderr); // Update IR from the session file self.update_ir_from_session(expr); if result.status.success() { let output_text = stdout.trim(); if output_text.is_empty() { self.repl_state .add_entry(HistoryEntry::new(expr).with_output("ok")); } else { self.repl_state .add_entry(HistoryEntry::new(expr).with_output(output_text)); } } else { let err = if stderr.is_empty() { format!("exit: {:?}", result.status.code()) } else { stderr.trim().to_string() }; self.repl_state .add_entry(HistoryEntry::new(expr).with_error(&err)); } } Err(e) => { self.add_error_entry(expr, &format!("Run error: {}", e)); } ``` **Replace with:** ```rust Ok(result) => { let stdout = String::from_utf8_lossy(&result.stdout); let stderr = String::from_utf8_lossy(&result.stderr); if result.status.success() { // Update IR from the session file - only on success self.update_ir_from_session(expr); let output_text = stdout.trim(); if output_text.is_empty() { self.repl_state .add_entry(HistoryEntry::new(expr).with_output("ok")); } else { self.repl_state .add_entry(HistoryEntry::new(expr).with_output(output_text)); } } else { // Rollback on runtime error - don't keep failed expression in session if let Err(rollback_err) = fs::write(&self.session_path, &original) { self.status_message = Some(format!( "Warning: Could not rollback session file: {}", rollback_err )); } let err = if stderr.is_empty() { format!("exit: {:?}", result.status.code()) } else { stderr.trim().to_string() }; self.repl_state .add_entry(HistoryEntry::new(expr).with_error(&err)); } } Err(e) => { // Rollback on run error - don't keep failed expression in session if let Err(rollback_err) = fs::write(&self.session_path, &original) { self.status_message = Some(format!( "Warning: Could not rollback session file: {}", rollback_err )); } self.add_error_entry(expr, &format!("Run error: {}", e)); } ``` ## Rationale ### Stack prompt - The `»` character is visually distinct, won't be confused with input - Reduces noise since context is already clear in the REPL - Widely supported Unicode (Latin-1, U+00BB) ### Error rollback Previously, if an expression like `5 roll` compiled but failed at runtime (e.g., insufficient stack depth), the expression stayed in the session file, breaking all subsequent evaluations. Now: 1. **Runtime errors** (non-zero exit): Session rolled back 2. **Run errors** (binary can't execute): Session rolled back 3. **IR update**: Only happens on success, keeping IR in sync with session This gives the REPL proper error recovery behavior.
navicore commented 2026-01-17 17:22:49 +00:00 (Migrated from github.com)
https://github.com/navicore/patch-seq/pull/270
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
navicore/patch-seq#269
No description provided.