Page 1 of 2
Verb-Noun parser?
Posted: Wed Apr 02, 2014 9:51 pm
by Zach
Apologies as I think I've kind of discussed this before, but the search function is proving to be elusive on results and I can't find what I thought were examples made by others to me in the past
Well, I'm afraid I really have hit a wall on this one.. I've had a couple ideas for how I wanted to try implementing a parser, although even the ones I end up trying to flesh out I end up giving up, simply because I can't visualize the kind of logic I need to use at each step..
At least not without it becoming a terribly gruesome chain of IF/ENDIF blocks, nested fathoms deep, etc..
I've tried to do some googling and research, but frankly just cannot come up with examples I can follow; or if I do understand examples without using real code, I can't seem to extrapolate what I need to do based on those examples..
I often wonder if it wouldn't be best to use some kind of limited command set with multiple choice prompting, or some kind of other menu system, perhaps housed in the GUI to the right of the text window...
But Verb-Noun is about as basic as it gets, and I want to try to at least make something on this level.. One thing I saw mentioned was, in addition to using a Verb dictionary and Noun dictionary, there was also having a 3rd dictionary of sorts -- A Map with all the possible combinations I guess..
I'm kind of floundering, with perhaps too many ideas and not a clear focus on how to proceed.
Could anyone share some very simple mockups using a couple verbs and nouns that can form a coherent command, and then how that would tie into the command to execute, and how you pass those words on to it?
Re: Verb-Noun parser?
Posted: Wed Apr 02, 2014 10:26 pm
by Tenaja
For some simple lookups, I have even used a Map instead of if string$ = "text".
You can also implement FindMapElement(NounMap()) etc., and if you want, that NounMap() can contain a list of allowable verbs. IIRC, you have to wrap the nested map into a structure--see the code ts-soft shares at the bottom of this thread:
http://purebasic.fr/english/viewtopic.p ... ap#p386054
You just need to sketch out what data you want, where you want it, and how to access it. Then you can select the data structures required.
Re: Verb-Noun parser?
Posted: Wed Apr 02, 2014 11:05 pm
by Zach
I think that's part of the problem... I'm not even sure "how" to sketch it out, and in a way so as I can read it and follow through with clear, logical steps of what to manipulate and compare ,and in what order..
I guess I'm having the equivalent of a not understanding how "2+2=4" actually works moment, on a step by step basis.
Re: Verb-Noun parser?
Posted: Wed Apr 02, 2014 11:19 pm
by Demivec
Here is a very simple example over at Rosetta Code.
Hint: Take and equip a sledge. Get some gold to ask for hints (from the program) and move a ladder into a room to go up if necessary.
Re: Verb-Noun parser?
Posted: Thu Apr 03, 2014 11:50 am
by spikey
This is how I might handle verbs. Notice that I provide several synonyms for 'Go' - look at the data section. I can add synonyms for other verbs in exactly the same way. Key to this is determining what types of actions I want to be able to perform first - although its still pretty easy to add new verbs later on if I've missed something. In practice you'd need to be a bit smarter tokenising the verbs in order to support the intercardinal directions fully - but not much more. You know there is a possibility that a cardinal verb might be followed by a second token which turns it into an intercardinal verb - so you just need to check that this is or isn't true before executing the movement.
I'd probably handle nouns in a very similar way - assign them an integer value and use a map to cross reference.
Now I've got tokens kicking around to describe the entered action I'd expand this with event procedures for each of the verb types to handle the specific jobs - movement, add to inventory, remove from inventory, object interactions...
Code: Select all
EnableExplicit
Enumeration
#cmdUnknown
#cmdGo
#cmdNorth
#cmdSouth
#cmdEast
#cmdWest
#cmdLook
#cmdExamine
#cmdGet
#cmdDrop
#cmdUse
#cmdInventory
#cmdQuit
EndEnumeration
Define.I intQuit, intVerb
Define.S strInput, strVerb, strNoun
NewMap mapVerbs.I()
If OpenConsole() = 0
End
EndIf
; Load the verb map.
Repeat
Read.S strVerb
Read.I intVerb
mapVerbs(LCase(strVerb)) = intVerb
Until strVerb = "<end>"
; Remove the end marker.
DeleteMapElement(mapVerbs(), "<end>")
; Interaction loop.
Repeat
PrintN("Now what?")
strInput = Input()
strVerb = LCase(StringField(strInput, 1, " "))
strNoun = LCase(StringField(strInput, 2, " "))
intVerb = mapVerbs(strVerb)
; Handle 'Go' and synonyms - turn the 'noun' into a 'verb'.
If intVerb = #cmdGo
intVerb = mapVerbs(strNoun)
EndIf
; Handle verbs.
Select intVerb
Case #cmdNorth, #cmdSouth, #cmdEast, #cmdWest
PrintN("A movement command.")
Case #cmdLook
PrintN("A location description command.")
Case #cmdExamine
PrintN("An object examination command.")
Case #cmdGet
PrintN("An object get command.")
Case #cmdDrop
PrintN("An object drop command.")
Case #cmdUse
PrintN("An object use command.")
Case #cmdInventory
PrintN("An inventory command.")
Case #cmdQuit
PrintN("A quit command.")
intQuit = #True
Default
PrintN("An unrecognised command.")
EndSelect
Until intQuit = #True
PrintN("Press return to exit")
Input()
DataSection
Data.S "Go" : Data.I #cmdGo
Data.S "Walk" : Data.I #cmdGo
Data.S "Run" : Data.I #cmdGo
Data.S "North" : Data.I #cmdNorth
Data.S "South" : Data.I #cmdSouth
Data.S "East" :Data.I #cmdEast
Data.S "West" : Data.I #cmdWest
Data.S "Look" : Data.I #cmdLook
Data.S "Examine" : Data.I #cmdExamine
Data.S "Get" : Data.I #cmdGet
Data.S "Drop" : Data.I #cmdDrop
Data.S "Use" : Data.I #cmdUse
Data.S "Inventory" : Data.I #cmdInventory
Data.S "Quit" : Data.I #cmdQuit
Data.S "<end>" : Data.I #cmdUnknown
EndDataSection
Re: Verb-Noun parser?
Posted: Fri Apr 04, 2014 1:51 pm
by spikey
And this is how I might handle object nouns. This isn't fully robust as I haven't implemented player inventory. It will let you do things like interact with objects that you haven't picked up etc but you can play it and I hope it should give you some ideas.
Code: Select all
EnableExplicit
Declare LoadVerbs()
Declare LoadObjects()
Declare DefaultLook()
Declare DefaultExamine()
Declare DefaultGet()
Declare DefaultDrop()
Declare DefaultThrow()
Declare DefaultUse()
Declare DangerousThrow()
Declare AmuletDrop()
Declare BrickDrop()
Prototype.I ExaminePrototype()
Prototype.I GetPrototype()
Prototype.I DropPrototype()
Prototype.I ThrowPrototype(At.I = 0)
Prototype.I UsePrototype(On.I = 0)
Structure VERBTABLE
Examine.ExaminePrototype
Get.GetPrototype
Drop.DropPrototype
Throw.ThrowPrototype
Use.UsePrototype
EndStructure
Structure PLAYER
Location.S
Carried.I
Lamp.I
EndStructure
Structure OBJECT
Name.S
Description.S
Methods.VERBTABLE
EndStructure
Enumeration
#cmdUnknown
#cmdGo
#cmdNorth
#cmdSouth
#cmdEast
#cmdWest
#cmdLook
#cmdExamine
#cmdGet
#cmdDrop
#cmdThrow
#cmdUse
#cmdInventory
#cmdQuit
EndEnumeration
Define.I intNoun, intQuit, intVerb
Define.S strInput, strVerb, strNoun
Define objObject.OBJECT
Global NewMap mapVerbs.I()
Global NewMap mapObjects.I()
Global NewList lstObjects.OBJECT()
Procedure LoadVerbs()
; Load the verb map.
Protected.I intVerb
Protected.S strVerb, strNoun
Restore Verbs
Repeat
Read.S strVerb
Read.I intVerb
mapVerbs(LCase(strVerb)) = intVerb
Until strVerb = "<end>"
; Remove the end marker.
DeleteMapElement(mapVerbs(), "<end>")
EndProcedure
Procedure LoadObjects()
; Load the object list and map.
Protected.I intNoun, intExamine, intGet, intDrop, intThrow, intUse
Protected.S strNoun, strDesc
Restore Objects
; Need an empty element at the top of the list to avoid map not found bugs.
AddElement(lstObjects())
Repeat
Read.S strNoun
Read.S strDesc
Read.I intExamine
Read.I intGet
Read.I intDrop
Read.I intThrow
Read.I intUse
; Add the object to the list.
AddElement(lstObjects())
lstObjects()\Name = strNoun
lstObjects()\Description = strDesc
; Index to the map.
mapObjects(LCase(strNoun)) = ListIndex(lstObjects())
; Set up specified methods or default if unspecified.
; Examine
If intExamine > 0
lstObjects()\Methods\Examine = intExamine
Else
lstObjects()\Methods\Examine = @DefaultExamine()
EndIf
; Get
If intGet > 0
lstObjects()\Methods\Get = intGet
Else
lstObjects()\Methods\Get= @DefaultGet()
EndIf
; Drop
If intDrop > 0
lstObjects()\Methods\Drop = intDrop
Else
lstObjects()\Methods\Drop = @DefaultDrop()
EndIf
; Throw
If intThrow > 0
lstObjects()\Methods\Throw = intThrow
Else
lstObjects()\Methods\Throw= @DefaultThrow()
EndIf
; Use
If intUse > 0
lstObjects()\Methods\Use = intUse
Else
lstObjects()\Methods\Use= @DefaultUse()
EndIf
Until strNoun = "<end>"
; Remove the end marker.
DeleteMapElement(mapVerbs(), "<end>")
DeleteElement(lstObjects())
EndProcedure
If OpenConsole() = 0
End
EndIf
LoadVerbs()
LoadObjects()
; Interaction loop.
Repeat
PrintN("Now what?")
strInput = Input()
PrintN("")
strVerb = LCase(StringField(strInput, 1, " "))
intVerb = mapVerbs(strVerb)
strNoun = LCase(StringField(strInput, 2, " "))
; Handle 'Go' and synonyms - turn the 'noun' into a 'verb'.
If intVerb = #cmdGo
intVerb = mapVerbs(strNoun)
EndIf
; Handle verbs.
Select intVerb
Case #cmdNorth, #cmdSouth, #cmdEast, #cmdWest
PrintN("A movement command.")
Case #cmdLook
DefaultLook()
Case #cmdExamine
intNoun = mapObjects(strNoun)
SelectElement(lstObjects(), intNoun)
lstObjects()\Methods\Examine()
Case #cmdGet
intNoun = mapObjects(strNoun)
If intNoun > 0
SelectElement(lstObjects(), intNoun)
lstObjects()\Methods\Get()
Else
PrintN("You can't see one one.")
EndIf
Case #cmdDrop
intNoun = mapObjects(strNoun)
If intNoun > 0
SelectElement(lstObjects(), intNoun)
lstObjects()\Methods\Drop()
Else
PrintN("You don't have one.")
EndIf
Case #cmdThrow
intNoun = mapObjects(strNoun)
If intNoun > 0
SelectElement(lstObjects(), intNoun)
lstObjects()\Methods\Throw()
Else
PrintN("You don't have one.")
EndIf
Case #cmdUse
intNoun = mapObjects(strNoun)
If intNoun > 0
SelectElement(lstObjects(), intNoun)
lstObjects()\Methods\Use()
Else
PrintN("You don't have one.")
EndIf
Case #cmdInventory
PrintN("An inventory command.")
Case #cmdQuit
PrintN("A quit command.")
intQuit = #True
Default
PrintN("An unrecognised command.")
EndSelect
PrintN("")
Until intQuit = #True
PrintN("Press return to exit")
Input()
DataSection
Verbs:
Data.S "Go" : Data.I #cmdGo
Data.S "Walk" : Data.I #cmdGo
Data.S "Run" : Data.I #cmdGo
Data.S "North" : Data.I #cmdNorth
Data.S "South" : Data.I #cmdSouth
Data.S "East" :Data.I #cmdEast
Data.S "West" : Data.I #cmdWest
Data.S "Look" : Data.I #cmdLook
Data.S "Examine" : Data.I #cmdExamine
Data.S "Get" : Data.I #cmdGet
Data.S "Drop" : Data.I #cmdDrop
Data.S "Throw" : Data.I #cmdThrow
Data.S "Use" : Data.I #cmdUse
Data.S "Inventory" : Data.I #cmdInventory
Data.S "Quit" : Data.I #cmdQuit
Data.S "<end>" : Data.I #cmdUnknown
Objects:
; Name, Description, Examine, Get, Drop, Throw, Use
Data.S "Amulet" : Data.S "a metal roundel with sinister looking engravings on both faces" : Data.I 0, 0, @AmuletDrop(), @AmuletDrop(), 0
Data.S "Banana" : Data.S "a ripe, yellow fruit" : Data.I 0, 0, 0, 0, 0
Data.S "Brick" : Data.S "an otherwise undistinguished, red, rectangular block" : Data.I 0, 0, @BrickDrop(), @DangerousThrow(), 0
Data.S "Screwdriver" : Data.S "an old, slightly rusty, pointy tool" : Data.I 0, 0, 0, @DangerousThrow(), 0
Data.S "<end>": Data.S "" : Data.I 0, 0, 0, 0, 0
EndDataSection
Procedure DefaultLook()
Protected.S strMsg
PrintN("You can see:-")
ForEach lstObjects()
If lstObjects()\Name <> #NULL$
strMsg + lstObjects()\Name + ", "
EndIf
Next lstObjects()
strMsg = Left(strMsg, Len(strMsg) - 2) + "."
PrintN(strMsg)
EndProcedure
Procedure DefaultExamine()
Protected.S strMsg
strMsg = "It is " + lstObjects()\Description + "."
PrintN(strMsg)
EndProcedure
Procedure DefaultGet()
Protected.S strMsg
strMsg = "You pick up the " + lstObjects()\Name + "."
PrintN(strMsg)
EndProcedure
Procedure DefaultDrop()
Protected.S strMsg
strMsg = "You drop the " + lstObjects()\Name + "."
PrintN(strMsg)
EndProcedure
Procedure DefaultThrow()
Protected.S strMsg
strMsg = "You throw the " + lstObjects()\Name + "."
PrintN(strMsg)
EndProcedure
Procedure DefaultUse()
Protected.S strMsg
strMsg = "You use the " + lstObjects()\Name + "."
PrintN(strMsg)
EndProcedure
Procedure DangerousThrow()
PrintN("Perhaps not a good idea, it might be dangerous.")
EndProcedure
Procedure AmuletDrop()
PrintN("You are unable to remove the amulet. Perhaps it has been cursed.")
EndProcedure
Procedure BrickDrop()
PrintN("You drop the brick on your toe. It hurts. Perhaps you will be more careful next time.")
lstObjects()\Methods\Drop = @DefaultDrop()
EndProcedure
Re: Verb-Noun parser?
Posted: Thu Apr 24, 2014 2:36 am
by Zach
So, in the interest of "making progress" I have decided that I won't be doing a few things. Perhaps in the future when I am more skilled I will know how to accomplish them with my own ideas. The biggest point I've abandoned is giving the users the ability to create new command sets without requiring access to source code for compilation..
I imagine I could (and probably have to) do this with a scripting language, but I am not ready to brook that issue yet.. It also calls into question on how I intend to call commands once the parser returns a valid token stream that matches something..
I'm just keeping things simple, and will be using IF chains to run through the data. I'll have to include a separate chain at the end of the parser to loop through all the possible command procedures (check against Token$(0) )so they can be called, when a match is found.. but hopefully those will be simple 2 - 3 line definitions with data passed in. I'm hoping I won't have to custom-tailor a lot of logic checks to commands..
Something I want to keep that I set up earlier as part of my Dictionary Structure were aliases. A command word can have multiple aliases so that data field is implemented as a list.
I'm trying to keep the initial check on a single line, but I can't figure out how to do it. I'm not even sure its possible?
This was my initial attempt until my (insert Picard Facepalm meme) moment.
Code: Select all
If FindMapElement(Dictionary(), Tokens$(0)) Or FindMapElement(Dictionary()\alias$, Tokens$(0) <> 0 ;// If Token is in dictionary
Can't do that because Alias$ is a list and has to be iterated.. The problem with sticking it in another IF check is, these two conditions need to lead to the same code branch in my IF block.
There is no guarantee a command will have an alias listed, nor is there a way match the alias to the original command word when the alias is used - unless I want to take up additional memory space defining things twice.
Should I just use a GOTO label? Or perhaps I should just iterate the Alias list before the primary command check, and store it in a variable for comparison...?
edit: Not having luck with that second idea for some reason.. Might have to rethink things if its not a syntax issue.. =\
I guess the easiest thing would be to store only one alias in a string instead of a list, or just add aliases to the main dictionary with a reference back to the main command.....blech

Re: Verb-Noun parser?
Posted: Thu Apr 24, 2014 4:54 am
by Zach
After some playing around and syntax fubar, I at least got primary keywords for command matching (I hope) set up and working.
So far tested with the move command.. It's
ugly but there it is..
Code: Select all
Procedure ParseInput(Var.s)
;// Setup some local shit
Nbfound.i = #Null
RegExp.i = #Null
Token_Index.i = #Null
Alias$ = #NULL$
;SendMessage_(GadgetID(#TextDisplay), #EM_SETTARGETDEVICE, #Null, 0)
;NewMap MyTokens.Structure_Tokens()
If Var.s = #NULL$
SetActiveGadget(#InputBar)
ElseIf Var.s = " "
SetActiveGadget(#InputBar)
Else
;// Echo user input to screen
AddGadgetItem(#TextDisplay, -1, Var.s)
SendMessage_(GadgetID(#TextDisplay), #EM_SETSEL, -1, -1)
;// Tokenize input
Shared Tokens$()
;// Building Regular Expression to capture words as tokens
RegExp = CreateRegularExpression(#PB_Any, "\w+([.,\']\w+)*")
;// Assign number of words tokenized to an int.
NbFound = ExtractRegularExpression(RegExp, Var, Tokens$())
If FindMapElement(Dictionary(), Tokens$(0)) <> 0 ;// If Token is in dictionary
If Dictionary(Tokens$(0))\type = "verb" ;// Verify Token is a command/verb
Debug "First Token is a command"
For i = 0 To Nbfound-1 ;// Build a Grammar list of all tokens
If FindMapElement(Dictionary(), Tokens$(i))
Token_Grammar$ = Token_Grammar$ + Dictionary(Tokens$(i))\type + " "
EndIf
Next
ForEach Dictionary(Tokens$(0))\grammar$()
Token_Grammar$ = LTrim(RTrim(Token_Grammar$))
If Dictionary()\grammar$() = Token_Grammar$
Match.s = Tokens$(0)
EndIf
Next
Select Match
Case "move"
Cmd_Move(Tokens$(1))
EndSelect
EndIf
EndIf
Debug "Grammar for command " + Tokens$(0) + " is: " + Token_Grammar$
OF course this is only going to work for single argument commands at the moment.. I need to do some serious thinking, and work out how to step through the entire thing and remove non-critical words, but still feed it appropriate words for multi-word commands.
Probably gonna end up rewriting it

Re: Verb-Noun parser?
Posted: Thu Apr 24, 2014 3:02 pm
by Tenaja
Have you seen this? It covers several topics, although you'll have to translate it from a fourin [sic] language...
http://mocagh.org/usborne-hayes/writead ... ograms.pdf
Re: Verb-Noun parser?
Posted: Thu Apr 24, 2014 4:01 pm
by Demivec
@Tenaja: Hey I actually own this book. I bought it back in 1985.

Re: Verb-Noun parser?
Posted: Thu Apr 24, 2014 4:49 pm
by Tenaja
Me too...

Re: Verb-Noun parser?
Posted: Thu Apr 24, 2014 5:14 pm
by TI-994A
Demivec wrote:Hey I actually own this book. I bought it back in 1985.

I had many such books too; but I had to painstakingly convert the listings to TI-Basic. Best years of my life!

Re: Verb-Noun parser?
Posted: Thu Apr 24, 2014 9:10 pm
by Zach
I haven't seen that before, thanks. Looks like a fun thing to read either way
Re: Verb-Noun parser?
Posted: Thu Apr 24, 2014 9:54 pm
by Tenaja
It is very rudimentary, and obviously the code will need translation (modernization), but that book shows a solution to every problem you will encounter. If you start by translating Haunted House, you should end up with a core than you can enhance. Then maybe just a couple text files to define each new "adventure".
Re: Verb-Noun parser?
Posted: Fri Apr 25, 2014 5:02 am
by Zach
Well, I can pretty much do anything I need to right now, from what I can see.
My main problem is learning to thinking like a CPU I guess, and in very abstract and logical terms. Especially when it comes to the parser. In the book, it uses a simple 2 command pattern, which I've already accomplished. So for me, the issue is taking everything I've learned and extrapolating that into a new idea to handle 3 words, or 4 words, etc..
And maybe the solution will be there, hidden somewhere. I've drudged around the net and found a few other usborne books along the same lines as well.
Perhaps I am just too ambitious for my own good
