msp430-repl/examples/msp430-asm/main.rs

137 lines
4.5 KiB
Rust

//! 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<dyn Error>> {
let mut buf = String::new();
if let Some(file) = parse_args_or_exit::<args::Args>(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<PathBuf>,
}
}
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<dyn Error>> {
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<dyn Error>> {
match Assembler::assemble(&Parser::default().parse(&buf)?) {
Err(error) => println!("{error}"),
Ok(out) => {
for word in out {
print!("{:04x} ", word.swap_bytes())
}
println!();
}
}
Ok(())
}