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 :oops:

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 :oops:

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 wrote: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
@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... :D

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! 8)

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 :oops: