diff --git a/Cargo.lock b/Cargo.lock index b9e1222..4f3817f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -212,6 +212,19 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -239,6 +252,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "errno" version = "0.3.9" @@ -379,6 +398,7 @@ dependencies = [ "assert_cmd", "clap", "colored", + "console", "fxhash", "hotwatch", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index bfe0f47..eaa0aa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ lazy_static = "1.5.0" miette = { version = "7.2.0", features = ["fancy"] } fxhash = "0.2.1" hotwatch = "0.5.0" +console = "0.15.8" [dev-dependencies] assert_cmd = "2.0.14" diff --git a/README.md b/README.md index a7f9fee..0cf78b0 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ [![Creator rozukke](https://img.shields.io/badge/Creator-rozukke-f497af.svg)](https://github.com/rozukke) [![Made with Rust](https://img.shields.io/badge/Made%20with-Rust-b7410e.svg)](https://www.rust-lang.org) -`lace` is an all-in-one **LC3** (Little Computer 3) assembly toolchain. `lace` currently supports compiling, checking and -running LC3 assembly files. It will support fancy convenience features like `watch`ing, a formatter and (probably) running +`lace` is an all-in-one **LC3** (Little Computer 3) assembly toolchain. `lace` currently supports compiling, checking, running +and placing a watch on LC3 assembly files. It will support fancy convenience features like a formatter and (probably) running with a copy of the original OS. **Some features are missing!** ## Commands @@ -17,7 +17,7 @@ with a copy of the original OS. **Some features are missing!** - `compile`: creates a binary file with a *.lc3* extension. Note that `lace` cannot currently run these files, so you may need a different LC3 virtual machine to test them out until that is implemented. - `check`: verifies that your file is correct without outputting it or running it. -- `watch`: **(work in progress)** runs `check` for a specified file while you develop. Neat! +- `watch`: runs `check` for a specified file on save while you develop. Neat! - `fmt`: **(planned)** formats your *.asm* file to fit my arbitrary style guide. - `clean`: **(planned)** used to clean debug artifacts that will be implemented in the future. @@ -29,10 +29,9 @@ your binaries with other virtual machines. ## Work in progress There are several features and fixes under development: -- Input traps +- Putsp trap - Showing multiple errors per compilation - Different number formats for console output -- Watching a file - File formatting - Debug symbols - A step-through debugger (big one!) diff --git a/src/air.rs b/src/air.rs index 3f16553..c88f41c 100644 --- a/src/air.rs +++ b/src/air.rs @@ -327,7 +327,7 @@ impl AsmLine { bail!( severity = Severity::Error, r#"Difference between label and label reference is too large: at line {}, referencing line {} - Consider opening an issue if you think that is not the case."#, + Please note that this could be because of a long .stringz literal or large .blkw allocation."#, self.line, label_pos ) diff --git a/src/error.rs b/src/error.rs index 97eb757..26faf35 100644 --- a/src/error.rs +++ b/src/error.rs @@ -49,7 +49,7 @@ pub fn lex_unknown(span: Span, src: &'static str) -> Report { code = "lex::unknown", help = "make sure that your int literals start with #", labels = vec![LabeledSpan::at(span, "unknown token")], - "Encounetered an unknown token", + "Encountered an unknown token", ) .with_source_code(src) } @@ -86,7 +86,7 @@ pub fn preproc_no_str(span: Span, src: &'static str) -> Report { code = "preproc::stringz", help = ".stringz requires a valid string literal like \"hello\\n\"", labels = vec![LabeledSpan::at(span, "not a string literal")], - "Expected a valid string literal.", + "Expected a valid string literal", ) .with_source_code(src) } diff --git a/src/lib.rs b/src/lib.rs index 478625d..aa331a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,16 @@ +// Parsing mod parser; pub use parser::AsmParser; - mod air; pub use air::Air; + +// Running mod runtime; pub use runtime::RunState; -mod error; -mod lexer; +// Reset global state for watch mod symbol; pub use symbol::{reset_state, StaticSource}; + +mod error; +mod lexer; diff --git a/src/main.rs b/src/main.rs index 3330a17..a853bc5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,8 @@ -#![allow(unused)] // Remove later - use std::fs::{self, File}; use std::io::Write; -use std::ops::RangeBounds; use std::path::PathBuf; -use std::process::exit; use std::time::Duration; -use clap::builder::styling::Style; use clap::{Parser, Subcommand}; use colored::Colorize; use hotwatch::notify::Event; @@ -15,9 +10,10 @@ use hotwatch::{ blocking::{Flow, Hotwatch}, EventKind, }; -use miette::{GraphicalTheme, IntoDiagnostic, MietteHandlerOpts, Result}; +use miette::{IntoDiagnostic, Result}; -use lace::{reset_state, AsmParser, RunState, StaticSource}; +use lace::reset_state; +use lace::{Air, RunState, StaticSource}; /// Lace is a complete & convenient assembler toolchain for the LC3 assembly language. #[derive(Parser)] @@ -67,113 +63,77 @@ enum Command { } fn main() -> miette::Result<()> { + use MsgColor::*; let args = Args::parse(); if let Some(command) = args.command { match command { Command::Run { name } => { - let contents = StaticSource::new(fs::read_to_string(&name).into_diagnostic()?); - println!( - "{:>12} target {}", - "Assembling".green().bold(), - name.to_str().unwrap() - ); - // Process asm - let parser = lace::AsmParser::new(contents.src())?; - let mut air = parser.parse()?; - air.backpatch()?; - // Run file - println!("{:>12} binary", "Running".green().bold()); - let mut program = RunState::try_from(air)?; - program.run(); - println!( - "{:>12} target {}", - "Completed".green().bold(), - name.to_str().unwrap() - ); + run(&name)?; Ok(()) } Command::Compile { name, dest } => { - let contents = StaticSource::new(fs::read_to_string(&name).into_diagnostic()?); - println!( - "{:>12} target {}", - "Assembling".green().bold(), - name.to_str().unwrap() - ); - // Process asm - let parser = lace::AsmParser::new(contents.src())?; - let mut air = parser.parse()?; - air.backpatch()?; - // Write to file - let out_file_name = dest.unwrap_or( - format!("{}.lc3", name.file_stem().unwrap().to_str().unwrap()).into(), - ); + file_message(Green, "Assembing", &name); + let air = assemble(&name)?; + + let out_file_name = + dest.unwrap_or(name.with_extension("lc3").file_stem().unwrap().into()); let mut file = File::create(&out_file_name).unwrap(); + // Deal with .orig if let Some(orig) = air.orig() { - file.write(&orig.to_be_bytes()); + let _ = file.write(&orig.to_be_bytes()); } else { - file.write(&0x3000u16.to_be_bytes()); + let _ = file.write(&0x3000u16.to_be_bytes()); } - // Write lines - for i in 0..air.len() { - file.write(&air.get(i).emit()?.to_be_bytes()); + + for stmt in air { + let _ = file.write(&stmt.emit()?.to_be_bytes()); } - println!("{:>12} binary", "Finished".green().bold(),); - println!( - "{:>12} {}", - "Saved to".green().bold(), - out_file_name.to_str().unwrap() - ); + + message(Green, "Finished", "emit binary"); + file_message(Green, "Saved", &out_file_name); Ok(()) } Command::Check { name } => { - println!( - "{:>12} target {}", - "Checking".green().bold(), - name.to_str().unwrap() - ); - check(&name); - println!("{:>12} with 0 errors", "Finished".green().bold(),); + file_message(Green, "Checking", &name); + let _ = assemble(&name)?; + message(Green, "Success", "no errors found!"); Ok(()) } - Command::Clean { name } => todo!(), + Command::Clean { name: _ } => todo!("There are no debug files implemented to clean!"), Command::Watch { name } => { // Clear screen and move cursor to top left print!("\x1B[2J\x1B[2;1H"); - println!( - "{:>12} target {}", - "Watching".green().bold(), - name.clone().to_str().unwrap() - ); - println!("{:>12} press CTRL+C to exit", "TIP:".cyan()); + file_message(Green, "Watching", &name); + message(Cyan, "Help", "press CTRL+C to exit"); + let mut watcher = Hotwatch::new_with_custom_delay(Duration::from_millis(500)) .into_diagnostic()?; + watcher .watch(name.clone(), move |event: Event| match event.kind { EventKind::Modify(_) => { // Clear screen print!("\x1B[2J\x1B[2;1H"); - println!( - "{:>12} target {}", - "Watching".green().bold(), - name.clone().to_str().unwrap() - ); - println!("{:>12} re-checking...", "File changed".green()); - println!("{:>12} press CTRL+C to exit", "Help".cyan()); - match check(&name) { + file_message(Green, "Watching", &name); + message(Green, "Re-checking", "file change detected"); + message(Cyan, "Help", "press CTRL+C to exit"); + + let _ = match assemble(&name) { Ok(_) => { - println!("{:>12} no errors found", "Success!".green()); + message(Green, "Success", "no errors found!"); } Err(e) => { println!("\n{:?}", e); } }; + reset_state(); Flow::Continue } EventKind::Remove(_) => { - println!("{}", "Watched file was deleted. Exiting...".red()); + message(Red, "Error", "watched file was deleted. Exiting..."); std::process::exit(1); } _ => Flow::Continue, @@ -182,11 +142,12 @@ fn main() -> miette::Result<()> { watcher.run(); Ok(()) } - Command::Fmt { name } => todo!(), + Command::Fmt { name: _ } => todo!("Formatting is not currently implemented"), } } else { if let Some(path) = args.path { - todo!("Should allow for running files with no subcommand") + run(&path)?; + Ok(()) } else { println!("\n~ lace v{VERSION} - Copyright (c) 2024 Artemis Rosman ~"); println!("{}", LOGO.truecolor(255, 183, 197).bold()); @@ -196,15 +157,48 @@ fn main() -> miette::Result<()> { } } -fn check(name: &PathBuf) -> Result<()> { +enum MsgColor { + Green, + Cyan, + Red, +} + +fn file_message(color: MsgColor, left: &str, right: &PathBuf) { + let right = format!("target {}", right.to_str().unwrap()); + message(color, left, &right); +} + +fn message(color: MsgColor, left: S, right: S) +where + S: Colorize + std::fmt::Display, +{ + let left = match color { + MsgColor::Green => left.green(), + MsgColor::Cyan => left.cyan(), + MsgColor::Red => left.red(), + }; + println!("{left:>12} {right}"); +} + +fn run(name: &PathBuf) -> Result<()> { + file_message(MsgColor::Green, "Assembling", &name); + let air = assemble(&name)?; + + message(MsgColor::Green, "Running", "emitted binary"); + let mut program = RunState::try_from(air)?; + program.run(); + + file_message(MsgColor::Green, "Completed", &name); + Ok(()) +} + +/// Return assembly intermediate representation of source file for further processing +fn assemble(name: &PathBuf) -> Result { let contents = StaticSource::new(fs::read_to_string(&name).into_diagnostic()?); let parser = lace::AsmParser::new(contents.src())?; let mut air = parser.parse()?; air.backpatch()?; - for stmt in air { - let _ = stmt.emit()?; - } - Ok(()) + Ok(air) } const LOGO: &str = r#" @@ -227,8 +221,4 @@ an all-in-one toolchain for working with LC3 assembly code. Please use `-h` or `--help` to access the usage instructions and documentation. "; -const SHORT_HELP: &str = r" -Unable to recognise command. Please use `-h` or `--help` to view usage instructions. -"; - const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/runtime.rs b/src/runtime.rs index 2e2cb0e..69f1c13 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -1,9 +1,9 @@ -#![allow(unused)] use core::panic; -use std::{cmp::Ordering, i16, u16, u32, u8, usize}; +use std::{cmp::Ordering, i16, io::Write, u16, u32, u8, usize}; -use crate::{symbol::Flag, Air}; +use crate::Air; use colored::Colorize; +use console::Term; use miette::Result; /// LC3 can address 128KB of memory. @@ -21,7 +21,7 @@ pub struct RunState { /// Condition code flag: RunFlag, /// Processor status register - psr: u16, + _psr: u16, } #[derive(Clone, Copy)] @@ -55,7 +55,7 @@ impl RunState { pc: orig as u16, reg: [0; 8], flag: RunFlag::Uninit, - psr: 0, + _psr: 0, }) } @@ -134,11 +134,11 @@ impl RunState { let dr = (instr >> 9) & 0b111; let sr = (instr >> 6) & 0b111; - let val1 = *self.reg(dr); + let val1 = *self.reg(sr); // Check if imm let val2 = if instr & 0b100000 == 0 { // reg - *self.reg((instr & 0b111)) + *self.reg(instr & 0b111) } else { // imm Self::s_ext(instr, 5) @@ -152,11 +152,11 @@ impl RunState { let dr = (instr >> 9) & 0b111; let sr = (instr >> 6) & 0b111; - let val1 = *self.reg(dr); + let val1 = *self.reg(sr); // Check if imm let val2 = if instr & 0b100000 == 0 { // reg - *self.reg((instr & 0b111)) + *self.reg(instr & 0b111) } else { // imm Self::s_ext(instr, 5) @@ -229,7 +229,7 @@ impl RunState { self.set_flags(val); } - fn rti(&mut self, instr: u16) { + fn rti(&mut self, _instr: u16) { todo!("Please open an issue and I'll get RTI implemented in a jiffy :)") } @@ -250,7 +250,8 @@ impl RunState { let sr = (instr >> 9) & 0b111; let br = (instr >> 6) & 0b111; let ptr = *self.reg(br); - *self.mem(ptr.wrapping_add(Self::s_ext(instr, 6))); + let val = *self.reg(sr); + *self.mem(ptr.wrapping_add(Self::s_ext(instr, 6))) = val; } fn trap(&mut self, instr: u16) { @@ -258,14 +259,15 @@ impl RunState { match trap_vect { // getc 0x20 => { - // TODO: impl getc - todo!("TODO: Some dependencies are required for immediate response.") + let cons = Term::stdout(); + let c = cons.read_char().unwrap(); + *self.reg(0) = c as u16; } // out - 0x21 => unsafe { + 0x21 => { let chr = (*self.reg(0) & 0xFF) as u8 as char; - print!("{chr}") - }, + print!("{chr}"); + } // puts 0x22 => { // could probably rewrite with iterators but idk if worth @@ -284,8 +286,11 @@ impl RunState { } // in 0x23 => { - // TODO: impl in - todo!("TODO: Some dependencies are required for immediate response.") + let mut cons = Term::stdout(); + let c = cons.read_char().unwrap(); + *self.reg(0) = c as u16; + write!(cons, "{c}").unwrap(); + cons.flush().unwrap(); } // putsp 0x24 => {