20using namespace std::literals;
24enum Emit { AST, Dot, H, PY, Md,
Mim, NestDot, Profile, Num_Emits };
29 std::vector<std::string> plugins, search_paths, import_paths, prefix_paths, plugin_args;
30 std::array<Out, Num_Emits> outs;
35void emit_help(fe::Cli& cli,
Driver& driver,
const std::vector<std::string>& plugins,
bool md) {
36 for (
auto&& plugin : plugins)
40 cli.section(md ?
"Plugin Arguments {#xargs}" :
"Plugin Arguments");
42 for (
const auto& [plugin, args] : driver.
known_args()) {
43 auto rows = fe::Cli::Rows();
44 for (
const auto& arg : args)
45 rows.emplace_back(arg.syntax, arg.descr);
47 auto title = md ? std::format(
"[{0}](@ref {0}) {{#xarg_{0}}}", plugin) : plugin;
48 cli.section(std::move(title),
"Argument", std::move(rows));
52 cli.section(md ?
"Plugin Environment Variables {#envs}" :
"Plugin Environment Variables");
54 for (
const auto& [plugin, envs] : driver.
known_envs()) {
55 auto rows = fe::Cli::Rows();
56 for (
const auto& env : envs)
57 rows.emplace_back(env.name, env.descr);
59 auto title = md ? std::format(
"[{0}](@ref {0}) {{#env_{0}}}", plugin) : std::string(plugin);
60 cli.section(std::move(title),
"Variable", std::move(rows));
65 cli.markdown(std::cout);
70void emit_profile(
Driver& driver, std::ostream& os) {
80int compile(
Driver& driver, Opts& opts) {
81 auto& world = driver.
world();
82 auto& outs = opts.outs;
85 auto name = fs::path(opts.input).filename().replace_extension().string();
91 auto md = std::ostringstream();
92 auto file = parser.import_main(opts.input, opts.plugins, outs[Md].name().empty() ?
nullptr : &md);
96 fe::throwf(
"could not read file `{}`", opts.input);
99 auto ast_out = std::ostringstream();
100 if (!outs[AST].name().
empty()) {
101 auto tab = fe::Tab::spaces();
102 file->stream(tab, ast_out);
107 if (
auto s = outs[Md].os()) *
s << md.str();
109 if (
auto s = outs[AST].os()) {
113 = opts.plugin_args.empty() && std::ranges::none_of(std::views::iota(0,
int(Num_Emits)), [&](
int i) {
114 return i != AST && i != Md && !outs[i].name().empty();
116 if (only_parse)
return EXIT_SUCCESS;
119 if (!outs[H].name().
empty() || !outs[PY].name().
empty()) {
120 auto plugin = world.sym(name);
121 if (
auto h = outs[H].os())
ast.bootstrap(plugin, *h);
122 if (
auto py = outs[PY].os())
ast.bootstrap_py(plugin, *py);
127 std::exception_ptr opt_error;
134 opt_error = std::current_exception();
137 if (
auto s = outs[Dot].os()) world.dot(*s, opts.dot);
138 if (
auto s = outs[
Mim].os()) world.dump(*s);
139 if (
auto s = outs[NestDot].os())
mim::Nest(world).
dot(*s, opts.dot);
140 if (
auto s = outs[Profile].os()) emit_profile(driver, *s);
141 if (opt_error) std::rethrow_exception(opt_error);
142 }
catch (
const Error::Bail& e) {
152int main(
int argc,
char** argv) {
153 fe::term::resolve_mode();
157 bool show_help =
false;
158 bool show_help_md =
false;
159 bool show_version =
false;
160 bool list_search_paths =
false;
163 auto& flags = driver.
flags();
164 auto& diag = driver.diag();
165 auto inc_verbose = [&](bool) { ++verbose; };
166#ifdef MIM_ENABLE_CHECKS
167 std::vector<uint32_t> breakpoints, watchpoints;
170 auto loc_style = [&](
const std::string& t) -> std::string {
172 if (t ==
"full" ) diag.loc_style = fe::Loc::Style::Full;
173 else if (t ==
"rowcol") diag.loc_style = fe::Loc::Style::RowCol;
174 else if (t ==
"row" ) diag.loc_style = fe::Loc::Style::Row;
175 else if (t ==
"msvc" ) diag.loc_style = fe::Loc::Style::MSVC;
176 else return std::format(
"'{}' is not a location style", t);
181 auto profile = [&](
const std::string& t) -> std::string {
186 else return std::format(
"'{}' is not a profile mode", t);
192 auto cli = fe::Cli(
"mim",
"MimIR is my Intermediate Representation.")
193 .arg(opts.input,
"file",
"Input file.")
195 .opt(show_help_md ,
"" ,
"" ,
"--help-md" ,
"Displays this help as Markdown and exits.")
196 .opt(show_version ,
"" ,
"-v",
"--version" ,
"Displays version info and exits.")
197 .opt(list_search_paths ,
"" ,
"-l",
"--list-search-paths" ,
"Lists the search paths in order and exits.")
198 .opt(opts.plugins ,
"plugin" ,
"-p",
"--plugin" ,
"Dynamically loads a plugin; `foo/bar` finds `libmim_bar` in each search path's `foo`.")
199 .opt(opts.search_paths ,
"path" ,
"-P",
"--plugin-path" ,
"Path to search for plugins; also searched for imports.")
200 .opt(opts.import_paths ,
"path" ,
"-I",
"--import-path" ,
"Path to search for imports.")
201 .opt(opts.prefix_paths ,
"path" ,
"-R",
"--prefix-path" ,
"Install prefix/root to derive plugin, import, and runtime directories from.")
202 .opt(opts.plugin_args ,
"plugin:arg",
"-X",
"--plugin-arg" ,
"Passes an argument to a plugin/phase, e.g. `-X ll:o=output.ll`. Repeatable.")
203 .opt(flags.force_load ,
"" ,
"" ,
"--force-load" ,
"Loads plugins even on version mismatch.")
204 .opt(flags.bootstrap ,
"" ,
"" ,
"--bootstrap" ,
"Bootstrap mode: only read Mim AST, don't compile to MimIR.")
205 .opt(inc_verbose ,
"" ,
"-V",
"--verbose" ,
"Raises the log level from error to warn, info, verbose, debug, trace; repeatable.").cardinality(0, 5)
207 .opt(flags.ascii ,
"" ,
"-a",
"--ascii" ,
"Uses ASCII alternatives in output instead of UTF-8.")
208 .opt(opts.outs[AST].name() ,
"file" ,
"" ,
"--output-ast" ,
"Emits the AST of the input.")
209 .opt(opts.outs[Dot].name() ,
"file" ,
"" ,
"--output-dot" ,
"Emits the Mim program as a MimIR graph using Graphviz' DOT language.")
210 .opt(opts.outs[H].name() ,
"file" ,
"" ,
"--output-h" ,
"Emits a header file to be used to interface with a plugin in C++.")
211 .opt(opts.outs[Md].name() ,
"file" ,
"" ,
"--output-md" ,
"Emits the input formatted as Markdown.")
212 .opt(opts.outs[
Mim].name() ,
"file" ,
"-o",
"--output-mim" ,
"Emits the Mim program again.")
213 .opt(opts.outs[NestDot].name() ,
"file" ,
"" ,
"--output-nest" ,
"Emits the program's nesting tree using Graphviz' DOT language.")
214 .opt(opts.outs[PY].name() ,
"file" ,
"" ,
"--output-py" ,
"Emits a Python enum to be used to interface with a plugin in Python.")
216 .opt(opts.dot.
dark ,
"" ,
"" ,
"--dot-dark" ,
"Drops the white background of the DOT output and lightens its edges, so that it embeds into a dark backdrop.")
217 .opt(opts.dot.
default_filter ,
"" ,
"" ,
"--dot-default-filter" ,
"Always shows a lambda's filter in DOT output - even if it is the default one (`ff` for continuations, `tt` for direct-style functions).")
218 .opt(opts.dot.
follow_types ,
"" ,
"" ,
"--dot-follow-types" ,
"Follows type dependencies in DOT output.")
219 .opt(opts.dot.
inline_consts ,
"" ,
"" ,
"--dot-inline-consts" ,
"Wires up literals, axioms, etc. with normal edges in DOT output instead of detaching them into a separate row; useful for small graphs.")
220 .opt(opts.dot.
lean_labels ,
"" ,
"" ,
"--dot-lean-labels" ,
"Labels a DOT node `<node_name> <unique_name>` instead of the HTML table; this drops the per-op ports, so an edge no longer docks under the op it stands for.")
221 .opt(opts.dot.
no_tooltip ,
"" ,
"" ,
"--dot-no-tooltip" ,
"Omits the per-node tooltip from DOT output; it accounts for roughly two fifths of the file.")
222 .opt(opts.dot.
show_hidden ,
"" ,
"" ,
"--dot-show-hidden" ,
"Renders otherwise-transparent detached edges in DOT output - back-edges from a Var to its binder, shared literals/axioms, and type edges - in a subtle gray.")
224 .opt(flags.mim_typed_let ,
"" ,
"" ,
"--mim-typed-let" ,
"Ascribes the type of each `let`-bound name; it is inferable from the right-hand side and hence elided by default.")
226 .opt(diag.gutter ,
"width" ,
"" ,
"--gutter" ,
"Width of a diagnostic's line-number column.")
227 .opt(loc_style ,
"style" ,
"" ,
"--loc-style" ,
"How a diagnostic spells out a source location: `full` (`path:row:col-row:col`), `rowcol` (`path:row:col`), `row` (`path:row`), or `msvc` (`path(row,col)`).")
228 .opt(diag.max_errors ,
"num" ,
"" ,
"--max-errors" ,
"Maximum number of errors to report before dropping the rest; 0 reports all of them.")
229 .opt(diag.max_rows ,
"num" ,
"" ,
"--max-rows" ,
"Maximum number of rows a diagnostic's snippet renders before eliding its middle; 0 elides nothing.")
230 .opt(diag.no_snippet ,
"" ,
"" ,
"--no-snippet" ,
"Does not render the offending source line and caret underneath a diagnostic.")
231 .opt(diag.werror ,
"" ,
"" ,
"--werror" ,
"Treats warnings as errors.")
233 .opt(opts.outs[Profile].name() ,
"file" ,
"" ,
"--output-profile" ,
"Where to write the profiling information; defaults to stdout and implies --profile trace, if no `<mode>` is given.")
234 .opt(profile ,
"mode" ,
"" ,
"--profile" ,
"Measures how long each phase takes; `<mode>` is `summary`, `tree`, or `trace` (`chrome://tracing` compatible).")
236 .opt(flags.aggressive_lam_spec ,
"" ,
"" ,
"--aggr-lam-spec" ,
"Overrides LamSpec behavior to follow recursive calls.")
237 .opt(flags.max_fp_iters ,
"num" ,
"" ,
"--max-fp-iters" ,
"Maximum number of fixed-point iterations before a phase errors out; guards against non-monotone analyses.")
238 .opt(flags.pe_fuel ,
"num" ,
"" ,
"--pe-fuel" ,
"Maximum nesting depth of partial evaluations before erroring out; guards against non-terminating specialization.")
239 .opt(opts.no_opt ,
"" ,
"" ,
"--no-opt" ,
"Elaborates the input but skips the optimization pipeline, so no backend runs; `--output-*` still works.")
240 .opt(flags.scalarize_threshold ,
"threshold" ,
"" ,
"--scalarize-threshold" ,
"MimIR will not scalarize packs/arrays with a number of elements greater than or equal this threshold.")
241#ifdef MIM_ENABLE_CHECKS
242 .grp(
"Developer Options")
243 .opt(breakpoints ,
"gid" ,
"-b",
"--break" ,
"Triggers a breakpoint when a node with this global id is created.")
244 .opt(flags.break_on_alpha ,
"" ,
"" ,
"--break-on-alpha" ,
"Triggers a breakpoint as soon as two expressions turn out not to be alpha-equivalent.")
245 .opt(flags.break_on_error ,
"" ,
"" ,
"--break-on-error" ,
"Triggers a breakpoint on an error log.")
246 .opt(flags.break_on_warn ,
"" ,
"" ,
"--break-on-warn" ,
"Triggers a breakpoint on a warning log.")
247 .opt(flags.reeval_breakpoints ,
"" ,
"" ,
"--reeval-breakpoints" ,
"Triggers a breakpoint even upon unifying a node that has already been built.")
248 .opt(flags.trace_gids ,
"" ,
"" ,
"--trace-gids" ,
"Outputs gids during `World::unify`/`insert`.")
249 .opt(watchpoints ,
"gid" ,
"-w",
"--watch" ,
"Triggers a breakpoint when a node with this global id is set.")
251 .section(
"Environment Variables",
"Variable", {
252 {
"MIM_PLUGIN_PATH", std::format(
"{}-separated list of plugin search paths, searched after those given via `-P`.", fe::sys::Path_Sep_Word)},
253 {
"MIM_IMPORT_PATH", std::format(
"{}-separated list of import search paths, searched after those given via `-I`.", fe::sys::Path_Sep_Word)},
254 {
"MIM_PREFIX_PATH", std::format(
"{}-separated list of install prefixes, searched after those given via `--prefix-path`.", fe::sys::Path_Sep_Word)},
255 {
"NO_COLOR" ,
"Disables colored output if set to a non-empty value; wins over the two below."},
256 {
"CLICOLOR_FORCE" ,
"Forces colored output if set to a non-empty value other than 0."},
257 {
"CLICOLOR" ,
"Disables colored output if set to 0."},
259 .epilog(R
"(Every output option accepts "-" to write to stdout.)");
262 if (
auto err = cli.parse(argc, argv)) fe::throwf<std::invalid_argument>(
"{}", *err);
265 auto& profile_file = opts.outs[Profile].name();
269 for (
auto&& path : opts.search_paths)
272 for (
auto&& path : opts.import_paths)
275 for (
auto&& path : opts.prefix_paths)
278 if (show_help || show_help_md) {
279 emit_help(cli, driver, opts.plugins, show_help_md);
284 std::cout <<
"mim " << driver.
version() << std::endl;
288 for (
auto&& pa : opts.plugin_args) {
289 auto pos = pa.find(
':');
290 if (pos == std::string::npos)
291 fe::throwf<std::invalid_argument>(
"`--plugin-arg` expects `<plugin>:<arg>`, got `{}`", pa);
292 driver.
add_arg(std::string_view(pa).substr(0, pos), pa.substr(pos + 1));
295 if (list_search_paths) {
296 auto list = [](std::string_view kind,
const fe::Vector<fs::path>& paths) {
297 std::cout << kind <<
':' << std::endl;
298 for (
auto&& path : paths)
299 if (!path.empty()) std::cout <<
" " << path << std::endl;
303 list(
"runtimes", driver.
rt_paths());
307 driver.
log().set(&std::cerr).set((fe::Log::Level)verbose);
308#ifdef MIM_ENABLE_CHECKS
309 driver.
log().break_on_error = flags.break_on_error;
310 driver.
log().break_on_warn = flags.break_on_warn;
311 for (
auto b : breakpoints)
313 for (
auto w : watchpoints)
317 if (opts.input.empty()) fe::throwf<std::invalid_argument>(
"no input given");
319 return compile(driver, opts);
320 }
catch (
const std::exception& e) {
321 driver.error().report();
322 std::println(std::cerr,
"{}", e.what());
325 driver.error().report();
326 std::println(std::cerr,
"error: unknown exception");
Some "global" variables needed all over the place.
const auto & known_envs() const
The PluginEnvs each loaded Plugin declares, in load order; only for listing them, see PluginEnv.
void add_prefix_path(fs::path path)
void load(std::string_view name, Loc loc={})
void add_import_path(fs::path path)
const Version & version() const
MimIR Version.
const auto & known_args() const
The PluginArgs each loaded Plugin declares, in load order; only for listing them, see PluginArg.
fe::Vector< fs::path > import_paths() const
Where ast::Parser looks for <name>.mim; plugin directories are included, as a plugin ships both halve...
void add_arg(std::string_view plugin, std::string arg)
fe::Vector< fs::path > rt_paths() const
Where a backend looks for its runtime modules.
void move_to_library(World &world)
Moves everything of world into library() and imports only its externals back.
void add_plugin_path(fs::path path)
fe::Vector< fs::path > plugin_paths() const
Where Driver::load looks for libmim_<name>.
fe::Profiler & profiler()
Builds a nesting tree for all mutables/binders.
void dot(std::ostream &os, DotConfig cfg={}) const
void watchpoint(u32 gid)
Trigger breakpoint in your debugger when Def::setting a Def with this gid.
void breakpoint(u32 gid)
Trigger breakpoint in your debugger when creating a Def with this gid.
Owns the arena all AST nodes live in as well as the AnnexInfos of all plugins.
int main(int argc, char **argv)
bool follow_types
Follow Def::type() dependencies.
bool inline_consts
Wire up literals, axioms, etc. with normal edges instead of detaching them.
void optimize(World &)
Runs _compile or _default_compile, if available (in this order).
bool no_tooltip
Omit the per-node tooltip; it is by far the bulkiest part of the output.
bool dark
Drop the white background and lighten the edges for a dark backdrop.
bool show_hidden
Render otherwise-transparent detached edges (Var→binder back-edges, shared literals/axioms,...
bool lean_labels
Label a node <node_name> <unique_name> instead of the HTML table.
bool default_filter
Show Lam::filter() even if it has its default value.
@ Summary
Flat table aggregated by Phase name.
@ Tree
Indented tree preserving the order in which Phasees ran.
@ Trace
chrome://tracing compatible output.