To manage dialogue lines.


§1. Scanning the dialogue lines in pass 0. Lines have already been parsed a little. For example,

    Marcellus (this is the phantom line): "What, has this thing appear'd again to-night?"

will have become:

    DIALOGUE_LINE_NT
        DIALOGUE_SPEAKER_NT "Marcellus"
        DIALOGUE_SPEECH_NT ""What, has this thing appear'd again to-night?""
        DIALOGUE_CLAUSE_NT "this is the phantom line"

Here we have a simple tree where the beat node has at least two child nodes: exactly one each of DIALOGUE_SPEAKER_NT and DIALOGUE_SPEECH_NT, and then any number of DIALOGUE_CLAUSE_NT nodes (including none at all).

dialogue_line *DialogueLines::new(parse_node *PN) {
    int L = Annotations::read_int(PN, dialogue_level_ANNOT);
    if (L < 0) L = 0;
    dialogue_line *dl = CREATE(dialogue_line);
    Initialise the line1.2;
    Parse the clauses just enough to classify them1.3;
    Decide if this is elaborated1.4;
    Look for a line name1.7;
    dl->as_node = DialogueNodes::add_to_current_beat(L, dl, NULL);
    Add the line to the world model1.10;
    return dl;
}

§1.1.

typedef struct dialogue_line {
    struct wording line_name;
    struct instance *as_instance;
    struct parse_node *line_at;
    int narration;
    int elaborated;
    int without_speaking;
    struct wording speaker_text;
    int speaker_is_player;
    struct parse_node *speaker_description;
    struct wording interlocutor_text;
    int interlocutor_is_player;
    struct parse_node *interlocutor_description;
    struct wording speech_text;
    struct linked_list *mentioning;  of parse_node
    struct performance_style *how_performed;
    struct dialogue_node *as_node;
    struct dialogue_line_compilation_data compilation_data;
    CLASS_DEFINITION
} dialogue_line;

§1.2. Initialise the line1.2 =

    dl->line_name = EMPTY_WORDING;
    dl->line_at = PN;
    dl->narration = FALSE;
    dl->elaborated = FALSE;
    dl->speaker_text = EMPTY_WORDING;
    dl->speaker_is_player = FALSE;
    dl->interlocutor_text = EMPTY_WORDING;
    dl->interlocutor_is_player = FALSE;
    dl->speaker_description = NULL;
    dl->without_speaking = FALSE;
    dl->compilation_data = RTDialogueLines::new(PN, dl);
    dl->speech_text = EMPTY_WORDING;
    dl->mentioning = NEW_LINKED_LIST(parse_node);
    dl->how_performed = NULL;
    dl->interlocutor_description = NULL;

§1.3. Parse the clauses just enough to classify them1.3 =

    for (parse_node *clause = PN->down; clause; clause = clause->next) {
        wording CW = Node::get_text(clause);
        if (Node::is(clause, DIALOGUE_CLAUSE_NT)) {
            <dialogue-line-clause>(CW);
            Annotations::write_int(clause, dialogue_line_clause_ANNOT, <<r>>);
            if (<<r>> == TO_DLC) {
                dl->interlocutor_text = GET_RW(<dialogue-line-clause>, 1);
            }
        } else if (Node::is(clause, DIALOGUE_SPEAKER_NT)) {
            if (<dialogue-is-narration>(CW))
                dl->narration = TRUE;
            else
                dl->speaker_text = CW;
        } else if (Node::is(clause, DIALOGUE_SPEECH_NT)) {
            dl->speech_text = CW;
        } else internal_error("damaged DIALOGUE_LINE_NT subtree");
    }

§1.4. Decide if this is elaborated1.4 =

    int wn = Wordings::first_wn(dl->speech_text);
    if (wn >= 0) {
        int quote_marks = 0;
        inchar32_t *p = Lexer::word_text(wn);
        for (int i=0; p[i]; i++)
            if ((p[i] == '\'') &&
                ((i<=1) || (p[i+1] == '\"') ||
                    (Characters::is_whitespace(p[i+1])) || (Characters::is_whitespace(p[i-1]))))
                quote_marks++;
        if (quote_marks >= 2) dl->elaborated = TRUE;
    }
    if (dl->narration) dl->elaborated = FALSE;

§1.5. The special speaker "narration" marks out a line which isn't a speech at all:

<dialogue-is-narration> ::=
    narration

§1.6. As with the analogous clauses for Dialogue Beats, each clause can be one of the following possibilities:

enum LINE_NAME_DLC from 1
enum MENTIONING_DLC
enum IF_DLC
enum UNLESS_DLC
enum BEFORE_DLC
enum AFTER_DLC
enum NOW_DLC
enum TO_DLC
enum WITHOUT_SPEAKING_DLC
enum STYLE_DLC

§1.7. Look for a line name1.7 =

    int dialogue_line_name_count = 0;
    for (parse_node *clause = PN->down; clause; clause = clause->next) {
        wording CW = Node::get_text(clause);
        switch (Annotations::read_int(clause, dialogue_line_clause_ANNOT)) {
            case LINE_NAME_DLC:
                <dialogue-line-clause>(CW);
                wording NW = GET_RW(<dialogue-line-clause>, 1);
                if (<instance>(NW)) {
                    instance *I = <<rp>>;
                    DialogueBeats::non_unique_instance_problem(I, K_dialogue_line);
                } else {
                    dl->line_name = NW;
                }
                dialogue_line_name_count++;
                break;
        }
    }
    if (dialogue_line_name_count > 1)
        StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_LineNamedTwice),
            "this dialogue line seems to be named more than once",
            "which is not allowed. It can be anonymous, but otherwise can only have "
            "one name.");

§1.8. Using:

<dialogue-line-clause> ::=
    this is the { ... line } |                           ==> { LINE_NAME_DLC, - }
    mentioning ... |                                     ==> { MENTIONING_DLC, - }
    if ... |                                             ==> { IF_DLC, - }
    unless ... |                                         ==> { UNLESS_DLC, - }
    before ... |                                         ==> { BEFORE_DLC, - }
    after ... |                                          ==> { AFTER_DLC, - }
    to ... |                                             ==> { TO_DLC, - }
    now ... |                                            ==> { NOW_DLC, - }
    without speaking |                                   ==> { WITHOUT_SPEAKING_DLC, - }
    ...                                                  ==> { STYLE_DLC, - }

§1.9.

void DialogueLines::write_dlc(OUTPUT_STREAM, int c) {
    switch(c) {
        case LINE_NAME_DLC:             WRITE("LINE_NAME"); break;
        case IF_DLC:                    WRITE("IF"); break;
        case UNLESS_DLC:                WRITE("UNLESS"); break;
        case BEFORE_DLC:                WRITE("BEFORE"); break;
        case AFTER_DLC:                 WRITE("AFTER"); break;
        case NOW_DLC:                   WRITE("NOW"); break;
        case TO_DLC:                    WRITE("TO"); break;
        case WITHOUT_SPEAKING_DLC:      WRITE("WITHOUT_SPEAKING"); break;
        case STYLE_DLC:                 WRITE("STYLE"); break;
        default:                        WRITE("?"); break;
    }
}

§1.10. Each line produces an instance of the kind dialogue line, using the name given in its clauses if one was.

Add the line to the world model1.10 =

    if (K_dialogue_line == NULL) internal_error("DialogueKit has not created K_dialogue_line");
    wording W = dl->line_name;
    if (Wordings::empty(W)) {
        TEMPORARY_TEXT(faux_name)
        WRITE_TO(faux_name, "line-%d", dl->allocation_id + 1);
        W = Feeds::feed_text(faux_name);
        DISCARD_TEXT(faux_name)
    }
    pcalc_prop *prop = Propositions::Abstract::to_create_something(K_dialogue_line, W);
    Assert::true(prop, CERTAIN_CE);
    dl->as_instance = Instances::latest();

§2. Processing lines after pass 1. It's now a little later, and the following is called to look at each line and parse its clauses further. By this point, all instances have been created, and we can therefore parse the speaker name, "mentioning ..." clauses, "to ..." clauses, and so on. Instances of the "performance style" kind exist now, too, so we can also deal with style clauses.

void DialogueLines::decide_line_mentions(void) {
    dialogue_line *dl;
    LOOP_OVER(dl, dialogue_line) {
        current_sentence = dl->line_at;
        for (parse_node *clause = dl->line_at->down; clause; clause = clause->next) {
            if (Node::is(clause, DIALOGUE_CLAUSE_NT)) {
                wording CW = Node::get_text(clause);
                int c = Annotations::read_int(clause, dialogue_line_clause_ANNOT);
                switch (c) {
                    case MENTIONING_DLC: {
                        <dialogue-line-clause>(CW);
                        wording A = GET_RW(<dialogue-line-clause>, 1);
                        <np-articled-list>(A);
                        parse_node *AL = <<rp>>;
                        DialogueBeats::parse_topic(dl->mentioning, AL, DIALOGUE_LINE_NT);
                        break;
                    }
                    case WITHOUT_SPEAKING_DLC:
                        dl->without_speaking = TRUE;
                        break;
                    case STYLE_DLC: {
                        <dialogue-line-clause>(CW);
                        wording A = GET_RW(<dialogue-line-clause>, 1);
                        inference_subject *subj = Instances::as_subject(dl->as_instance);
                        if (<s-value-uncached>(A)) {
                            parse_node *val = <<rp>>;
                            if (Rvalues::is_CONSTANT_construction(val, CON_property)) {
                                property *prn = Rvalues::to_property(val);
                                if (Properties::is_either_or(prn)) {
                                    Assert that the line has this property2.1;
                                }
                            }
                            if ((Specifications::is_description(val)) || (Node::is(val, TEST_VALUE_NT))) {
                                Assert that the line has this property value2.2;
                            }
                        }
                        dl->how_performed = PerformanceStyles::parse_style(A);
                        break;
                    }
                }
            }
        }
        if (dl->narration == FALSE) {
            Parse the speaker description2.4;
            if (Wordings::nonempty(dl->interlocutor_text)) Parse the interlocutor description2.5;
        }
        if (dl->how_performed == NULL) dl->how_performed = PerformanceStyles::default();
        if ((dl->elaborated) && (P_elaborated)) {
            inference_subject *subj = Instances::as_subject(dl->as_instance);
            pcalc_prop *prop = AdjectivalPredicates::new_atom_on_x(
                EitherOrProperties::as_adjective(P_elaborated), FALSE);
            prop = Propositions::concatenate(
                Propositions::Abstract::prop_to_set_kind(K_dialogue_line), prop);
            Assert::true_about(prop, subj, LIKELY_CE);
        }
    }
}

§2.1. Assert that the line has this property2.1 =

    pcalc_prop *prop = AdjectivalPredicates::new_atom_on_x(
        EitherOrProperties::as_adjective(prn), FALSE);
    prop = Propositions::concatenate(
        Propositions::Abstract::prop_to_set_kind(K_dialogue_line), prop);
    Assert::true_about(prop, subj, CERTAIN_CE);
    break;

§2.2. Assert that the line has this property value2.2 =

    pcalc_prop *prop = Descriptions::to_proposition(val);
    if (prop) {
        prop = Propositions::concatenate(
            Propositions::Abstract::prop_to_set_kind(K_dialogue_line), prop);
        Assert::true_about(prop, subj, CERTAIN_CE);
        break;
    }

§2.3.

<speaker-description> ::=
    <definite-article> player |     ==> { TRUE, NULL }
    player |                        ==> { TRUE, NULL }
    <s-type-expression> |           ==> { FALSE, RP[1] }
    <s-value>                       ==> { NOT_APPLICABLE, RP[1] }

§2.4. Parse the speaker description2.4 =

    wording S = dl->speaker_text;
    if (<speaker-description>(S) == TRUE) {
        if (<<r>> == TRUE) dl->speaker_is_player = TRUE;
        else {
            parse_node *desc = <<rp>>;
            kind *K = Specifications::to_kind(desc);
            if (Kinds::Behaviour::is_object(K)) {
                dl->speaker_description = desc;
            } else {
                LOG("Speaker parsed as $T\n", desc);
                Problems::quote_source(1, current_sentence);
                Problems::quote_wording(2, S);
                Problems::quote_kind(3, K);
                StandardProblems::handmade_problem(Task::syntax_tree(),
                    _p_(PM_LineSpeakerNonObject));
                Problems::issue_problem_segment(
                    "The dialogue line %1 is apparently spoken by '%2', but that "
                    "seems to describe %3, not a person or some other object.");
                Problems::issue_problem_end();
            }
        }
    } else {
        Problems::quote_source(1, current_sentence);
        Problems::quote_wording(2, S);
        StandardProblems::handmade_problem(Task::syntax_tree(), _p_(PM_LineSpeakerUnknown));
        Problems::issue_problem_segment(
            "The dialogue line %1 is apparently spoken by '%2', but that "
            "isn't something I recognise as the name of a thing or person.");
        Problems::issue_problem_end();
    }

§2.5. Parse the interlocutor description2.5 =

    wording S = dl->interlocutor_text;
    if (<speaker-description>(S)) {
        if (<<r>> == TRUE) dl->interlocutor_is_player = TRUE;
        else {
            parse_node *desc = <<rp>>;
            kind *K = Specifications::to_kind(desc);
            if (Kinds::Behaviour::is_object(K)) {
                dl->interlocutor_description = desc;
            } else {
                LOG("interlocutor parsed as $T\n", desc);
                Problems::quote_source(1, current_sentence);
                Problems::quote_wording(2, S);
                Problems::quote_kind(3, K);
                StandardProblems::handmade_problem(Task::syntax_tree(), _p_(PM_LineToNonObject));
                Problems::issue_problem_segment(
                    "The dialogue line %1 is apparently spoken to '%2', but that "
                    "seems to describe %3, not a person or some other object.");
                Problems::issue_problem_end();
            }
        }
    } else {
        Problems::quote_source(1, current_sentence);
        Problems::quote_wording(2, S);
        StandardProblems::handmade_problem(Task::syntax_tree(), _p_(PM_LineToUnknown));
        Problems::issue_problem_segment(
            "The dialogue line %1 is apparently spoken to '%2', but that "
            "isn't something I recognise as the name of a thing or person.");
        Problems::issue_problem_end();
    }

§3. So what remains to be done? The unparsed clauses remaining are IF and UNLESS, BEFORE and AFTER, and NOW: but all of those are essentially code rather than data, and we will parse those in Dialogue Line Instances (in runtime).