From f3c491d1f98f7333f312dda92b80ac25b8f9d10e Mon Sep 17 00:00:00 2001 From: darcy Date: Thu, 19 Dec 2024 11:31:55 +1100 Subject: [PATCH] feat!: read commands from argument first, then read from stdin/terminal --- src/debugger/eval.rs | 2 - src/debugger/mod.rs | 6 +-- src/debugger/source.rs | 97 ++++++++++++++++++++++++++++-------------- 3 files changed, 67 insertions(+), 38 deletions(-) diff --git a/src/debugger/eval.rs b/src/debugger/eval.rs index 0e0bb38..82131d6 100644 --- a/src/debugger/eval.rs +++ b/src/debugger/eval.rs @@ -5,8 +5,6 @@ use crate::runtime::RunState; use crate::symbol::Span; use crate::{dprintln, AsmParser}; -// TODO(feat): Warn on `eval br* ...` and suggest `jump ...` - pub fn eval(state: &mut RunState, line: String) { // Required to make temporarily 'static // Automatically dropped at end of scope diff --git a/src/debugger/mod.rs b/src/debugger/mod.rs index 3836444..e65f242 100644 --- a/src/debugger/mod.rs +++ b/src/debugger/mod.rs @@ -4,7 +4,7 @@ mod parse; mod source; use self::command::{Command, Label, Location, MemoryLocation}; -use self::source::{SourceMode, SourceReader}; +use self::source::{Source, SourceReader}; use crate::air::AsmLine; use crate::dprintln; use crate::output::{Condition, Output}; @@ -19,7 +19,7 @@ pub struct DebuggerOptions { pub struct Debugger { status: Status, - source: SourceMode, + source: Source, // TODO(refactor): Make private, use method to increment pub(super) instruction_count: u32, @@ -163,7 +163,7 @@ impl Debugger { ) -> Self { Self { status: Status::default(), - source: SourceMode::from(opts.command), + source: Source::from(opts.command), instruction_count: 0, was_pc_changed: true, initial_state, diff --git a/src/debugger/source.rs b/src/debugger/source.rs index de7a620..22e11fb 100644 --- a/src/debugger/source.rs +++ b/src/debugger/source.rs @@ -5,23 +5,19 @@ use console::Key; use crate::{dprint, dprintln, output::Output}; -// TODO(feat): If argument ends in '...' (or something) then switch to `SourceMode::Terminal` once -// arguments are exhausted - +/// Read from argument first, if `Some`. Then read from stream. #[allow(private_interfaces)] // Perhaps a bad practice #[derive(Debug)] -pub enum SourceMode { - Argument(Argument), - Stdin(Stdin), - Terminal(Terminal), +pub struct Source { + argument: Option, + stream: Stream, } -/// Stdin which is not attached to a terminal, i.e. piped. +/// Stdin or interactive terminal #[derive(Debug)] -struct Stdin { - stdin: io::Stdin, - /// Command must be stored somewhere to be referenced - buffer: String, +enum Stream { + Stdin(Stdin), + Terminal(Terminal), } /// Command-line argument @@ -32,6 +28,14 @@ struct Argument { cursor: usize, } +/// Stdin which is not attached to a terminal, i.e. piped. +#[derive(Debug)] +struct Stdin { + stdin: io::Stdin, + /// Command must be stored somewhere to be referenced + buffer: String, +} + /// Interactive unbuffered terminal // TODO(feat): Support CTRL+Arrow keybinds #[derive(Debug)] @@ -51,44 +55,71 @@ struct Terminal { history_file: File, } +fn echo_command_prompt(command: Option<&str>) { + // Echo prompt and command for non-terminal source + // Equivalent code found in terminal source + if !Output::is_minimal() || command.is_some() { + dprint!(Always, Normal, "\x1b[1mCommand: "); + dprintln!( + Always, + Normal, + "{}", + command.unwrap_or("\x1b[3m(end of input)").trim() + ); + } +} + pub trait SourceReader { /// `None` indicates EOF /// Returned string slice MAY include leading or trailing whitespace fn read(&mut self) -> Option<&str>; } -impl SourceMode { +impl Source { pub fn from(argument: Option) -> Self { - if let Some(argument) = argument { - return SourceMode::Argument(Argument::from(argument)); + Self { + argument: argument.map(|argument| Argument::from(argument)), + stream: Stream::new(), } + } +} + +impl SourceReader for Source { + fn read(&mut self) -> Option<&str> { + // Always try to read from argument first + // If argument is `None`, or if read from argument returns `None`, then read from stream + // Note that `self.argument` cannot then be set to `None`, due to lifetime of returned value + if let Some(argument) = &mut self.argument { + if let Some(command) = argument.read() { + echo_command_prompt(Some(command)); + return Some(command); + } + } + self.stream.read() + } +} + +impl Stream { + pub fn new() -> Self { let stdin = io::stdin(); if stdin.is_terminal() { - return SourceMode::Terminal(Terminal::new()); + return Self::Terminal(Terminal::new()); } - SourceMode::Stdin(Stdin::from(stdin)) + Self::Stdin(Stdin::from(stdin)) } } -impl SourceReader for SourceMode { +impl SourceReader for Stream { fn read(&mut self) -> Option<&str> { - let command = match self { - Self::Argument(argument) => argument.read(), - Self::Stdin(stdin) => stdin.read(), + match self { + Self::Stdin(stdin) => { + let command = stdin.read(); + echo_command_prompt(command); + command + } + // Don't echo command for terminal source, that would be redundant Self::Terminal(terminal) => return terminal.read(), - }; - // Echo prompt and command for non-terminal source - // Equivalent code found in terminal source - if !Output::is_minimal() || command.is_some() { - dprint!(Always, Normal, "\x1b[1mCommand: "); - dprintln!( - Always, - Normal, - "{}", - command.unwrap_or("\x1b[3m(end of input)").trim() - ); } - command } }