Got eliza working
[eliza.git] / eliza.py
diff --git a/eliza.py b/eliza.py
new file mode 100644 (file)
index 0000000..2a3e04c
--- /dev/null
+++ b/eliza.py
@@ -0,0 +1,125 @@
+
+# coding: utf-8
+import yaml
+import collections
+import random
+
+Match = collections.namedtuple('Match', 'text, rule, bindings')
+
+pronoun_swaps = {
+    'i': 'you',
+    'me': 'you',
+    'my': 'your',
+    'mine': 'yours',
+    'am': 'are'
+}
+
+def read_rules(rules_file):
+    with open(rules_file) as f:
+        rules = [{'pattern': r['pattern'].split(),
+                 'responses': [t.split() for t in r['responses']]}
+            for r in yaml.load(f)]
+    return rules
+
+def is_var(word):
+    return word[0] == '?'
+
+
+def splits(item):
+    return [(item[:i], item[i:]) for i in range(len(item)+1)]
+
+def match(text, rule):
+    return all_matches([Match(text, rule, {})])
+
+def all_matches(matches):
+    successes = []
+    while matches:
+        # print(matches, successes)
+        current = matches[0]
+        new_matches = []
+        if successful_match(current):
+            successes += [current.bindings]
+        elif current.rule:
+            new_matches = match_item(current.text, current.rule, current.bindings)
+        matches = matches[1:] + new_matches
+    return successes
+
+def successful_match(match):
+    return match.text == [] and match.rule == []
+
+def match_item(text, rule, bindings):
+    r0 = rule[0]
+    if is_var(r0):
+        if r0 in bindings:
+            # already seen this variable
+            if text[:len(bindings[r0])] == bindings[r0]:
+                return [Match(text[(len(bindings[r0])):], rule[1:], bindings)]
+            else:
+                return []
+        else:
+            # not seen this variable yet
+            matches = []
+            for pre, suf in splits(text):
+                new_bindings = bindings.copy()
+                new_bindings[r0] = pre
+                matches += [Match(suf, rule[1:], new_bindings)]
+            return matches
+    elif text and text[0] == r0:
+        return [Match(text[1:], rule[1:], bindings)]
+    else:
+        return []
+
+def candidate_rules(rules, comment):
+    return [(rule, bindings) 
+            for rule in rules 
+            for bindings in match(comment, rule['pattern'])]
+
+
+def fill(response, bindings):
+    filled_response = []
+    for w in response:
+        if is_var(w):
+            if w in bindings:
+                filled_response += bindings[w]
+            else:
+                filled_response += ['MISSING']
+        else:
+            filled_response += [w]
+    return filled_response
+
+
+def pronoun_person_swap(bindings):
+    def swapped(words):
+        sw = []
+        for w in words:
+            if w in pronoun_swaps:
+                sw += [pronoun_swaps[w]]
+            else:
+                sw += [w]
+        return sw
+    
+    return {var: swapped(bindings[var]) for var in bindings}
+
+def respond(rule, bindings):
+    return fill(random.choice(rule['responses']), bindings)
+
+
+def eliza_loop(rules):
+    print("Hello. I'm Eliza. What seems to be the problem?")
+    while True:
+        c = input("> ")
+        if c.strip() in 'quit halt exit stop'.split(): break
+        comment = c.split()
+        rule, bindings = candidate_rules(rules, comment)[0]
+        swapped_bindings = pronoun_person_swap(bindings)
+        print(' '.join(respond(rule, swapped_bindings)))
+
+
+all_rules = read_rules('rules.yaml')
+eliza_loop(all_rules)
+
+
+
+
+
+