rubylabs 0.6.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/data/eliza/doctor.txt +357 -0
- data/data/mars/chang1.txt +18 -0
- data/data/mars/dwarf.txt +11 -0
- data/data/mars/ferret.txt +18 -0
- data/data/mars/imp.txt +6 -0
- data/data/mars/mice.txt +15 -0
- data/data/mars/midget.txt +10 -0
- data/data/mars/piper.txt +36 -0
- data/data/mars/plague.txt +24 -0
- data/data/mars/test_celsius.txt +20 -0
- data/data/mars/test_div.txt +13 -0
- data/data/mars/test_hello.txt +7 -0
- data/data/mars/test_mult.txt +12 -0
- data/data/mars/test_threads.txt +8 -0
- data/lib/bitlab.rb +0 -38
- data/lib/elizalab.rb +562 -440
- data/lib/rubylabs.rb +43 -5
- data/test/bit_test.rb +7 -17
- data/test/eliza_test.rb +159 -0
- data/test/encryption_test.rb +4 -1
- data/test/iteration_test.rb +4 -3
- data/test/mars_test.rb +4 -0
- data/test/random_test.rb +4 -0
- data/test/recursion_test.rb +4 -3
- data/test/rubylabs_test.rb +5 -6
- data/test/sieve_test.rb +10 -7
- data/test/sphere_test.rb +4 -5
- metadata +17 -2
data/lib/elizalab.rb
CHANGED
@@ -10,31 +10,15 @@ writing their own scripts.
|
|
10
10
|
|
11
11
|
=end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
require 'readline'
|
16
|
-
include Readline
|
17
|
-
|
18
|
-
|
19
|
-
class String
|
13
|
+
module RubyLabs
|
20
14
|
|
21
|
-
|
22
|
-
A useful operation on strings -- call +s.unquote+ to remove double quotes from
|
23
|
-
the beginning and end of string +s+.
|
24
|
-
=end
|
15
|
+
module ElizaLab
|
25
16
|
|
26
|
-
|
27
|
-
|
28
|
-
return self.slice(1..-2)
|
29
|
-
else
|
30
|
-
return self
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|
17
|
+
require 'readline'
|
18
|
+
include Readline
|
35
19
|
|
36
20
|
=begin rdoc
|
37
|
-
|
21
|
+
|
38
22
|
A transformation rule is associated with a key word, and is triggered
|
39
23
|
when that word is found in an input sentence. Rules have integer
|
40
24
|
priorities, and if more than one rule is enabled ELIZA applies the one
|
@@ -46,37 +30,49 @@ a sentence, build the output using the current reassembly rule.
|
|
46
30
|
|
47
31
|
=end
|
48
32
|
|
49
|
-
class Rule
|
33
|
+
class Rule
|
50
34
|
|
51
|
-
|
35
|
+
attr_accessor :key, :priority, :patterns
|
52
36
|
|
53
37
|
=begin rdoc
|
54
38
|
Specify the key word for a rule when the rule is created.
|
55
39
|
=end
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
@patterns << Pattern.new(expr)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def [](n)
|
74
|
-
@patterns[n]
|
75
|
-
end
|
40
|
+
|
41
|
+
def initialize(key, priority = 1)
|
42
|
+
@key = key
|
43
|
+
@priority = priority
|
44
|
+
@patterns = Array.new
|
45
|
+
end
|
46
|
+
|
47
|
+
=begin rdoc
|
48
|
+
Compare rule priorities. r1 precedes r2 in the queue if r1 has a higher
|
49
|
+
priority than r2. The >= is important, in order to make sure the default
|
50
|
+
rule stays at the end of the queue (i.e. new rules will be inserted at the
|
51
|
+
front).
|
52
|
+
=end
|
76
53
|
|
77
|
-
|
78
|
-
|
79
|
-
|
54
|
+
def <(x)
|
55
|
+
@priority >= x.priority
|
56
|
+
end
|
57
|
+
|
58
|
+
def addPattern(expr)
|
59
|
+
if expr.class == Pattern
|
60
|
+
@patterns << expr
|
61
|
+
else
|
62
|
+
if expr.class == String
|
63
|
+
expr = Regexp.new(expr.slice(1..-2))
|
64
|
+
end
|
65
|
+
@patterns << Pattern.new(expr)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def [](n)
|
70
|
+
@patterns[n]
|
71
|
+
end
|
72
|
+
|
73
|
+
def addReassembly(line, n = -1)
|
74
|
+
@patterns[n].add_response(line)
|
75
|
+
end
|
80
76
|
|
81
77
|
|
82
78
|
=begin rdoc
|
@@ -85,33 +81,33 @@ class Rule
|
|
85
81
|
the patterns and the reassemblies if they contain variables.
|
86
82
|
=end
|
87
83
|
|
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
|
-
|
84
|
+
def apply(s, opt)
|
85
|
+
@patterns.each do |p|
|
86
|
+
if @@verbose
|
87
|
+
print "trying pattern "
|
88
|
+
p p.regexp
|
89
|
+
end
|
90
|
+
res = p.apply(s, opt)
|
91
|
+
return res if ! res.nil?
|
92
|
+
end
|
93
|
+
return nil
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_s
|
97
|
+
s = @key + " / " + @priority.to_s + "\n"
|
98
|
+
@patterns.each { |r| s += " " + r.to_s + "\n" }
|
99
|
+
return s
|
100
|
+
end
|
101
|
+
|
102
|
+
def inspect
|
103
|
+
# s = @key.inspect
|
104
|
+
s = ""
|
105
|
+
s += " [#{@priority}]" if @priority > 1
|
106
|
+
s += " --> [\n" + @patterns.join("\n") + "]"
|
107
|
+
return s
|
108
|
+
end
|
113
109
|
|
114
|
-
end
|
110
|
+
end # class Rule
|
115
111
|
|
116
112
|
=begin rdoc
|
117
113
|
|
@@ -131,393 +127,519 @@ call the regexp accessor. Example:
|
|
131
127
|
>> p.regexp
|
132
128
|
=> /\bhi\b/i
|
133
129
|
|
130
|
+
Another convenience: add group delimiters around wildcards (.*), groups of
|
131
|
+
words (a|b|c), and variable names ($x) if they aren't already there.
|
132
|
+
|
134
133
|
=end
|
135
134
|
|
135
|
+
# Pattern.new called internally only from Rule#addPattern, which is called
|
136
|
+
# to add /.*/ for default rule, or when reading /.../ line from script.
|
136
137
|
|
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
|
-
|
138
|
+
# In interactive experiments, users can call Pattern.new(s) or Pattern.new(s,a)
|
139
|
+
# where s is a string or regexp, and a is an array of response strings.
|
140
|
+
|
141
|
+
class Pattern
|
142
|
+
attr_accessor :regexp, :list, :index, :md
|
143
|
+
|
144
|
+
def initialize(expr, list = [])
|
145
|
+
raise "Pattern#initialize: expr must be String or Regexp" unless (expr.class == String || expr.class == Regexp)
|
146
|
+
re = (expr.class == String) ? expr : expr.source
|
147
|
+
add_parens(re, /\(?\.\*\)?/ )
|
148
|
+
add_parens(re, /\(?[\w' ]+(\|[\w' ]+)+\)?/ )
|
149
|
+
add_parens(re, /\(?\$\w+\)?/ )
|
150
|
+
re.insert(0,'\b') if re =~ /^\w/
|
151
|
+
re.insert(-1,'\b') if re =~ /\w$/
|
152
|
+
@regexp = Regexp.new(re, :IGNORECASE)
|
153
|
+
@list = list.nil? ? Array.new : list
|
154
|
+
@index = 0
|
155
|
+
end
|
156
|
+
|
157
|
+
def reset
|
158
|
+
@index = 0
|
159
|
+
end
|
160
|
+
|
161
|
+
# s is a source string, r is a pattern with optional parens -- add parens if they're not there
|
162
|
+
|
163
|
+
def add_parens(s, r)
|
164
|
+
s.gsub!(r) { |m| ( m[0] == ?( ) ? m : "(" + m + ")" }
|
165
|
+
end
|
166
|
+
|
167
|
+
def add_response(sentence)
|
168
|
+
@list << sentence
|
169
|
+
end
|
170
|
+
|
171
|
+
def apply(s, opt = :preprocess)
|
172
|
+
Eliza.preprocess(s) if opt == :preprocess
|
173
|
+
@md = s.match(@regexp)
|
174
|
+
return nil if @list.empty? || @md == nil
|
175
|
+
res = @list[inc()].clone
|
176
|
+
return res if res[0] == ?@
|
177
|
+
puts "reassembling '#{res}'" if @@verbose
|
178
|
+
res.gsub!(/\$\d+/) do |ns|
|
179
|
+
n = ns.slice(1..-1).to_i # strip leading $, convert to int
|
180
|
+
if n && @md[n]
|
181
|
+
puts "postprocess #{@md[n]}" if @@verbose
|
182
|
+
@md[n].gsub(/[a-z\-$']+/i) do |w|
|
183
|
+
(@@post.has_key?(w) && @@post[w][0] != ?$) ? @@post[w] : w
|
184
|
+
end
|
185
|
+
else
|
186
|
+
warn "Pattern.apply: no match for #{ns} in '#{res}'"
|
187
|
+
""
|
188
|
+
end
|
189
|
+
end
|
190
|
+
return res
|
191
|
+
end
|
192
|
+
|
193
|
+
def match(s)
|
194
|
+
@md = s.match(@regexp)
|
195
|
+
return @md != nil
|
196
|
+
end
|
197
|
+
|
198
|
+
def parts
|
199
|
+
return @md.nil? ? nil : @md.captures
|
200
|
+
end
|
201
|
+
|
202
|
+
def to_s
|
203
|
+
s = " /" + cleanRegexp + "/\n"
|
204
|
+
@list.each { |x| s += " \"" + x + "\"\n" }
|
205
|
+
return s
|
206
|
+
end
|
207
|
+
|
208
|
+
def inspect
|
209
|
+
return cleanRegexp + ": " + @list.inspect
|
210
|
+
end
|
211
|
+
|
212
|
+
def cleanRegexp
|
213
|
+
res = @regexp.source
|
214
|
+
res.gsub!(/\\b/,"")
|
215
|
+
return res
|
216
|
+
end
|
217
|
+
|
218
|
+
private
|
219
|
+
|
220
|
+
def inc
|
221
|
+
n = @index
|
222
|
+
@index = (@index + 1) % @list.length
|
223
|
+
return n
|
224
|
+
end
|
169
225
|
|
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
|
-
def to_s
|
199
|
-
s = cleanRegexp + "\n"
|
200
|
-
@list.each { |x| s += " \"" + x + "\"\n" }
|
201
|
-
return s
|
202
|
-
end
|
203
|
-
|
204
|
-
def inspect
|
205
|
-
return cleanRegexp + ": " + @list.inspect
|
206
|
-
end
|
207
|
-
|
208
|
-
end
|
226
|
+
end # class Pattern
|
227
|
+
|
228
|
+
|
229
|
+
=begin rdoc
|
230
|
+
A Dictionary is basically a Hash, but it overrides [] and []= to be case-insensitive
|
231
|
+
=end
|
232
|
+
|
233
|
+
class Dictionary < Hash
|
234
|
+
|
235
|
+
def initialize
|
236
|
+
super
|
237
|
+
@lc_keys = Hash.new
|
238
|
+
end
|
239
|
+
|
240
|
+
def [](x)
|
241
|
+
@lc_keys[x.downcase]
|
242
|
+
end
|
243
|
+
|
244
|
+
def []=(x,y)
|
245
|
+
super
|
246
|
+
@lc_keys[x.downcase] = y
|
247
|
+
end
|
248
|
+
|
249
|
+
def has_key?(x)
|
250
|
+
return @lc_keys.has_key?(x.downcase)
|
251
|
+
end
|
252
|
+
|
253
|
+
end # class Dictionary
|
209
254
|
|
255
|
+
class Eliza
|
256
|
+
|
257
|
+
# These class variables define the "application" processed by ELIZA -- the rule
|
258
|
+
# sets used to transform inputs to outputs. When ELIZA is initialized or reset it
|
259
|
+
# gets a default rule that just echoes the user input.
|
260
|
+
|
261
|
+
# Note: I haven't figured out how to have this method called when the module is first
|
262
|
+
# loaded. As a workaround, any method that refers to a class variable (run, info, etc)
|
263
|
+
# checks to see if they have been defined yet, and if not, call the reset method.
|
264
|
+
|
265
|
+
def Eliza.clear
|
266
|
+
@@script = nil
|
267
|
+
@@aliases = Hash.new
|
268
|
+
@@vars = Hash.new
|
269
|
+
@@starts = Array.new
|
270
|
+
@@stops = Array.new
|
271
|
+
@@queue = PriorityQueue.new
|
272
|
+
|
273
|
+
@@verbose = false
|
274
|
+
@@pre.clear
|
275
|
+
@@post.clear
|
276
|
+
@@rules.clear
|
210
277
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
@@script = nil
|
219
|
-
@@aliases = Hash.new
|
220
|
-
@@vars = Hash.new
|
221
|
-
@@starts = Array.new
|
222
|
-
@@stops = Array.new
|
223
|
-
@@pre = Hash.new
|
224
|
-
@@post = Hash.new
|
225
|
-
@@default = nil
|
226
|
-
@@rules = Hash.new
|
227
|
-
@@queue = PriorityQueue.new
|
228
|
-
|
229
|
-
@@word = /[a-z\-$']+/i # pattern for a "word" in the input language
|
230
|
-
@@iword = /^[a-z\-$']+/i # same, but must be the first item on the line
|
231
|
-
@@var = /\$\d+/ # variable name in reassembly string
|
232
|
-
|
233
|
-
@@verbose = false
|
234
|
-
|
235
|
-
def Eliza.rules
|
236
|
-
return @@rules
|
237
|
-
end
|
238
|
-
|
239
|
-
def Eliza.queue
|
240
|
-
return @@queue
|
241
|
-
end
|
242
|
-
|
243
|
-
def Eliza.aliases
|
244
|
-
return @@aliases
|
245
|
-
end
|
246
|
-
|
247
|
-
def Eliza.vars
|
248
|
-
return @@vars
|
249
|
-
end
|
278
|
+
@@default = Rule.new(:default)
|
279
|
+
@@default.addPattern(/(.*)/)
|
280
|
+
@@default.addReassembly("$1")
|
281
|
+
|
282
|
+
return true
|
283
|
+
end
|
250
284
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
285
|
+
#
|
286
|
+
# def Eliza.queue
|
287
|
+
# return @@queue
|
288
|
+
# end
|
289
|
+
#
|
290
|
+
# def Eliza.aliases
|
291
|
+
# return @@aliases
|
292
|
+
# end
|
293
|
+
#
|
294
|
+
# def Eliza.vars
|
295
|
+
# return @@vars
|
296
|
+
# end
|
297
|
+
#
|
298
|
+
|
299
|
+
def Eliza.pre
|
300
|
+
return @@pre
|
301
|
+
end
|
302
|
+
|
303
|
+
def Eliza.post
|
304
|
+
return @@post
|
305
|
+
end
|
306
|
+
|
307
|
+
def Eliza.rules
|
308
|
+
return @@rules
|
309
|
+
end
|
310
|
+
|
311
|
+
def Eliza.verbose
|
312
|
+
@@verbose = true
|
313
|
+
end
|
314
|
+
|
315
|
+
def Eliza.quiet
|
316
|
+
@@verbose = false
|
317
|
+
end
|
318
|
+
|
319
|
+
=begin rdoc
|
320
|
+
Save a copy of a script that is distributed with RubyLabs; if no output file name specified
|
321
|
+
make a file name from the program name.
|
322
|
+
=end
|
323
|
+
|
324
|
+
def Eliza.checkout(script, filename = nil)
|
325
|
+
scriptfilename = script.to_s + ".txt"
|
326
|
+
scriptfilename = File.join(@@dataDirectory, scriptfilename)
|
327
|
+
if !File.exists?(scriptfilename)
|
328
|
+
puts "Script not found: #{scriptfilename}"
|
329
|
+
return nil
|
330
|
+
end
|
331
|
+
outfilename = filename.nil? ? (script.to_s + ".txt") : filename
|
332
|
+
dest = File.open(outfilename, "w")
|
333
|
+
File.open(scriptfilename).each do |line|
|
334
|
+
dest.puts line.chomp
|
335
|
+
end
|
336
|
+
dest.close
|
337
|
+
puts "Copy of #{script} saved in #{outfilename}"
|
338
|
+
end
|
339
|
+
|
340
|
+
# Utility procedure to get the rule for a word -- can be called interactively or
|
341
|
+
# when processing a script
|
342
|
+
|
343
|
+
def Eliza.rule_for(w)
|
344
|
+
@@rules[w] || ((x = @@aliases[w]) && (r = @@rules[x]))
|
345
|
+
end
|
346
|
+
|
347
|
+
# Preprocessing -- turn string into single line, words separated by single space,
|
348
|
+
# apply pre-processing substitutions
|
349
|
+
|
350
|
+
def Eliza.preprocess(s)
|
351
|
+
s.gsub!( /\s+/, " " )
|
352
|
+
s.gsub!(@@word) { |w| @@pre.has_key?(w) ? @@pre[w] : w }
|
353
|
+
puts "preprocess: line = '#{s}'" if @@verbose
|
354
|
+
end
|
266
355
|
|
267
|
-
|
268
|
-
|
269
|
-
|
356
|
+
# First pass over the input -- scan each word, apply preprocessing substitutions,
|
357
|
+
# add rule names to the priority queue. NOTE: this method does a destructive
|
358
|
+
# update to the input line....
|
270
359
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
queue.insert(r)
|
282
|
-
puts "add rule for '#{x}' to queue" if @@verbose
|
283
|
-
end
|
284
|
-
end
|
285
|
-
# queue.each { |r| puts r.key }
|
286
|
-
end
|
360
|
+
def Eliza.scan(line, queue)
|
361
|
+
Eliza.preprocess(line)
|
362
|
+
line.scan(@@word) do |w|
|
363
|
+
w.downcase!
|
364
|
+
if r = Eliza.rule_for(w)
|
365
|
+
queue << r
|
366
|
+
puts "add rule for '#{w}' to queue" if @@verbose
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
287
370
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
371
|
+
def Eliza.apply(line, rule)
|
372
|
+
puts "applying rule: key = '#{rule.key}'" if @@verbose
|
373
|
+
if res = rule.apply(line, :no_preprocess)
|
374
|
+
if res[0] == ?@
|
375
|
+
rulename = res.slice(1..-1)
|
376
|
+
if @@rules[rulename]
|
377
|
+
return Eliza.apply( line, @@rules[rulename] )
|
378
|
+
else
|
379
|
+
warn "Eliza.apply: no rule for #{rulename}"
|
380
|
+
return nil
|
381
|
+
end
|
382
|
+
else
|
383
|
+
return res
|
384
|
+
end
|
385
|
+
else
|
386
|
+
return nil
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
# The heart of the program -- apply transformation rules to an input sentence.
|
391
|
+
|
392
|
+
def Eliza.transform(s)
|
393
|
+
s.sub!(/[\n\.\?!\-]*$/,"") # strip trailing punctuation
|
394
|
+
# s.downcase!
|
312
395
|
|
313
|
-
|
314
|
-
|
396
|
+
@@queue = PriorityQueue.new
|
397
|
+
@@queue << @@default # initialize queue with default rule
|
315
398
|
|
316
|
-
|
399
|
+
Eliza.scan(s, @@queue) # add rules for recognized key words
|
317
400
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
401
|
+
while @@queue.length > 0 # apply rules in order of priority
|
402
|
+
if @@verbose
|
403
|
+
print "queue: "
|
404
|
+
p @@queue.collect { |r| r.key }
|
405
|
+
end
|
406
|
+
rule = @@queue.shift
|
407
|
+
if result = Eliza.apply(s, rule)
|
408
|
+
return result
|
409
|
+
end
|
410
|
+
end
|
325
411
|
|
326
|
-
|
327
|
-
|
328
|
-
|
412
|
+
warn "No rules applied" if @@queue.empty?
|
413
|
+
return nil
|
414
|
+
end
|
329
415
|
|
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
|
-
|
416
|
+
# The parser calls this method to deal with directives (lines where the first
|
417
|
+
# word begins with a colon)
|
418
|
+
|
419
|
+
def Eliza.parseDirective(line)
|
420
|
+
word = Eliza.detachWord(line)
|
421
|
+
case word
|
422
|
+
when "alias"
|
423
|
+
if line.empty? || line[0] != ?$
|
424
|
+
warn "symbol after :alias must be a variable name; ignoring '#{word} #{line}'"
|
425
|
+
return
|
426
|
+
else
|
427
|
+
sym = Eliza.detachWord(line)
|
428
|
+
@@vars[sym] = Array.new
|
429
|
+
line.split.each do |s|
|
430
|
+
@@aliases[s] = sym
|
431
|
+
@@vars[sym] << s
|
432
|
+
end
|
433
|
+
end
|
434
|
+
when "start"
|
435
|
+
@@starts << line.unquote
|
436
|
+
when "stop"
|
437
|
+
@@stops << line.unquote
|
438
|
+
when "pre"
|
439
|
+
sym = Eliza.detachWord(line)
|
440
|
+
@@pre[sym] = line.unquote
|
441
|
+
when "post"
|
442
|
+
sym = Eliza.detachWord(line)
|
443
|
+
@@post[sym] = line.unquote
|
444
|
+
when "default"
|
445
|
+
@@default = line[@@word]
|
446
|
+
else
|
447
|
+
warn "unknown directive: :#{word} (ignored)"
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
# Remove a word from the front of a line
|
452
|
+
|
453
|
+
def Eliza.detachWord(line)
|
454
|
+
word = line[@@word] # pattern matches the first word
|
455
|
+
if line.index(" ")
|
456
|
+
line.slice!(0..line.index(" ")) # delete up to end of the word
|
457
|
+
line.lstrip! # in case there are extra spaces after word
|
458
|
+
else
|
459
|
+
line.slice!(0..-1) # line just had the one word
|
460
|
+
end
|
461
|
+
return word
|
462
|
+
end
|
463
|
+
|
464
|
+
# Check each pattern's regular expression and replace var names by alternation
|
465
|
+
# constructs. If the script specified a default rule name look up that
|
466
|
+
# rule and save it as the default.
|
467
|
+
|
468
|
+
def Eliza.compileRules
|
469
|
+
@@rules.each do |key,val|
|
470
|
+
a = val.patterns()
|
471
|
+
a.each do |p|
|
472
|
+
expr = p.regexp.inspect
|
473
|
+
expr.gsub!(/\$\w+/) { |x| @@vars[x].join("|") }
|
474
|
+
p.regexp = eval(expr)
|
475
|
+
end
|
476
|
+
end
|
477
|
+
if @@default.class == String
|
478
|
+
@@default = @@rules[@@default]
|
479
|
+
end
|
480
|
+
end
|
395
481
|
|
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
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
482
|
+
# Parse rules in file f, store them in global arrays. Strategy: use a local
|
483
|
+
# var named 'rule', initially set to nil. New rules start with a single word
|
484
|
+
# at the start of a line. When such a line is found in the input file, create a
|
485
|
+
# new Rule object and store it in 'rule'. Subsequent lines that are part of the
|
486
|
+
# current rule (lines that contain regular expressions or strings) are added to
|
487
|
+
# current Rule object. Directives indicate the end of a rule, so 'rule' is reset
|
488
|
+
# to nil when a directive is seen.
|
489
|
+
|
490
|
+
def Eliza.load(filename)
|
491
|
+
begin
|
492
|
+
Eliza.clear
|
493
|
+
rule = nil
|
494
|
+
if filename.class == Symbol
|
495
|
+
filename = File.join(@@dataDirectory, filename.to_s + ".txt")
|
496
|
+
end
|
497
|
+
File.open(filename).each do |line|
|
498
|
+
line.strip!
|
499
|
+
next if line.empty? || line[0] == ?#
|
500
|
+
if line[0] == ?:
|
501
|
+
Eliza.parseDirective(line)
|
502
|
+
rule = nil
|
503
|
+
else
|
504
|
+
if line =~ @@iword
|
505
|
+
rulename, priority = line.split
|
506
|
+
rule = priority ? Rule.new(rulename, priority.to_i) : Rule.new(rulename)
|
507
|
+
@@rules[rule.key] = rule
|
508
|
+
elsif rule.nil?
|
509
|
+
warn "missing rule name? unexpected input '#{line}'"
|
510
|
+
elsif line[0] == ?/
|
511
|
+
if line[-1] == ?/
|
512
|
+
rule.addPattern(line)
|
513
|
+
else
|
514
|
+
warn "badly formed expression (missing /): '#{line}'"
|
515
|
+
end
|
516
|
+
elsif line[0] == ?"
|
517
|
+
if line[-1] == ?"
|
518
|
+
rule.addReassembly(line.unquote)
|
519
|
+
else
|
520
|
+
warn "badly formed string (missing \"): '#{line}'"
|
521
|
+
end
|
522
|
+
elsif line[0] == ?@
|
523
|
+
rule.addReassembly(line)
|
524
|
+
else
|
525
|
+
warn "unexpected line in rule for #{rulename}: '#{line}'"
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|
529
|
+
Eliza.compileRules
|
530
|
+
@@script = filename
|
531
|
+
rescue
|
532
|
+
puts "Eliza: Error processing #{filename}: #{$!}"
|
533
|
+
return false
|
534
|
+
end
|
535
|
+
return true
|
536
|
+
end
|
537
|
+
|
538
|
+
def Eliza.dump
|
539
|
+
Eliza.clear unless defined? @@default
|
540
|
+
puts "Script: #{@@script}"
|
541
|
+
print "Starts:\n "; p @@starts
|
542
|
+
print "Stops:\n "; p @@stops
|
543
|
+
print "Vars:\n "; p @@vars
|
544
|
+
print "Aliases:\n "; p @@aliases
|
545
|
+
print "Pre:\n "; p @@pre
|
546
|
+
print "Post:\n "; p @@post
|
547
|
+
print "Default:\n "; p @@default
|
548
|
+
print "Queue:\n "; p @@queue.collect { |r| r.key }
|
549
|
+
puts
|
550
|
+
@@rules.each { |key,val| puts val }
|
551
|
+
return nil
|
552
|
+
end
|
553
|
+
|
554
|
+
def Eliza.info
|
555
|
+
Eliza.clear unless defined? @@default
|
556
|
+
|
557
|
+
words = Hash.new
|
558
|
+
npatterns = 0
|
559
|
+
|
560
|
+
@@rules.each do |k,r|
|
561
|
+
words[k] = 1 unless k[0] == ?$
|
562
|
+
r.patterns.each do |p|
|
563
|
+
npatterns += 1
|
564
|
+
p.cleanRegexp.split.each do |w|
|
565
|
+
Eliza.saveWords(w, words)
|
566
|
+
end
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
@@aliases.keys.each do |k|
|
571
|
+
Eliza.saveWords(k, words)
|
572
|
+
end
|
573
|
+
|
574
|
+
puts "Script: #{@@script}"
|
575
|
+
puts " #{@@rules.size} rules with #{npatterns} sentence patterns"
|
576
|
+
puts " #{words.length} key words: #{words.keys.sort.join(', ')}"
|
577
|
+
end
|
578
|
+
|
579
|
+
def Eliza.reset
|
580
|
+
@@rules.each do |k, r|
|
581
|
+
r.patterns.each { |p| p.reset }
|
582
|
+
end
|
583
|
+
return true
|
584
|
+
end
|
585
|
+
|
586
|
+
def Eliza.saveWords(s, hash)
|
587
|
+
return if ["a","an","in","of","the"].include?(s)
|
588
|
+
s.gsub! "(", ""
|
589
|
+
s.gsub! ")", ""
|
590
|
+
s.gsub! ".*", ""
|
591
|
+
s.gsub! "?", ""
|
592
|
+
return if s.length == 0
|
593
|
+
s.split(/\|/).each { |w| hash[w.downcase] = 1 }
|
594
|
+
end
|
468
595
|
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
@@pre.clear
|
485
|
-
@@post.clear
|
486
|
-
@@rules.clear
|
487
|
-
@@queue.clear
|
488
|
-
|
489
|
-
@@default = Rule.new(:default)
|
490
|
-
@@default.addPattern(/(.*)/)
|
491
|
-
@@default.addReassembly("$1")
|
492
|
-
end
|
493
|
-
|
494
|
-
def Eliza.run
|
495
|
-
puts @@starts[rand(@@starts.length)] if ! @@starts.empty?
|
496
|
-
loop do
|
497
|
-
s = readline(" H: ", true)
|
498
|
-
return if s.nil?
|
499
|
-
s.chomp!
|
500
|
-
next if s.empty?
|
501
|
-
if s == "bye" || s == "quit"
|
502
|
-
puts @@stops[rand(@@stops.length)] if ! @@stops.empty?
|
503
|
-
return
|
504
|
-
end
|
505
|
-
puts " C: " + Eliza.transform(s)
|
506
|
-
end
|
507
|
-
end
|
596
|
+
def Eliza.run
|
597
|
+
Eliza.clear unless defined? @@default
|
598
|
+
puts @@starts[rand(@@starts.length)] if ! @@starts.empty?
|
599
|
+
loop do
|
600
|
+
s = readline(" H: ", true)
|
601
|
+
return if s.nil?
|
602
|
+
s.chomp!
|
603
|
+
next if s.empty?
|
604
|
+
if s == "bye" || s == "quit"
|
605
|
+
puts @@stops[rand(@@stops.length)] if ! @@stops.empty?
|
606
|
+
return
|
607
|
+
end
|
608
|
+
puts " C: " + Eliza.transform(s)
|
609
|
+
end
|
610
|
+
end
|
508
611
|
|
509
|
-
end
|
612
|
+
end # class Eliza
|
510
613
|
|
511
|
-
#
|
512
|
-
#
|
614
|
+
# These state variables are accessible by any methods in a class defined inside
|
615
|
+
# the ElizaLab module
|
616
|
+
|
617
|
+
@@verbose = false
|
618
|
+
@@dataDirectory = File.join(File.dirname(__FILE__), '..', 'data', 'eliza')
|
619
|
+
@@pre = Dictionary.new
|
620
|
+
@@post = Dictionary.new
|
621
|
+
@@rules = Dictionary.new
|
622
|
+
@@word = /[a-z\-$']+/i # pattern for a "word" in the input language
|
623
|
+
@@iword = /^[a-z\-$']+/i # same, but must be the first item on the line
|
624
|
+
@@var = /\$\d+/ # variable name in reassembly string
|
625
|
+
|
626
|
+
end # module ElizaLab
|
627
|
+
|
628
|
+
end # module RubyLabs
|
629
|
+
|
630
|
+
class String
|
631
|
+
|
632
|
+
=begin rdoc
|
633
|
+
A useful operation on strings -- call +s.unquote+ to remove double quotes from
|
634
|
+
the beginning and end of string +s+.
|
635
|
+
=end
|
513
636
|
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
637
|
+
def unquote
|
638
|
+
if self[0] == ?" && self[-1] == ?"
|
639
|
+
return self.slice(1..-2)
|
640
|
+
else
|
641
|
+
return self
|
642
|
+
end
|
519
643
|
end
|
520
|
-
else
|
521
|
-
Eliza.reset
|
522
|
-
end
|
523
644
|
|
645
|
+
end
|