main.rs: Basic TUI experience: line numbers, better errors
This commit is contained in:
parent
417ef03e41
commit
d73c5b2e5d
13
Cargo.toml
13
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 <j@soft.fish>"]
|
||||
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" }
|
||||
|
136
examples/msp430-asm/main.rs
Normal file
136
examples/msp430-asm/main.rs
Normal 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(())
|
||||
}
|
@ -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(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user