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/Inter Schemas.w
2022-01-23 10:34:04 +00:00

666 lines
21 KiB
OpenEdge ABL

[InterSchemas::] Inter Schemas.
Building an inter schema, a form of annotated syntax tree.
@h Introduction.
See //What This Module Does// for an introduction to what an Inter schema is.
For this section of code, it's sufficient to know that an //inter_schema//
is an annotated syntax tree made up of //inter_schema_node// nodes.
Relatively few inter schemas are generated during Inform's run -- typically a
few hundred -- so they do not need to be stored compactly or compiled quickly.
=
typedef struct inter_schema {
struct text_stream *converted_from; /* a copy of the source notation */
struct inter_schema_node *node_tree; /* the structure */
int mid_case; /* does this seem to be used inside a switch case? */
int dereference_mode; /* emit from this in dereference-pointers mode */
struct linked_list *parsing_errors; /* of |schema_parsing_error| */
CLASS_DEFINITION
} inter_schema;
@ =
inter_schema *InterSchemas::new(text_stream *source) {
inter_schema *sch = CREATE(inter_schema);
sch->converted_from = Str::duplicate(source);
sch->node_tree = NULL;
sch->mid_case = FALSE;
sch->dereference_mode = FALSE;
sch->parsing_errors = NULL;
return sch;
}
@ Each node has one of the following |*_ISNT| types. (For a while eliminated nodes
were retained in the tree but given the no-op type |OH_NO_IT_ISNT|, but unfortunately
we now more efficiently remove them.)
@e EXPRESSION_ISNT from 1
@e CODE_ISNT
@e EVAL_ISNT
@e STATEMENT_ISNT
@e OPERATION_ISNT
@e SUBEXPRESSION_ISNT
@e DIRECTIVE_ISNT
@e ASSEMBLY_ISNT
@e LABEL_ISNT
@e CALL_ISNT
@e MESSAGE_ISNT
@e CALLMESSAGE_ISNT
=
typedef struct inter_schema_node {
struct inter_schema *parent_schema;
struct inter_schema_node *parent_node;
struct inter_schema_node *child_node;
struct inter_schema_node *next_node;
int node_marked; /* used fleetingly during traverses */
int isn_type; /* one of the |*_ISNT| values */
inter_ti isn_clarifier; /* for |STATEMENT_ISNT| and |OPERATION_ISNT| only */
int dir_clarifier; /* for |DIRECTIVE_ISNT| only */
struct inter_schema_token *expression_tokens; /* for |EXPRESSION_ISNT| only */
int semicolon_terminated; /* for |EXPRESSION_ISNT| only */
int unclosed; /* for |CODE_ISNT| only */
int unopened; /* for |CODE_ISNT| only */
int blocked_by_conditional; /* used in code generation */
CLASS_DEFINITION
} inter_schema_node;
@ =
inter_schema_node *InterSchemas::new_node(inter_schema *sch, int isnt) {
inter_schema_node *isn = CREATE(inter_schema_node);
isn->parent_schema = sch;
isn->parent_node = NULL;
isn->child_node = NULL;
isn->next_node = NULL;
isn->node_marked = FALSE;
isn->isn_type = isnt;
isn->expression_tokens = NULL;
isn->isn_clarifier = 0;
isn->dir_clarifier = -1;
isn->semicolon_terminated = FALSE;
isn->unclosed = FALSE;
isn->unopened = FALSE;
isn->blocked_by_conditional = FALSE;
return isn;
}
@ Ordinarily, a |CODE_ISNT| node represents a complete block of I6 code.
For example, in
= (text as Inform 6)
if (x == 1) { print "Hello!"; }
=
the print statement occurs inside a complete block, which will eventually
be represented as a |CODE_ISNT|. But some inline phrase definitions leave
blocks half-open. For example,
= (text as Inform 6)
if (x == 1)
=
is a legal phrase definition: we read it as
= (text as Inform 6)
if (x == 1) {
=
and the schema has to contain a |CODE_ISNT| marked as having been left
unclosed. Similarly, some definitions close an I6 block which is assumed
as having been opened by an earlier phrase invocation.
Here we mark a code node as being unclosed. Clearly, if a node is unclosed
then any parent code nodes of it must also be unclosed, so we recurse
upwards.
=
void InterSchemas::mark_unclosed(inter_schema_node *isn) {
while (isn) {
if (isn->isn_type == CODE_ISNT) isn->unclosed = TRUE;
isn = isn->parent_node;
}
}
@ The situation with unopened nodes is different: we must ensure that a
single code node is left unopened, even if we have to create a code node
at the top of the tree to serve that role.
=
void InterSchemas::mark_unopened(inter_schema_node *isn) {
while (isn) {
if (isn->isn_type == CODE_ISNT) {
isn->unopened = TRUE;
return;
}
if (isn->parent_node == NULL) {
inter_schema *sch = isn->parent_schema;
inter_schema_node *top = sch->node_tree;
inter_schema_node *code_isn =
InterSchemas::new_node(isn->parent_schema, CODE_ISNT);
code_isn->child_node = top;
sch->node_tree = code_isn;
for (inter_schema_node *n = top; n; n = n->next_node)
n->parent_node = code_isn;
code_isn->unopened = TRUE;
return;
}
isn = isn->parent_node;
}
}
@ A further complication is that some schemas arise from definitions used
only in the middle of switch statements; they contain neither the start nor
the end of the construct, so the above mechanisms are not sufficient.
For example, the inline definition
= (text as Inform 6)
{X}:
=
implies, by use of the colon, that it's a switch case. We can't conveniently
associate this with any single node, so we mark the schema as a whole.
=
void InterSchemas::mark_case_closed(inter_schema_node *isn) {
if (isn) isn->parent_schema->mid_case = TRUE;
}
@ |EXPRESSION_ISNT| nodes carry a linked list of tokens with them. When we
begin compiling a schema, we form a single expression node with a long list
of tokens: gradually this is transformed into a tree of nodes with more,
but much shorter, expression nodes, and in the process most of the token
types are eliminated. The following types are present only during the
compilation process, and never survive into the final schema:
@e RAW_ISTT from 1 /* something unidentified as yet */
@e WHITE_SPACE_ISTT /* a stretch of white space */
@e RESERVED_ISTT /* am I6 reserved word such as |while| */
@e OPERATOR_ISTT /* an I6 operator such as |-->| or |+| */
@e DIVIDER_ISTT /* a semicolon used to divide I6 statements */
@e OPEN_ROUND_ISTT /* open round bracket */
@e CLOSE_ROUND_ISTT /* close round bracket */
@e OPEN_BRACE_ISTT /* open brace bracket */
@e CLOSE_BRACE_ISTT /* close brace bracket */
@e COMMA_ISTT /* comma */
@e COLON_ISTT /* colon */
@ Whereas these token types do make it into the compiled schema:
@e IDENTIFIER_ISTT /* an I6 identifier such as |my_var12| */
@e OPCODE_ISTT /* an Inform assembly language opcode such as |@pull| */
@e DIRECTIVE_ISTT /* an Inform compiler directive such as |#iftrue| */
@e NUMBER_ISTT /* a constant number */
@e BIN_NUMBER_ISTT /* a constant number */
@e HEX_NUMBER_ISTT /* a constant number */
@e REAL_NUMBER_ISTT /* a constant number */
@e DQUOTED_ISTT /* a constant piece of text |"like this"| */
@e SQUOTED_ISTT /* a constant piece of text such as |'x'| */
@e I7_ISTT /* I7 material in |(+ ... +)| notation */
@e INLINE_ISTT /* an inline command such as |{-my:1}| */
@e ASM_ARROW_ISTT /* the arrow sign |->| used in assembly language only */
@e ASM_SP_ISTT /* the stack pointer pseudo-variable |sp| */
@e ASM_LABEL_ISTT /* the label sign |?| used in assembly language only */
@e ASM_NEGATED_LABEL_ISTT /* the label sign |?~| used in assembly language only */
=
typedef struct inter_schema_token {
struct inter_schema_node *owner; /* these form a linked list attached to the owner node */
struct inter_schema_token *next;
int ist_type; /* one of the |*_ISTT| values above */
struct text_stream *material; /* textual form of token */
inter_ti operation_primitive; /* |OPERATOR_ISTT| only: e.g. |PLUS_BIP| for |+| */
int reserved_word; /* |RESERVED_ISTT| only: which one */
int constant_number; /* |NUMBER_ISTT| only: if non-negative, value of number */
struct inter_name *as_quoted; /* |IDENTIFIER_ISTT| only: the identified symbol if known */
int inline_command; /* |INLINE_ISTT| only: one of the |*_ISINC| values */
int inline_modifiers;
int inline_subcommand; /* |INLINE_ISTT| only: one of the |*_ISINSC| values */
struct text_stream *bracing;
struct text_stream *command;
struct text_stream *operand;
struct text_stream *operand2;
#ifdef CORE_MODULE
struct property *extremal_property;
int extremal_property_sign;
#endif
int preinsert; /* fleeting markers only */
int postinsert;
CLASS_DEFINITION
} inter_schema_token;
@ =
inter_schema_token *InterSchemas::new_token(int type, text_stream *material,
inter_ti operation_primitive, int reserved_word, int n) {
inter_schema_token *t = CREATE(inter_schema_token);
t->ist_type = type;
t->material = Str::duplicate(material);
t->bracing = NULL;
t->command = NULL;
t->operand = NULL;
t->operand2 = NULL;
#ifdef CORE_MODULE
t->extremal_property = NULL; /* that is, none given */
t->extremal_property_sign = MEASURE_T_EXACTLY; /* that is, none given */
#endif
t->inline_command = no_ISINC;
t->inline_subcommand = no_ISINSC;
t->next = NULL;
t->owner = NULL;
t->operation_primitive = operation_primitive;
t->as_quoted = NULL;
t->reserved_word = reserved_word;
t->constant_number = n;
t->preinsert = FALSE;
t->postinsert = FALSE;
return t;
}
@ The value of |reserved_word|, in a |RESERVED_ISTT| node, must be one of:
@e IF_I6RW from 1
@e ELSE_I6RW
@e STYLE_I6RW
@e RETURN_I6RW
@e RTRUE_I6RW
@e RFALSE_I6RW
@e FOR_I6RW
@e OBJECTLOOP_I6RW
@e WHILE_I6RW
@e DO_I6RW
@e UNTIL_I6RW
@e PRINT_I6RW
@e PRINTRET_I6RW
@e NEWLINE_I6RW
@e GIVE_I6RW
@e MOVE_I6RW
@e REMOVE_I6RW
@e JUMP_I6RW
@e SWITCH_I6RW
@e DEFAULT_I6RW
@e FONT_I6RW
@e BREAK_I6RW
@e CONTINUE_I6RW
@e QUIT_I6RW
@e RESTORE_I6RW
@e SPACES_I6RW
@e READ_I6RW
@e INVERSION_I6RW
@e IFDEF_I6RW
@e IFNDEF_I6RW
@e IFTRUE_I6RW
@e IFFALSE_I6RW
@e IFNOT_I6RW
@e ENDIF_I6RW
@ The value of |inline_command|, in an |INLINE_ISTT| node, must be one of:
@e no_ISINC from 1
@e primitive_definition_ISINC
@e new_ISINC
@e new_list_of_ISINC
@e printing_routine_ISINC
@e next_routine_ISINC
@e previous_routine_ISINC
@e ranger_routine_ISINC
@e strong_kind_ISINC
@e weak_kind_ISINC
@e backspace_ISINC
@e erase_ISINC
@e open_brace_ISINC
@e close_brace_ISINC
@e label_ISINC
@e counter_ISINC
@e counter_storage_ISINC
@e counter_up_ISINC
@e counter_down_ISINC
@e counter_makes_array_ISINC
@e by_reference_ISINC
@e by_reference_blank_out_ISINC
@e reference_exists_ISINC
@e lvalue_by_reference_ISINC
@e by_value_ISINC
@e box_quotation_text_ISINC
@e try_action_ISINC
@e try_action_silently_ISINC
@e return_value_ISINC
@e return_value_from_rule_ISINC
@e property_holds_block_value_ISINC
@e mark_event_used_ISINC
@e my_ISINC
@e unprotect_ISINC
@e copy_ISINC
@e initialise_ISINC
@e matches_description_ISINC
@e now_matches_description_ISINC
@e arithmetic_operation_ISINC
@e say_ISINC
@e show_me_ISINC
@e segment_count_ISINC
@e final_segment_marker_ISINC
@e list_together_ISINC
@e rescale_ISINC
@e unknown_ISINC
@e substitute_ISINC
@e combine_ISINC
@ The value of |inline_subcommand|, in an |INLINE_ISTT| node, must be one of:
@e no_ISINSC from 1
@e unarticled_ISINSC
@e articled_ISINSC
@e repeat_through_ISINSC
@e repeat_through_list_ISINSC
@e number_of_ISINSC
@e random_of_ISINSC
@e total_of_ISINSC
@e extremal_ISINSC
@e function_application_ISINSC
@e description_application_ISINSC
@e solve_equation_ISINSC
@e switch_ISINSC
@e break_ISINSC
@e verbose_checking_ISINSC
@h Token insertion.
=
void InterSchemas::add_token(inter_schema *sch, inter_schema_token *t) {
if (sch->node_tree == NULL)
sch->node_tree = InterSchemas::new_node(sch, EXPRESSION_ISNT);
InterSchemas::add_token_to_node(sch->node_tree, t);
}
void InterSchemas::add_token_to_node(inter_schema_node *isn, inter_schema_token *t) {
if (isn->expression_tokens == NULL) isn->expression_tokens = t;
else {
inter_schema_token *p = isn->expression_tokens;
while ((p) && (p->next)) p = p->next;
p->next = t;
}
t->owner = isn;
}
void InterSchemas::add_token_after(inter_schema_token *t, inter_schema_token *existing) {
if (existing == NULL) internal_error("can't add after null element");
inter_schema_token *was = existing->next;
existing->next = t;
t->next = was;
t->owner = existing->owner;
}
@ When more drastic things happen, we need to fix all the ownership links:
=
void InterSchemas::changed_tokens_on(inter_schema_node *isn) {
if (isn)
for (inter_schema_token *l = isn->expression_tokens; l; l=l->next)
l->owner = isn;
}
@ These are useful for scanning tokens:
=
int InterSchemas::opening_reserved_word(inter_schema_node *node) {
inter_schema_token *f = InterSchemas::first_dark_token(node);
if ((f) && (f->ist_type == RESERVED_ISTT)) return f->reserved_word;
return 0;
}
int InterSchemas::opening_directive_word(inter_schema_node *node) {
inter_schema_token *f = InterSchemas::first_dark_token(node);
if ((f) && (f->ist_type == DIRECTIVE_ISTT)) return f->reserved_word;
return 0;
}
inter_schema_token *InterSchemas::first_dark_token(inter_schema_node *node) {
if (node == NULL) return NULL;
inter_schema_token *n = node->expression_tokens;
while ((n) && (n->ist_type == WHITE_SPACE_ISTT)) n = n->next;
return n;
}
inter_schema_token *InterSchemas::next_dark_token(inter_schema_token *t) {
if (t == NULL) return NULL;
inter_schema_token *n = t->next;
while ((n) && (n->ist_type == WHITE_SPACE_ISTT)) n = n->next;
return n;
}
inter_schema_token *InterSchemas::second_dark_token(inter_schema_node *node) {
return InterSchemas::next_dark_token(InterSchemas::first_dark_token(node));
}
@h Logging.
It is invaluable to be able to see compiled schemas in the debugging log, so
we go to some trouble here.
=
void InterSchemas::log(OUTPUT_STREAM, void *vis) {
inter_schema *sch = (inter_schema *) vis;
if (sch == NULL) LOG("<null schema>\n");
else if (sch->node_tree == NULL) LOG("<schema without nodes>\n");
else InterSchemas::log_depth(sch->node_tree, 0);
}
void InterSchemas::log_depth(inter_schema_node *isn, int depth) {
for (; isn; isn=isn->next_node)
InterSchemas::log_just(isn, depth);
}
void InterSchemas::log_just(inter_schema_node *isn, int depth) {
if (isn->blocked_by_conditional) LOG("XX"); else LOG(" ");
for (int d = 0; d < depth; d++) LOG(" ");
switch (isn->isn_type) {
case STATEMENT_ISNT:
LOG("* (statement) %S\n", Primitives::BIP_to_name(isn->isn_clarifier));
break;
case OPERATION_ISNT:
LOG("* (operation) %S\n", Primitives::BIP_to_name(isn->isn_clarifier));
break;
case CODE_ISNT:
LOG("* (code)");
if (isn->unclosed) LOG(" <");
if (isn->unopened) LOG(" >");
LOG("\n");
break;
case EVAL_ISNT:
LOG("* (eval)\n");
break;
case ASSEMBLY_ISNT:
LOG("* (assembly)\n");
break;
case DIRECTIVE_ISNT:
LOG("* (directive) ");
switch(isn->dir_clarifier) {
case IFDEF_I6RW: LOG("#ifdef"); break;
case IFNDEF_I6RW: LOG("#ifndef"); break;
case IFTRUE_I6RW: LOG("#iftrue"); break;
case IFFALSE_I6RW: LOG("#iffalse"); break;
case IFNOT_I6RW: LOG("#ifnot"); break;
case ENDIF_I6RW: LOG("#endif"); break;
default: LOG("<unknown>"); break;
}
LOG("\n");
break;
case LABEL_ISNT:
LOG("* (label)\n");
break;
case CALL_ISNT:
LOG("* (call)\n");
break;
case MESSAGE_ISNT:
LOG("* (message)\n");
break;
case CALLMESSAGE_ISNT:
LOG("* (call-message)\n");
break;
case SUBEXPRESSION_ISNT:
LOG("* (subexpression)\n");
break;
case EXPRESSION_ISNT:
LOG("* (expr)");
if (isn->semicolon_terminated) LOG(" ;");
if (isn->unclosed) LOG(" <");
if (isn->unopened) LOG(" >");
if (isn->expression_tokens == NULL) LOG(" - empty");
LOG("\n");
for (inter_schema_token *t = isn->expression_tokens; t; t=t->next) {
for (int d = 0; d < depth + 1; d++) LOG(" ");
InterSchemas::log_ist(t);
if (isn != t->owner) LOG(" !!! ownership incorrect here");
LOG("\n");
}
break;
}
InterSchemas::log_depth(isn->child_node, depth+1);
}
void InterSchemas::log_ist(inter_schema_token *t) {
if (t == NULL) { LOG("<NULL-IST>"); return; }
switch (t->ist_type) {
case RAW_ISTT: LOG("RAW "); break;
case OPERATOR_ISTT: LOG("OPERATOR "); break;
case OPCODE_ISTT: LOG("OPCODE "); break;
case DIRECTIVE_ISTT: LOG("DIRECTIVE "); break;
case IDENTIFIER_ISTT: LOG("IDENTIFIER "); break;
case RESERVED_ISTT: LOG("RESERVED "); break;
case NUMBER_ISTT: LOG("NUMBER "); break;
case BIN_NUMBER_ISTT: LOG("BIN_NUMBER "); break;
case HEX_NUMBER_ISTT: LOG("HEX_NUMBER "); break;
case REAL_NUMBER_ISTT: LOG("REAL_NUMBER "); break;
case DQUOTED_ISTT: LOG("DQUOTED "); break;
case SQUOTED_ISTT: LOG("SQUOTED "); break;
case WHITE_SPACE_ISTT: LOG("WHITE_SPACE "); break;
case DIVIDER_ISTT: LOG("DIVIDER "); break;
case OPEN_ROUND_ISTT: LOG("OPEN_ROUND "); break;
case CLOSE_ROUND_ISTT: LOG("CLOSE_ROUND "); break;
case OPEN_BRACE_ISTT: LOG("OPEN_BRACE "); break;
case CLOSE_BRACE_ISTT: LOG("CLOSE_BRACE "); break;
case COMMA_ISTT: LOG("COMMA "); break;
case COLON_ISTT: LOG("COLON "); break;
case I7_ISTT: LOG("I7 "); break;
case INLINE_ISTT: LOG("INLINE "); break;
case ASM_ARROW_ISTT: LOG("ASM_ARROW "); break;
case ASM_SP_ISTT: LOG("ASM_SP "); break;
case ASM_LABEL_ISTT: LOG("ASM_LABEL "); break;
case ASM_NEGATED_LABEL_ISTT: LOG("NEGASM_LABEL "); break;
default: LOG("<unknown>"); break;
}
LOG("%S", t->material);
if (t->inline_modifiers & GIVE_KIND_ID_ISSBM) LOG(" GIVE_KIND_ID");
if (t->inline_modifiers & GIVE_COMPARISON_ROUTINE_ISSBM) LOG(" GIVE_COMPARISON_ROUTINE");
if (t->inline_modifiers & DEREFERENCE_PROPERTY_ISSBM) LOG(" DEREFERENCE_PROPERTY");
if (t->inline_modifiers & ADOPT_LOCAL_STACK_FRAME_ISSBM) LOG(" ADOPT_LOCAL_STACK_FRAME");
if (t->inline_modifiers & CAST_TO_KIND_OF_OTHER_TERM_ISSBM) LOG(" CAST_TO_KIND_OF_OTHER_TERM");
if (t->inline_modifiers & BY_REFERENCE_ISSBM) LOG(" BY_REFERENCE");
}
@h Lint.
As can be seen, the |inter_schema| structure is quite complicated, and there
are numerous invariants it has to satisfy. As a precaution, then, we check that
all of these invariants hold before shipping out a compiled schema. This is
where the check is done:
=
void InterSchemas::lint(inter_schema *sch) {
if ((sch) && (sch->parsing_errors == NULL)) {
text_stream *err = InterSchemas::lint_isn(sch->node_tree, 0);
if (err) {
LOG("Lint fail: %S\n$1\n", err, sch);
WRITE_TO(STDERR, "Lint fail: %S\n%S\n", err, sch->converted_from);
internal_error("inter schema failed lint");
}
}
}
text_stream *InterSchemas::lint_isn(inter_schema_node *isn, int depth) {
for (; isn; isn=isn->next_node) {
if (isn->isn_type == EXPRESSION_ISNT) {
int asm = FALSE;
for (inter_schema_token *t = isn->expression_tokens; t; t=t->next) {
switch (t->ist_type) {
case OPCODE_ISTT: if (t == isn->expression_tokens) asm = TRUE;
else return I"contains loose assembler";
break;
case RAW_ISTT: return I"contains raw node";
case OPEN_BRACE_ISTT: return I"contains open-brace node";
case CLOSE_BRACE_ISTT: return I"contains close-brace node";
case OPEN_ROUND_ISTT: return I"contains open-round node";
case CLOSE_ROUND_ISTT: return I"contains close-round node";
case COMMA_ISTT: return I"contains comma node";
case DIVIDER_ISTT: return I"contains divider node";
case RESERVED_ISTT: return I"contains reserved word node";
case COLON_ISTT: return I"contains colon node";
case OPERATOR_ISTT: return I"contains operator node";
}
if ((t->ist_type == INLINE_ISTT) && (t->inline_command == open_brace_ISINC))
return I"contains manual open-brace";
if ((t->ist_type == INLINE_ISTT) && (t->inline_command == close_brace_ISINC))
return I"contains manual close-brace";
if ((t->ist_type == NUMBER_ISTT) && (t->next) &&
(t->next->ist_type == NUMBER_ISTT) && (asm == FALSE))
return I"two consecutive numbers";
if (isn != t->owner)
return I"ownership linkage broken";
}
if (isn->child_node) return I"expression has child nodes";
} else {
if (isn->expression_tokens) return I"non-expression has elements";
}
if ((isn->child_node) && (isn->child_node->parent_node != isn))
return I"child-parent linkage broken";
if ((isn->isn_clarifier) && (isn->expression_tokens))
return I"ambiguous is-node";
text_stream *R = InterSchemas::lint_isn(isn->child_node, depth+1);
if (R) return R;
}
return NULL;
}
@h Errors.
Errors can be detected both when parsing text into schemas, and also when emitting
code from them. In either case, the following is used to attach error message(s)
to the schema itself.
Note that the |parsing_errors| field is null until the first error is detected --
which, of course, it usually isn't.
=
typedef struct schema_parsing_error {
struct text_stream *message;
CLASS_DEFINITION
} schema_parsing_error;
@ =
void InterSchemas::throw_error(inter_schema_node *at, text_stream *message) {
if (at->parent_schema->parsing_errors == NULL)
at->parent_schema->parsing_errors = NEW_LINKED_LIST(schema_parsing_error);
schema_parsing_error *err = CREATE(schema_parsing_error);
err->message = Str::duplicate(message);
ADD_TO_LINKED_LIST(err, schema_parsing_error, at->parent_schema->parsing_errors);
LOG("Schema error: %S\n", message);
LOG("$1\n", at->parent_schema);
}
@ And this is an especially drastic way to deal with such errors:
=
void InterSchemas::internal_error_on_schema_errors(inter_schema *sch) {
if (LinkedLists::len(sch->parsing_errors) > 0) {
WRITE_TO(STDERR, "Parsing error(s) in the internal schema '%S':\n",
sch->converted_from);
schema_parsing_error *err;
LOOP_OVER_LINKED_LIST(err, schema_parsing_error, sch->parsing_errors)
WRITE_TO(STDERR, "- %S\n", err->message);
internal_error("malformed schema");
}
}