diff --git a/Cargo.toml b/Cargo.toml index c8246e4..c45bf58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,11 +2,22 @@ name = "msp430-asm" version = "0.2.0" edition = "2021" -authors = ["John Breaux"] +rust-version = "1.70" +authors = ["John Breaux "] publish = false +[features] +default = [] +[[example]] +name = "msp430-asm" +path = "examples/msp430-asm/main.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] regex = "1.9.3" +# TODO: Remove dependency on regex + +[dev-dependencies] +anes = { version = "0.1.6" } +argp = { version = "0.3.0" } diff --git a/examples/msp430-asm/main.rs b/examples/msp430-asm/main.rs new file mode 100644 index 0000000..7cf8870 --- /dev/null +++ b/examples/msp430-asm/main.rs @@ -0,0 +1,136 @@ +//! Simple frontend for the assembler +#![feature(decl_macro)] +use argp::parse_args_or_exit; +use msp430_asm::preamble::*; +use std::{ + error::Error, + io::{stdin, IsTerminal, Read}, +}; + +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 { + // if stdin is a terminal, enter parse-checked REPL mode. + repl::repl(&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::{Color, MoveCursorToPreviousLine, ResetAttributes, SetForegroundColor}; + use msp430_asm::{ + assembler::error::AssemblyError, error::Error as MspError, lexer::error::LexError, parser::error::ParseError, + }; + 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 printflush ($($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!( + "{}{} v{}{}", + SetForegroundColor(Color::DarkGray), + env!("CARGO_BIN_NAME"), + env!("CARGO_PKG_VERSION"), + ResetAttributes + ); + printflush!("{}", 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::default().parse(&line) { + Err(error) => errpp(&line, linenr, &error.into()), + Ok(_) => { + okpp(&line, linenr); + *buf += &line; + linenr += 1; + } + } + line.clear(); + printflush!("{}", linenr!(linenr)); + } + println!(); + Ok(()) + } + + fn okpp(line: &str, linenr: i32) { + println!("{}{}{}", move_cursor!(1, 5), color!(Color::Green, "{:4}", linenr!(linenr)), line.trim_end(),); + } + + /// Pretty-prints a line error + fn errpp(line: &str, linenr: i32, err: &msp430_asm::error::Error) { + if stderr().is_terminal() { + let line = line.trim_end(); + eprint!("{}{}", MoveCursorToPreviousLine(1), color!(Color::Red, "{}", linenr!(linenr))); + match err { + // TODO: use a recursive enum to store all valid states + MspError::LexError(LexError::Contextual(c, e)) + | MspError::ParseError(ParseError::LexError(LexError::Contextual(c, e))) + | MspError::AssemblyError(AssemblyError::ParseError(ParseError::LexError(LexError::Contextual( + c, + e, + )))) => { + let (start, end) = line.split_at(c.position() - 1); + eprintln!("{start}{} ({e})", color!(Color::Red, "{}", end)); + } + _ => { + eprintln!("{} ({err})", color!(Color::Red, "{}", line)); + } + } + } else { + eprintln!("{} ({err})", line.trim()) + } + } +} + +// Parses and assembles a buffer, then prints it in hex to stdout +fn asm(buf: &str) -> Result<(), Box> { + match Assembler::assemble(&Parser::default().parse(&buf)?) { + Err(error) => println!("{error}"), + Ok(out) => { + for word in out { + print!("{:04x} ", word.swap_bytes()) + } + println!(); + } + } + Ok(()) +} diff --git a/src/bin/msp430-asm/main.rs b/src/bin/msp430-asm/main.rs deleted file mode 100644 index cf1f111..0000000 --- a/src/bin/msp430-asm/main.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! Simple frontend for the assembler - -use msp430_asm::preamble::*; -use std::error::Error; -use std::io::Read; - -fn main() -> Result<(), Box> { - let mut repl = true; - for arg in std::env::args() { - match arg.as_str() { - "-" | "-f" | "--file" => repl = false, - _ => (), - } - } - - let mut buf = String::new(); - if repl { - let mut line = String::new(); - while let Ok(len) = std::io::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::default().parse(&line) { - Ok(_) => { - buf += &line; - } - Err(error) => println!("{error}"), - } - line.clear(); - } - } - } - match Assembler::assemble(&Parser::default().parse(&buf)?) { - Err(error) => println!("{error}"), - Ok(out) => { - for word in out { - print!("{:04x} ", word.swap_bytes()) - } - } - } - - println!(); - } else { - std::io::stdin().lock().read_to_string(&mut buf)?; - let tree = Parser::default().parse(&buf); - match &tree { - Ok(tree) => { - //msp430_asm::linker::Printer::default().visit_root(tree); - for insn in msp430_asm::assembler::Assembler::assemble(tree)? { - print!("{:04x} ", insn.swap_bytes()) - } - println!(); - } - Err(error) => eprintln!("{error}"), - } - } - - Ok(()) -}