Converted to use json for rules instead of yaml
[eliza.git] / eliza.py
1
2 # coding: utf-8
3 #import yaml
4 import json
5 import collections
6 import random
7
8 Match = collections.namedtuple('Match', 'text, rule, bindings')
9
10 pronoun_swaps = {
11 'i': 'you',
12 'me': 'you',
13 'my': 'your',
14 'mine': 'yours',
15 'am': 'are',
16 'you': 'i',
17 'your': 'my',
18 'yours': 'mine'
19 }
20
21 halt_words = 'quit halt exit stop'.split()
22
23 # def read_rules(rules_file):
24 # with open(rules_file) as f:
25 # rules = [{'pattern': r['pattern'].split(),
26 # 'responses': [t.split() for t in r['responses']]}
27 # for r in yaml.safe_load(f)]
28 # return rules
29
30 def read_rules(rules_file):
31 with open(rules_file) as f:
32 rules = [{'pattern': r['pattern'].split(),
33 'responses': [t.split() for t in r['responses']]}
34 for r in json.load(f)]
35 return rules
36
37 def is_var(word):
38 return word[0] == '?'
39
40
41 def splits(item):
42 return [(item[:i], item[i:]) for i in range(len(item)+1)]
43
44 def match(text, rule):
45 return all_matches([Match(text, rule, {})])
46
47 def all_matches(matches):
48 successes = []
49 while matches:
50 # print(matches, successes)
51 current = matches[0]
52 new_matches = []
53 if successful_match(current):
54 successes += [current.bindings]
55 elif current.rule:
56 new_matches = match_item(current.text, current.rule, current.bindings)
57 matches = matches[1:] + new_matches
58 return successes
59
60 def successful_match(match):
61 return match.text == [] and match.rule == []
62
63 def match_item(text, rule, bindings):
64 r0 = rule[0]
65 if is_var(r0):
66 if r0 in bindings:
67 # already seen this variable
68 if text[:len(bindings[r0])] == bindings[r0]:
69 return [Match(text[(len(bindings[r0])):], rule[1:], bindings)]
70 else:
71 return []
72 else:
73 # not seen this variable yet
74 matches = []
75 for pre, suf in splits(text):
76 new_bindings = bindings.copy()
77 new_bindings[r0] = pre
78 matches += [Match(suf, rule[1:], new_bindings)]
79 return matches
80 elif text and text[0] == r0:
81 return [Match(text[1:], rule[1:], bindings)]
82 else:
83 return []
84
85 def candidate_rules(rules, comment):
86 return [(rule, bindings)
87 for rule in rules
88 for bindings in match(comment, rule['pattern'])]
89
90
91 def fill(response, bindings):
92 filled_response = []
93 for w in response:
94 if is_var(w):
95 if w in bindings:
96 filled_response += bindings[w]
97 else:
98 filled_response += ['MISSING']
99 else:
100 filled_response += [w]
101 return filled_response
102
103
104 def pronoun_person_swap(bindings):
105 def swapped(words):
106 sw = []
107 for w in words:
108 if w in pronoun_swaps:
109 sw += [pronoun_swaps[w]]
110 else:
111 sw += [w]
112 return sw
113
114 return {var: swapped(bindings[var]) for var in bindings}
115
116 def respond(rule, bindings):
117 return fill(random.choice(rule['responses']), bindings)
118
119
120 def eliza_loop(rules):
121 print("Hello. I'm Eliza. What seems to be the problem?")
122 while True:
123 c = input("> ")
124 if c.strip() in halt_words: break
125 comment = c.split()
126 rule, bindings = candidate_rules(rules, comment)[0]
127 swapped_bindings = pronoun_person_swap(bindings)
128 print(' '.join(respond(rule, swapped_bindings)))
129
130
131 all_rules = read_rules('rules.json')
132 eliza_loop(all_rules)
133
134
135
136
137
138