Use of 'and' in a verb's grammar

I would like a grammar rule which matches ‘verb noun and noun’.

It’s in the French language actually, but I can give an example in english:

Verb "combine"
* noun -> Tie
* noun 'with'/'and' noun -> Tie;

Of course, this doesn’t work, because two nouns separated by ‘and’ will be interpreted as a multi token.

I found a very hacky way to do something which works, but I won’t detail it here for now unless asked (it involves making AND1__WD a Global instead of a Constant and temporarily setting it to a different value before calling ParseNoun in a routine). I’m hoping that there is a cleaner way.

Ideally, the grammar could, instead of fighting against the handling of multiple objects, embrace it:

[ TieTwoSub;
    ! Check exactly two nouns were provided
    ! and call `Tie` with both.
    ! Else politely refuse.
];

Verb "combine"
* noun -> Tie
* noun 'with' noun -> Tie
* multi -> TieTwo;

I attempted to do so, but TieTwoSub would get called once for every object rather than once with both objects.

That’s where I am and I figured it’s the right time to ask for advice here :smiley:

I believe the issue here is that ‘verb noun and noun’ is treated as a multi action and is auto converted to ‘verb noun’ then ‘verb noun’. The way I’ve dealt with this in Inform 7 is to create an action matching the grammar ‘verb nouns’ (plural) then add a rule to extract the first and last items from the multiple object list and then redirect that to the original action. The Inform 6 equivalent of this is most likely the cleanest way of doing it.

So, multiple_object-->0 will give you the size of the multiple object list (needs to be exactly 2), multiple_object-->1 will give you the first element and multiple_object-->(multiple_object-->0) will give you the last element.

Hope this helps.

I’m not aware of a nouns or equivalent token in Inform 6, unfortunately. If something like this exists, I haven’t seen it in DM4.

Looks like it’s multi in Inform 6.

This is exactly what I tried :sweat_smile:.

But I couldn’t find a way to intercept the multiple object before the action is split into multiple actions.

You don’t. However, on the first run, after pulling out the first and last elements, you set the multiple object list to zero elements (empty it). That way the action only runs once.

1 Like

I tried this implementation:

[ TieMultiSub nb_objects;
    nb_objects = multiple_object-->0;
    ! empty the multiple object
    multiple_object-->0 = 0;
    if (nb_objects == 2) <<Tie multiple_object-->1 multiple_object-->2>>;
    else if (nb_objects > 2) L__M(##TieMulti, 1, 0);
    else return false;
];

But despite I set multiple_object to 0, TieMultiSub is called for every object. The outcome changes, but I still get an output along the lines of:

> combine object1 and object2
object1: [answer to Tie object1 object2]
object2: [empty]

If you’re already up for some parser hacking, you can find and modify this block…

    #Ifdef DEBUG;
    if (parser_trace >= 3) print "  [Read connective '", (address) o, "']^";
    #Endif; ! DEBUG

	! BEGIN ADDITION
	if (PrepositionChain(o, pcount) > 0) {
		if (~~token_allows_multiple) jump ConnectivePass;
	}
	! END ADDITION

in parserm.h. You’ll also need to add this line:

.ConnectivePass;	! ADDED

after the end of the if block in which you inserted the new bypass block.

After that, the original syntax that you tried should work (tested with StdLib 6/11).

1 Like

I’d rather avoid modifying parserm.h, because that’s not something I could embed in the library translations or in an optional extension distributed with it.

I gave it a go anyway. I hope that I followed your instructions precisely. It produces the following diff:

diff --git a/parserm.h b/parserm.h
index 0b41728..31a79cc 100644
--- a/parserm.h
+++ b/parserm.h
@@ -2617,6 +2617,12 @@ Constant UNLIT_BIT  =  32;
         if (parser_trace >= 3) print "  [Read connective '", (address) o, "']^";
         #Endif; ! DEBUG
 
+        ! BEGIN ADDITION
+        if (PrepositionChain(o, pcount) > 0) {
+            if (~~token_allows_multiple) jump ConnectivePass;
+        }
+        ! END ADDITION
+
         if (~~token_allows_multiple) {
             if (multiflag) jump PassToken; ! give UPTO_PE error
             etype=MULTI_PE;
@@ -2637,6 +2643,8 @@ Constant UNLIT_BIT  =  32;
         jump ObjectList;                          ! And back around
     }
 
+    .ConnectivePass;   ! ADDED
+
     wn--;   ! Word marker back to first not-understood word
 
     !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

However, I don’t see a difference in my tests. Did I place the .ConnectivePass label at the right place?

Oh, sorry, I didn’t pay attention to you saying “the original syntax that you tried”. It does work indeed.

Ah. I thought that you were already doing some parser hacking because you said you had modified the definition of AND1__WD, but I understand that in your case you are changing that anyway because you’re supplying a new language file. (I am curious about the details of your current workaround. Is ParseNoun() used routinely in the French library?)

The method that climbingstars recommended from the world of I7 is unfortunately not easily translatable to StdLib 6/11. There are no hooks for interacting with the loop that processes multiple actions as there are in I7. As you have already discovered, once the parser logic has committed to the branch for processing a multiple object list, it is not responsive to attempts to manipulate the length of the list during loop processing (because it uses cached data about the size of the multiple object list).

If you don’t want to ship a modified parserm.h file, you can try to use the Replace directive to change only the affected routine. I’m not sure that this is something that can be handled with changes only to the language file, though.

I haven’t toyed around with direct I6 enough to know the exact methods here. I kinda assumed that I6 loop for multiple actions is equivalent to a for loop running through the multiple object list.

Generally, you can “manually” end a for loop early using a break command or adjusting the loop variable to fail the loop conditional.

Yes, it would certainly be possible to modify the loop in question, but it’s part of core parser code, so it would presumably be out-of-bounds for stormi’s purposes. This is what the relevant code looks like in StdLib 6/11:

			...
            j = multiple_object-->0;
			...
            for (k=1 : k<=j : k++) {
				...
				l = multiple_object-->k;
				...
                print (name) l, ": ";
                if (inp1 == 0) {
                    inp1 = l; self.begin_action(action, l, second, 0); inp1 = 0;
                }
                else {
                    inp2 = l; self.begin_action(action, noun, l, 0); inp2 = 0;
                }
            }
			...

As you can see, the equivalent of I7’s announce items from multiple object lists rule is just a hardcoded print statement here, and it is placed before the attempt to execute the action for the current object. Without the rulebook hooks of I7, it seemed like it would be more painful to try to convert from multiple action to single action than to just get it to parse a single action in the first place.

1 Like

Very true! Although I don’t know if that is possible without replacing chunks of parser code since the mechanism is pretty much hard coded in there (I could be wrong though).

My current workaround I only implemented in one game, then opened a discussion within the French community to see if changing constant AND1__WD would be acceptable in the lib translation, and whether they had better ideas.

Here it is, simplified (it used to also check that at least one of the two objects is held), translated, and untested after these changes.

[Noun1AndNoun2 noun1 noun2 saved_wn;
    AND1__WD = '.N/A';

    noun1 = ParseToken(NOUN_TOKEN);
    if (noun1 == GPR_FAIL) jump fail;

    if (~~(NextWord() == 'and')) jump fail;

    saved_wn = wn;
    noun2 = ParseToken(NOUN_TOKEN);
    if (noun2 == GPR_FAIL) jump fail;

    ! We can only return one noun, so rewind before the second one
    wn = saved_wn;
    AND1__WD = 'and';
    return noun1;

    .fail;
    AND1__WD = 'and';
    return GPR_FAIL;
];

Verb 'combine'
    * noun -> Tie
    ! Noun1AndNoun2 actually only returns one noun,
    ! despite it parses both, so we need to repeat noun in the grammar line
    * Noun1AndNoun2 noun -> Tie;

Full version at: Utilisation de 'et' dans la grammaire d'un verbe (#69) · Issues · Fiction-interactive.fr / inform6-fr · GitLab

Yes. It’s one of the routines we already redefine:

I wouldn’t dare replacing the whole big __ParseToken routine just for this need :sweat_smile:

You might try to write a GPR that will accept AND*__WD values in addition to noun words. Then you could use a grammar line like:

* NounAnd noun -> Tie
1 Like

Do you know if there’s a way to tell ParseToken: “parse exactly one noun token, ignoring ANDx__WD in the process”?

IIRC the very reason I had to modify AND1__WD is ParseToken would be too smart.

I don’t think there’s any way to do something like that. However, you can use ParseToken() within a GPR, as you are doing already.

You seem to be familiar with GPRs as a concept, since the Noun1AndNoun2() routine is being used as one. What I’m suggesting is that you modify your GPR to match a noun and then a word from the AND*__WD group. Leave wn pointing to the word after the AND*_WD, and the next token will be a noun token that the parser will automatically interpret as seeking second. The command should be parsed correctly as a single command with two nouns.

1 Like

So basically what I’m doing already, but without parsing the last noun unnecessarily? It looks like a useful simplification indeed.

I’m trying to find a way to parse just the first noun + AND1_WD, but am currently failing because ParseToken insists on parsing a multiple object (unless I keep this ugly AND1__WD = '.N/A' in the GPR).

(actually there’s a reason why I was parsing both nouns in the GPR, because in the version of the routine I actually used, I’m also checking that at least one of the two objects is held)