changeset 5:a049cb93db46

Now doing a much smarter parsing of the rest of the sentence after the verb, and describing expected arguments in (italics).
author jonathandicarlo@jonathan-dicarlos-macbook-pro.local
date Tue, 13 May 2008 18:57:55 -0700
parents 4ea09b7ce820
children 98935af12b04
files hip.css hip.js
diffstat 2 files changed, 131 insertions(+), 59 deletions(-) [+]
line wrap: on
line diff
--- a/hip.css	Tue May 13 13:04:25 2008 -0700
+++ b/hip.css	Tue May 13 18:57:55 2008 -0700
@@ -14,4 +14,9 @@
 
 .hilited {
     background: #00aa00;
+}
+
+.needarg {
+    font-style: italic;
+    color: #999999;
 }
\ No newline at end of file
--- a/hip.js	Tue May 13 13:04:25 2008 -0700
+++ b/hip.js	Tue May 13 18:57:55 2008 -0700
@@ -52,26 +52,41 @@
   }
 };
 
-function ParsedSentence( rawText, verb, DO, DOType, modifiers ) {
-  this._init( rawText, verb, DO, DOType, modifiers );
+function ParsedSentence( verb, DO, modifiers ) {
+  this._init( verb, DO, modifiers );
 }
 ParsedSentence.prototype = {
- _init: function( rawText, verb, DO, DOType, modifiers ) {
-
-    // needs state to represent what's locked in so far.
+ _init: function( verb, DO, modifiers ) {
+    /* modifiers is dictionary of preposition: noun */
+    this._verb = verb;
+    this._DO = DO;
+    this._modifiers = modifiers;
   },
 
- lockInLatestWord: function( selectedCompletion ) {
-    // 
+ getCompletionText: function() {
+    // returns completed and canonicalized sentence
+    var sentence = this._verb._name;
+    if ( this._verb._DOType ) {
+      if ( this._DO ) {
+	sentence = sentence + " " + this._DO;
+      } else {
+	sentence = sentence + " <span class=\"needarg\">(" + this._verb._DOLabel + ")</span>";
+      }
+    }
 
-  },
-
- getCompletions: function() {
+    for ( var x in this._verb._modifiers ) {
+      if ( this._modifiers[ x ] ) {
+	sentence = sentence + " " + x + " " + this._modifiers[x];
+      } else {
+	sentence = sentence + " <span class=\"needarg\">(" + this._verb._modifiers[x]._name + ")</span>";
+      }
+    }
+    return sentence;
   },
 
  getDescription: function() {
     // returns a string describing what the sentence will do if executed
-  },
+  }
 
 };
 
@@ -91,60 +106,106 @@
     // example:  { "from" : City, "to" : City, "on" : Day }
   },
 
- getCompletions: function( words ) {
+ recursiveParse: function( unusedWords, filledMods, unfilledMods ) {
+    var x;
+    var suggestions = [];
+    var completions = [];
+    var directObject = "";
+    if ( [key for (key in unfilledMods)].length == 0 ) {
+      // Done with modifiers, try to parse direct object.
+      if ( unusedWords.length == 0 ) {
+	// No words left, no direct object.  Try parsing sentence
+	// without them.
+	return [ new ParsedSentence( this, "", filledMods ) ];
+      }
+
+      if ( this._DOType == null ) {
+	// intransitive verb; no direct object, only modifiers.
+	// We can't use the extra words, so fail.
+	  return [];
+      } else {
+	// Transitive verb, can have direct object.  Try to use the
+	// remaining words in that slot.
+	directObject = unusedWords.join( " " );
+	if ( this._DOType.match( directObject ) ) {
+	  // it's a valid direct object.  Make a sentence for each
+	  // possible noun completion based on it; return them all.
+	  suggestions = this._DOType.suggest( unusedWords[0] );
+	  for ( var x in suggestions ) {
+	    completions.push( new ParsedSentence( this, suggestions[x],
+						  filledMods ) );
+	  }
+	  return completions;
+	} else {
+	  // word is invalid direct object.  Fail!
+	  return [];
+	}
+      }
+    } 
+  },
+
+ /*
+   else {
+      // "pop" a preposition off of the properties of unfilledMods
+      var preposition = [key for (key in unfilledMods)][0];
+      var newUnfilledMods = unfilledMods.splice();
+      delete newUnfilledMods[preposition];
+
+      // Look for a match for this preposition
+      var nounType = unfilledMods[ preposition ];
+      var matchIndices = [];
+      for ( var x = 0; x < unusedWords.length - 1; x++ ) {
+	if ( preposition.indexOf( unusedWords[x] ) == 0 ) {
+	  if ( nounType.match( unusedWords[ x + 1 ] ) == 0 ) {
+	    // Match for the preposition at index x followed by
+	    // an appropriate noun at index x+1
+	    matchIndices.push( x );
+	  }
+	}
+      }
+      if ( matchIndices.length == 0 ) {
+	// no match for this preposition.
+	// Leave it blank and try to parse the rest:
+	filledMods[preposition] = "";
+	return recursiveParse( unusedWords, filledMods, newUnfilledMods );
+      } else {
+	for ( x in matchIndices ) {
+	  // remove the words that matched the preposition and following
+	  // noun:
+	  var noun = unusedWords[ matchIndices[x]+1 ];
+	  var newUnusedWords = unusedWords.slice();
+	  newUnusedWords.splice( possibilities[x], 2 );
+	  // add every noun completion from every possibility...
+	  suggestions = nounType.suggest( noun );
+	  for ( var y in suggestions ) {
+	    var newFilledMods = filledMods.slice();
+	    newFilledMods[ preposition ] = suggestions[y];
+	    var newCompletions = recursiveParse( newUnusedWords,
+						 newFilledMods,
+						 newUnfilledMods );
+	    completions = completions.concat( newCompletions );
+	  }
+	}
+	return completions;
+      }
+    }
+    }, */
+
+ parse: function( words ) {
+    /* returns a list of ParsedSentences. */
     /* words is an array of words that were space-separated.
-     already determined that words[0] matches this verb;
+       The first word, which matched this verb, has already been removed.
        Everything after that is either:
        1. my direct object
        2. a preposition
        3. a noun following a preposition. 
     */
-    var predicate = "";
-    var completions = [];
-    var suggestions = [];
-    var x;
-    var y;
-
-    // TODO pull out things that might be modifiers, try using
-    // the remainder as the direct object.
-
-    for ( x = 1; x < words.length; x++ ) {
-      // a horrible way of reassembling input minus verb
-      predicate = predicate + words[x];
-      if ( x < words.length - 1 ) {
-	predicate = predicate + " ";
-      }
-    }
+    return this.recursiveParse( words, {}, this._modifiers );
+  },
 
-    // first approx: just try out each word as direct object
-    if ( this._DOType == null ) {
-      // No direct object accepted!!
-      if ( predicate.length == 0 ) {
-	return [this._name]; 
-      } else {
-	return [];
-      }
-    } else if ( this._DOType.match( predicate ) ){
-      // direct object accepts whole input at once?
-      suggestions = this._DOType.suggest( predicate );
-      for( y in suggestions ) {
-	completions.push( this._name + " " + suggestions[y] );
-      }
-      return completions;
-    } else {
-      // try each word as the direct object
-      for ( x=1; x<words.length; x++ ) {
-	if ( this._DOType.match( words[x] ) ){
-	  suggestions = this._DOType.suggest( predicate );
-	  for( y in suggestions ) {
-	    completions.push( this._name + " " + suggestions[y] );
-	  }
-	}
-      }
-      return completions;
-    }
-
-
+ getCompletions: function( words ) {
+    var sentences = this.parse( words );
+    return [ sentences[x].getCompletionText() for ( x in sentences ) ];
   },
 
  match: function( sentence ) {
@@ -203,7 +264,7 @@
     for ( var x in verbs ) {
       var verb = verbs[x];
       if ( verb.match( words[0] ) ) {
-	completions = verb.getCompletions( words );
+	completions = verb.getCompletions( words.slice(1) );
 	this._suggestionList = this._suggestionList.concat( completions );
       }
     } // TODO sort in order of match quality
@@ -303,6 +364,7 @@
     break;
   case 32: // spacebar
     event.target.value = gQs.autocomplete( event.target.value );
+    gQs.updateSuggestionList( event.target.value );
     break;
   default:
     gQs.updateSuggestionList( event.target.value );
@@ -322,3 +384,8 @@
         $("#search-box").css("width")
     );
 });
+
+/* Minor problems:
+1. verbs with indirect objects are returning empty completion lists
+2. multiple word direct objects are truncated to single word
+*/