Dealing with punctuation in a [text]-based action

I’m trying to write a new action that takes text; let’s say it’s a “draw” action. I’ve got it mostly coded up, but I’ve run into a hitch: if the player types “Draw cat. Draw dog,” Inform assumes they want to draw a “cat. Draw dog.” which of course is not handled properly. My first inclination was to tell Inform to understand “draw [text].” (with the period) as drawing, but that throws a complaint that punctuation doesn’t belong in understand lines. Looking at Jim Aikin’s Notepad extension, it seems that he handles this problem by only having it understand drawing “in [something],” but I’d really like to handle the naked/second-noun-less case as well, since in my game there will only be one thing to draw on.

Of course, I’ve already got some elaborate regex-y stuff in how I handle the text, so I can strip out the period myself, but I’m unclear on how to feed the second half back into the game as a new command, or when in the turn cycle that should be done.

If you’re going to strip the period with a regex, you have to do it in “reading a command”.

The topic token in verb grammar has eaten periods, going back to I6. Once, I might have known why…

Hm? I think I’m misunderstanding your answer.

When I receive “the topic understood” in my “carry out drawing,” it still includes periods, and I am successfully able to break a command into the parts I want: if the player types “draw dog. draw bird” I can get “dog” as my thing-to-be-drawn and “draw bird” as a second command. (This breaks down if a player tries to “draw Mr. Bill,” but I am reasonably convinced that I don’t care about that at the moment.) What I don’t quite understand is how/when I can tell Inform to then take “draw bird” as a new command.

At that point, you cannot.

I think the right (“right”) way to work around this is to change the parsing of the topic token to exclude periods. This is in the ParseToken__() function, and might be as simple (“simple)” as changing the (o == -1 || …) expression to (o == -1 || o == THEN1__WD || …). But I don’t know what the side effects are. Has anyone gone through this process before?

Ah, okay. I must have been thinking of “stored actions” which are, of course, already actions instead of commands.

That does sound scary. Perhaps an alternate question is, does anyone here think that it’s likely that players will actually type multiple commands separated by periods? I never do it myself, but it seems like a silly thing to leave unsupported.

Nice fix! From what I see, there are no side effects.

Well, I’ll give it a shot. Unfortunately, I have no experience with I6 inclusions in I7, so I am not entirely sure what you mean I should be typing. I can find the original ParseToken_() function in the source, and could just modify that, but is there a way to do it from within my project’s code?

You do this:

Include (-
    token_filter = 0;
    parser_inflection = name;

    switch (given_ttype) {
        switch (given_tdata) {
            l = TryNumber(wn);
            special_word = NextWord();
            #Ifdef DEBUG;
            if (l ~= -1000)
                if (parser_trace >= 3) print "  [Read special as the number ", l, "]^";
            #Endif; ! DEBUG
            if (l == -1000) {
                #Ifdef DEBUG;
                if (parser_trace >= 3) print "  [Read special word at word number ", wn, "]^";
                #Endif; ! DEBUG
                l = special_word;
            parsed_number = l;
            return GPR_NUMBER;

            if (l == -1000) {
                etype = NUMBER_PE;
                return GPR_FAIL;
            #Ifdef DEBUG;
            if (parser_trace>=3) print "  [Read number as ", l, "]^";
            #Endif; ! DEBUG
            parsed_number = l;
            return GPR_NUMBER;

            if (action_to_be == ##Answer or ##Ask or ##AskFor or ##Tell)
                scope_reason = TALKING_REASON;

            consult_from = wn;
            if ((line_ttype-->(token_n+1) ~= PREPOSITION_TT) &&
               (line_token-->(token_n+1) ~= ENDIT_TOKEN))
            do o = NextWordStopped();
            until (o == -1 || o == THEN1__WD || PrepositionChain(o, token_n+1) ~= -1);
            consult_words = wn-consult_from;
            if (consult_words == 0) return GPR_FAIL;
            if (action_to_be == ##Ask or ##Answer or ##Tell) {
                o = wn; wn = consult_from; parsed_number = NextWord();
                wn = o; return 1;
            if (o==-1 && (line_ttype-->(token_n+1) == PREPOSITION_TT))
                return GPR_FAIL;    ! don't infer if required preposition is absent
            return GPR_PREPOSITION;

        ! Is it an unnecessary alternative preposition, when a previous choice
        ! has already been matched?
        if ((token->0) & $10) return GPR_PREPOSITION;

        ! If we've run out of the player's input, but still have parameters to
        ! specify, we go into "infer" mode, remembering where we are and the
        ! preposition we are inferring...

        if (wn > num_words) {
            if (inferfrom==0 && parameters<params_wanted) {
                inferfrom = pcount; inferword = token;
                pattern-->pcount = REPARSE_CODE + VM_DictionaryAddressToNumber(given_tdata);

            ! If we are not inferring, then the line is wrong...

            if (inferfrom == 0) return -1;

            ! If not, then the line is right but we mark in the preposition...

            pattern-->pcount = REPARSE_CODE + VM_DictionaryAddressToNumber(given_tdata);
            return GPR_PREPOSITION;

        o = NextWord();

        pattern-->pcount = REPARSE_CODE + VM_DictionaryAddressToNumber(o);

        ! Whereas, if the player has typed something here, see if it is the
        ! required preposition... if it's wrong, the line must be wrong,
        ! but if it's right, the token is passed (jump to finish this token).

        if (o == given_tdata) return GPR_PREPOSITION;
        if (PrepositionChain(o, token_n) ~= -1) return GPR_PREPOSITION;
        return -1;

        l = indirect(given_tdata);
        #Ifdef DEBUG;
        if (parser_trace >= 3) print "  [Outside parsing routine returned ", l, "]^";
        #Endif; ! DEBUG
        return l;

        scope_token = given_tdata;
        scope_stage = 1;
        #Ifdef DEBUG;
        if (parser_trace >= 3) print "  [Scope routine called at stage 1]^";
        #Endif; ! DEBUG
        l = indirect(scope_token);
        #Ifdef DEBUG;
        if (parser_trace >= 3) print "  [Scope routine returned multiple-flag of ", l, "]^";
        #Endif; ! DEBUG
        if (l == 1) given_tdata = MULTI_TOKEN; else given_tdata = NOUN_TOKEN;

        token_filter = 1 + given_tdata;
        given_tdata = NOUN_TOKEN;

        token_filter = given_tdata;
        given_tdata = NOUN_TOKEN;

    } ! end of switch(given_ttype)

    token = given_tdata;

-) instead of "Parse Token Letter A" in "Parser.i6t".

However, I am not convinced this is a good idea. My test code has the line:

Instead of asking someone about, say “Instead rule: ‘[the topic understood]’.”;

This generates a run-time error if you type a command with a period. The snippet values are correct, but the snippet-printer seems to think we’re into the next command, so it refuses to print words from the current command. (WordCount() is the number of words in the second command, rather than the number of words in the first command.)

This being the case, there are probably other problems as well. I’m not recommending this.

On further poking at the Notepad extension, it seems that it’s not actually much better off:

(using the basic “Memorabilia” example code.)

So I think I’ll just have to be content with assuming that dual commands won’t likely come up in organic gameplay. (Or rather, I’ll do my regex for detecting them, and give a response about how they’ll need to re-type the second command.)