rubylabs 0.6.2 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
data/lib/elizalab.rb CHANGED
@@ -10,31 +10,15 @@ writing their own scripts.
10
10
 
11
11
  =end
12
12
 
13
- require 'priorityqueue'
14
-
15
- require 'readline'
16
- include Readline
17
-
18
-
19
- class String
13
+ module RubyLabs
20
14
 
21
- =begin rdoc
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
- def unquote
27
- if self[0] == ?" && self[-1] == ?"
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
- attr_accessor :key, :priority, :patterns
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
- def initialize(key, priority = 1)
57
- @key = key
58
- @priority = priority
59
- @patterns = Array.new
60
- end
61
-
62
- def addPattern(expr)
63
- if expr.class == Pattern
64
- @patterns << expr
65
- else
66
- if expr.class == String
67
- expr = Regexp.new(expr.slice(1..-2))
68
- end
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
- def addReassembly(line, n = -1)
78
- @patterns[n].addText(line)
79
- end
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
- def apply(s, post = {}, verbose = false)
90
- @patterns.each do |p|
91
- if verbose
92
- print "trying pattern "
93
- p p.regexp
94
- end
95
- res = p.apply( s, post, verbose )
96
- return res if ! res.nil?
97
- end
98
- return nil
99
- end
100
-
101
- def to_s
102
- s = @key + " / " + @priority.to_s + "\n"
103
- @patterns.each { |r| s += " " + r.to_s + "\n" }
104
- return s
105
- end
106
-
107
- def inspect
108
- s = @key.inspect
109
- s += " [#{@priority}]" if @priority > 1
110
- s += " --> [\n" + @patterns.join("\n") + "]"
111
- return s
112
- end
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
- class Pattern
138
- attr_accessor :regexp, :list, :index
139
-
140
- def initialize(expr, text = nil)
141
- re = expr.inspect
142
- re.insert(1,'\b') if re =~ /^\/\w/
143
- re.insert(-2,'\b') if re =~ /\w\/$/
144
- re += "i" unless re =~ /\/i$/
145
- @regexp = eval(re)
146
-
147
- if text.nil?
148
- @list = Array.new
149
- elsif text.class == String
150
- @list = [text]
151
- elsif text.class == Array
152
- @list = text
153
- else
154
- raise "Pattern.initialize: text argument must be nil, String, or Array"
155
- end
156
-
157
- @index = 0
158
- end
159
-
160
- def addText(line)
161
- @list << line
162
- end
163
-
164
- def inc
165
- n = @index
166
- @index = (@index + 1) % @list.length
167
- return n
168
- end
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
- def apply(s, post = {}, verbose = false)
171
- md = s.match(@regexp)
172
- return nil if @list.empty? || md == nil
173
- res = @list[inc()].clone
174
- return res if res[0] == ?@
175
- puts "reassembling '#{res}'" if verbose
176
- res.gsub!(/\$\d+/) do |ns|
177
- n = ns.slice(1..-1).to_i # strip leading $, convert to int
178
- if n && md[n]
179
- puts "postprocess #{md[n]}" if verbose
180
- md[n].gsub(/[a-z\-$']+/i) do |w|
181
- (post.has_key?(w) && post[w][0] != ?$) ? post[w] : w
182
- end
183
- else
184
- warn "Pattern.apply: bad argument #{ns} in #{res}"
185
- ""
186
- end
187
- end
188
- return res
189
- end
190
-
191
- def cleanRegexp
192
- res = @regexp.inspect
193
- res.gsub!(/\\b/,"")
194
- res.gsub!(/i$/,"")
195
- return res
196
- end
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
- module Eliza
212
-
213
- # These top level constants define the "application" processed by ELIZA -- the rule
214
- # sets used to transform inputs to outputs. The arrays are all initially empty.
215
- # The 'load' method reads and parses rules and fills in these arrays. The 'reset'
216
- # method empties them.
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
- def Eliza.pre
252
- return @@pre
253
- end
254
-
255
- def Eliza.post
256
- return @@post
257
- end
258
-
259
- def Eliza.verbose
260
- @@verbose = true
261
- end
262
-
263
- def Eliza.quiet
264
- @@verbose = false
265
- end
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
- # First pass over the input -- scan each word, apply preprocessing substitutions,
268
- # add rule names to the priority queue. NOTE: this method does a destructive
269
- # update to the input line....
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
- def Eliza.scan(line, queue)
272
- line.gsub!(@@word) { |w| @@pre.has_key?(w) ? @@pre[w] : w }
273
- puts "preprocess: line = '#{line}'" if @@verbose
274
- line.scan(@@word) do |w|
275
- w.downcase!
276
- if r = @@rules[w]
277
- queue.insert(r)
278
- puts "add rule for '#{w}' to queue" if @@verbose
279
- end
280
- if (x = @@aliases[w]) && (r = @@rules[x])
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
- def Eliza.apply(line, rule)
289
- puts "applying rule: key = '#{rule.key}'" if @@verbose
290
- if res = rule.apply(line, @@post, @@verbose)
291
- if res[0] == ?@
292
- rulename = res.slice(1..-1)
293
- if @@rules[rulename]
294
- return Eliza.apply( line, @@rules[rulename] )
295
- else
296
- warn "Eliza.apply: no rule for #{rulename}"
297
- return nil
298
- end
299
- else
300
- return res
301
- end
302
- else
303
- return nil
304
- end
305
- end
306
-
307
- # The heart of the program -- apply transformation rules to an input sentence.
308
-
309
- def Eliza.transform(s)
310
- s.sub!(/[\n\.\?!\-]*$/,"") # strip trailing punctuation
311
- s.downcase!
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
- @@queue = PriorityQueue.new
314
- @@queue << @@default # initialize queue with default rule
396
+ @@queue = PriorityQueue.new
397
+ @@queue << @@default # initialize queue with default rule
315
398
 
316
- Eliza.scan(s, @@queue) # add rules for recognized key words
399
+ Eliza.scan(s, @@queue) # add rules for recognized key words
317
400
 
318
- while @@queue.length > 0 # apply rules in order of priority
319
- p @@queue.collect { |r| r.key } if @@verbose
320
- rule = @@queue.shift
321
- if result = Eliza.apply(s, rule)
322
- return result
323
- end
324
- end
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
- warn "No rules applied" if @@queue.empty?
327
- return nil
328
- end
412
+ warn "No rules applied" if @@queue.empty?
413
+ return nil
414
+ end
329
415
 
330
- # The parser calls this method to deal with directives (lines where the first
331
- # word begins with a colon)
332
-
333
- def Eliza.parseDirective(line)
334
- word = Eliza.detachWord(line)
335
- case word
336
- when "alias"
337
- if line.empty? || line[0] != ?$
338
- warn "symbol after :alias must be a variable name; ignoring '#{word} #{line}'"
339
- return
340
- else
341
- sym = Eliza.detachWord(line)
342
- @@vars[sym] = Array.new
343
- line.split.each do |s|
344
- @@aliases[s] = sym
345
- @@vars[sym] << s
346
- end
347
- end
348
- when "start"
349
- @@starts << line.unquote
350
- when "stop"
351
- @@stops << line.unquote
352
- when "pre"
353
- sym = Eliza.detachWord(line)
354
- @@pre[sym] = line.unquote
355
- when "post"
356
- sym = Eliza.detachWord(line)
357
- @@post[sym] = line.unquote
358
- when "default"
359
- @@default = line[@@word]
360
- else
361
- warn "unknown directive: :#{word} (ignored)"
362
- end
363
- end
364
-
365
- # Remove a word from the front of a line
366
-
367
- def Eliza.detachWord(line)
368
- word = line[@@word] # pattern matches the first word
369
- if line.index(" ")
370
- line.slice!(0..line.index(" ")) # delete up to end of the word
371
- line.lstrip! # in case there are extra spaces after word
372
- else
373
- line.slice!(0..-1) # line just had the one word
374
- end
375
- return word
376
- end
377
-
378
- # Check each pattern's regular expression and replace var names by alternation
379
- # constructs. If the script specified a default rule name look up that
380
- # rule and save it as the default.
381
-
382
- def Eliza.compileRules
383
- @@rules.each do |key,val|
384
- a = val.patterns()
385
- a.each do |p|
386
- expr = p.regexp.inspect
387
- expr.gsub!(/\$\w+/) { |x| @@vars[x].join("|") }
388
- p.regexp = eval(expr)
389
- end
390
- end
391
- if @@default.class == String
392
- @@default = @@rules[@@default]
393
- end
394
- end
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
- # Parse rules in file f, store them in global arrays. Strategy: use a local
397
- # var named 'rule', initially set to nil. New rules start with a single word
398
- # at the start of a line. When such a line is found in the input file, create a
399
- # new Rule object and store it in 'rule'. Subsequent lines that are part of the
400
- # current rule (lines that contain regular expressions or strings) are added to
401
- # current Rule object. Directives indicate the end of a rule, so 'rule' is reset
402
- # to nil when a directive is seen.
403
-
404
- def Eliza.load(filename)
405
- begin
406
- Eliza.reset
407
- rule = nil
408
- File.open(filename).each do |line|
409
- line.strip!
410
- next if line.empty? || line[0] == ?#
411
- if line[0] == ?:
412
- Eliza.parseDirective(line)
413
- rule = nil
414
- else
415
- if line =~ @@iword
416
- rulename, priority = line.split
417
- rule = priority ? Rule.new(rulename, priority.to_i) : Rule.new(rulename)
418
- @@rules[rule.key] = rule
419
- elsif rule.nil?
420
- warn "missing rule name? unexpected input '#{line}'"
421
- elsif line[0] == ?/
422
- if line[-1] == ?/
423
- rule.addPattern(line)
424
- else
425
- warn "badly formed expression (missing /): '#{line}'"
426
- end
427
- elsif line[0] == ?"
428
- if line[-1] == ?"
429
- rule.addReassembly(line.unquote)
430
- else
431
- warn "badly formed string (missing \"): '#{line}'"
432
- end
433
- elsif line[0] == ?@
434
- rule.addReassembly(line)
435
- else
436
- warn "unexpected line in rule for #{rulename}: '#{line}'"
437
- end
438
- end
439
- end
440
- Eliza.compileRules
441
- @@script = filename
442
- rescue
443
- puts "Error processing #{filename}: #{$!}"
444
- return false
445
- end
446
- return true
447
- end
448
-
449
- def Eliza.dump
450
- puts "Script: #{@@script}"
451
- print "Starts:\n "; p @@starts
452
- print "Stops:\n "; p @@stops
453
- print "Vars:\n "; p @@vars
454
- print "Aliases:\n "; p @@aliases
455
- print "Pre:\n "; p @@pre
456
- print "Post:\n "; p @@post
457
- print "Default:\n "; p @@default
458
- print "Queue:\n "; p @@queue.collect { |r| r.key }
459
- puts
460
- @@rules.each { |key,val| puts val }
461
- return nil
462
- end
463
-
464
- def Eliza.info
465
- puts "Script: #{@@script}"
466
- puts "#{@@rules.size} rules"
467
- puts "Keys: "
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
- keys = Array.new
470
- @@rules.each_key { |k| keys << k unless k[0] == ?$ }
471
- keys += @@aliases.keys
472
- puts " " + keys.sort.join(", ")
473
- end
474
-
475
- # Initialization -- reset all the aliases, etc, and make a single rule
476
- # that echoes the input.
477
-
478
- def Eliza.reset
479
- @@script = nil
480
- @@aliases.clear
481
- @@vars.clear
482
- @@starts.clear
483
- @@stops.clear
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
- # When invoked from command line load the script specified by the command line
512
- # argument and then start ELIZA
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
- if !defined? IRB
515
- filename = ARGV.shift or abort "Usage: eliza.rb F\n Run ELIZA using rules in file F"
516
- Eliza.verbose if ARGV[0] == "-v"
517
- if Eliza.load(filename)
518
- Eliza.run
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