1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-07-03 07:24:58 +03:00
inform7/inter/building-module/Chapter 2/Parsing Inter Schemas.w
2022-03-24 10:53:32 +00:00

170 lines
6.3 KiB
OpenEdge ABL

[ParsingSchemas::] Parsing Inter Schemas.
A simple API for turning textual code written in Inform 6 syntax into an inter
schema.
@h Just plain code.
If all we need is a schema from some code in a text, we can call this.
If the text contains syntax errors, these are attached to the schema returned;
so it's the caller's responsibility to check for those and act accordingly.
Note that the results can be tested independently of //inform7// using the
//building-test// unit test tool, whose tests verify that a great many I6
samples produce the correct schemas.
=
inter_schema *ParsingSchemas::from_text(text_stream *from) {
return ParsingSchemas::back_end(from, FALSE, 0, NULL);
}
@h Abbreviated I6S notation.
This is a slicker notation used inside the //calculus// module for purposes
such as representing how to compile a test of a given binary predicate, or
how to store data in a given storage object. For example, |*1.frog == *2.frog|
is a valid I6S notation, using the placeholders |*1| and |*2| to represent
the two terms of a binary predicate. See //calculus: Compilation Schemas//
for more on this notation.
Here, it's quite possible that the same piece of notation will be asked for
more than once, and we want to reply quickly, so we use a hashed dictionary
to return any already-computed answer quickly.
If the text contains syntax errors, these throw an internal error. Erroneous
I6S code can only come from within the compiler itself, and means a bug.
=
dictionary *i6s_inter_schema_cache = NULL;
inter_schema *ParsingSchemas::from_i6s(text_stream *from,
int no_quoted_inames, void **quoted_inames) {
if (i6s_inter_schema_cache == NULL) {
i6s_inter_schema_cache = Dictionaries::new(512, FALSE);
}
dict_entry *de = Dictionaries::find(i6s_inter_schema_cache, from);
if (de) return (inter_schema *) Dictionaries::value_for_entry(de);
inter_schema *result = ParsingSchemas::back_end(from, TRUE,
no_quoted_inames, quoted_inames);
Dictionaries::create(i6s_inter_schema_cache, from);
Dictionaries::write_value(i6s_inter_schema_cache, from, (void *) result);
InterSchemas::internal_error_on_schema_errors(result);
return result;
}
@h Inline phrase definitions.
This is a typical inline phrase definition which //inform7// must handle:
= (text as Inform 7)
To say (L - a list of values) in brace notation:
(- LIST_OF_TY_Say({-by-reference:L}, 1); -).
=
Essentially, this defines "say ... in brace notation" as meaning the schema
coming from the text |LIST_OF_TY_Say({-by-reference:L}, 1);|.
Note that the //inform7// compiler calls //ParsingSchemas::from_inline_phrase_definition//
only once on such a definition -- it would clearly be slow and wasteful to parse
it anew each time it is used. Because of that, only 100 or so calls to this function
are made in a typical run, and so speed is not critical here.
@ That was a simple example, in that only one schema was involved: it is a
head which has no tail.
However, a few inline phrases make use of the notation |{-block}|, which
represents a block of code -- usually a loop body -- and which divides the
definition into a head part, before the block, and a tail part, after. So
in general we may have to compile two schemas, not one.
The text |from| is in a wide C string because it's coming raw from the lexer,
as the content of a |(- ... -)| lexeme, but with the |(-| and |-)| removed.
If the text contains syntax errors, these are attached to the schema returned;
so it's the caller's responsibility to check for those and act accordingly.
=
void ParsingSchemas::from_inline_phrase_definition(wchar_t *from, inter_schema **head,
inter_schema **tail) {
*head = NULL; *tail = NULL;
text_stream *head_defn = Str::new();
text_stream *tail_defn = Str::new();
@<Fetch the head and tail definitions@>;
*head = ParsingSchemas::from_text(head_defn);
if (Str::len(tail_defn) > 0)
*tail = ParsingSchemas::from_text(tail_defn);
}
@ A tail will only be present if the definition contains |{-block}|. If it
does, we then split the definition into a head and a tail, and again trim
white space from each. Note that |{-block}| is not legal anywhere else.
For example:
>> To repeat with a King's Court begin -- end loop:
could be given the definition:
= (text as Inform 6)
@push {-my:trcount};
for (trcount=1; trcount<=3; trcount++)
{-block}
@pull trcount;
=
This then repeats what it's given three times, while guaranteeing that the
counter is always a local variable called |trcount|, and that no matter how
such operations are nested, they will work. We might then write:
= (text as Inform 7)
To say iteration: (- print {-my:trcount}; -).
=
and then this will work as might be hoped:
= (text as Inform 7)
repeat with a King's Court:
say "[iteration]...";
repeat with a King's Court:
say "[iteration]. You play a Shanty Town, getting +2 Actions.";
=
@<Fetch the head and tail definitions@> =
while (Characters::is_whitespace(*from)) from++;
WRITE_TO(head_defn, "%w", from);
int effective_end = 0;
for (int i=0, L=Str::len(head_defn); i<L; i++)
if (!(Characters::is_whitespace(Str::get_at(head_defn, i))))
effective_end = i+1;
Str::truncate(head_defn, effective_end);
for (int i=0, L=Str::len(head_defn); i<L; i++)
if (Str::includes_wide_string_at(head_defn, L"{-block}", i)) {
int after = i+8, before = i;
while (Characters::is_whitespace(Str::get_at(head_defn, after))) after++;
while (Characters::is_whitespace(Str::get_at(head_defn, before-1))) before--;
Str::copy_tail(tail_defn, head_defn, after);
Str::truncate(head_defn, before);
break;
}
@ The public API above funnels down through this more private function:
=
inter_schema *ParsingSchemas::back_end(text_stream *from, int abbreviated,
int no_quoted_inames, void **quoted_inames) {
inter_schema *sch = InterSchemas::new(from);
if ((Log::aspect_switched_on(SCHEMA_COMPILATION_DA)) ||
(Log::aspect_switched_on(SCHEMA_COMPILATION_DETAILS_DA)))
LOG("\n\n------------\nCompiling inter schema from: <%S>\n", from);
int pos = 0;
if ((abbreviated) && (Str::begins_with_wide_string(from, L"*=-"))) {
sch->dereference_mode = TRUE; pos = 3;
}
Tokenisation::go(sch, from, pos, abbreviated, no_quoted_inames, quoted_inames);
Ramification::go(sch);
InterSchemas::lint(sch);
if ((Log::aspect_switched_on(SCHEMA_COMPILATION_DA)) ||
(Log::aspect_switched_on(SCHEMA_COMPILATION_DETAILS_DA)))
LOG("Completed inter schema:\n$1", sch);
return sch;
}