/
session.py
463 lines (398 loc) · 16.9 KB
/
session.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
import site
site.addsitedir('/afs/athena.mit.edu/user/b/r/broder/lib/python2.5/site-packages')
import zephyr
from fuzzystack import FuzzyStack
from helper import print_list, tokenize
from nlp import get_sentence_type, find_topic, find_compound_noun, find_PP, find_noun, QUESTION, STATEMENT, COMMAND
from xml_parser import update_files
from parsetree import Parser
#######################################
# The Session class stores a "session",
# or conversation, between Dodona and
# the user.
#######################################
class Session:
def __init__(self, name, topics, bot):
self.memory = FuzzyStack(20)
self.memory.push("data", topics)
self.memory.push("name", name)
self.name = name
self.topics = topics
self.parser = Parser()
self.bot = bot
def _topic(self, top, d=None, k=None, ques_word=None):
if d == None: d = self.topics
subtop = None
nouns = set()
n = find_noun(top)
while n:
noun = " ".join(n.leaves())
nouns.add(noun)
n = find_noun(top, nouns)
nouns = list(nouns)
if "me" in nouns: nouns.remove("me")
if "you" in nouns: nouns.remove("you")
print "Nouns: " + str(nouns)
ans = None
for topic in nouns:
for subtopic in nouns:
# check to see if topic is a key in the knowledge
# dictionary, and that the the entry corresponding
# to topic is also a dictionary
if d.has_key(topic) and isinstance(d[topic], dict):
# if subtopic is a key in the entry corresponding
# to topic, then set the subtopic entry as the answer.
if d[topic].has_key(subtopic):
print "TOPIC:", topic
print "SUBTOPIC:", subtopic
ans = d[topic][subtopic]
# check to see if the current topic stored in memory is
# the same as the topic we found.
elif topic == k:
# is the subtopic we found a key in the dictionary?
# if so, set it's entry as the anser.
if d.has_key(subtopic):
print "TOPIC:", topic
print "SUBTOPIC:", subtopic
ans = d[subtopic]
if not ans:
for topic in nouns:
# if the topic is a key in the dictionary
if d.has_key(topic):
# if the entry matching topic is a dictionary, then
# we should ask what subtopic they want to know about
if isinstance(d[topic], dict):
print "TOPIC:", topic
ans = d[topic]['default'] + "\n" + \
"Multiple keywords match your query. " + \
"What did you mean to ask about?\n\n" + \
print_list(d[topic].keys())
self.memory.push("topic", topic)
self.memory.push("data", d[topic])
# otherwise, just give them the entry that corresponds
# to topic
else:
print "TOPIC:", topic
ans = d[topic]
# if the topic we found is the same as the topic in
# memory, then ask (again) which subtopic they
# want to ask about
elif topic == k:
print "TOPIC:", topic
ans = d['default'] + "\n" + \
"Multiple keywords match your query. " + \
"What did you mean to ask about?\n\n" + \
print_list(d.keys())
if not ans:
if nouns == [] and ques_word == "what":
print "TOPIC: knowledge"
ans = "I know about:\n" + print_list(self.topics.keys())
elif "what you know" in nouns or \
"knowledge" in nouns:
print "TOPIC: knowledge"
ans = "I know about:\n" + print_list(self.topics.keys())
else:
l = nouns.pop(0)
if len(nouns) > 0:
for n in xrange(len(nouns)-1):
l += ", " + nouns[n]
l += ", or " + nouns[-1]
ans = "Sorry, I don't know about " + l + "."
return ans
def _AI(self, mess, d = None, k = None):
"""
Parses the message, and attempts to locate a topic. If it is
able to find a topic, it tells the user about the topic,
otherwise it prints a message saying that it can't parse the
sentence, or it doesn't know about the topic.
"""
# make sure the dictionary is set to something
if d == None: d = self.topics
# parse the sentence, and print the parse
parse = self.parser.parse_sent(mess)
print "PARSE:\n", parse
ans = None
# if the parse is returned as a tuple, then we know
# that the parse failed.
if isinstance(parse, tuple):
# if the second value in the tuple is valid, then there were
# words that were not in the grammar. Tell the user about the
# words, and then enter a function to learn the foreign words.
if parse[1]:
self.bot.send("Sorry, I don't understand the following words: " + \
", ".join(parse[1]) + ".", self.name)
self.memory.push("topic", list(parse[1]))
return self._learn()
# otherwise, we just couldn't parse the sentence
else:
parse = self.parser.parse_NP(mess)
print "NP PARSE:\n", parse
if parse:
ans = self._topic(parse, d=d, k=k)
else:
ans = "Sorry, I couldn't parse what you just said."
# otherwise, the parse succeeded
else:
# find the sentence type: STATEMENT, QUESTION, or COMMAND
type = get_sentence_type(parse)
print "TYPE: " + str(type)
# based on the sentence type, find the topic of the sentence.
# we don't yet know what the subtopic is, so just set it to
# None.
top = find_topic(parse, type)
print top
# if the sentence is a question and find_topic() found a
# topic, then top is a tuple, and we need to store the
# parts separately.
ques_word = None
if type == QUESTION and top:
ques_word = top[1]
top = top[0]
# if a topic was found, then we want to look for a
# prepositional phrase. For example, we want to be able
# to get TOPIC=emacs, SUBTOPIC=keys from "keys in emacs"
if top:
ans = self._topic(top, d=d, k=k, ques_word=ques_word)
# otherwise, we couldn't find a topic from the sentence, so
# tell them so
else:
if type == QUESTION and ques_word == "what":
print "TOPIC: knowledge"
ans = "I know about:\n" + print_list(self.topics.keys())
else:
print "TOPIC: None found"
ans = "Sorry, I couldn't determine the topic of what you are asking me."
# print the answer out to the terminal, and send the answer
# to the user.
print ans
self.bot.send(ans, self.name)
def _add_new_word(self, word, pos):
"""
Adds a new vocabulary word and rule to vocabulary.gr and
to the ContextFreeGrammar.
"""
vocab = open("vocabulary.gr", "a")
vocab.write("\n1\t" + pos + "\t" + word)
vocab.close()
self.parser.add_new_vocab_rule([pos, [word]])
def _part_of_speech(self, mess, step):
"""
Part of Dodona's word-learning algorithm. Learns the
part of speech for the word, and either moves to the next step
(for example, if the word is a verb, we want to know all
conjugations of that verb) or ends the learning process.
"""
word = self.memory.pop("topic")[1]
name = self.name
# first step
if step == "first":
# plural noun
if mess.find("plural noun") != -1:
self._add_new_word(word, "Noun_Pl")
self.memory.pop("status")
return self._learn()
# noun
elif mess.find("noun") != -1:
self._add_new_word(word, "Noun")
self.memory.pop("status")
return self._learn()
# adjective
elif mess.find("adjective") != -1:
self._add_new_word(word, "Adj_State")
self.memory.pop("status")
return self._learn()
# adverb
elif mess.find("adverb") != -1:
self._add_new_word(word, "Adv")
self.memory.pop("status")
return self._learn()
# intransitive verb
elif mess.find("intransitive verb") != -1:
self.bot.send("What is the infinitive for the verb " + word + "?", name)
self.memory.pop("status")
self.memory.push("status", "pos_verb1in")
self.memory.push("topic", word)
# transitive verb
elif mess.find("transitive verb") != -1:
self.bot.send("What is the infinitive for the verb " + word + "?", name)
self.memory.pop("status")
self.memory.push("status", "pos_verb1tr")
self.memory.push("topic", word)
# preposition
elif mess.find("preposition") != -1:
self._add_new_word(word, "Prep")
self.memory.pop("status")
return self._learn()
# anything else
else:
self.memory.pop("status")
return self._learn()
# step 2, stores the infinitive
elif step.startswith("verb1"):
if step.endswith("in"):
self._add_new_word(mess, "V_Inf_In")
self.memory.pop("status")
self.memory.push("status", "pos_verb2in")
elif step.endswith("tr"):
self._add_new_word(mess, "V_Inf_Tr")
self.memory.pop("status")
self.memory.push("status", "pos_verb2tr")
self.memory.push("topic", word)
self.bot.send("What is the present participle for the verb " + word + "?", name)
return None
# step 3, stores the present participle
elif step.startswith("verb2"):
if step.endswith("in"):
self._add_new_word(mess, "V_Pres_Part_In")
self.memory.pop("status")
self.memory.push("status", "pos_verb3in")
elif step.endswith("tr"):
self._add_new_word(mess, "V_Pres_Part_Tr")
self.memory.pop("status")
self.memory.push("status", "pos_verb3tr")
self.memory.push("topic", word)
self.bot.send("What is the past participle for the verb " + word + "?", name)
return None
# step 4, stores the past participle
elif step.startswith("verb3"):
if step.endswith("in"):
self._add_new_word(mess, "V_Past_Part_In")
self.memory.pop("status")
self.memory.push("status", "pos_verb4in")
elif step.endswith("tr"):
self._add_new_word(mess, "V_Past_Part_Tr")
self.memory.pop("status")
self.memory.push("status", "pos_verb4tr")
self.memory.push("topic", word)
self.bot.send("What is the 1st person singular present for the verb " + word + "?", name)
return None
# step 5, stores the present base
elif step.startswith("verb4"):
if step.endswith("in"):
self._add_new_word(mess, "V_Base_Pres_In")
self.memory.pop("status")
self.memory.push("status", "pos_verb5in")
elif step.endswith("tr"):
self._add_new_word(mess, "V_Base_Pres_Tr")
self.memory.pop("status")
self.memory.push("status", "pos_verb5tr")
self.memory.push("topic", word)
self.bot.send("What is the 3rd person singular present for the verb " + word + "?", name)
return None
# step 6, stores the 3rd person singular
elif step.startswith("verb5"):
if step.endswith("in"):
self._add_new_word(mess, "V_3rdSing_Pres_In")
self.memory.pop("status")
self.memory.push("status", "pos_verb6in")
elif step.endswith("tr"):
self._add_new_word(mess, "V_3rdSing_Pres_Tr")
self.memory.pop("status")
self.memory.push("status", "pos_verb6tr")
self.memory.push("topic", word)
self.bot.send("What is the 1st person singular past for the verb " + word + "?", name)
return None
# step 7, stores the past base
elif step.startswith("verb6"):
if step.endswith("in"):
self._add_new_word(mess, "V_Base_Past_In")
elif step.endswith("tr"):
self._add_new_word(mess, "V_Base_Past_Tr")
self.memory.pop("status")
return self._learn()
def _learn(self):
"""
Begins the learning process. Asks the user what
part of speech the word is, and keeps track of the
remaining words which we still need to learn about
"""
name = self.name
unknown_all = list(self.memory.pop("topic"))[1]
if len(unknown_all) > 0:
unknown = unknown_all[0]
del unknown_all[0]
pos = ["Noun", \
"Plural Noun", \
"Adjective", \
"Adverb", \
"Transitive Verb", \
"Intransitive Verb", \
"Preposition", \
"Other"]
self.bot.send("Which of the following parts of speech is \'" + \
unknown + "\'?\n" + print_list(pos), name)
self.memory.push("topic", unknown_all)
self.memory.push("topic", unknown)
self.memory.pop("status")
self.memory.push("status", "pos_first")
return None
else:
self.memory.pop("status")
return "reset"
def clear(self):
"""
Clears (resets) the session and the
memory, but does not kill it.
"""
self.memory = FuzzyStack(20)
self.memory.push("data", self.topics)
def question(self):
"""
Parses the user's most recent message,
and decides what to do based on the
content and the current status.
"""
name = self.name
mess = self.memory.read("message")
m = tokenize(mess)
mess = " ".join(m)
if mess == None: return "reset"
# if the user wants to exit, then
# return True (kill the session)
if mess.startswith("exit") or \
"bye" in m or \
"goodbye" in m:
self.bot.send("Glad to be of help :)", name)
return "exit"
# if the user says "nevermind", then
# clear the session
if mess.find("nevermind") != -1:
self.bot.send("Ok.", name)
self.clear()
return "reset"
# if the user greets Dodona, then respond
# in kind.
if "hi" in m or \
"hey" in m or \
"hello" in m != -1:
self.bot.send("Hello, " + name + "!")
return None
# check the status, and return the corresponding
# function if necessary
s = self.memory.read("status")
#if s == "unknown": return self.unknown(mess)
if s == "learn": return self._learn()
if s:
if s.startswith("pos"):
return self._part_of_speech(mess, s.split("_")[1])
d = self.memory.read("data")
k = self.memory.read("topic")
# if there is no current topic, then decipher one
# from the most recent message.
if k == None:
self._AI(mess)
if self.memory.read("topic"):
return None
else:
return "reset"
# if there is a current topic, search for a subtopic
else:
self._AI(mess, d, k)
if self.memory.read("status") == "pos_first":
return None
else:
self.memory.pop("topic")
self.memory.pop("data")
return "reset"
# -*- indent-tabs-mode: nil; tab-width: 4; -*-
# vi: set ts=4 sw=4 et: