mod build;
mod cache;
mod deps;
mod new;
mod repl;
mod test;
use std::path::Path;
use std::process::ExitCode;
use deps::Located;
use doge_compiler::DependencyMap;
const EXIT_OK: u8 = 0;
const EXIT_FAILURE: u8 = 1;
const EXIT_USAGE: u8 = 2;
const USAGE: &str = "such usage: doge <new|bark|build|check|fmt|test|lsp|repl> [name|script.doge]";
const LSP_ERROR_HEADLINE: &str = "very server. much broken.";
const MISSING_FILE_HEADLINE: &str = "very missing. much file.";
const UNFORMATTED_HEADLINE: &str = "very messy. much unformatted.";
const RUNTIME_ERROR_HEADLINE: &str = "very error. much broken.";
const DEFAULT_BINARY_STEM: &str = "doge_program";
const INTERP_ENV: &str = "DOGE_INTERP";
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().skip(1).collect();
match args.as_slice() {
[cmd, name] if cmd == "new" => new::run(name),
[cmd, path, rest @ ..] if cmd == "bark" => run_bark(Some(path), rest),
[cmd] if cmd == "bark" => run_bark(None, &[]),
[cmd, path] if cmd == "build" => run_build(Some(path)),
[cmd] if cmd == "build" => run_build(None),
[cmd, path] if cmd == "check" => run_check(Some(path)),
[cmd] if cmd == "check" => run_check(None),
[cmd, path] if cmd == "fmt" => run_fmt(path, false),
[cmd, flag, path] if cmd == "fmt" && flag == "--check" => run_fmt(path, true),
[cmd, path] if cmd == "test" => {
let path = path.to_string();
on_big_stack(move || test::run_test(&path))
}
[cmd] if cmd == "lsp" => run_lsp(),
[cmd] if cmd == "repl" => on_big_stack(repl::run),
[] => on_big_stack(repl::run),
_ => {
eprintln!("{USAGE}");
ExitCode::from(EXIT_USAGE)
}
}
}
fn run_bark(path: Option<&str>, args: &[String]) -> ExitCode {
let located = match deps::locate(path) {
Ok(located) => located,
Err(message) => {
eprintln!("{message}");
return ExitCode::from(EXIT_FAILURE);
}
};
if std::env::var_os(INTERP_ENV).is_some() {
let args = args.to_vec();
return on_big_stack(move || run_interpreted(located, args));
}
let (source, generated) = match compile_program(&located.entry, located.deps) {
Ok(pair) => pair,
Err(code) => return code,
};
let binary = match build::ensure_binary(&source, &generated) {
Ok(binary) => binary,
Err(message) => {
eprintln!("{message}");
return ExitCode::from(EXIT_FAILURE);
}
};
match build::spawn(&binary, args) {
Ok(code) => ExitCode::from(code as u8),
Err(message) => {
eprintln!("{message}");
ExitCode::from(EXIT_FAILURE)
}
}
}
fn run_build(path: Option<&str>) -> ExitCode {
let located = match deps::locate(path) {
Ok(located) => located,
Err(message) => {
eprintln!("{message}");
return ExitCode::from(EXIT_FAILURE);
}
};
let stem = binary_stem(&located);
let (source, generated) = match compile_program(&located.entry, located.deps) {
Ok(pair) => pair,
Err(code) => return code,
};
let binary = match build::ensure_binary(&source, &generated) {
Ok(binary) => binary,
Err(message) => {
eprintln!("{message}");
return ExitCode::from(EXIT_FAILURE);
}
};
match build::copy_to_cwd(&binary, &stem) {
Ok(()) => {
println!("such binary: ./{stem}");
ExitCode::from(EXIT_OK)
}
Err(message) => {
eprintln!("{message}");
ExitCode::from(EXIT_FAILURE)
}
}
}
fn binary_stem(located: &Located) -> String {
if let Some(name) = &located.package_name {
return name.clone();
}
located
.entry
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or(DEFAULT_BINARY_STEM)
.to_string()
}
fn run_check(path: Option<&str>) -> ExitCode {
let located = match deps::locate(path) {
Ok(located) => located,
Err(message) => {
eprintln!("{message}");
return ExitCode::from(EXIT_FAILURE);
}
};
let entry = located.entry.to_string_lossy().into_owned();
let source = match read_source(&entry) {
Ok(source) => source,
Err(code) => return code,
};
let program = match doge_compiler::load_program_with_deps(&entry, &source, located.deps) {
Ok(program) => program,
Err(diag) => {
eprint!("{}", diag.render());
return ExitCode::from(EXIT_FAILURE);
}
};
if let Err(diag) = doge_compiler::check_program(&program) {
eprint!("{}", diag.render());
return ExitCode::from(EXIT_FAILURE);
}
print!("{}", doge_compiler::dump(&program.files[0].script));
ExitCode::from(EXIT_OK)
}
fn run_fmt(path: &str, check: bool) -> ExitCode {
let source = match read_source(path) {
Ok(source) => source,
Err(code) => return code,
};
let formatted = match doge_compiler::format(path, &source) {
Ok(formatted) => formatted,
Err(diag) => {
eprint!("{}", diag.render());
return ExitCode::from(EXIT_FAILURE);
}
};
if check {
if formatted == source {
return ExitCode::from(EXIT_OK);
}
eprintln!("{UNFORMATTED_HEADLINE}\n\n {path} needs formatting: run doge fmt {path}");
return ExitCode::from(EXIT_FAILURE);
}
if formatted == source {
return ExitCode::from(EXIT_OK);
}
match std::fs::write(path, &formatted) {
Ok(()) => {
println!("such format: {path}");
ExitCode::from(EXIT_OK)
}
Err(err) => {
eprintln!("very disk. much sad.\n\n doge could not write {path}: {err}");
ExitCode::from(EXIT_FAILURE)
}
}
}
fn run_lsp() -> ExitCode {
match doge_lsp::run_stdio() {
Ok(()) => ExitCode::from(EXIT_OK),
Err(err) => {
eprintln!("{LSP_ERROR_HEADLINE}\n\n the doge language server stopped: {err}");
ExitCode::from(EXIT_FAILURE)
}
}
}
fn run_interpreted(located: Located, args: Vec<String>) -> ExitCode {
doge_runtime::set_script_args(args);
let entry = located.entry.to_string_lossy().into_owned();
let source = match read_source(&entry) {
Ok(source) => source,
Err(code) => return code,
};
let program = match doge_compiler::load_program_with_deps(&entry, &source, located.deps) {
Ok(program) => program,
Err(diag) => {
eprint!("{}", diag.render());
return ExitCode::from(EXIT_FAILURE);
}
};
if let Err(diag) = doge_compiler::check_program(&program) {
eprint!("{}", diag.render());
return ExitCode::from(EXIT_FAILURE);
}
let program = std::sync::Arc::new(program);
let mut interp = doge_interp::Interp::new();
match interp.run(program.clone()) {
Ok(()) => ExitCode::from(EXIT_OK),
Err(err) => {
let (fid, line) = interp.error_site();
let file = &program.files[fid];
let src_line = file
.source
.lines()
.nth((line as usize).saturating_sub(1))
.unwrap_or("");
eprintln!(
"{RUNTIME_ERROR_HEADLINE}\n\n {}:{}\n {}\n {}",
file.path, line, src_line, err
);
ExitCode::from(EXIT_FAILURE)
}
}
}
fn compile_program(entry: &Path, deps: DependencyMap) -> Result<(String, String), ExitCode> {
let entry = entry.to_string_lossy().into_owned();
let source = read_source(&entry)?;
let program = match doge_compiler::load_program_with_deps(&entry, &source, deps) {
Ok(program) => program,
Err(diag) => {
eprint!("{}", diag.render());
return Err(ExitCode::from(EXIT_FAILURE));
}
};
if let Err(diag) = doge_compiler::check_program(&program) {
eprint!("{}", diag.render());
return Err(ExitCode::from(EXIT_FAILURE));
}
match doge_compiler::generate_program(&program) {
Ok(generated) => Ok((cache_source(&program), generated)),
Err(diag) => {
eprint!("{}", diag.render());
Err(ExitCode::from(EXIT_FAILURE))
}
}
}
fn cache_source(program: &doge_compiler::Program) -> String {
let mut blob = String::new();
for file in &program.files {
blob.push_str(&file.path);
blob.push('\0');
blob.push_str(&file.source);
blob.push('\0');
}
blob
}
fn on_big_stack<T: Send + 'static>(f: impl FnOnce() -> T + Send + 'static) -> T {
const STACK: usize = 256 * 1024 * 1024;
std::thread::Builder::new()
.stack_size(STACK)
.spawn(f)
.expect("spawning the interpreter thread")
.join()
.expect("the interpreter thread panicked")
}
fn read_source(path: &str) -> Result<String, ExitCode> {
std::fs::read_to_string(path).map_err(|err| {
eprintln!("{MISSING_FILE_HEADLINE}\n\n doge cannot read {path}: {err}");
ExitCode::from(EXIT_FAILURE)
})
}