Attaching general-purpose data to nodes in the syntax tree.
- §1. Annotation types
- §3. Annotations
- §6. Reading annotations
- §8. Writing annotations
- §10. Setters and getters
- §12. Copying annotations
- §13. Annotation permissions
§1. Annotation types. 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 annotations, allowing each node in principle to have an arbitrary set (though see below).
The following annotations used by the syntax module.
enumerate heading_level_ANNOT 1 /* int: for HEADING nodes, a hierarchical level, 0 (highest) to 9 (lowest) */
enumerate language_element_ANNOT /* int: this node is not really a sentence, but a language definition Use */
enumerate suppress_heading_dependencies_ANNOT /* int: ignore extension dependencies on this heading node */
enumerate implied_heading_ANNOT /* int: set only for the heading of implied inclusions */
enumerate dialogue_level_ANNOT /* int: for DIALOGUE_CUE and DIALOGUE_LINE nodes, indendation level */
enumerate dialogue_during_text_w1_ANNOT /* int: first word of during scene wording */
enumerate dialogue_during_text_w2_ANNOT /* int: first word of during scene wording */
define 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); Annotations::declare_type(dialogue_level_ANNOT, Annotations::write_dialogue_level_ANNOT); Annotations::declare_type( dialogue_during_text_w1_ANNOT, Annotations::write_dialogue_during_text_ANNOT); Annotations::declare_type( dialogue_during_text_w2_ANNOT, Annotations::do_not_write_dialogue_during_text_ANNOT); } 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}"); } 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)); } void Annotations::write_dialogue_during_text_ANNOT(text_stream *OUT, parse_node *p) { int w1 = Annotations::read_int(p, dialogue_during_text_w1_ANNOT); int w2 = Annotations::read_int(p, dialogue_during_text_w2_ANNOT); wording W = Wordings::new(w1, w2); if ((w1 > 0) && (Wordings::nonempty(W))) WRITE(" {dialogue during text: %W}", W); } void Annotations::do_not_write_dialogue_during_text_ANNOT(text_stream *OUT, parse_node *p) { }
§2. 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"); if (f == NULL) internal_error("no logging function"); 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); } }
- The structure parse_node_annotation_type is private to this section.
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;
- The structure parse_node_annotation is accessed in 2/tv and here.
§4. 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) { if ((id < 0) || (id >= MAX_ANNOT_NUMBER)) internal_error("annot out of range"); if (known_annotation_types[id] == NULL) internal_error("undeclared annot"); 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; }
§5. 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; }
§6. 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; }
§7. 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; }
§8. 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; }
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; }
§10. 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_pointerss,
so we use macros to make convenient get and set functions.
define 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; }
define 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);
§12. 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; } }
§13. 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); Annotations::allow(HEADING_NT, suppress_heading_dependencies_ANNOT); Annotations::allow(HEADING_NT, implied_heading_ANNOT); Annotations::allow(SENTENCE_NT, language_element_ANNOT); Annotations::allow(DIALOGUE_CUE_NT, dialogue_level_ANNOT); Annotations::allow(DIALOGUE_CUE_NT, dialogue_during_text_w1_ANNOT); Annotations::allow(DIALOGUE_CUE_NT, dialogue_during_text_w2_ANNOT); Annotations::allow(DIALOGUE_CHOICE_NT, dialogue_level_ANNOT); Annotations::allow(DIALOGUE_LINE_NT, dialogue_level_ANNOT); #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 #ifdef STILL_MORE_ANNOTATION_PERMISSIONS_SYNTAX_CALLBACK STILL_MORE_ANNOTATION_PERMISSIONS_SYNTAX_CALLBACK(); #endif }
§14. 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); }
§15. 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; }
§16. 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; }