Skip to content

Commit

Permalink
Add file watching
Browse files Browse the repository at this point in the history
  • Loading branch information
rozukke committed Sep 16, 2024
1 parent 65ad172 commit d0fefcf
Show file tree
Hide file tree
Showing 8 changed files with 391 additions and 51 deletions.
315 changes: 288 additions & 27 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ lazy_static = "1.5.0"
miette = { version = "7.2.0", features = ["fancy"] }
indexmap = { version = "2.4.0", features = ["std"] }
fxhash = "0.2.1"
hotwatch = "0.5.0"

[dev-dependencies]
assert_cmd = "2.0.14"
Expand Down
6 changes: 3 additions & 3 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
pub fn lex_invalid_dir(span: Span, src: &'static str) -> Report {
miette!(
severity = Severity::Error,
code = "parse::dir",
code = "lex::dir",
help = "check the list of available directives in the documentation.",
labels = vec![LabeledSpan::at(span, "incorrect directive")],
"Encountered an invalid directive.",
Expand All @@ -24,7 +24,7 @@ pub fn lex_invalid_dir(span: Span, src: &'static str) -> Report {
pub fn lex_unclosed_str(span: Span, src: &'static str) -> Report {
miette!(
severity = Severity::Error,
code = "parse::str_lit",
code = "lex::str_lit",
help = "make sure to close string literals with a \" character.",
labels = vec![LabeledSpan::at(span, "incorrect literal")],
"Encountered an unterminated string literal.",
Expand All @@ -35,7 +35,7 @@ pub fn lex_unclosed_str(span: Span, src: &'static str) -> Report {
pub fn lex_invalid_lit(span: Span, src: &'static str, e: ParseIntError) -> Report {
miette!(
severity = Severity::Error,
code = "parse::bad_lit",
code = "lex::bad_lit",
help = "ranges from -32,768 to 32,767 or 0 to 65,535 are allowed",
labels = vec![LabeledSpan::at(span, "incorrect literal")],
"Encountered an invalid literal: {e}",
Expand Down
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
pub mod parser;
mod parser;
pub use parser::AsmParser;

pub mod air;
mod air;
pub use air::Air;
mod runtime;
pub use runtime::RunState;

mod error;
mod lexer;
mod symbol;
pub use symbol::{reset_state, StaticSource};
86 changes: 68 additions & 18 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@ 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 lace::{AsmParser, RunState};
use lace::{reset_state, AsmParser, RunState, StaticSource};

/// Lace is a complete & convenient assembler toolchain for the LC3 assembly language.
#[derive(Parser)]
Expand Down Expand Up @@ -65,15 +72,14 @@ fn main() -> miette::Result<()> {
if let Some(command) = args.command {
match command {
Command::Run { name } => {
let contents: &'static str =
Box::leak(Box::new(fs::read_to_string(&name).into_diagnostic()?));
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)?;
let parser = lace::AsmParser::new(contents.src())?;
let mut air = parser.parse()?;
air.backpatch()?;
// Run file
Expand All @@ -88,16 +94,14 @@ fn main() -> miette::Result<()> {
Ok(())
}
Command::Compile { name, dest } => {
// Available until end of program
let contents: &'static str =
Box::leak(Box::new(fs::read_to_string(&name).into_diagnostic()?));
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)?;
let parser = lace::AsmParser::new(contents.src())?;
let mut air = parser.parse()?;
air.backpatch()?;
// Write to file
Expand All @@ -124,25 +128,60 @@ fn main() -> miette::Result<()> {
Ok(())
}
Command::Check { name } => {
let contents: &'static str =
Box::leak(Box::new(fs::read_to_string(&name).into_diagnostic()?));
println!(
"{:>12} target {}",
"Checking".green().bold(),
name.to_str().unwrap()
);
// Process asm
let parser = lace::AsmParser::new(&contents)?;
let mut air = parser.parse()?;
air.backpatch()?;
for stmt in air {
let _ = stmt.emit()?;
}
check(&name);
println!("{:>12} with 0 errors", "Finished".green().bold(),);
Ok(())
}
Command::Clean { name } => todo!(),
Command::Watch { name } => todo!(),
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());
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) {
Ok(_) => {
println!("{:>12} no errors found", "Success!".green());
}
Err(e) => {
println!("\n{:?}", e);
}
};
reset_state();
Flow::Continue
}
EventKind::Remove(_) => {
println!("{}", "Watched file was deleted. Exiting...".red());
std::process::exit(1);
}
_ => Flow::Continue,
})
.into_diagnostic()?;
watcher.run();
Ok(())
}
Command::Fmt { name } => todo!(),
}
} else {
Expand All @@ -157,6 +196,17 @@ fn main() -> miette::Result<()> {
}
}

fn check(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(())
}

const LOGO: &str = r#"
..
x .d88"
Expand Down
2 changes: 1 addition & 1 deletion src/parser.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{borrow::Cow, fmt::Display, iter::Peekable, u16, vec::IntoIter};
use std::{borrow::Cow, fmt::Display, iter::Peekable, vec::IntoIter};

use miette::Result;

Expand Down
26 changes: 26 additions & 0 deletions src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ thread_local! {
pub static SYMBOL_TABLE: RefCell<FxMap<String, u16>> = RefCell::new(IndexMap::with_hasher(FxBuildHasher::default()));
}

pub fn reset_state() {
with_symbol_table(|sym| sym.clear());
}

/// Access to symbol table via closure
pub fn with_symbol_table<R, F>(f: F) -> R
where
Expand All @@ -19,6 +23,28 @@ where
SYMBOL_TABLE.with_borrow_mut(f)
}

/// This is not allowed to be cloned to avoid double frees.
pub struct StaticSource {
src: *mut String,
}

impl StaticSource {
pub fn new(src: String) -> Self {
let raw_ptr = Box::into_raw(Box::new(src));
Self { src: raw_ptr }
}

pub fn src(&self) -> &'static str {
unsafe { &*self.src }
}
}

impl Drop for StaticSource {
fn drop(&mut self) {
unsafe { drop(Box::from_raw(self.src)) }
}
}

/// Line number of referenced label
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Label {
Expand Down
1 change: 1 addition & 0 deletions tests/files/hw.asm
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
; comment
lea r0 hw
puts
halt
Expand Down

0 comments on commit d0fefcf

Please sign in to comment.