use std::path::PathBuf;
use std::process::ExitCode;
use tclrs::Interp;
#[cfg(feature = "tk")]
mod main_thread;
mod repl;
mod repl_line;
const USAGE: &str = "\
usage: tclrs [options] FILE ?arg ...?
tclrs [options] -c SCRIPT ?arg ...?
tclrs [options] read the script from stdin
options:
-c SCRIPT run SCRIPT instead of a file
--aot OUT compile the script to a standalone native executable at OUT
--aot-object O emit the relocatable AOT object only (link it yourself)
--lsp speak the Language Server Protocol on stdin/stdout
--dap speak the Debug Adapter Protocol on stdin/stdout
--tiers run the script, then report which fusevm tiers took its chunk
--dump-tokens print the parser's lexical output instead of running it
--dump-ast print the parse tree instead of running it
--disasm print the compiled bytecode instead of running it
-h, --help this message
--version, -V version";
#[cfg(feature = "tk")]
const TK_USAGE: &str =
"\n --tk run on the main thread, with the Tk event loop available";
#[cfg(not(feature = "tk"))]
const TK_USAGE: &str = "";
fn main() -> ExitCode {
match tk_session() {
false => std::thread::Builder::new()
.stack_size(tclrs::runtime::RECOMMENDED_STACK)
.spawn(drive)
.expect("spawn interpreter thread")
.join()
.unwrap_or(ExitCode::FAILURE),
#[cfg(feature = "tk")]
true => main_thread::run(drive),
#[cfg(not(feature = "tk"))]
true => unreachable!("--tk is not a recognized option in this build"),
}
}
#[cfg(feature = "tk")]
fn tk_session() -> bool {
for arg in std::env::args().skip(1) {
match arg.as_str() {
"--tk" => return true,
"-c" => return false,
a if a.starts_with('-') => continue,
_ => return false,
}
}
false
}
#[cfg(not(feature = "tk"))]
fn tk_session() -> bool {
false
}
enum Action {
Run,
Aot(PathBuf),
AotObject(PathBuf),
Tiers,
DumpTokens,
DumpAst,
Disasm,
}
enum Source {
File(String),
Command(String),
Stdin,
}
fn drive() -> ExitCode {
let mut argv = std::env::args();
let program = argv.next().unwrap_or_else(|| "tclrs".to_string());
let args: Vec<String> = argv.collect();
let mut action = Action::Run;
let mut source = Source::Stdin;
let mut script_args: &[String] = &[];
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--version" | "-V" => {
println!("tclrs {}", env!("CARGO_PKG_VERSION"));
return ExitCode::SUCCESS;
}
"-h" | "--help" => {
println!("{USAGE}{TK_USAGE}");
return ExitCode::SUCCESS;
}
#[cfg(feature = "tk")]
"--tk" => {}
"--lsp" => return ExitCode::from(!tclrs::lsp::run_stdio() as u8),
"--dap" => return ExitCode::from(tclrs::dap::run_stdio() as u8),
"--tiers" => action = Action::Tiers,
"--dump-tokens" => action = Action::DumpTokens,
"--dump-ast" => action = Action::DumpAst,
"--disasm" => action = Action::Disasm,
flag @ ("--aot" | "--aot-object") => {
let Some(out) = args.get(i + 1) else {
return fail(&format!("{flag} requires a path"));
};
action = match flag {
"--aot" => Action::Aot(PathBuf::from(out)),
_ => Action::AotObject(PathBuf::from(out)),
};
i += 1;
}
"-c" => {
let Some(script) = args.get(i + 1) else {
return fail("-c requires a script");
};
source = Source::Command(script.clone());
script_args = &args[(i + 2).min(args.len())..];
break;
}
option if option.starts_with('-') => {
return fail(&format!("unknown option \"{option}\""))
}
file => {
source = Source::File(file.to_string());
script_args = &args[i + 1..];
break;
}
}
i += 1;
}
if let (Action::Run, Source::Stdin) = (&action, &source) {
let mut interp = interp_for(&program, script_args, None);
let status = match repl::stdin_is_terminal() {
true => repl_line::run(&mut interp),
false => repl::run(&mut interp, false),
};
if status == ExitCode::SUCCESS {
tk_main_loop();
}
return status;
}
let (src, file) = match &source {
Source::File(path) => match std::fs::read_to_string(path) {
Ok(src) => {
tclrs::runtime::note_script(path);
(src, Some(path.as_str()))
}
Err(e) => {
eprintln!("couldn't read file \"{path}\": {}", read_failure(&e));
return ExitCode::FAILURE;
}
},
Source::Command(script) => (script.clone(), None),
Source::Stdin => match std::io::read_to_string(std::io::stdin()) {
Ok(src) => (src, None),
Err(e) => return fail(&format!("stdin: {e}")),
},
};
match action {
Action::Run => {
let mut interp = interp_for(file.unwrap_or(&program), script_args, file);
let status = run_source(&mut interp, &src, file);
if status == ExitCode::SUCCESS {
tk_main_loop();
}
status
}
Action::Aot(out) => report(tclrs::aot::compile_executable(&src, &out)),
Action::AotObject(out) => report(tclrs::aot::compile_object(&src, &out)),
Action::Tiers => match tclrs::tiers::report(&src) {
Ok(r) => {
println!("{r}");
ExitCode::SUCCESS
}
Err(e) => fail(&e),
},
Action::DumpTokens => match tclrs::dump::tokens(&src) {
Ok(listing) => {
print!("{listing}");
ExitCode::SUCCESS
}
Err(e) => fail(&e),
},
Action::DumpAst => match tclrs::dump::ast(&src) {
Ok(tree) => {
print!("{tree}");
ExitCode::SUCCESS
}
Err(e) => fail(&e),
},
Action::Disasm => match tclrs::runtime::compile(&src) {
Ok(chunk) => {
print!("{}", chunk.disassemble());
ExitCode::SUCCESS
}
Err(e) => fail(&e),
},
}
}
fn fail(reason: &str) -> ExitCode {
eprintln!("tclrs: {reason}");
ExitCode::FAILURE
}
fn report(outcome: Result<(), String>) -> ExitCode {
match outcome {
Ok(()) => ExitCode::SUCCESS,
Err(e) => fail(&e),
}
}
fn interp_for(argv0: &str, args: &[String], file: Option<&str>) -> Interp {
let mut interp = Interp::new();
interp.set_global("argv0", argv0);
interp.set_global("argc", args.len().to_string());
interp.set_global("argv", tclrs::list::join(args));
#[cfg(feature = "tk")]
if tk_session() {
tclrs::tk::session::open(&interp, file);
}
#[cfg(not(feature = "tk"))]
let _ = file;
interp
}
#[cfg(feature = "tk")]
fn tk_main_loop() {
if tk_session() {
tclrs::tk::session::main_loop();
}
}
#[cfg(not(feature = "tk"))]
fn tk_main_loop() {}
fn run_source(interp: &mut Interp, src: &str, file: Option<&str>) -> ExitCode {
match interp.eval(src) {
Ok(_) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("{}", e.msg);
if let (Some(file), Some(line)) = (file, e.line) {
eprintln!(" (file \"{file}\" line {line})");
}
ExitCode::FAILURE
}
}
}
fn read_failure(e: &std::io::Error) -> String {
use std::io::ErrorKind;
match e.kind() {
ErrorKind::NotFound => "no such file or directory".to_string(),
ErrorKind::PermissionDenied => "permission denied".to_string(),
ErrorKind::IsADirectory => "is a directory".to_string(),
_ => e.to_string(),
}
}