ID:138229
 
Here is the speculation post on the parsing system that I kept assuring everyone I'd post about.

Basically this came about when I told Guy I was going to ask for more features that would make pseudoparsing expansion a bit less painful to Cerulea. And when Guy heard it he asked why didn't I just make a real parsing system. It was, of course, because I always assumed it'd be really hard. But the more Guy talked about it the more it seemed not-very-much-harder than the complex pseudoparsing system I labored over.

The idea goes like this: Tom would sprinkle some magic dust on the Dream Seeker to give me a kind of chat mode, but without the say " or whatever. So that people could just type whatever they wanted. No expansion. My parser would then analyze what people typed in to determine what the verb and arguments were.

And we decided that I'd write this as a flexible library that could be applied to any game. Oi! Here are some of the things we worked out:

The library would assume that all things had an adjectives[] list. Thus I could have a "really big spiked goblin-killing club".

adj[1] = "really"
adj[2] = "big"
adj[3] = "spiked"
adj[4] = "goblin-killing"
name = "club"
article = "a"

Article is a separate variable because sometimes you want it and sometimes you don't. "You pick up [article][name]." "You tap your [name]."

Then people using the system would have this stuff:

#define SRC Fullname()

obj/proc/Fullname()
var/textout
var/v
for (v in adj) textout += "[v] "
textout += name
return textout

So people could just code: usr << "You tap your [SRC]." Or whatever they defined. This stuff is the easy part. The actual parsing is a bit trickier.

First (Guy tells me) the parser could chop the input up into a list, with spaces as the separators. There is a rat in separate. It would look at parselist[1] to see what verb the usr was trying to invoke. Here's how I can think to do that:

var/v
for (v in usr.verbs)
if (findtext(v,parselist[1]) == 1) usr << "Found it!"

It would help if usr.verbs were in alphabetical order (is it?). That way the players could always be assured that if they typed 'gla emp' they'd glance rather than glare at the emperor.

But there would be special cases. Typing 'l' alone would get you the look verb, not the laugh verb, because look is so common and that's a standard MUD convention anyway. And the parser would also have to check, before breaking words into lists, if ' was the first character, so I can use that as a say verb clone.

I believe I want to check for verbs that take text strings as arguments before I break up what the user inputted. I just typed up the reasons and deleted them and I'm layZ, so you don't get to know why.

Now here comes the tricky part. After we've got the verb figured out, how do we know which valid mobs/objs/etc. in the world to look at to see what the usr meant? What are the verb's arguments supposed to be?

Here's what I came up with just while I was typing that last paragraph. There could be a global list with every verb in it. The value for each of these members would be the name of the proc that would return valid arguments. Like so.

world/New()
verbargs["belch"] = "Noargs"
verbargs["hug"] = "RoomM"
verbargs["kick"] = "RoomMO"
verbargs["teleport"] = "WorldM"

Then the parser would use the call proc to call verbargs[theverb]. Here is where things get sketchy. For RoomM(), for instance, I originally thought I'd return a list of text strings, of all the names and adjectives of mobs in the room. That's what my pseudoparsing system does. But... I'm not exactly sure what I'd do with it.

All right... let's see. Z thinks on the fly. If I ordered the list that gets returned so that all the names were first, then all the adj[1]s, then adj[2]s, etc...

I'm playing Cerulea, in a room with a wild cat and a catsup monster, and I type in "hug ca". The parser calls verbargs["hug"], and empties RoomM() into a list, argslist.

Now argslist = ("cat","monster","Zilal","catsup","wild")

We've taken "hug" out of the original parselist. Now we can do what we did to find the verb...

for (v in argslist)
if (findtext(v,parselist[1]) == 1) usr << "Found it!"

It would assume we were going for the cat rather than the monster, which is correct, and what I want. It would then call RoomMreverse("cat"), which would pick the first mob named cat and return it.

But if we had typed "hug catsup monster"... less dangerous but messier than hugging a wild cat... RoomMreverse("catsup") would return 0, as there's no mob by that name. So we'd pull out our handy namelist[] that we keep lying around parser procs.

namelist += "catsup"
for (v in argslist)
if (findtext(v,parselist[2]) == 1) usr << "Found it!"

In reality this would be done in a loop. Here, we're searching the list ("cat","monster","Zilal","catsup","wild") for parselist[2], AKA "monster". We'd add "monster" to namelist, call RoomMreverse(namelist) which would do something spiffy, and return the correct mob. The question is... is all the RoomM() and RoomMreverse() stuff more complex than it needs to be?

Anyway, once we had our argument, we'd use the call proc to call the verb (hug), passing the mob in. The verb would remain pretty much as it is in my current code, I think. I hope. If I just had to copy everything into a proc called hug() that'd be okay. I might want to do this because then from the parser I could call cat.hug(). Or, more flexibly, cat.myverbs["hug"], through the call proc.

This setup would give me some ability to attach variations of verbs to particular objects without types and inheritance. You'll recall my example of a sword that hisses when you wave it or something. I'd simply set sword.myverbs["wave"] to "hisswave1" instead of the default "wave". O Happy Call Proc!

But Z! you say. Not everything will be as simple as you imply in these 32 pages! What if the user types "put my coin under second nutshell"? What then, smartarse?

I will deal with all such situations, I answer you. And there is no need to call me a smartarse. But Tom tells me not to jump in over my head so soon. Tom is not my mother. And Cerulea is nothing if not the epitome of jumping in over my head. But I'll pretend to listen to him anyway because this post is long enough as it is.

One more thing. It occurred to me today that expansion is very useful to me in one regard, and that is when I'm choosing room tags from a list. So if Tom institutes this thing where you can just type away with no expansion, it'd be neat if the coder could give players (GMs, I mean) the ability to override it.

So I don't have to type out "goto Cerule, Shari Nar South_34" AND I don't have to look up and see what number it is and I don't have to use prompts which give me a rash.

Z
But the more Guy talked about it the more it seemed not-very-much-harder than the complex pseudoparsing system I labored over.

But then the more I thought about it later, the more I realized I was wrong. :) Nah, actually I still think it should be quite do-able!


world/New()
verbargs["belch"] = "Noargs"
verbargs["hug"] = "RoomM"
verbargs["kick"] = "RoomMO"
verbargs["teleport"] = "WorldM"

You might want even more complexity to handle more advanced cases. For example:

var/list
origList
newArgs

origList = GetArgsSet("give") //will check to see if "give" has a list yet, and give it one if it doesn't have one
newArgs = NewArgs("UsrO", "to", "RoomMO")
origList += newArgs
newArgs = NewArgs("RoomMO", "UsrO")
origList += newArgs
SetArgsSet("give", origList)
Alias("g", "give") //would create pointers to give's args set for "g", "gi", and "giv"
Alias("donate", "give")

This would let you dictate two valid types of syntax for the "give" verb. One would accept "give tomato to Tom", and one would accept "give Tom tomato". On the other hand, that may be a lot more complexity than you really need.

You know, just for the fun of it, you might want to take a look at the documentation for Inform and see if it gives you any ideas: Inform manual
Well, I went to work on the parser the other night, and after writing a couple short procs and adapting one of the procs my pseudoparsing system uses, I made a working parser in no time at all. You can test it at polaris/zilal/cerulea. But right now you can only apply one adjective before the target, as it's adapted off pseudoparsing.

And now, a look at the tricky part.

Now here comes the tricky part. After we've got the verb figured out, how do we know which valid mobs/objs/etc. in the world to look at to see what the usr meant? What are the verb's arguments supposed to be?

In my previous post, I had it lined up so that there'd be a global list, verbargs[], that would have the name of a proc that returned a list of possible arguments associated with each verb. Since you might want "attack" to only work on mobs in view(1), but "teleport" to work on all mobs in the world.

And the parser itself would call these valid-argument-returning procs, match the abbreviated version the usr had typed in to the valid arguments, and send the most likely match out to another proc, which would return the actual mob/obj/etc. desired, which would be passed into the verb.

This is the part I felt could surely be done better.

Now, all my verbs already had some of this functionality built in, because I had the pseudoparsing system.

mob/verb/kick(msg as null|anything in Adjective(Visible(view(0))), M as null|mob|obj in Whichone(msg,Visible(view(0))))
if (msg) M = Cardinal(M,msg,Visible(view(0)))

This allows the usr to expand kick to "kick my sword", "kick sword", "kick blue sword", "kick first sword", or just "kick", et cetera. What's going on here? When the usr tries to expand after "kick", she receives a list of the adjectives (and possible cardinal numbers) of visible mobs and objs in view(0). If what she expands to is the name of a mob/obj, she can hit enter and use the verb. Otherwise she can expand on to the M argument. Expansion here will return a list of possible names based on what msg was and on what's visible in view(0).

But the important part is on the next line when the Cardinal() proc is called. It may be called with (/mob/player/Dan,"lovable",Visible(view(0)), or perhaps just (null,"Dan",Visible(view(0)). It's Cardinal()'s job to return the exact mob or obj that the player wants.

It's called Cardinal() because it was originally written to sort out which mob "first Dan" and "fifth Dan" were, and such.

Now, it originally hadn't occurred to me to do this sort of thing for my REAL parsing library, because I hadn't thought the users wanted to go into all their verbs and put something like "var/mob/M = Target(inlist,MobsIn(oview()))" in each of them. But in the end that's not a whole lot more work than doing:

world/New()
verbargs["kick"] = "oview()"

and it's certainly a lot less work for the parser, which doesn't have to do any of that RoomM()/RoomMreverse() crap. Instead, whatever the parser believes to be the target the usr wants (in abbreviated version) would be passed into the verb, and the Target() proc (a more powerful version of Cardinal()) would find the correct target in the range specified.

I say "more powerful" because, we will recall, Cardinal() can only handle one adjective/cardinal number per target. Target(), however, would be able to handle an essentially infinite number of adjectives, as well as a possible cardinal number, or modifier such as "my", or articles, or even prepositional phrases such as "from my cloak". So "get second wand from my cloak" would be parsed beautifully. Even, dare I say, "get second w from my cl".

I don't know exactly how I'll deal with prepositions yet, but I assume that the direction I'm heading will be adaptable, and won't preclude the parsing of more complex statements like "put wand in box on table". I'm not really sure yet how to code any of the stuff I talked about today. But hey, if other games can do it, so can I.

But there is another issue...

It would look at parselist[1] to see what verb the usr was trying to invoke. Here's how I can think to do that:

var/v
for (v in usr.verbs)
if (findtext(v,parselist[1]) == 1) usr << "Found it!"

It would help if usr.verbs were in alphabetical order (is it?). That way the players could always be assured that if they typed 'gla emp' they'd glance rather than glare at the emperor.

As I noticed today, this approach is incomplete because it doesn't take into account area/verbs and obj/verbs and turf/verbs. And I'm not completely sure how to do that. Let me think.

I guess I could search for mob verbs first, as I do above, then verbs connected with usr.loc, then with objs in usr.loc. This would work, sort of, for MUDs, but not so much for graphical games, where an obj 5 tiles away could be set src = view(5). Is there any good way to get a list of all verbs accessible to the usr?

Z

In response to Zilal
As I noticed today, this approach is incomplete because it doesn't take into account area/verbs and obj/verbs and turf/verbs. And I'm not completely sure how to do that. Let me think.

Well, how about this: you know the first word in a sentence is going to be the command. So, you just come up with something like Cardinal() that will look at all the commands available across the usr, the area, the local mobs, and the turf. But, of course, that means that you don't end up using BYOND verbs. Instead, you have one single solitary verb, like "execute" or "do" or even "parse", which is used in chat mode and always transparent to the user. Then, the user's actual command is just the first word of many in the text string.

It's an idea, anyway!
In response to Guy T.
On 1/16/01 5:50 pm Guy T. wrote:
As I noticed today, this approach is incomplete because it doesn't take into account area/verbs and obj/verbs and turf/verbs. And I'm not completely sure how to do that. Let me think.

But, of course, that means that you don't end up using BYOND verbs. Instead, you have one single solitary verb, like "execute" or "do" or even "parse", which is used in chat mode and always transparent to the user. Then, the user's actual command is just the first word of many in the text string.

I have this part done already! I was hoping Tom would give me a format in which I could supply one verb, like "parse", and everything people entered, it would be like they were using that word. Kind of a hidden, forced chat mode.

Well, how about this: you know the first word in a sentence is going to be the command. So, you just come up with something like Cardinal() that will look at all the commands available across the usr, the area, the local mobs, and the turf.

But how? This is what I'm asking. How do I know all the commands available? What's the best way?

Z