use std::borrow::Cow;
use std::fs::{read_to_string, write};
use std::io::{stdin, stdout, Write};
use std::io::{Read, Result as IoResult};
use std::path::PathBuf;
use structopt::StructOpt;
use a4_core::compiler::Context;
use a4_core::std_rt::std_builtins;
use a4_core::{Error, StepResult, WhichToken};
#[derive(Debug, StructOpt)]
#[structopt(
name = "a4",
about = "A forth-inspired, bytecode-compiled scripting language for Anachro Powerbus"
)]
enum Opt {
Repl {
input: Option<PathBuf>,
#[structopt(short, long)]
debug: bool,
},
Run {
input: PathBuf,
#[structopt(short, long)]
debug: bool,
},
Compile {
input: PathBuf,
output: Option<PathBuf>,
#[structopt(short, long = "omit-word-names")]
omit_word_names: bool,
},
}
fn main() -> Result<(), Error> {
let opt = Opt::from_args();
match opt {
Opt::Repl { input, debug } => {
println!("Entering Repl...");
repl_main(input, debug)?;
}
Opt::Compile {
input,
output,
omit_word_names,
} => {
let output = output.unwrap_or({
let mut out = input.clone();
assert!(out.set_extension("a4"), "no filename?");
out
});
compile_main(input, output, omit_word_names)?;
}
Opt::Run { input, debug } => {
run_main(input, debug)?;
}
}
Ok(())
}
fn compile_main(input: PathBuf, output: PathBuf, omit_word_names: bool) -> Result<(), Error> {
let mut ctxt = Context::with_builtins(std_builtins());
let source = read_to_string(&input).map_err(|_| Error::Input)?;
for line in source.lines() {
let parts = line.split_whitespace().map(str::to_string).collect();
ctxt.evaluate(parts)?;
}
let mut extras = false;
ctxt.dict.data.retain(|k, _| {
let keep = !k.starts_with("__");
if !keep {
extras = true;
}
keep
});
eprintln!(
"
WARNING: Found at least one non-definition in the input file.
These line(s) will NOT be serialized or executed. Please review
your source file to ensure it ONLY includes definitions, which
start with a ':', and end with a ';'.
"
);
let mut ser = ctxt.serialize();
if omit_word_names {
ser.data_map = None;
}
let pcser = postcard::to_stdvec(&ser).unwrap();
let mut zc = rzcobs::encode(&pcser);
zc.push(0);
write(&output, &zc).map_err(|_| Error::OutputFormat)?;
println!("Input file: {:?}", input);
println!("Output file: {:?}", output);
println!("===========================================");
println!("Builtin words used: {}", ser.bis.len());
println!("User defined words: {}", ser.data.len());
println!("Serialized size (bytes): {}", zc.len());
Ok(())
}
fn run_main(input: PathBuf, debug: bool) -> Result<(), Error> {
let mut ctxt = Context::with_builtins(std_builtins());
let input = read_to_string(input).map_err(|_| Error::Input)?;
for line in input.lines() {
let input: Vec<String> = line.split_whitespace().map(str::to_string).collect();
if input.is_empty() {
continue;
}
if debug {
println!("=> {}", line);
}
ctxt.evaluate(input)?;
let is_ok = loop {
match ctxt.step() {
Ok(StepResult::Working(WhichToken::Single(ft))) => {
ft.exec(&mut ctxt.rt).unwrap();
}
Ok(StepResult::Working(WhichToken::Ref(rtw))) => {
let c = ctxt
.dict
.data
.get(&rtw.tok)
.and_then(|n| n.inner.get(rtw.idx))
.map(|n| n.clone().word);
ctxt.rt.provide_seq_tok(c).unwrap();
}
Ok(StepResult::Done) => break true,
Err(e) => {
eprintln!("ERROR! -> {:?}", e);
break false;
}
}
if debug {
println!("# {:?} - {:?}", ctxt.data_stack().data(), ctxt.return_stack().data());
}
};
ctxt.dict.data.retain(|k, _| !k.starts_with("__"));
print(&mut ctxt, is_ok);
}
Ok(())
}
fn repl_main(input: Option<PathBuf>, debug: bool) -> Result<(), Error> {
let mut ctxt = Context::with_builtins(std_builtins());
if let Some(pb) = input {
match pb.extension().map(|x| x.to_string_lossy()) {
Some(Cow::Borrowed("a4")) => {
let mut f = std::fs::File::open(pb).unwrap();
let mut buf = Vec::new();
f.read_to_end(&mut buf).unwrap();
assert_eq!(Some(&0x00), buf.last());
buf.pop();
let unrz = rzcobs::decode(&buf).unwrap();
let deser = postcard::from_bytes(&unrz).unwrap();
ctxt.load_ser_dict(&deser);
}
Some(_) => todo!("No .fth loading yet, sorry"),
None => {
eprintln!("ERROR: No extension found!");
return Err(Error::InternalError);
}
}
}
loop {
let input = read().map_err(|_| Error::Input)?;
ctxt.evaluate(input)?;
let is_ok = loop {
match ctxt.step() {
Ok(StepResult::Working(WhichToken::Single(ft))) => {
ft.exec(&mut ctxt.rt).unwrap();
}
Ok(StepResult::Working(WhichToken::Ref(rtw))) => {
let c = ctxt
.dict
.data
.get(&rtw.tok)
.and_then(|n| n.inner.get(rtw.idx))
.map(|n| n.clone().word);
ctxt.rt.provide_seq_tok(c).unwrap();
}
Ok(StepResult::Done) => break true,
Err(e) => {
eprintln!("ERROR! -> {:?}", e);
break false;
}
}
if debug {
println!("# {:?} - {:?}", ctxt.data_stack().data(), ctxt.return_stack().data());
}
};
ctxt.dict.data.retain(|k, _| !k.starts_with("__"));
print(&mut ctxt, is_ok);
}
}
fn read() -> IoResult<Vec<String>> {
print!("=> ");
stdout().flush().ok();
let mut buf = String::new();
stdin().read_line(&mut buf)?;
Ok(buf.split_whitespace().map(str::to_string).collect())
}
fn print(ctxt: &mut Context, good: bool) {
print!("{}", ctxt.output());
if good {
println!(" ok ");
} else {
println!(" bad ");
}
}