Skip to content

Commit

Permalink
Merge pull request #22 from rozukke/fix/check-not-emti
Browse files Browse the repository at this point in the history
Fix not emitting and several runtime bugs
  • Loading branch information
rozukke authored Sep 16, 2024
2 parents 6d16148 + 64057b8 commit 0d82fe9
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 115 deletions.
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@
[![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
- `run`: assemble and run a file - all in one command.
- `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.

Expand All @@ -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!)
Expand Down
2 changes: 1 addition & 1 deletion src/air.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
4 changes: 2 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down
10 changes: 7 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
162 changes: 76 additions & 86 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
#![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;
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)]
Expand Down Expand Up @@ -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,
Expand All @@ -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());
Expand All @@ -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<S>(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<Air> {
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#"
Expand All @@ -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");
Loading

0 comments on commit 0d82fe9

Please sign in to comment.