use hase::{assemble, debug, run, ExecutionResult};
use is_terminal::is_terminal;
use std::{
fs,
io::{self, stdin, BufRead, Write},
iter::Peekable,
path::PathBuf,
};
enum RunMode {
Normal,
ToEnd,
Debug,
}
fn main() {
if let Err(err) = handle_args() {
eprintln!("{err}");
std::process::exit(1)
}
}
/// Run a program:
/// $ hase debug program.hase
/// $ hase to-end program.hase
/// $ hase program.hase
/// $ hase program.rom
/// $ echo '=1' | hase
/// $ hase <<< '=1'
/// $ hase -
///
/// Assemble a program:
/// $ hase asm program.hase
/// $ hase asm program.hase program.rom
/// $ echo '=1' | hase asm program.rom
/// $ hase asm program.rom<<< '=1'
/// $ hase asm -
/// $ hase asm - program.rom
/// $ hase asm program.rom
fn handle_args() -> Result<(), String> {
let mut args = std::env::args().peekable();
let path = args.next();
let mode = loop {
if args.peek().map(|v| v == "debug").unwrap_or_default() {
args.next();
break RunMode::Debug;
} else if args.peek().map(|v| v == "to-end").unwrap_or_default() {
args.next();
break RunMode::ToEnd;
} else {
break RunMode::Normal;
}
};
if let Some(arg) = args.next() {
if arg == "-" {
run_stdin(mode)
} else if arg == "asm" {
handle_assembly(args)
} else {
handle_running(arg, mode)
}
} else if !is_terminal(&stdin()) {
run_stdin(mode)
} else {
return Err(format!(
"Hase, a simple paper computing language.\nUsage: {} [debug|asm|to-end] (<file.hase> | <file.o> | -)\n\nRun a program:\t\thase file.hase\nCompile a program:\thase asm file.hase > file.o\nDebug a program:\thase debug file.hase\nRun stdin:\t\thase -",
path.to_owned().unwrap()
));
}
}
fn read_file(path: &PathBuf) -> Result<String, String> {
fs::read_to_string(path).map_err(|e| {
format!(
"Couldn't read {}: {e}",
path.as_os_str().to_str().unwrap_or_default()
)
})
}
fn handle_running(arg: String, mode: RunMode) -> Result<(), String> {
let path = PathBuf::from(arg);
match path.extension().map(|e| e.to_str().unwrap()) {
Some("hase") => handle_running_source(
path.file_name().unwrap().to_str().unwrap(),
read_file(&path)?,
mode,
),
Some("o") => match mode {
RunMode::Debug => Err("Can't debug compiled code, did you mean to-end?".to_owned()),
other => run_bytecode(
fs::read(&path).map_err(|e| {
format!(
"Couldn't read {}: {e}",
path.as_os_str().to_str().unwrap_or_default()
)
})?,
matches!(other, RunMode::ToEnd),
),
},
_ => Err("Expected .hase or .o file".to_string()),
}
}
fn handle_running_source(name: &str, source: String, mode: RunMode) -> Result<(), String> {
match mode {
RunMode::Debug => debug(&source),
other => run_bytecode(
assemble(&source).map_err(|e| format!("{}:{e}", name))?,
matches!(other, RunMode::ToEnd),
),
}
}
fn handle_assembly(mut args: Peekable<std::env::Args>) -> Result<(), String> {
let Some(source) = args.next() else {
return print_bytecode(assemble_stdin()?);
};
let path = PathBuf::from(&source);
if source == "-" {
return print_or_write(&mut args, assemble_stdin()?);
}
match path.extension().map(|e| e.to_str().unwrap()) {
Some("hase") => print_or_write(
&mut args,
assemble(
&fs::read_to_string(&source)
.map_err(|e| format!("Couldn't read file {source}: {e}"))?,
)
.map_err(|e| format!("{source}:{e}"))?,
),
Some("o") => {
todo!()
}
_ => Err("Expected .hase or .rom file or stdin".to_string()),
}
}
fn print_or_write(args: &mut Peekable<std::env::Args>, bytecode: Vec<u8>) -> Result<(), String> {
match args.next() {
Some(out) => {
fs::write(&out, &bytecode).map_err(|e| format!("Couldn't write to file {out}: {e}"))
}
None => print_bytecode(bytecode),
}
}
fn print_bytecode(bytecode: Vec<u8>) -> Result<(), String> {
io::stdout()
.lock()
.write(&bytecode)
.map_err(|e| format!("error writing to stdout: {e}"))
.map(|_| ())
}
fn run_stdin(mode: RunMode) -> Result<(), String> {
match io::stdin()
.lock()
.lines()
.collect::<Result<Vec<String>, _>>()
{
Ok(content) => handle_running_source("stdin", content.join("\n"), mode),
Err(err) => Err(format!("Couldn't read stdin: {err}")),
}
}
fn run_bytecode(bytecode: Vec<u8>, debug: bool) -> Result<(), String> {
match run(bytecode, debug) {
Ok(ExecutionResult::Number(val)) => println!("{val}"),
Ok(ExecutionResult::Full(registers)) => println!("{registers:?}"),
Ok(ExecutionResult::List(list)) => println!("{list:?}"),
Err(e) => return Err(e),
};
Ok(())
}
fn read_stdin() -> Result<String, String> {
Ok(io::stdin()
.lock()
.lines()
.collect::<Result<Vec<String>, _>>()
.map_err(|e| format!("Couldn't read stdin: {e}"))?
.join("\n"))
}
fn assemble_stdin() -> Result<Vec<u8>, String> {
assemble(&read_stdin()?).map_err(|e| format!("stdin:{e}"))
}