main.rs: Basic TUI experience: line numbers, better errors

This commit is contained in:
John 2023-09-15 18:21:01 -05:00
parent 417ef03e41
commit d73c5b2e5d
3 changed files with 148 additions and 62 deletions

View File

@ -2,11 +2,22 @@
name = "msp430-asm" name = "msp430-asm"
version = "0.2.0" version = "0.2.0"
edition = "2021" edition = "2021"
authors = ["John Breaux"] rust-version = "1.70"
authors = ["John Breaux <j@soft.fish>"]
publish = false 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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
regex = "1.9.3" regex = "1.9.3"
# TODO: Remove dependency on regex
[dev-dependencies]
anes = { version = "0.1.6" }
argp = { version = "0.3.0" }

136
examples/msp430-asm/main.rs Normal file
View File

@ -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<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(())
}

View File

@ -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<dyn Error>> {
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(())
}