1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-05-02 00:59:37 +03:00
inform7/inform7/Chapter 1/Main.w

376 lines
13 KiB
OpenEdge ABL
Executable file

[Main::] Main.
The command-line interface for the Inform 7 compiler tool.
@ On some platforms the core Inform compiler is a separate command-line tool,
so that execution should begin with |main()|, as in all C programs. But some
Inform UI applications need to compile it into the body of a larger program:
those should define the symbol |SUPPRESS_MAIN| and call |Main::deputy|
when they want I7 to run.
@d PROGRAM_NAME "inform7"
=
#ifndef SUPPRESS_MAIN
int main(int argc, char *argv[]) {
return Main::deputy(argc, argv);
}
#endif
@ And so the //Main::deputy// function now has the opportunity to be head
honcho, and for the first fifteen years of Inform 7, it was exactly that. In
2020, though, it was deposed in a boardroom coup by a new CEO, the //supervisor//
module. High-level decisions on what to compile, where to put the result, and
so on, are all now taken by //supervisor//. Even the command line is very largely
read and dealt with by //supervisor// and not by this section after all. The
upshot is that //Main::deputy// is now a manager in name only, reduced to the
equivalent of unlocking the doors and turning the lights on in the morning.
=
int silence_is_golden = FALSE;
int index_explicitly_set = FALSE, problems_explicitly_set = FALSE;
pathname *diagnostics_path = NULL;
int Main::deputy(int argc, char *argv[]) {
@<Start up@>;
int proceed = Main::read_command_line(argc, argv);
PluginCalls::start();
if (proceed) {
if (silence_is_golden == FALSE)
PRINT("Inform 7 v[[Version Number]] has started.\n", FALSE, TRUE);
inform_project *proj = NULL;
@<Find the project identified for us by Inbuild@>;
@<Open the debugging log and the problems report@>;
@<Name the telemetry@>;
@<Build the project@>;
}
@<Post mortem logging@>;
if (proceed) @<Shutdown and rennab@>;
if (problem_count > 0) ProblemSigils::exit(1);
@<Shut down the modules@>;
return 0;
}
@ We need to make sure that internal errors, though they should never happen,
are reported as problem messages (fed to our HTML problems report) rather than
simply causing an abrupt exit with only a plain text error written to |stderr|.
See the |problems| module for more.
@<Start up@> =
@<Start up the modules@>;
Errors::set_internal_handler(&StandardProblems::internal_error_fn);
Task::start_timers();
@<Start up the modules@> =
Foundation::start(argc, argv); /* must be started first */
WordsModule::start();
InflectionsModule::start();
SyntaxModule::start();
LexiconModule::start();
LinguisticsModule::start();
KindsModule::start();
CalculusModule::start();
ProblemsModule::start();
CoreModule::start();
AssertionsModule::start();
KnowledgeModule::start();
ImperativeModule::start();
RuntimeModule::start();
ValuesModule::start();
IFModule::start();
MultimediaModule::start();
HTMLModule::start();
IndexModule::start();
ArchModule::start();
BytecodeModule::start();
BuildingModule::start();
PipelineModule::start();
FinalModule::start();
SupervisorModule::start();
@ The //supervisor// would happily send us instructions to compile multiple
projects, but we can only accept one; and in fact the //inform7// command line
isn't set up to allow more, so this error is not easy to generate.
@<Find the project identified for us by Inbuild@> =
inform_project *P;
LOOP_OVER(P, inform_project) {
if (proj) Problems::fatal("Multiple projects given on the command line");
proj = P;
}
if ((proj) && (proj->stand_alone)) {
if (index_explicitly_set == FALSE)
Task::disable_or_enable_index(TRUE); /* disable it */
if (problems_explicitly_set == FALSE)
Task::disable_or_enable_problems(TRUE); /* disable it */
ProgressBar::enable_or_disable(FALSE); /* disable it */
if (Log::get_debug_log_filename() == NULL)
Log::set_aspect_from_command_line(I"nothing", TRUE);
}
if (silence_is_golden) Task::disable_or_enable_problems(TRUE); /* disable it */
@ //supervisor// supplies us with a folder in which to write the debugging log
and the Problems report (the HTML version of our error messages or success
message, which is displayed in the Inform app when a compilation has finished).
This folder will usually be the |Build| subfolder of the project folder,
but we won't assume that. Remember, //supervisor// knows best.
@<Open the debugging log and the problems report@> =
if (((proj) && (proj->stand_alone == FALSE)) || (Log::get_debug_log_filename())) {
if (proj) {
pathname *build_folder = Projects::build_path(proj);
if (Pathnames::create_in_file_system(build_folder) == 0)
Problems::fatal(
"Unable to create Build folder for project: is it read-only?");
filename *DF = Filenames::in(build_folder, I"Debug log.txt");
Log::set_debug_log_filename(DF);
}
Log::open();
LOG("inform7 was called as:");
for (int i=0; i<argc; i++) LOG(" %s", argv[i]);
LOG("\n");
CommandLine::play_back_log();
}
if (proj) {
if (Task::problems_enabled()) {
pathname *build_folder = Projects::build_path(proj);
filename *PF = Filenames::in(build_folder, I"Problems.html");
StandardProblems::start_problems_report(PF);
} else {
StandardProblems::start_problems_report(NULL);
}
HTML::set_link_abbreviation_path(Projects::path(proj));
}
@ Telemetry is not as sinister as it sounds: the app isn't sending data out
on the Internet, only (if requested) logging what it's doing to a local file.
This was provided for classroom use, so that teachers can see what their
students have been getting stuck on. In any case, it needs to be activated
with a use option, so by default this file will never be written.
@<Name the telemetry@> =
pathname *T = Supervisor::transient();
if (T) {
pathname *P = Pathnames::down(T, I"Telemetry");
if (Pathnames::create_in_file_system(P)) {
TEMPORARY_TEXT(leafname_of_telemetry)
int this_month = the_present->tm_mon + 1;
int this_day = the_present->tm_mday;
int this_year = the_present->tm_year + 1900;
WRITE_TO(leafname_of_telemetry,
"Telemetry %04d-%02d-%02d.txt", this_year, this_month, this_day);
filename *F = Filenames::in(P, leafname_of_telemetry);
Telemetry::locate_telemetry_file(F);
DISCARD_TEXT(leafname_of_telemetry)
}
}
@ The compiler is now ready for use. We ask //supervisor// to go ahead and
build that project: it will incrementally build some of the resources needed,
if any of them are, and then call upon //core// to perform the actual
compilation.
@<Build the project@> =
Supervisor::go_operational();
if (proj) {
InterSkill::echo_kit_building();
Copies::build(STDOUT, proj->as_copy, BuildMethodology::stay_in_current_process());
Task::stop_timers();
}
@ Diagnostics files fall into the category of "be careful what you wish for";
they can be rather lengthy.
@<Post mortem logging@> =
if ((problem_count == 0) && (diagnostics_path)) {
Main::write_diagnostics(
I"timings-diagnostics.txt", &Task::log_stopwatch);
Main::write_diagnostics(
I"memory-diagnostics.txt", &Memory::log_statistics);
Main::write_diagnostics(
I"syntax-diagnostics.txt", &Main::log_task_syntax_tree);
Main::write_diagnostics(
I"syntax-summary.txt", &Main::log_task_syntax_summary);
Main::write_diagnostics(
I"preform-diagnostics.txt", &Instrumentation::log);
Main::write_diagnostics(
I"preform-summary.txt", &Main::log_preform_summary);
Main::write_diagnostics(
I"documentation-diagnostics.txt", &DocReferences::log_statistics);
Main::write_diagnostics(
I"verbs-diagnostics.txt", &VerbUsages::log_all);
Main::write_diagnostics(
I"excerpts-diagnostics.txt", &FromLexicon::statistics);
Main::write_diagnostics(
I"stock-diagnostics.txt", &Stock::log_all);
}
@<Shutdown and rennab@> =
if (silence_is_golden == FALSE) {
ProblemBuffer::write_reports(FALSE);
LOG("Total of %d files written as streams.\n", total_file_writes);
Writers::log_escape_usage();
WRITE_TO(STDOUT, "Inform 7 has finished.\n");
}
@<Shut down the modules@> =
WordsModule::end();
InflectionsModule::end();
SyntaxModule::end();
LexiconModule::end();
LinguisticsModule::end();
KindsModule::end();
CalculusModule::end();
ProblemsModule::end();
MultimediaModule::end();
CoreModule::end();
AssertionsModule::end();
KnowledgeModule::end();
ImperativeModule::end();
RuntimeModule::end();
ValuesModule::end();
IFModule::end();
IndexModule::end();
HTMLModule::end();
BytecodeModule::end();
ArchModule::end();
BuildingModule::end();
PipelineModule::end();
FinalModule::end();
SupervisorModule::end();
Foundation::end(); /* must be ended last */
@ =
void Main::write_diagnostics(text_stream *leafname, void (*write_fn)(void)) {
filename *F = Filenames::in(diagnostics_path, leafname);
text_stream diagnostics_file;
if (STREAM_OPEN_TO_FILE(&diagnostics_file, F, ISO_ENC) == FALSE)
internal_error("can't open diagnostics file");
text_stream *save_DL = DL;
DL = &diagnostics_file;
Streams::enable_debugging(DL);
(*write_fn)();
DL = save_DL;
STREAM_CLOSE(&diagnostics_file);
}
void Main::log_task_syntax_tree(void) {
Node::log_tree(DL, Task::syntax_tree()->root_node);
}
void Main::log_preform_summary(void) {
Instrumentation::log_nt(<s-literal>, TRUE);
}
void Main::log_task_syntax_summary(void) {
Node::summarise_tree(DL, Task::syntax_tree()->root_node);
}
@h Command line processing.
The bulk of the command-line options are both registered and processed by
//supervisor// rather than here: in particular, every switch ever used by the
Inform UI apps is really a command to //supervisor// not to |inform7|.
=
int Main::read_command_line(int argc, char *argv[]) {
CommandLine::declare_heading(
L"inform7: a compiler from source text to Inter code\n\n"
L"Usage: inform7 [OPTIONS]\n");
@<Register command-line arguments@>;
Supervisor::declare_options();
int proceed = CommandLine::read(argc, argv, NULL, &Main::switch, &Main::bareword);
if (proceed) {
if (shared_internal_nest == NULL) {
pathname *path_to_inform = Pathnames::installation_path("INFORM7_PATH", I"inform7");
Supervisor::add_nest(Pathnames::down(path_to_inform, I"Internal"), INTERNAL_NEST_TAG);
}
Supervisor::optioneering_complete(NULL, TRUE, &CorePreform::load);
}
return proceed;
}
@ What remains here are just some eldritch options for testing the |inform7|
compiler via Delia scripts in |intest|.
@e INFORM_TESTING_CLSG
@e CRASHALL_CLSW
@e DIAGNOSTICS_CLSW
@e INDEX_CLSW
@e PROBLEMS_CLSW
@e CENSUS_UPDATE_CLSW
@e PROGRESS_CLSW
@e REQUIRE_PROBLEM_CLSW
@e SIGILS_CLSW
@e TEST_OUTPUT_CLSW
@e SILENCE_CLSW
@e CHECK_RESOURCES_CLSW
@<Register command-line arguments@> =
CommandLine::begin_group(INFORM_TESTING_CLSG, I"for testing and debugging inform7");
CommandLine::declare_boolean_switch(CRASHALL_CLSW, L"crash-all", 1,
L"intentionally crash on Problem messages, for backtracing", FALSE);
CommandLine::declare_boolean_switch(INDEX_CLSW, L"index", 1,
L"produce an Index", TRUE);
CommandLine::declare_boolean_switch(PROBLEMS_CLSW, L"problems", 1,
L"produce (an HTML) Problems report page", TRUE);
CommandLine::declare_boolean_switch(CENSUS_UPDATE_CLSW, L"census-update", 1,
L"update the extensions census", TRUE);
CommandLine::declare_boolean_switch(PROGRESS_CLSW, L"progress", 1,
L"display progress percentages", TRUE);
CommandLine::declare_boolean_switch(SIGILS_CLSW, L"sigils", 1,
L"print Problem message sigils", FALSE);
CommandLine::declare_boolean_switch(CHECK_RESOURCES_CLSW, L"resource-checking", 1,
L"check that figures, sounds and similar resources exist at compile-time", TRUE);
CommandLine::declare_boolean_switch(DIAGNOSTICS_CLSW, L"diagnostics", 2,
L"if no problems occur, write diagnostics files to directory X", FALSE);
CommandLine::declare_switch(REQUIRE_PROBLEM_CLSW, L"require-problem", 2,
L"return 0 unless exactly this Problem message is generated");
CommandLine::declare_switch(TEST_OUTPUT_CLSW, L"test-output", 2,
L"write output of internal tests to file X");
CommandLine::declare_boolean_switch(SILENCE_CLSW, L"silence", 1,
L"practice 'silence is golden': print only Unix-style errors", FALSE);
CommandLine::end_group();
@ Three of the five options here actually configure the |problems| module
rather than |core|.
=
void Main::switch(int id, int val, text_stream *arg, void *state) {
switch (id) {
case CRASHALL_CLSW: debugger_mode = val;
ProblemSigils::crash_on_problems(val); break;
case INDEX_CLSW: Task::disable_or_enable_index(val?FALSE:TRUE);
index_explicitly_set = TRUE; break;
case PROBLEMS_CLSW: Task::disable_or_enable_problems(val?FALSE:TRUE);
problems_explicitly_set = TRUE; break;
case CENSUS_UPDATE_CLSW: Task::disable_or_enable_census(val?FALSE:TRUE); break;
case PROGRESS_CLSW: ProgressBar::enable_or_disable(val); break;
case SIGILS_CLSW: ProblemSigils::echo_sigils(val); break;
case REQUIRE_PROBLEM_CLSW: ProblemSigils::require(arg); break;
case DIAGNOSTICS_CLSW: diagnostics_path = Pathnames::from_text(arg); break;
case CHECK_RESOURCES_CLSW: ResourceFinder::set_mode(val); break;
case TEST_OUTPUT_CLSW: InternalTests::set_file(Filenames::from_text(arg)); break;
case SILENCE_CLSW: silence_is_golden = TRUE; break;
}
Supervisor::option(id, val, arg, state);
}
void Main::bareword(int id, text_stream *opt, void *state) {
if (Str::is_whitespace(opt) == FALSE) {
if (Str::get_last_char(opt) == FOLDER_SEPARATOR)
Errors::fatal_with_text(
"to compile a project in a directory, use '-project %S'", opt);
filename *F = Filenames::from_text(opt);
if (Supervisor::set_I7_source(F) == FALSE)
Errors::fatal_with_text("unknown command line argument: %S (see -help)", opt);
}
}
int Main::silence_is_golden(void) {
return silence_is_golden;
}