#[allow(unused)]
use std::{
collections::HashSet,
env, fs,
path::{Path, PathBuf},
process::Command,
process::ExitCode,
time::Instant,
};
use crate::{
build::{build, BuildConfig, BuildOutput, EznoParsePostCheckVisitors, FailedBuildOutput},
check::check,
reporting::emit_diagnostics,
utilities::{self, print_to_cli},
};
use argh::FromArgs;
use checker::CheckOutput;
use parser::ParseOptions;
#[derive(FromArgs, Debug)]
struct TopLevel {
#[argh(subcommand)]
nested: CompilerSubCommand,
}
#[derive(FromArgs, Debug)]
#[argh(subcommand)]
enum CompilerSubCommand {
Info(Info),
ASTExplorer(crate::ast_explorer::ExplorerArguments),
Check(CheckArguments),
Experimental(ExperimentalArguments),
Repl(crate::repl::ReplArguments),
}
#[derive(FromArgs, Debug)]
#[argh(subcommand, name = "info")]
struct Info {}
#[derive(FromArgs, Debug)]
#[argh(subcommand, name = "experimental")]
pub(crate) struct ExperimentalArguments {
#[argh(subcommand)]
nested: ExperimentalSubcommand,
}
#[derive(FromArgs, Debug)]
#[argh(subcommand)]
pub(crate) enum ExperimentalSubcommand {
Build(BuildArguments),
Format(FormatArguments),
#[cfg(not(target_family = "wasm"))]
Upgrade(UpgradeArguments),
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand, name = "build")]
#[allow(clippy::struct_excessive_bools)]
pub(crate) struct BuildArguments {
#[argh(positional)]
pub input: PathBuf,
#[argh(positional)]
pub output: Option<PathBuf>,
#[argh(option, short = 'd')]
pub definition_file: Option<PathBuf>,
#[argh(switch, short = 'm')]
pub minify: bool,
#[argh(switch)]
pub no_comments: bool,
#[argh(switch)]
pub source_maps: bool,
#[argh(switch)]
pub compact_diagnostics: bool,
#[argh(switch)]
pub non_standard_syntax: bool,
#[argh(switch)]
pub non_standard_library: bool,
#[argh(switch)]
pub optimise: bool,
#[cfg(not(target_family = "wasm"))]
#[argh(switch)]
pub timings: bool,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand, name = "check")]
pub(crate) struct CheckArguments {
#[argh(positional)]
pub input: PathBuf,
#[argh(option, short = 'd')]
pub definition_file: Option<PathBuf>,
#[argh(switch)]
pub watch: bool,
#[argh(switch)]
pub timings: bool,
#[argh(switch)]
pub count_diagnostics: bool,
#[argh(switch)]
pub compact_diagnostics: bool,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand, name = "format")]
pub(crate) struct FormatArguments {
#[argh(positional)]
pub path: PathBuf,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand, name = "upgrade")]
#[cfg(not(target_family = "wasm"))]
pub(crate) struct UpgradeArguments {}
#[allow(unused)]
fn file_system_resolver(path: &Path) -> Option<String> {
if path.to_str() == Some("BLANK") {
return Some(String::new());
}
match fs::read_to_string(path) {
Ok(source) => Some(source),
Err(_) => None,
}
}
pub fn run_cli<T: crate::ReadFromFS, U: crate::WriteToFS, V: crate::CLIInputResolver>(
cli_arguments: &[&str],
read_file: &T,
write_file: U,
cli_input_resolver: V,
) -> ExitCode {
let command = match FromArgs::from_args(&["ezno-cli"], cli_arguments) {
Ok(TopLevel { nested }) => nested,
Err(err) => {
print_to_cli(format_args!("{}", err.output));
return ExitCode::FAILURE;
}
};
match command {
CompilerSubCommand::Info(_) => {
crate::utilities::print_info();
ExitCode::SUCCESS
}
CompilerSubCommand::Check(check_arguments) => {
let CheckArguments {
input,
watch: _,
definition_file,
timings,
count_diagnostics,
compact_diagnostics,
} = check_arguments;
let entry_points = vec![input];
#[cfg(not(target_family = "wasm"))]
let start = timings.then(std::time::Instant::now);
let type_check_options = Default::default();
let CheckOutput { diagnostics, module_contents, .. } =
check(entry_points, read_file, definition_file.as_deref(), type_check_options);
#[cfg(not(target_family = "wasm"))]
if let Some(start) = start {
eprintln!("Checked in {:?}", start.elapsed());
};
if diagnostics.has_error() {
if count_diagnostics {
let count = diagnostics.into_iter().count();
print_to_cli(format_args!("Found {count} type errors and warnings 😬"))
} else {
emit_diagnostics(diagnostics, &module_contents, compact_diagnostics).unwrap();
}
ExitCode::FAILURE
} else {
emit_diagnostics(diagnostics, &module_contents, compact_diagnostics).unwrap();
print_to_cli(format_args!("No type errors found 🎉"));
ExitCode::SUCCESS
}
}
CompilerSubCommand::Experimental(ExperimentalArguments {
nested: ExperimentalSubcommand::Build(build_config),
}) => {
let output_path = build_config.output.unwrap_or("ezno_output.js".into());
let default_builders = EznoParsePostCheckVisitors {
expression_visitors_mut: vec![Box::new(
crate::transformers::optimisations::ExpressionOptimiser,
)],
statement_visitors_mut: vec![Box::new(
crate::transformers::optimisations::StatementOptimiser,
)],
variable_visitors_mut: Default::default(),
block_visitors_mut: Default::default(),
};
let input_paths = vec![build_config.input];
let output = build(
input_paths,
read_file,
build_config.definition_file.as_deref(),
&output_path,
&BuildConfig { strip_whitespace: build_config.minify },
Some(default_builders),
);
let compact_diagnostics = build_config.compact_diagnostics;
match output {
Ok(BuildOutput { diagnostics, fs, outputs }) => {
for output in outputs {
write_file(output.output_path.as_path(), output.content);
}
emit_diagnostics(diagnostics, &fs, compact_diagnostics).unwrap();
print_to_cli(format_args!("Project built successfully 🎉"));
ExitCode::SUCCESS
}
Err(FailedBuildOutput { fs, diagnostics }) => {
emit_diagnostics(diagnostics, &fs, compact_diagnostics).unwrap();
ExitCode::FAILURE
}
}
}
CompilerSubCommand::Experimental(ExperimentalArguments {
nested: ExperimentalSubcommand::Format(FormatArguments { path }),
}) => {
use parser::{source_map::FileSystem, ASTNode, Module, ToStringOptions};
let input = match fs::read_to_string(&path) {
Ok(string) => string,
Err(err) => {
print_to_cli(format_args!("{err:?}"));
return ExitCode::FAILURE;
}
};
let mut files =
parser::source_map::MapFileStore::<parser::source_map::NoPathMap>::default();
let source_id = files.new_source_id(path.clone(), input.clone());
let res = Module::from_string(
input,
ParseOptions { retain_blank_lines: true, ..Default::default() },
);
match res {
Ok(module) => {
let options =
ToStringOptions { trailing_semicolon: true, ..Default::default() };
let _ = fs::write(path.clone(), module.to_string(&options));
print_to_cli(format_args!("Formatted {} 🎉", path.display()));
ExitCode::SUCCESS
}
Err(err) => {
emit_diagnostics(std::iter::once((err, source_id).into()), &files, false)
.unwrap();
ExitCode::FAILURE
}
}
}
#[cfg(not(target_family = "wasm"))]
CompilerSubCommand::Experimental(ExperimentalArguments {
nested: ExperimentalSubcommand::Upgrade(UpgradeArguments {}),
}) => match utilities::upgrade_self() {
Ok(name) => {
print_to_cli(format_args!("Successfully updated to {name}"));
std::process::ExitCode::SUCCESS
}
Err(err) => {
print_to_cli(format_args!("Error: {err}\nCould not upgrade binary. Retry manually from {repository}/releases", repository=env!("CARGO_PKG_REPOSITORY")));
std::process::ExitCode::FAILURE
}
},
CompilerSubCommand::ASTExplorer(mut repl) => {
repl.run(read_file, cli_input_resolver);
ExitCode::SUCCESS
}
CompilerSubCommand::Repl(argument) => {
crate::repl::run_repl(cli_input_resolver, argument);
ExitCode::SUCCESS
}
}
}