137 lines
4.5 KiB
Rust
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(())
|
||
|
}
|