//! 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(()) }