1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-07-05 08:34:22 +03:00
inform7/inblorb/Chapter 1/Blurb Parser.w

399 lines
16 KiB
OpenEdge ABL
Raw Normal View History

2019-02-05 02:44:07 +02:00
[Parser::] Blurb Parser.
2019-02-10 13:27:48 +02:00
Blurb is an interpreted language, and this is the interpreter for it.
2019-02-05 02:44:07 +02:00
@h Reading the file.
We divide the file into blurb commands at line breaks, so:
=
void Parser::parse_blurb_file(filename *F) {
TextFiles::read(F, FALSE, "can't open blurb file", TRUE, Parser::interpret_line, 0, NULL);
BlorbErrors::set_error_position(NULL);
}
@ The sequence of values enumerated here must correspond exactly to
indexes into the syntaxes table below.
2019-02-10 13:27:48 +02:00
@e author_COMMAND from 0
@e auxiliary_COMMAND
@e base64_COMMAND
@e copyright_COMMAND
@e cover_COMMAND
@e css_COMMAND
@e ifiction_COMMAND
@e ifiction_public_COMMAND
@e ifiction_file_COMMAND
@e interpreter_COMMAND
@e palette_COMMAND
@e palette_16_bit_COMMAND
@e palette_32_bit_COMMAND
@e picture_scaled_COMMAND
@e picture_COMMAND
@e picture_text_COMMAND
@e picture_noid_COMMAND
@e picture_with_alt_text_COMMAND
@e placeholder_COMMAND
@e project_folder_COMMAND
@e release_COMMAND
@e release_file_COMMAND
@e release_file_from_COMMAND
@e release_source_COMMAND
@e release_to_COMMAND
@e resolution_max_COMMAND
@e resolution_min_max_COMMAND
@e resolution_min_COMMAND
@e resolution_COMMAND
@e solution_COMMAND
@e solution_public_COMMAND
@e sound_music_COMMAND
@e sound_repeat_COMMAND
@e sound_forever_COMMAND
@e sound_song_COMMAND
@e sound_COMMAND
@e sound_text_COMMAND
@e sound_noid_COMMAND
@e sound_with_alt_text_COMMAND
@e source_COMMAND
@e source_public_COMMAND
@e status_COMMAND
@e status_alternative_COMMAND
@e status_instruction_COMMAND
@e storyfile_include_COMMAND
@e storyfile_COMMAND
@e storyfile_leafname_COMMAND
@e template_path_COMMAND
@e website_COMMAND
2019-02-05 02:44:07 +02:00
2019-02-10 13:27:48 +02:00
@ A single number specifying various possible combinations of operands. For
example, |NT_OPS| means "number, text". Clearly the list below is not
exhaustive of the possibilities, but these are the only ones arising for
Blurb commands.
2019-02-05 02:44:07 +02:00
2019-02-10 13:27:48 +02:00
@e VOID_OPS from 1
@e N_OPS
@e NN_OPS
@e NNN_OPS
@e NT_OPS
@e NTN_OPS
@e NTT_OPS
@e T_OPS
@e TT_OPS
@e TTN_OPS
@e TTT_OPS
2019-02-05 02:44:07 +02:00
@ Each legal command syntax is stored as one of these structures.
=
typedef struct blurb_command {
char *explicated; /* plain English form of the command */
wchar_t *prototype; /* regular expression prototype */
2019-02-10 13:27:48 +02:00
int operands; /* one of the above |*_OPS| codes */
2019-02-05 02:44:07 +02:00
int deprecated;
} blurb_command;
@ And here they all are. They are tested in the sequence given, and
the sequence must exactly match the numbering of the |*_COMMAND|
values above, since those are indexes into this table.
In blurb syntax, a line whose first non-white-space character is an
exclamation mark |!| is a comment, and is ignored. (This is the I6
comment character, too.) It appears in the table as a command
but, as we shall see, has no effect.
=
blurb_command syntaxes[] = {
2019-02-10 13:27:48 +02:00
{ "author \"name\"", L"author \"(%q*)\"", T_OPS, FALSE },
2019-02-05 02:44:07 +02:00
{ "auxiliary \"filename\" \"description\" \"subfolder\"",
2019-02-10 13:27:48 +02:00
L"auxiliary \"(%q*)\" \"(%q*)\" \"(%q*)\"", TTT_OPS, FALSE },
2019-02-05 02:44:07 +02:00
{ "base64 \"filename\" to \"filename\"",
2019-02-10 13:27:48 +02:00
L"base64 \"(%q*)\" to \"(%q*)\"", TT_OPS, FALSE },
{ "copyright \"message\"", L"copyright \"(%q*)\"", T_OPS, FALSE },
{ "cover \"filename\"", L"cover \"(%q*)\"", T_OPS, FALSE },
{ "css", L"css", VOID_OPS, FALSE },
{ "ifiction", L"ifiction", VOID_OPS, FALSE },
{ "ifiction public", L"ifiction public", VOID_OPS, FALSE },
{ "ifiction \"filename\" include", L"ifiction \"(%q*)\" include", T_OPS, FALSE },
2019-02-05 02:44:07 +02:00
{ "interpreter \"interpreter-name\" \"vm-letter\"",
2019-02-10 13:27:48 +02:00
L"interpreter \"(%q*)\" \"([gz])\"", TT_OPS, FALSE },
{ "palette { details }", L"palette {(%c*?)}", T_OPS, TRUE },
{ "palette 16 bit", L"palette 16 bit", VOID_OPS, TRUE },
{ "palette 32 bit", L"palette 32 bit", VOID_OPS, TRUE },
2019-02-05 02:44:07 +02:00
{ "picture ID \"filename\" scale ...",
2019-02-10 13:27:48 +02:00
L"picture (%i+?) \"(%q*)\" scale (%c*)", TTT_OPS, TRUE },
{ "picture N \"filename\"", L"picture (%d+) \"(%q*)\"", NT_OPS, FALSE },
{ "picture ID \"filename\"", L"picture (%i+) \"(%q*)\"", TT_OPS, FALSE },
{ "picture \"filename\"", L"picture \"(%q*)\"", T_OPS, FALSE },
2022-05-12 12:09:12 +03:00
{ "picture N \"filename\" \"alt-text\"", L"picture (%d+) \"(%q*)\" \"(%q*)\"", NTT_OPS, FALSE },
2019-02-10 13:27:48 +02:00
{ "placeholder [name] = \"text\"", L"placeholder %[(%C+)%] = \"(%q*)\"", TT_OPS, FALSE },
{ "project folder \"pathname\"", L"project folder \"(%q*)\"", T_OPS, FALSE },
{ "release \"text\"", L"release \"(%q*)\"", T_OPS, FALSE },
{ "release file \"filename\"", L"release file \"(%q*)\"", T_OPS, FALSE },
2019-02-05 02:44:07 +02:00
{ "release file \"filename\" from \"template\"",
2019-02-10 13:27:48 +02:00
L"release file \"(%q*)\" from \"(%q*)\"", TT_OPS, FALSE },
2019-02-05 02:44:07 +02:00
{ "release source \"filename\" using \"filename\" from \"template\"",
2019-02-10 13:27:48 +02:00
L"release source \"(%q*)\" using \"(%q*)\" from \"(%q*)\"", TTT_OPS, FALSE },
{ "release to \"pathname\"", L"release to \"(%q*)\"", T_OPS, FALSE },
{ "resolution NxN max NxN", L"resolution (%d+) max (%d+)", NN_OPS, TRUE },
{ "resolution NxN min NxN max NxN", L"resolution (%d+) min (%d+) max (%d+)", NNN_OPS, TRUE },
{ "resolution NxN min NxN", L"resolution (%d+) min (%d+)", NN_OPS, TRUE },
{ "resolution NxN", L"resolution (%d+)", N_OPS, TRUE },
{ "solution", L"solution", VOID_OPS, FALSE },
{ "solution public", L"solution public", VOID_OPS, FALSE },
{ "sound ID \"filename\" music", L"sound (%i+) \"(%q*)\" music", TT_OPS, TRUE },
2019-02-05 02:44:07 +02:00
{ "sound ID \"filename\" repeat N",
2019-02-10 13:27:48 +02:00
L"sound (%i+) \"(%q*)\" repeat (%d+)", TTN_OPS, TRUE },
2019-02-05 02:44:07 +02:00
{ "sound ID \"filename\" repeat forever",
2019-02-10 13:27:48 +02:00
L"sound (%i+) \"(%q*)\" repeat forever", TT_OPS, TRUE },
{ "sound ID \"filename\" song", L"sound (%i+) \"(%q*)\" song", TT_OPS, TRUE },
{ "sound N \"filename\"", L"sound (%d+) \"(%q*)\"", NT_OPS, FALSE },
{ "sound ID \"filename\"", L"sound (%i+) \"(%q*)\"", TT_OPS, FALSE },
{ "sound \"filename\"", L"sound \"(%q*)\"", T_OPS, FALSE },
{ "sound N \"filename\" \"alt-text\"", L"sound (%d+) \"(%q*)\" \"(%q*)\"", NTT_OPS, FALSE },
{ "source", L"source", VOID_OPS, FALSE },
{ "source public", L"source public", VOID_OPS, FALSE },
{ "status \"template\" \"filename\"", L"status \"(%q*)\" \"(%q*)\"", TT_OPS, FALSE },
2019-02-05 02:44:07 +02:00
{ "status alternative ||link to Inform documentation||",
2019-02-10 13:27:48 +02:00
L"status alternative ||(%c*)||", T_OPS, FALSE },
2019-02-05 02:44:07 +02:00
{ "status instruction ||link to Inform source text||",
2019-02-10 13:27:48 +02:00
L"status instruction ||(%c*)||", T_OPS, FALSE },
{ "storyfile \"filename\" include", L"storyfile \"(%q*)\" include", T_OPS, FALSE },
{ "storyfile \"filename\"", L"storyfile \"(%q*)\"", T_OPS, TRUE },
{ "storyfile leafname \"leafname\"", L"storyfile leafname \"(%q*)\"", T_OPS, FALSE },
{ "template path \"folder\"", L"template path \"(%q*)\"", T_OPS, FALSE },
{ "website \"template\"", L"website \"(%q*)\"", T_OPS, FALSE },
{ NULL, NULL, VOID_OPS, FALSE }
2019-02-05 02:44:07 +02:00
};
@h Summary.
For the |-help| information:
=
void Parser::summarise_blurb(void) {
PRINT("\nThe blurbfile is a script of commands, one per line, in these forms:\n");
for (int t=0; syntaxes[t].prototype; t++)
if (syntaxes[t].deprecated == FALSE)
PRINT(" %s\n", syntaxes[t].explicated);
PRINT("\nThe following syntaxes, though legal in Blorb 2001, are not supported:\n");
for (int t=0; syntaxes[t].prototype; t++)
if (syntaxes[t].deprecated == TRUE)
PRINT(" %s\n", syntaxes[t].explicated);
}
@h The interpreter.
The following routine is called for each line of the blurb file in sequence,
including any blank lines.
=
void Parser::interpret_line(text_stream *command, text_file_position *tf, void *state) {
BlorbErrors::set_error_position(tf);
match_results mr = Regexp::create_mr();
if (Regexp::match(&mr, command, L" *(%c*?) *")) Str::copy(command, mr.exp[0]);
if (Str::len(command) == 0) return; /* thus skip a line containing only blank space */
if (Str::get_first_char(command) == '!') return; /* thus skip a comment line */
2019-02-10 13:27:48 +02:00
if (verbose_mode) PRINT("! %03d: %S\n", TextFiles::get_line_count(tf), command);
2019-02-05 02:44:07 +02:00
int num1 = 0, num2 = 0, num3 = 0, outcome = -1; /* which of the legal command syntaxes is used */
2020-06-28 01:18:54 +03:00
TEMPORARY_TEXT(text1)
TEMPORARY_TEXT(text2)
TEMPORARY_TEXT(text3)
2019-02-05 02:44:07 +02:00
@<Parse the command and set operands appropriately@>;
@<Take action on the command@>;
2020-06-28 01:18:54 +03:00
DISCARD_TEXT(text1)
DISCARD_TEXT(text2)
DISCARD_TEXT(text3)
2019-02-05 02:44:07 +02:00
Regexp::dispose_of(&mr);
}
@ Here we set |outcome| to the index in the syntaxes table of the line matched,
or leave it as $-1$ if no match can be made. Text and number operands are
copied in |text1|, |num1|, ..., accordingly.
@<Parse the command and set operands appropriately@> =
for (int t=0; syntaxes[t].prototype; t++)
if (Regexp::match(&mr, command, syntaxes[t].prototype)) {
switch (syntaxes[t].operands) {
2019-02-10 13:27:48 +02:00
case VOID_OPS: break;
case T_OPS: Str::copy(text1, mr.exp[0]); break;
case TT_OPS: Str::copy(text1, mr.exp[0]);
Str::copy(text2, mr.exp[1]); break;
case TTN_OPS: Str::copy(text1, mr.exp[0]);
Str::copy(text2, mr.exp[1]);
num1 = Str::atoi(mr.exp[2], 0); break;
case N_OPS: num1 = Str::atoi(mr.exp[0], 0); break;
case NN_OPS: num1 = Str::atoi(mr.exp[0], 0);
num2 = Str::atoi(mr.exp[1], 0); break;
case NT_OPS: num1 = Str::atoi(mr.exp[0], 0);
Str::copy(text1, mr.exp[1]); break;
case NTT_OPS: num1 = Str::atoi(mr.exp[0], 0);
Str::copy(text1, mr.exp[1]);
Str::copy(text2, mr.exp[2]); break;
case NTN_OPS: num1 = Str::atoi(mr.exp[0], 0);
Str::copy(text1, mr.exp[1]);
num2 = Str::atoi(mr.exp[2], 0); break;
case NNN_OPS: num1 = Str::atoi(mr.exp[0], 0);
num2 = Str::atoi(mr.exp[1], 0);
num3 = Str::atoi(mr.exp[2], 0); break;
case TTT_OPS: Str::copy(text1, mr.exp[0]);
Str::copy(text2, mr.exp[1]);
Str::copy(text3, mr.exp[2]); break;
default: internal_error("unknown operand type");
2019-02-05 02:44:07 +02:00
}
outcome = t; break;
}
if (outcome == -1) {
BlorbErrors::error_1S("not a valid blurb command", command);
return;
}
if (syntaxes[outcome].deprecated) {
BlorbErrors::error_1("this Blurb syntax is no longer supported",
syntaxes[outcome].explicated);
return;
}
@ The command is now fully parsed, and is one that we support. We can act.
@<Take action on the command@> =
switch (outcome) {
case author_COMMAND:
Placeholders::set_to(I"AUTHOR", text1, 0);
Writer::author_chunk(text1);
break;
case auxiliary_COMMAND: Links::create_auxiliary_file(text1, text2, text3); break;
case base64_COMMAND:
Requests::request_2(BASE64_REQ, text1, text2, FALSE); break;
case copyright_COMMAND: Writer::copyright_chunk(text1); break;
case cover_COMMAND: @<Declare which file is the cover art@>; break;
case css_COMMAND: use_css_code_styles = TRUE; break;
case ifiction_file_COMMAND: Writer::metadata_chunk(Filenames::from_text(text1)); break;
case ifiction_COMMAND: Requests::request_1(IFICTION_REQ, I"", TRUE); break;
case ifiction_public_COMMAND: Requests::request_1(IFICTION_REQ, I"", FALSE); break;
case interpreter_COMMAND:
Placeholders::set_to(I"INTERPRETERVMIS", text2, 0);
Requests::request_1(INTERPRETER_REQ, text1, FALSE); break;
case picture_COMMAND: Writer::picture_chunk(num1, Filenames::from_text(text1), I""); break;
case picture_text_COMMAND: Writer::picture_chunk_text(text1, Filenames::from_text(text2)); break;
case picture_noid_COMMAND: Writer::picture_chunk_text(I"", Filenames::from_text(text1)); break;
2022-05-12 12:09:12 +03:00
case picture_with_alt_text_COMMAND:
Writer::picture_chunk(num1, Filenames::from_text(text1), text2); break;
2019-02-05 02:44:07 +02:00
case placeholder_COMMAND: Placeholders::set_to(text1, text2, 0); break;
case project_folder_COMMAND: project_folder = Pathnames::from_text(text1); break;
case release_COMMAND:
Placeholders::set_to_number(I"RELEASE", num1);
Writer::release_chunk(num1);
break;
case release_file_COMMAND: {
filename *to_release = Filenames::from_text(text1);
2020-06-28 01:18:54 +03:00
TEMPORARY_TEXT(leaf)
WRITE_TO(leaf, "%S", Filenames::get_leafname(to_release));
Requests::request_3(COPY_REQ, text1, leaf, I"--", FALSE);
2020-06-28 01:18:54 +03:00
DISCARD_TEXT(leaf)
break;
2019-02-05 02:44:07 +02:00
}
case release_file_from_COMMAND:
Requests::request_2(RELEASE_FILE_REQ, text1, text2, FALSE); break;
case release_to_COMMAND:
release_folder = Pathnames::from_text(text1);
@<Make pathname placeholders in three different formats@>;
break;
case release_source_COMMAND:
Requests::request_3(RELEASE_SOURCE_REQ, text1, text2, text3, FALSE); break;
case solution_COMMAND: Requests::request_1(SOLUTION_REQ, I"", TRUE); break;
case solution_public_COMMAND: Requests::request_1(SOLUTION_REQ, I"", FALSE); break;
case sound_COMMAND: Writer::sound_chunk(num1, Filenames::from_text(text1), I""); break;
case sound_text_COMMAND: Writer::sound_chunk_text(text1, Filenames::from_text(text2)); break;
case sound_noid_COMMAND: Writer::sound_chunk_text(I"", Filenames::from_text(text1)); break;
case sound_with_alt_text_COMMAND: Writer::sound_chunk(num1, Filenames::from_text(text1), text2); break;
case source_COMMAND: Requests::request_1(SOURCE_REQ, I"", TRUE); break;
case source_public_COMMAND: Requests::request_1(SOURCE_REQ, I"", FALSE); break;
2019-02-10 13:27:48 +02:00
case status_COMMAND:
status_template = Filenames::from_text(text1);
status_file = Filenames::from_text(text2);
break;
2019-02-05 02:44:07 +02:00
case status_alternative_COMMAND: Requests::request_1(ALTERNATIVE_REQ, text1, FALSE); break;
case status_instruction_COMMAND: Requests::request_1(INSTRUCTION_REQ, text1, FALSE); break;
case storyfile_include_COMMAND: Writer::executable_chunk(Filenames::from_text(text1)); break;
case storyfile_leafname_COMMAND: Placeholders::set_to(I"STORYFILE", text1, 0); break;
case template_path_COMMAND: Templates::new_path(Pathnames::from_text(text1)); break;
case website_COMMAND: Requests::request_1(WEBSITE_REQ, text1, FALSE); break;
default: BlorbErrors::error_1S("***", command);
BlorbErrors::fatal("*** command unimplemented ***\n");
2019-02-05 02:44:07 +02:00
}
@ We only ever set the frontispiece as resource number 1, since Inform
has the assumption that the cover art is image number 1 built in.
@<Declare which file is the cover art@> =
Placeholders::set_to(I"BIGCOVER", text1, 0);
cover_exists = TRUE;
cover_is_in_JPEG_format = FALSE;
filename *cover_filename = Filenames::from_text(text1);
if (Filenames::guess_format(cover_filename) == FORMAT_PERHAPS_JPEG)
cover_is_in_JPEG_format = TRUE;
Writer::frontispiece_chunk(1);
if (Str::eq_wide_string(Filenames::get_leafname(cover_filename), L"DefaultCover.jpg"))
default_cover_used = TRUE;
Placeholders::set_to(I"SMALLCOVER", text1, 0);
@ Here, |text1| is the pathname of the Release folder. If we suppose that
2019-02-10 01:58:02 +02:00
Inblorb is being run from Inform, then this folder is a subfolder of the
2019-02-05 02:44:07 +02:00
Materials folder for an I7 project. It follows that we can obtain the
pathname to the Materials folder by trimming the leaf and the final separator.
That makes the |MATERIALSFOLDERPATH| placeholder. We then set |MATERIALSFOLDER|
to the name of the Materials folder, e.g., "Spaceman Spiff Materials".
However, we also need two variants on the pathname, one to be supplied to the
Javascript function |openUrl| and one to |fileUrl|. For platform dependency
reasons these need to be manipulated to deal with awkward characters.
@<Make pathname placeholders in three different formats@> =
pathname *Release = Pathnames::from_text(text1);
pathname *Materials = Pathnames::up(Release);
2020-06-28 01:18:54 +03:00
TEMPORARY_TEXT(as_txt)
2019-02-05 02:44:07 +02:00
WRITE_TO(as_txt, "%p", Materials);
Placeholders::set_to(I"MATERIALSFOLDERPATH", as_txt, 0);
2020-06-28 01:18:54 +03:00
DISCARD_TEXT(as_txt)
2019-02-05 02:44:07 +02:00
Placeholders::set_to(I"MATERIALSFOLDER",
Pathnames::directory_name(Materials), 0);
Parser::qualify_placeholder(
I"MATERIALSFOLDERPATHOPEN",
I"MATERIALSFOLDERPATHFILE",
I"MATERIALSFOLDERPATH");
@ And here is that very "qualification" routine. The placeholder |original| contains
2019-02-05 02:44:07 +02:00
the pathname to a folder, a pathname which might contain spaces or backslashes,
and which needs to be quoted as a literal Javascript string supplied to
either the function |openUrl| or the function |fileUrl|. Depending on the
platform in use, this may entail escaping spaces or reversing slashes in the
pathname in order to make versions for these two functions to use.
=
void Parser::qualify_placeholder(text_stream *openUrl_path, text_stream *fileUrl_path,
text_stream *original) {
text_stream *OU = Placeholders::read(openUrl_path);
text_stream *FU = Placeholders::read(fileUrl_path);
text_stream *U = Placeholders::read(original);
LOOP_THROUGH_TEXT(P, U) {
2019-02-05 02:44:07 +02:00
int c = Str::get(P);
if (c == ' ') {
if (escape_openUrl) WRITE_TO(OU, "%%2520");
else PUT_TO(OU, c);
if (escape_fileUrl) WRITE_TO(FU, "%%2520");
else PUT_TO(FU, c);
} else if (c == '\\') {
if (reverse_slash_openUrl) PUT_TO(OU, '/');
else PUT_TO(OU, c);
if (reverse_slash_fileUrl) PUT_TO(FU, '/');
else PUT_TO(FU, c);
} else {
PUT_TO(OU, c);
PUT_TO(FU, c);
}
}
}