v0.3.0: Total overhaul

- Everything has been rewritten
- Modularity is improved somewhat
  - No dependency injection in preprocessor/parser, though
- There are now early and late constant evaluation engines
  - This engine allows for by-value access to already-assembled code
  - Performs basic math operations, remainder, bitwise logic, bit shifts, negation, and bit inversion
  - Also allows for indexing into already-generated code using pointer-arithmetic syntax: `*(&main + 10)`. This is subject to change? It's clunky, and only allows word-aligned access. However, this rewrite is taking far too long, so I'll call the bikeshedding here.
  - Pretty sure this constant evaluation is computationally equivalent to Deadfish?
This commit is contained in:
2024-01-30 05:27:12 -06:00
parent e4a1b889c2
commit fc8f8b9622
44 changed files with 3119 additions and 3055 deletions

14
msp430-asm/Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[package]
name = "msp430-asm"
authors.workspace = true
version.workspace = true
license.workspace = true
edition.workspace = true
publish.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libmsp430 = { path = ".." }
anes = { version = "0.2.0" }
argp = { version = "0.3.0" }

116
msp430-asm/src/lib.rs Normal file
View File

@@ -0,0 +1,116 @@
//! Helper library for msp430-asm
#![feature(decl_macro)]
pub mod split_twice {
/// Slices a collection into a beginning, middle, and end, based on two unordered indices
pub trait SplitTwice<'t> {
type Slice;
type Idx;
/// Splits a collection into a beginning, middle, and end slice,
/// based on two unordered indices
///
/// # Examples
/// ```rust
/// # use msp430_asm::split_twice::SplitTwice;
/// let string = "foo,bar,baz";
/// let (foo, bar, baz) = string.split_twice(4, 8);
/// assert_eq!(foo, "foo,");
/// assert_eq!(bar, "bar,");
/// assert_eq!(baz, "baz");
/// ```
fn split_twice(
&'t self,
a: Self::Idx,
b: Self::Idx,
) -> (Self::Slice, Self::Slice, Self::Slice);
}
impl<'t, T: 't> SplitTwice<'t> for [T] {
type Slice = &'t [T];
type Idx = usize;
fn split_twice(
&'t self,
a: Self::Idx,
b: Self::Idx,
) -> (Self::Slice, Self::Slice, Self::Slice) {
let (a, b) = if a < b { (a, b) } else { (b, a) };
let (mid, end) =
if b < self.len() { self.split_at(b) } else { (self, Default::default()) };
let (start, mid) =
if a < mid.len() { mid.split_at(a) } else { (self, Default::default()) };
(start, mid, end)
}
}
impl<'t> SplitTwice<'t> for str {
type Slice = &'t str;
type Idx = usize;
fn split_twice(
&'t self,
a: Self::Idx,
b: Self::Idx,
) -> (Self::Slice, Self::Slice, Self::Slice) {
let (a, b) = if a < b { (a, b) } else { (b, a) };
let (mid, end) =
if b < self.len() { self.split_at(b) } else { (self, Default::default()) };
let (start, mid) =
if a < mid.len() { mid.split_at(a) } else { (self, Default::default()) };
(start, mid, end)
}
}
}
pub mod cursor {
use std::fmt::{Arguments, Display};
pub macro csi($($t:tt)*) {format_args!("\x1b[{}", format_args!($($t)*))}
pub macro color($fg:expr, $($t:tt)*) {
Colorized::new(Some($fg), None, format_args!($($t)*))
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Color {
#[default]
Black = 30,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
Gray,
DarkGray = 90,
Pink,
Lime,
Sunflower,
SkyBlue,
HotPink,
Turquoise,
White,
}
#[derive(Clone, Copy, Debug)]
pub struct Colorized<'args> {
fg: Option<Color>,
bg: Option<Color>,
args: Arguments<'args>,
}
impl<'t> Colorized<'t> {
pub fn new(fg: Option<Color>, bg: Option<Color>, args: Arguments<'t>) -> Self {
Self { fg, bg, args }
}
}
impl<'t> Display for Colorized<'t> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let &Self { fg, bg, args } = self;
if let Some(fg) = fg {
write!(f, "{}", csi!("{}m", fg as u8))?;
}
if let Some(bg) = bg {
write!(f, "{}", csi!("{}m", bg as u8 + 10))?;
}
write!(f, "{args}{}", csi!("0m"))
}
}
}

131
msp430-asm/src/main.rs Normal file
View File

@@ -0,0 +1,131 @@
//! Simple frontend for the assembler
#![feature(decl_macro)]
use argp::parse_args_or_exit;
use libmsp430::{
assembler::Assemble,
parser::ast::{canonical::Canonicalize, *},
parser::{error::Error as PError, Parser},
};
use msp430_asm::{
cursor::{color, Color::*},
split_twice::SplitTwice,
};
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::MoveCursorToPreviousLine;
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 printfl ($($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!(
"{}",
color!(DarkGray, "{} v{}", env!("CARGO_BIN_NAME"), env!("CARGO_PKG_VERSION"))
);
printfl!("{}", 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::new(&line).parse::<Statements>() {
Err(error) => errpp(&line, linenr, &error),
Ok(_) => {
okpp(&line, linenr);
*buf += &line;
linenr += 1;
}
}
line.clear();
printfl!("{}", linenr!(linenr));
}
println!("{}", color!(Gray, "[EOF]"));
Ok(())
}
fn okpp(line: &str, linenr: i32) {
println!(
"{}{}{}",
move_cursor!(1, 5),
color!(Green, "{:4}", linenr!(linenr)),
line.trim_end(),
);
}
/// Pretty-prints a line error
fn errpp(line: &str, linenr: i32, err: &PError) {
let loc = err.loc;
if stderr().is_terminal() {
let line = line.trim_end();
eprint!("{}{}", MoveCursorToPreviousLine(1), color!(Red, "{}", linenr!(linenr)));
let (start, mid, end) = line.split_twice(loc.start, loc.end);
eprintln!("{start}{}{end} {}", color!(Red, "{}", mid), color!(DarkGray, "; {}", err));
} 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 Parser::new(buf).parse::<Statements>()?.to_canonical().assemble() {
Err(error) => println!("{error}"),
Ok(out) => {
for word in out {
print!("{:04x} ", word.swap_bytes())
}
println!();
}
}
Ok(())
}