Attaching general-purpose data to nodes in the syntax tree.


§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.

enum heading_level_ANNOT from 1  int: for HEADING nodes, a hierarchical level, 0 (highest) to 9 (lowest)
enum language_element_ANNOT  int: this node is not really a sentence, but a language definition Use
enum suppress_heading_dependencies_ANNOT  int: ignore extension dependencies on this heading node
enum implied_heading_ANNOT  int: set only for the heading of implied inclusions
enum dialogue_level_ANNOT  int: for DIALOGUE_CUE and DIALOGUE_LINE nodes, indendation level
enum dialogue_during_text_w1_ANNOT  int: first word of during scene wording
enum 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);
        }
}

§3. Annotations.

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;

§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;
}

§9. 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;
}

§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;
}

§11. Access routines will be needed for some of these, and the following constructs them:

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;
}