diff --git a/msp430-asm/Cargo.toml b/msp430-asm/Cargo.toml index 6192e9d..d7a82c5 100644 --- a/msp430-asm/Cargo.toml +++ b/msp430-asm/Cargo.toml @@ -10,5 +10,4 @@ publish.workspace = true [dependencies] libmsp430 = { path = ".." } -anes = { version = "0.2.0" } argp = { version = "0.3.0" } diff --git a/msp430-asm/src/lib.rs b/msp430-asm/src/lib.rs index 715377c..33b77d4 100644 --- a/msp430-asm/src/lib.rs +++ b/msp430-asm/src/lib.rs @@ -62,12 +62,31 @@ pub mod split_twice { pub mod cursor { use std::fmt::{Arguments, Display}; - pub macro csi($($t:tt)*) {format_args!("\x1b[{}", format_args!($($t)*))} + /// Moves to the {line}th previous line + pub macro previous($line:literal) { + csi!("{}F", $line) + } - pub macro color($fg:expr, $($t:tt)*) { + /// Injects a Command Sequence Introducer + pub macro csi($($t:tt)*) { + format_args!("\x1b[{}", format_args!($($t)*)) + } + + /// Formats the args with a foreground [Color] + pub macro fg($fg:expr, $($t:tt)*) { Colorized::new(Some($fg), None, format_args!($($t)*)) } + /// Formats the args with a background [Color] + pub macro bg($bg:expr, $(t:tt)*) { + Colorized::new(None, Some($bg), format_args!($($t)*)) + } + + /// Formats the args with both a foreground and background [Color] + pub macro color($fg:expr, $bg:expr, $($t:tt)*) { + Colorized::new(Some($fg), Some($bg), format_args!($($t)*)) + } + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Color { #[default] diff --git a/msp430-asm/src/main.rs b/msp430-asm/src/main.rs index a40c4d0..910892e 100644 --- a/msp430-asm/src/main.rs +++ b/msp430-asm/src/main.rs @@ -7,7 +7,7 @@ use libmsp430::{ parser::{error::Error as PError, Parser}, }; use msp430_asm::{ - cursor::{color, Color::*}, + cursor::{fg, Color::*}, split_twice::SplitTwice, }; use std::{ @@ -19,103 +19,16 @@ fn main() -> Result<(), Box> { let mut buf = String::new(); if let Some(file) = parse_args_or_exit::(argp::DEFAULT).file { buf = std::fs::read_to_string(file)?; - } else if !stdin().is_terminal() { - // if stdin is not a terminal, don't parsecheck each line. - stdin().lock().read_to_string(&mut buf)?; - } else { + } else if stdin().is_terminal() { // if stdin is a terminal, enter parse-checked REPL mode. repl::repl(&mut buf)?; + } else { + // if stdin is not a terminal, don't parsecheck each line. + stdin().lock().read_to_string(&mut buf)?; } asm(&buf) } -mod args { - use argp::FromArgs; - use std::path::PathBuf; - - /// Assembles MSP430 assembly into 16-bit little-endian machine code. \ - /// If used interactively, syntax is checked on a per-line basis. - #[derive(Debug, FromArgs)] - pub struct Args { - /// File to load. If not provided, takes input from stdin. - #[argp(option, short = 'f')] - pub file: Option, - } -} -mod repl { - use super::*; - use anes::MoveCursorToPreviousLine; - use std::io::{stderr, Write}; - - // macro color ($color: expr, $fmt: literal, $($str: expr),*) { - // format_args!(concat!("{}", $fmt, "{}"), ::anes::SetForegroundColor($color),$($str,)* - // ::anes::ResetAttributes) } - - macro linenr($n: expr) { - format_args!("{:4}: ", $n) - } - - macro printfl ($($x: expr),+) { - {print!($($x),+); let _ = ::std::io::stdout().flush();} - } - - macro move_cursor($x:expr, $y:expr) { - format_args!("{}{}", ::anes::MoveCursorToPreviousLine($x), "") - } - - pub fn repl(buf: &mut String) -> Result<(), Box> { - let mut line = String::new(); - let mut linenr = 1; - println!( - "{}", - color!(DarkGray, "{} v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION")) - ); - printfl!("{}", linenr!(linenr)); - while let Ok(len) = stdin().read_line(&mut line) { - match len { - 0 => break, // No newline (reached EOF) - 1 => continue, // Line is empty - _ => (), - } - // Try to parse this line in isolation (this restricts preprocessing) - match Parser::new(&line).parse::() { - Err(error) => errpp(&line, linenr, &error), - Ok(_) => { - okpp(&line, linenr); - *buf += &line; - linenr += 1; - } - } - line.clear(); - printfl!("{}", linenr!(linenr)); - } - println!("{}", color!(Gray, "[EOF]")); - Ok(()) - } - - fn okpp(line: &str, linenr: i32) { - println!( - "{}{}{}", - move_cursor!(1, 5), - color!(Green, "{:4}", linenr!(linenr)), - line.trim_end(), - ); - } - - /// Pretty-prints a line error - fn errpp(line: &str, linenr: i32, err: &PError) { - let loc = err.loc; - if stderr().is_terminal() { - let line = line.trim_end(); - eprint!("{}{}", MoveCursorToPreviousLine(1), color!(Red, "{}", linenr!(linenr))); - let (start, mid, end) = line.split_twice(loc.start, loc.end); - eprintln!("{start}{}{end} {}", color!(Red, "{}", mid), color!(DarkGray, "; {}", err)); - } else { - eprintln!("{} ({err})", line.trim()) - } - } -} - // Parses and assembles a buffer, then prints it in hex to stdout fn asm(buf: &str) -> Result<(), Box> { match Parser::new(buf).parse::()?.to_canonical().assemble() { @@ -129,3 +42,80 @@ fn asm(buf: &str) -> Result<(), Box> { } Ok(()) } + +mod args { + use argp::FromArgs; + use std::path::PathBuf; + + /// Assembles MSP430 assembly into 16-bit little-endian machine code. \ + /// If used interactively, syntax is checked on a per-line basis. + #[derive(Debug, FromArgs)] + pub struct Args { + /// File to load. If not provided, takes input from stdin. + #[argp(option, short = 'f')] + pub file: Option, + } +} + +mod repl { + //! The REPL reads a line, parses it, evaluates the line, and prints, in a loop + use super::*; + use msp430_asm::cursor::*; + use std::io::{stderr, Write}; + + /// Prints the line number + macro linenr($n: expr) { + format_args!("{:4}: ", $n) + } + + /// [println], but without the newline + macro printfl ($($x: expr),+) { + {print!($($x),+); let _ = ::std::io::stdout().flush();} + } + + /// Runs the read-evaluate-print loop + pub fn repl(buf: &mut String) -> Result<(), Box> { + let mut line = String::new(); + let mut linenr = 1; + println!("{}", fg!(DarkGray, "{} v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION"))); + printfl!("{}", linenr!(linenr)); + while let Ok(len) = stdin().read_line(&mut line) { + match len { + 0 => break, // No newline (reached EOF) + 1 => continue, // Line is empty + _ => (), + } + // Try to parse this line in isolation (this restricts preprocessing) + match Parser::new(&line).parse::() { + Err(error) => format_err(&line, linenr, &error), + Ok(_) => { + format_ok(&line, linenr); + *buf += &line; + linenr += 1; + } + } + line.clear(); + printfl!("{}", linenr!(linenr)); + } + println!("{}", fg!(Gray, "[EOF]")); + Ok(()) + } + + /// Rewrites the line in OK format, with a green linenr + fn format_ok(line: &str, linenr: i32) { + println!("{}{}{}", previous!(1), fg!(Green, "{:4}", linenr!(linenr)), line.trim_end(),); + } + + /// Pretty-prints a line error + fn format_err(line: &str, linenr: i32, err: &PError) { + let loc = err.loc; + if stderr().is_terminal() { + let line = line.trim_end(); + eprint!("{}{}", previous!(1), fg!(Red, "{}", linenr!(linenr))); + let (start, mid, end) = line.split_twice(loc.start, loc.end); + eprintln!("{start}{}{end} {}", fg!(Red, "{}", mid), fg!(DarkGray, "; {}", err)); + } else { + eprintln!("{} ({err})", line.trim()) + } + } +}