1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
#![warn(clippy::all)] #![warn(rust_2018_idioms)] mod subcommand; use std::sync::Arc; use std::{env, path, process}; use arret_compiler::{find_arret_root, CompileCtx, FindArretRootError}; const ARRET_FILE_EXTENSION: &str = ".arret"; fn input_arg_to_source_file( source_loader: &arret_compiler::SourceLoader, input_param: &str, ) -> arret_compiler::SourceFile { if input_param == "-" { use std::io::prelude::*; let mut input_string = String::new(); std::io::stdin().read_to_string(&mut input_string).unwrap(); source_loader.load_string("<stdin>".into(), input_string) } else { let input_path = path::Path::new(input_param); source_loader .load_path(input_path) .expect("Unable to read input file") } } fn main() { use arret_compiler::initialise_llvm; use clap::{crate_version, App, AppSettings, Arg, SubCommand}; let matches = App::new("arret") .version(crate_version!()) .setting(AppSettings::SubcommandRequiredElseHelp) .about("Compiler and REPL for the Arret language") .arg( Arg::with_name("NOOPT") .long("no-llvm-opt") .takes_value(false) .help("Disables LLVM optimisation"), ) .arg( Arg::with_name("ARRET_ROOT") .long("arret-root") .takes_value(true) .help("Path to the root of a built `etaoins/arret` repository"), ) .subcommand( SubCommand::with_name("compile") .about("Compiles an Arret program to a standalone binary") .arg( Arg::with_name("INPUT") .required(true) .help("Input source file") .index(1), ) .arg( Arg::with_name("OUTPUT") .short("o") .value_name("FILE") .help("Output filename") .long_help( "Output filename.\n\ Four special extensions are recognised to output intermediate formats:\n\ \n\ `.mir` will output a text representation of Arret's internal middle IR\n\ `.ll` will output LLVM IR\n\ `.s` will output assembler for the target architecture\n\ `.o` will output an unlinked object file" ), ) .arg( Arg::with_name("DEBUG") .short("g") .long("debug-info") .help("Generates debugging information"), ) .arg( Arg::with_name("TARGET") .long("target") .value_name("TRIPLE") .help("Generates code for the given target"), ), ) .subcommand( SubCommand::with_name("eval") .about("Evaluates an Arret program once") .arg( Arg::with_name("INPUT") .required(true) .help("Input source file") .index(1), ), ) .subcommand( SubCommand::with_name("repl") .about("Starts an interactive REPL") .arg( Arg::with_name("INCLUDE") .short("i") .long("include") .value_name("FILE") .help("Preloads a file before starting REPL"), ), ) .get_matches(); let arret_root_dir = match find_arret_root(matches.value_of("ARRET_ROOT")) { Ok(arret_root) => arret_root, Err(FindArretRootError::InvalidOption(invalid_option)) => { eprintln!( "`{}` specified by the `--arret-root` option is not an Arret root directory", invalid_option.invalid_path().to_string_lossy(), ); process::exit(1); } Err(FindArretRootError::InvalidEnvVar(invalid_env_var)) => { eprintln!( "`{}` specified by the `{}` environment variable is not an Arret root directory", invalid_env_var.invalid_path().to_string_lossy(), invalid_env_var.env_var_name(), ); process::exit(1); } Err(FindArretRootError::NotFound) => { eprintln!("Unable to find the Arret root directory"); eprintln!("Either specify the `--arret-root` option or set the `ARRET_ROOT` environment variable"); process::exit(1); } }; let enable_optimisations = !matches.is_present("NOOPT"); if let Some(compile_matches) = matches.subcommand_matches("compile") { let package_paths = arret_compiler::PackagePaths::with_stdlib( &arret_root_dir, compile_matches.value_of("TARGET"), ); let ccx = CompileCtx::new(package_paths, enable_optimisations); let input_arg = compile_matches.value_of("INPUT").unwrap(); let input_file = input_arg_to_source_file(ccx.source_loader(), input_arg); let output_path = path::Path::new( if let Some(output_param) = compile_matches.value_of("OUTPUT") { output_param } else if input_arg.ends_with(ARRET_FILE_EXTENSION) { &input_arg[0..input_arg.len() - ARRET_FILE_EXTENSION.len()] } else { panic!( "Can't determine output filename from input arg `{}`", input_arg ); }, ); let debug_info = compile_matches.is_present("DEBUG"); let target_triple = compile_matches.value_of("TARGET"); initialise_llvm(target_triple.is_some()); if !subcommand::compile::compile_input_file( &ccx, &input_file, target_triple, output_path, debug_info, ) { process::exit(2); } } else if let Some(repl_matches) = matches.subcommand_matches("repl") { let package_paths = arret_compiler::PackagePaths::with_stdlib(&arret_root_dir, None); let ccx = Arc::new(CompileCtx::new(package_paths, enable_optimisations)); initialise_llvm(false); let include_path = repl_matches .value_of("INCLUDE") .map(|include_param| path::Path::new(include_param).to_owned()); subcommand::repl::interactive_loop(ccx, include_path); } else if let Some(eval_matches) = matches.subcommand_matches("eval") { let package_paths = arret_compiler::PackagePaths::with_stdlib(&arret_root_dir, None); let ccx = CompileCtx::new(package_paths, enable_optimisations); let input_param = eval_matches.value_of("INPUT").unwrap(); let input_file = input_arg_to_source_file(ccx.source_loader(), input_param); initialise_llvm(false); if !subcommand::eval::eval_input_file(&ccx, &input_file) { process::exit(2); } } else { eprintln!("Sub-command not specified"); process::exit(1); } }