2020-05-11 17:21:29 +03:00
|
|
|
[Annotations::] Node Annotations.
|
|
|
|
|
|
|
|
Attaching general-purpose data to nodes in the syntax tree.
|
|
|
|
|
2020-08-07 00:01:38 +03:00
|
|
|
@h Annotation types.
|
2020-05-11 17:21:29 +03:00
|
|
|
The parse tree annotations are miscellaneous, and many are needed only at a
|
|
|
|
few unusual nodes. Rather than have the structure grow large, we store
|
2020-08-07 00:01:38 +03:00
|
|
|
annotations, allowing each node in principle to have an arbitrary set (though
|
|
|
|
see below).
|
|
|
|
|
|
|
|
The following annotations used by the syntax module.
|
|
|
|
|
|
|
|
@e heading_level_ANNOT from 1 /* int: for HEADING nodes, a hierarchical level, 0 (highest) to 9 (lowest) */
|
|
|
|
@e language_element_ANNOT /* int: this node is not really a sentence, but a language definition Use */
|
|
|
|
@e suppress_heading_dependencies_ANNOT /* int: ignore extension dependencies on this heading node */
|
|
|
|
@e implied_heading_ANNOT /* int: set only for the heading of implied inclusions */
|
2022-09-02 15:01:25 +03:00
|
|
|
@e dialogue_level_ANNOT /* int: for DIALOGUE_CUE and DIALOGUE_LINE nodes, indendation level */
|
2020-08-07 00:01:38 +03:00
|
|
|
|
|
|
|
@d MAX_ANNOT_NUMBER (NO_DEFINED_ANNOT_VALUES+1)
|
|
|
|
|
|
|
|
=
|
|
|
|
void Annotations::begin(void) {
|
|
|
|
Annotations::declare_type(heading_level_ANNOT,
|
|
|
|
Annotations::write_heading_level_ANNOT);
|
|
|
|
Annotations::declare_type(language_element_ANNOT,
|
|
|
|
Annotations::write_language_element_ANNOT);
|
|
|
|
Annotations::declare_type(suppress_heading_dependencies_ANNOT,
|
|
|
|
Annotations::write_suppress_heading_dependencies_ANNOT);
|
|
|
|
Annotations::declare_type(implied_heading_ANNOT,
|
|
|
|
Annotations::write_implied_heading_ANNOT);
|
2022-09-02 15:01:25 +03:00
|
|
|
Annotations::declare_type(dialogue_level_ANNOT,
|
|
|
|
Annotations::write_dialogue_level_ANNOT);
|
2020-08-07 00:01:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void Annotations::write_heading_level_ANNOT(text_stream *OUT, parse_node *p) {
|
|
|
|
if (Annotations::read_int(p, heading_level_ANNOT) >= 0)
|
|
|
|
WRITE(" {heading %d}", Annotations::read_int(p, heading_level_ANNOT));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Annotations::write_language_element_ANNOT(text_stream *OUT, parse_node *p) {
|
|
|
|
if (Annotations::read_int(p, language_element_ANNOT))
|
|
|
|
WRITE(" {language element}");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Annotations::write_suppress_heading_dependencies_ANNOT(text_stream *OUT, parse_node *p) {
|
|
|
|
if (Annotations::read_int(p, suppress_heading_dependencies_ANNOT))
|
|
|
|
WRITE(" {suppress dependencies}");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Annotations::write_implied_heading_ANNOT(text_stream *OUT, parse_node *p) {
|
|
|
|
if (Annotations::read_int(p, implied_heading_ANNOT))
|
|
|
|
WRITE(" {implied}");
|
|
|
|
}
|
|
|
|
|
2022-09-02 15:01:25 +03:00
|
|
|
void Annotations::write_dialogue_level_ANNOT(text_stream *OUT, parse_node *p) {
|
|
|
|
if (Annotations::read_int(p, dialogue_level_ANNOT) >= 0)
|
|
|
|
WRITE(" {level %d}", Annotations::read_int(p, dialogue_level_ANNOT));
|
|
|
|
}
|
|
|
|
|
2020-08-07 00:01:38 +03:00
|
|
|
@ Annotations are identified by type, which are enumerated constants, and
|
|
|
|
these must be declared before use.
|
|
|
|
|
|
|
|
=
|
|
|
|
typedef struct parse_node_annotation_type {
|
|
|
|
void (*writer_function)(text_stream *, parse_node *p);
|
|
|
|
CLASS_DEFINITION
|
|
|
|
} parse_node_annotation_type;
|
|
|
|
|
|
|
|
int known_annotation_types_started = FALSE;
|
|
|
|
parse_node_annotation_type *known_annotation_types[MAX_ANNOT_NUMBER];
|
|
|
|
|
|
|
|
void Annotations::declare_type(int id, void (*f)(text_stream *, parse_node *)) {
|
|
|
|
if ((id < 0) || (id >= MAX_ANNOT_NUMBER)) internal_error("annot out of range");
|
2020-08-28 03:00:52 +03:00
|
|
|
if (f == NULL) internal_error("no logging function");
|
2020-08-07 00:01:38 +03:00
|
|
|
if (known_annotation_types_started == FALSE) {
|
|
|
|
for (int i=0; i<MAX_ANNOT_NUMBER; i++) known_annotation_types[i] = NULL;
|
|
|
|
known_annotation_types_started = TRUE;
|
|
|
|
}
|
|
|
|
if (known_annotation_types[id]) internal_error("annot declared twice");
|
|
|
|
known_annotation_types[id] = CREATE(parse_node_annotation_type);
|
|
|
|
known_annotation_types[id]->writer_function = f;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Annotations::write_annotations(text_stream *OUT, parse_node *PN) {
|
|
|
|
parse_node_annotation *pna;
|
|
|
|
if (PN)
|
|
|
|
for (pna=PN->annotations; pna; pna=pna->next_annotation) {
|
|
|
|
int id = pna->annotation_id;
|
|
|
|
if ((id < 0) || (id >= MAX_ANNOT_NUMBER)) internal_error("annot out of range");
|
|
|
|
if (known_annotation_types[id] == NULL) internal_error("undeclared annot");
|
|
|
|
if (known_annotation_types[id]->writer_function)
|
|
|
|
(*(known_annotation_types[id]->writer_function))(OUT, PN);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@h Annotations.
|
2020-05-11 17:21:29 +03:00
|
|
|
|
|
|
|
=
|
|
|
|
typedef struct parse_node_annotation {
|
|
|
|
int annotation_id; /* one of the |*_ANNOT| values */
|
|
|
|
int annotation_integer; /* if this is an integer annotation, or ... */
|
|
|
|
general_pointer annotation_pointer; /* ... if it holds an object */
|
|
|
|
struct parse_node_annotation *next_annotation;
|
|
|
|
} parse_node_annotation;
|
|
|
|
|
|
|
|
@ A new annotation is like a blank luggage ticket, waiting to be filled out
|
|
|
|
and attached to some suitcase. All is has is its ID:
|
|
|
|
|
|
|
|
=
|
|
|
|
parse_node_annotation *Annotations::new(int id) {
|
2020-08-07 00:01:38 +03:00
|
|
|
if ((id < 0) || (id >= MAX_ANNOT_NUMBER)) internal_error("annot out of range");
|
|
|
|
if (known_annotation_types[id] == NULL) internal_error("undeclared annot");
|
2020-05-11 17:21:29 +03:00
|
|
|
parse_node_annotation *pna = CREATE(parse_node_annotation);
|
|
|
|
pna->annotation_id = id;
|
|
|
|
pna->annotation_integer = 0;
|
|
|
|
pna->annotation_pointer = NULL_GENERAL_POINTER;
|
|
|
|
pna->next_annotation = NULL;
|
|
|
|
return pna;
|
|
|
|
}
|
|
|
|
|
|
|
|
@ Each node has a linked list of //parse_node_annotation// objects, but for
|
|
|
|
speed and to reduce memory usage we implement this by hand rather than using
|
|
|
|
the linked list class from //foundation//. A node |N| has a list |N->annotations|,
|
|
|
|
which points to its first //parse_node_annotation//, or is |NULL| if the node
|
|
|
|
is unannotated.
|
|
|
|
|
|
|
|
=
|
|
|
|
void Annotations::clear(parse_node *PN) {
|
|
|
|
PN->annotations = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
@h Reading annotations.
|
|
|
|
Though there will be many such lists, each one will always be short (worst case
|
|
|
|
about 5), so a more efficient search algorithm would not pay its overheads.
|
|
|
|
|
|
|
|
=
|
|
|
|
int Annotations::node_has(parse_node *PN, int id) {
|
|
|
|
parse_node_annotation *pna;
|
|
|
|
if (PN)
|
|
|
|
for (pna=PN->annotations; pna; pna=pna->next_annotation)
|
|
|
|
if (pna->annotation_id == id)
|
|
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
@ Reading annotations is similar. We need two variant forms: one for reading
|
|
|
|
integer-valued annotations (which is most of them, as it happens) and the
|
|
|
|
other for reading pointers to objects.
|
|
|
|
|
|
|
|
=
|
|
|
|
int Annotations::read_int(parse_node *PN, int id) {
|
|
|
|
parse_node_annotation *pna;
|
|
|
|
if (PN)
|
|
|
|
for (pna=PN->annotations; pna; pna=pna->next_annotation)
|
|
|
|
if (pna->annotation_id == id)
|
|
|
|
return pna->annotation_integer;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
general_pointer Annotations::read_object(parse_node *PN, int id) {
|
|
|
|
parse_node_annotation *pna;
|
|
|
|
if (PN)
|
|
|
|
for (pna=PN->annotations; pna; pna=pna->next_annotation)
|
|
|
|
if (pna->annotation_id == id)
|
|
|
|
return pna->annotation_pointer;
|
|
|
|
return NULL_GENERAL_POINTER;
|
|
|
|
}
|
|
|
|
|
|
|
|
@h Writing annotations.
|
|
|
|
Note that any second or subsequent annotation with the same ID as an existing
|
|
|
|
one (on the same node) overwrites it, but this is not an error.
|
|
|
|
|
|
|
|
Again, integers first:
|
|
|
|
|
|
|
|
=
|
|
|
|
void Annotations::write_int(parse_node *PN, int id, int v) {
|
|
|
|
parse_node_annotation *newpna, *pna, *final = NULL;
|
|
|
|
if (PN == NULL) internal_error("annotated null PN");
|
|
|
|
for (pna=PN->annotations; pna; pna=pna->next_annotation) {
|
|
|
|
if (pna->annotation_id == id) {
|
|
|
|
/* an annotation with this id exists already: overwrite it */
|
|
|
|
pna->annotation_integer = v;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (pna->next_annotation == NULL) final = pna;
|
|
|
|
}
|
|
|
|
/* no annotation with this id exists: create a new one and add to end of node's list */
|
|
|
|
newpna = Annotations::new(id); newpna->annotation_integer = v;
|
|
|
|
if (final) final->next_annotation = newpna; else PN->annotations = newpna;
|
|
|
|
}
|
|
|
|
|
|
|
|
@ And now objects:
|
|
|
|
|
|
|
|
=
|
|
|
|
void Annotations::write_object(parse_node *PN, int id, general_pointer data) {
|
|
|
|
if (PN == NULL) internal_error("annotated null PN");
|
|
|
|
parse_node_annotation *newpna, *pna, *final = NULL;
|
|
|
|
for (pna=PN->annotations; pna; pna=pna->next_annotation) {
|
|
|
|
if (pna->annotation_id == id) {
|
|
|
|
/* an annotation with this id exists already: overwrite it */
|
|
|
|
pna->annotation_pointer = data;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (pna->next_annotation == NULL) final = pna;
|
|
|
|
}
|
|
|
|
/* no annotation with this id exists: create a new one and add to end of node's list */
|
|
|
|
newpna = Annotations::new(id); newpna->annotation_pointer = data;
|
|
|
|
if (final) final->next_annotation = newpna; else PN->annotations = newpna;
|
|
|
|
}
|
|
|
|
|
|
|
|
@h Setters and getters.
|
|
|
|
It's a nuisance to use //Annotations::read_object// and //Annotations::write_object//
|
|
|
|
directly because of the need to wrap and unwrap the objects into |general_pointers|s,
|
|
|
|
so we use macros to make convenient get and set functions.
|
|
|
|
|
|
|
|
@d MAKE_ANNOTATION_FUNCTIONS(annotation_name, pointer_type)
|
|
|
|
void Node::set_##annotation_name(parse_node *pn, pointer_type *bp) {
|
|
|
|
Annotations::write_object(pn, annotation_name##_ANNOT,
|
|
|
|
STORE_POINTER_##pointer_type(bp));
|
|
|
|
}
|
|
|
|
pointer_type *Node::get_##annotation_name(parse_node *pn) {
|
|
|
|
pointer_type *pt = NULL;
|
|
|
|
if (Annotations::node_has(pn, annotation_name##_ANNOT))
|
|
|
|
pt = RETRIEVE_POINTER_##pointer_type(
|
|
|
|
Annotations::read_object(pn, annotation_name##_ANNOT));
|
|
|
|
return pt;
|
|
|
|
}
|
|
|
|
|
|
|
|
@ Access routines will be needed for some of these, and the following
|
|
|
|
constructs them:
|
|
|
|
|
|
|
|
@d DECLARE_ANNOTATION_FUNCTIONS(annotation_name, pointer_type)
|
|
|
|
void Node::set_##annotation_name(parse_node *pn, pointer_type *bp);
|
|
|
|
pointer_type *Node::get_##annotation_name(parse_node *pn);
|
|
|
|
|
|
|
|
@h Copying annotations.
|
|
|
|
For the most part, an annotation can be copied directly from one node to
|
|
|
|
another: if it's an integer, or a pointer to an immutable sort of object.
|
|
|
|
But this sort of shallow copy won't always suffice, and so we allow for
|
|
|
|
a callback function to deep-copy the data inside the annotation if it
|
|
|
|
wants to.
|
|
|
|
|
|
|
|
=
|
|
|
|
void Annotations::copy(parse_node *to, parse_node *from) {
|
|
|
|
to->annotations = NULL;
|
|
|
|
for (parse_node_annotation *pna = from->annotations, *latest = NULL;
|
|
|
|
pna; pna=pna->next_annotation) {
|
|
|
|
parse_node_annotation *pna_copy = CREATE(parse_node_annotation);
|
|
|
|
*pna_copy = *pna;
|
|
|
|
#ifdef ANNOTATION_COPY_SYNTAX_CALLBACK
|
|
|
|
ANNOTATION_COPY_SYNTAX_CALLBACK(pna_copy, pna);
|
|
|
|
#endif
|
|
|
|
pna_copy->next_annotation = NULL;
|
|
|
|
if (to->annotations == NULL) to->annotations = pna_copy;
|
|
|
|
else latest->next_annotation = pna_copy;
|
|
|
|
latest = pna_copy;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@h Annotation permissions.
|
|
|
|
As a piece of defensive coding, //syntax// will not allow arbitrary annotations
|
|
|
|
to be made: only annotations appropriate to the type of the node in question.
|
|
|
|
For example, attempting to give an |heading_level_ANNOT| to a |SENTENCE_NT|
|
|
|
|
node will throw an internal error -- it must mean a bug in Inform.
|
|
|
|
|
|
|
|
=
|
|
|
|
void Annotations::make_annotation_allowed_table(void) {
|
|
|
|
Annotations::allow(HEADING_NT, heading_level_ANNOT);
|
2020-08-28 03:00:52 +03:00
|
|
|
Annotations::allow(HEADING_NT, suppress_heading_dependencies_ANNOT);
|
|
|
|
Annotations::allow(HEADING_NT, implied_heading_ANNOT);
|
2020-05-11 17:21:29 +03:00
|
|
|
Annotations::allow(SENTENCE_NT, language_element_ANNOT);
|
2022-09-02 15:01:25 +03:00
|
|
|
Annotations::allow(DIALOGUE_CUE_NT, dialogue_level_ANNOT);
|
2022-09-14 13:03:49 +03:00
|
|
|
Annotations::allow(DIALOGUE_CHOICE_NT, dialogue_level_ANNOT);
|
2022-09-02 15:01:25 +03:00
|
|
|
Annotations::allow(DIALOGUE_LINE_NT, dialogue_level_ANNOT);
|
2020-05-11 17:21:29 +03:00
|
|
|
#ifdef ANNOTATION_PERMISSIONS_SYNTAX_CALLBACK
|
|
|
|
ANNOTATION_PERMISSIONS_SYNTAX_CALLBACK();
|
|
|
|
#endif
|
|
|
|
#ifdef MORE_ANNOTATION_PERMISSIONS_SYNTAX_CALLBACK
|
|
|
|
MORE_ANNOTATION_PERMISSIONS_SYNTAX_CALLBACK();
|
|
|
|
#endif
|
|
|
|
#ifdef EVEN_MORE_ANNOTATION_PERMISSIONS_SYNTAX_CALLBACK
|
|
|
|
EVEN_MORE_ANNOTATION_PERMISSIONS_SYNTAX_CALLBACK();
|
|
|
|
#endif
|
2020-08-28 03:00:52 +03:00
|
|
|
#ifdef STILL_MORE_ANNOTATION_PERMISSIONS_SYNTAX_CALLBACK
|
|
|
|
STILL_MORE_ANNOTATION_PERMISSIONS_SYNTAX_CALLBACK();
|
|
|
|
#endif
|
2020-05-11 17:21:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@ The |ANNOTATION_PERMISSIONS_SYNTAX_CALLBACK| function, if it exists, is
|
|
|
|
expected also to call the following:
|
|
|
|
|
|
|
|
=
|
|
|
|
int annotation_allowed[NO_DEFINED_NT_VALUES][MAX_ANNOT_NUMBER+1];
|
|
|
|
|
|
|
|
void Annotations::allow(node_type_t t, int annot) {
|
|
|
|
annotation_allowed[t - ENUMERATED_NT_BASE][annot] = TRUE;
|
|
|
|
}
|
|
|
|
void Annotations::allow_for_category(int cat, int annot) {
|
|
|
|
LOOP_OVER_ENUMERATED_NTS(t)
|
|
|
|
if (NodeType::category(t) == cat)
|
|
|
|
Annotations::allow(t, annot);
|
|
|
|
}
|
|
|
|
|
|
|
|
@ And this allows the following. Note that nodes with the temporary |*_MC|
|
|
|
|
types (i.e., those of an unenumerated node type) cannot be annotated.
|
|
|
|
|
|
|
|
=
|
|
|
|
int Annotations::is_allowed(node_type_t t, int annot) {
|
|
|
|
if ((annot <= 0) || (annot > MAX_ANNOT_NUMBER))
|
|
|
|
internal_error("annotation number out of range");
|
|
|
|
if (NodeType::is_enumerated(t))
|
|
|
|
return annotation_allowed[t - ENUMERATED_NT_BASE][annot];
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
@ The following removes any annotation not currently valid for the node; this
|
|
|
|
is rarely used by Inform, but is needed when a node changes its type.
|
|
|
|
|
|
|
|
=
|
|
|
|
void Annotations::clear_invalid(parse_node *pn) {
|
|
|
|
node_type_t nt = Node::get_type(pn);
|
|
|
|
while ((pn->annotations) &&
|
|
|
|
(!(Annotations::is_allowed(nt, pn->annotations->annotation_id))))
|
|
|
|
pn->annotations = pn->annotations->next_annotation;
|
|
|
|
for (parse_node_annotation *pna = pn->annotations; pna; pna = pna->next_annotation)
|
|
|
|
if ((pna->next_annotation) &&
|
|
|
|
(!(Annotations::is_allowed(nt, pna->next_annotation->annotation_id))))
|
|
|
|
pna->next_annotation = pna->next_annotation->next_annotation;
|
|
|
|
}
|