confabulator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.2@confabulator --create
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in confabulator.gemspec
4
+ gemspec
@@ -0,0 +1,34 @@
1
+ # Confabulator
2
+
3
+ A Ruby grammar for procedural generation of random sentences.
4
+
5
+ ## Choice blocks
6
+
7
+ > 5.times { puts Confabulator::Parser.new("{Choice one|Choice two} and stuff").confabulate }
8
+ Choice one and stuff
9
+ Choice one and stuff
10
+ ...
11
+ # Recursion is fine!
12
+ > 5.times { puts Confabulator::Parser.new("{Choice {1|2}|Choice 3} and stuff").confabulate }
13
+ Choice 1 and stuff
14
+ Choice 2 and stuff
15
+ Choice 3 and stuff
16
+ ...
17
+ # You can differentially weight the options: {5:This is 5 times more likely|than this}
18
+
19
+ ## Substitutions
20
+
21
+ > knowledge = Confabulator::Knowledge.new
22
+ > knowledge.add "world", "there"
23
+ > Confabulator::Parser.new("Hello, [world]!", :knowledge => knowledge)
24
+ => "Hello, there!"
25
+ # Equivalently:
26
+ > knowledge.confabulate("Hello, [world]!")
27
+ => "Hello, there!"
28
+ # You can ask a substitution to be capitalized:
29
+ > knowledge.confabulate("Hello, [world:c]!")
30
+ => "Hello, There!"
31
+ # Or pluralized:
32
+ > knowledge.add "dude", "friend"
33
+ > knowledge.confabulate("Hello, [dude:p]!")
34
+ => "Hello, friends!"
@@ -0,0 +1,13 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
8
+ desc "Rebuild the TreeTop grammer parser"
9
+ task :rebuild do
10
+ grammer = File.expand_path(File.join(File.dirname(__FILE__), "src", "confabulator_language.treetop"))
11
+ output = File.expand_path(File.join(File.dirname(__FILE__), "lib", "confabulator", "language.rb"))
12
+ puts `tt #{grammer} -o #{output}`
13
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "confabulator/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "confabulator"
7
+ s.version = Confabulator::VERSION
8
+ s.authors = ["Andrew Cantino"]
9
+ s.email = ["andrew@iterationlabs.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Ruby generative grammer for conversational text}
12
+ s.description = %q{}
13
+
14
+ s.rubyforge_project = "confabulator"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec"
23
+ s.add_runtime_dependency "treetop"
24
+ s.add_runtime_dependency "linguistics"
25
+ end
@@ -0,0 +1,6 @@
1
+ require 'treetop'
2
+ require "confabulator/version"
3
+ require 'confabulator/language'
4
+ require 'confabulator/parser'
5
+ require 'confabulator/knowledge'
6
+ require 'pp'
@@ -0,0 +1,36 @@
1
+ module Confabulator
2
+ class Knowledge
3
+ def initialize
4
+ end
5
+
6
+ def find(name)
7
+ knowledge[name] || empty_confabulator
8
+ end
9
+
10
+ def add(name, sentence = nil)
11
+ if name.is_a?(Hash)
12
+ name.each do |n, s|
13
+ knowledge[n] = Confabulator::Parser.new(s, :knowledge => self)
14
+ end
15
+ else
16
+ knowledge[name] = Confabulator::Parser.new(sentence, :knowledge => self)
17
+ end
18
+ end
19
+
20
+ def empty_confabulator
21
+ @empty ||= Confabulator::Parser.new("", :knowledge => self)
22
+ end
23
+
24
+ def knowledge
25
+ @knowledge ||= {}
26
+ end
27
+
28
+ def clear
29
+ @knowledge = {}
30
+ end
31
+
32
+ def confabulate(sentence)
33
+ Parser.new(sentence, :knowledge => self).confabulate
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,695 @@
1
+ # Autogenerated from a Treetop grammar. Edits may be lost.
2
+
3
+
4
+ module Confabulator
5
+ module ConfabulatorLanguage
6
+ include Treetop::Runtime
7
+
8
+ def root
9
+ @root ||= :sentence
10
+ end
11
+
12
+ module Sentence0
13
+ def compose(kb = nil)
14
+ elements.map {|e| e.compose(kb) }.join
15
+ end
16
+ end
17
+
18
+ def _nt_sentence
19
+ start_index = index
20
+ if node_cache[:sentence].has_key?(index)
21
+ cached = node_cache[:sentence][index]
22
+ if cached
23
+ cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
24
+ @index = cached.interval.end
25
+ end
26
+ return cached
27
+ end
28
+
29
+ s0, i0 = [], index
30
+ loop do
31
+ i1 = index
32
+ r2 = _nt_substitution
33
+ if r2
34
+ r1 = r2
35
+ else
36
+ r3 = _nt_choice
37
+ if r3
38
+ r1 = r3
39
+ else
40
+ r4 = _nt_escaped_char
41
+ if r4
42
+ r1 = r4
43
+ else
44
+ r5 = _nt_words
45
+ if r5
46
+ r1 = r5
47
+ else
48
+ @index = i1
49
+ r1 = nil
50
+ end
51
+ end
52
+ end
53
+ end
54
+ if r1
55
+ s0 << r1
56
+ else
57
+ break
58
+ end
59
+ end
60
+ if s0.empty?
61
+ @index = i0
62
+ r0 = nil
63
+ else
64
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
65
+ r0.extend(Sentence0)
66
+ end
67
+
68
+ node_cache[:sentence][start_index] = r0
69
+
70
+ r0
71
+ end
72
+
73
+ module Choice0
74
+ def weight
75
+ elements[0]
76
+ end
77
+
78
+ def sentence
79
+ elements[1]
80
+ end
81
+ end
82
+
83
+ module Choice1
84
+ def weight
85
+ elements[1]
86
+ end
87
+
88
+ def sentence
89
+ elements[2]
90
+ end
91
+ end
92
+
93
+ module Choice2
94
+ def first_sentence
95
+ elements[1]
96
+ end
97
+
98
+ def rest_sentences
99
+ elements[2]
100
+ end
101
+
102
+ end
103
+
104
+ module Choice3
105
+ def compose(kb = nil)
106
+ elems = []
107
+ (first_sentence.weight.empty? ? 1 : first_sentence.weight.value).times { elems << first_sentence.sentence }
108
+ rest_sentences.elements.each do |s|
109
+ (s.weight.empty? ? 1 : s.weight.value).times { elems << s.sentence }
110
+ end
111
+ elems[elems.length * rand].compose(kb)
112
+ end
113
+ end
114
+
115
+ def _nt_choice
116
+ start_index = index
117
+ if node_cache[:choice].has_key?(index)
118
+ cached = node_cache[:choice][index]
119
+ if cached
120
+ cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
121
+ @index = cached.interval.end
122
+ end
123
+ return cached
124
+ end
125
+
126
+ i0, s0 = index, []
127
+ if has_terminal?('{', false, index)
128
+ r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
129
+ @index += 1
130
+ else
131
+ terminal_parse_failure('{')
132
+ r1 = nil
133
+ end
134
+ s0 << r1
135
+ if r1
136
+ i2, s2 = index, []
137
+ r4 = _nt_weight
138
+ if r4
139
+ r3 = r4
140
+ else
141
+ r3 = instantiate_node(SyntaxNode,input, index...index)
142
+ end
143
+ s2 << r3
144
+ if r3
145
+ r5 = _nt_sentence
146
+ s2 << r5
147
+ end
148
+ if s2.last
149
+ r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
150
+ r2.extend(Choice0)
151
+ else
152
+ @index = i2
153
+ r2 = nil
154
+ end
155
+ s0 << r2
156
+ if r2
157
+ s6, i6 = [], index
158
+ loop do
159
+ i7, s7 = index, []
160
+ if has_terminal?('|', false, index)
161
+ r8 = instantiate_node(SyntaxNode,input, index...(index + 1))
162
+ @index += 1
163
+ else
164
+ terminal_parse_failure('|')
165
+ r8 = nil
166
+ end
167
+ s7 << r8
168
+ if r8
169
+ r10 = _nt_weight
170
+ if r10
171
+ r9 = r10
172
+ else
173
+ r9 = instantiate_node(SyntaxNode,input, index...index)
174
+ end
175
+ s7 << r9
176
+ if r9
177
+ r11 = _nt_sentence
178
+ s7 << r11
179
+ end
180
+ end
181
+ if s7.last
182
+ r7 = instantiate_node(SyntaxNode,input, i7...index, s7)
183
+ r7.extend(Choice1)
184
+ else
185
+ @index = i7
186
+ r7 = nil
187
+ end
188
+ if r7
189
+ s6 << r7
190
+ else
191
+ break
192
+ end
193
+ end
194
+ r6 = instantiate_node(SyntaxNode,input, i6...index, s6)
195
+ s0 << r6
196
+ if r6
197
+ if has_terminal?('}', false, index)
198
+ r12 = instantiate_node(SyntaxNode,input, index...(index + 1))
199
+ @index += 1
200
+ else
201
+ terminal_parse_failure('}')
202
+ r12 = nil
203
+ end
204
+ s0 << r12
205
+ end
206
+ end
207
+ end
208
+ if s0.last
209
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
210
+ r0.extend(Choice2)
211
+ r0.extend(Choice3)
212
+ else
213
+ @index = i0
214
+ r0 = nil
215
+ end
216
+
217
+ node_cache[:choice][start_index] = r0
218
+
219
+ r0
220
+ end
221
+
222
+ module Weight0
223
+ def w
224
+ elements[0]
225
+ end
226
+
227
+ end
228
+
229
+ module Weight1
230
+ def value
231
+ w.text_value.to_i
232
+ end
233
+ end
234
+
235
+ def _nt_weight
236
+ start_index = index
237
+ if node_cache[:weight].has_key?(index)
238
+ cached = node_cache[:weight][index]
239
+ if cached
240
+ cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
241
+ @index = cached.interval.end
242
+ end
243
+ return cached
244
+ end
245
+
246
+ i0, s0 = index, []
247
+ s1, i1 = [], index
248
+ loop do
249
+ if has_terminal?('\G[0-9]', true, index)
250
+ r2 = true
251
+ @index += 1
252
+ else
253
+ r2 = nil
254
+ end
255
+ if r2
256
+ s1 << r2
257
+ else
258
+ break
259
+ end
260
+ end
261
+ if s1.empty?
262
+ @index = i1
263
+ r1 = nil
264
+ else
265
+ r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
266
+ end
267
+ s0 << r1
268
+ if r1
269
+ if has_terminal?(':', false, index)
270
+ r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
271
+ @index += 1
272
+ else
273
+ terminal_parse_failure(':')
274
+ r3 = nil
275
+ end
276
+ s0 << r3
277
+ end
278
+ if s0.last
279
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
280
+ r0.extend(Weight0)
281
+ r0.extend(Weight1)
282
+ else
283
+ @index = i0
284
+ r0 = nil
285
+ end
286
+
287
+ node_cache[:weight][start_index] = r0
288
+
289
+ r0
290
+ end
291
+
292
+ module Substitution0
293
+ end
294
+
295
+ module Substitution1
296
+ end
297
+
298
+ module Substitution2
299
+ def w1
300
+ elements[2]
301
+ end
302
+
303
+ def name
304
+ elements[3]
305
+ end
306
+
307
+ def w2
308
+ elements[4]
309
+ end
310
+
311
+ def options
312
+ elements[5]
313
+ end
314
+
315
+ def w3
316
+ elements[6]
317
+ end
318
+
319
+ end
320
+
321
+ module Substitution3
322
+ def compose(kb = nil)
323
+ if kb
324
+ result = kb.find(name.text_value).confabulate
325
+ if options.text_value =~ /p/
326
+ result = result.en.plural
327
+ elsif options.text_value =~ /c/
328
+ result[0] = result[0].upcase if result[0]
329
+ end
330
+ result
331
+ else
332
+ ""
333
+ end
334
+ end
335
+ end
336
+
337
+ def _nt_substitution
338
+ start_index = index
339
+ if node_cache[:substitution].has_key?(index)
340
+ cached = node_cache[:substitution][index]
341
+ if cached
342
+ cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
343
+ @index = cached.interval.end
344
+ end
345
+ return cached
346
+ end
347
+
348
+ i0, s0 = index, []
349
+ i1 = index
350
+ if has_terminal?('\\\\', false, index)
351
+ r2 = instantiate_node(SyntaxNode,input, index...(index + 2))
352
+ @index += 2
353
+ else
354
+ terminal_parse_failure('\\\\')
355
+ r2 = nil
356
+ end
357
+ if r2
358
+ r1 = nil
359
+ else
360
+ @index = i1
361
+ r1 = instantiate_node(SyntaxNode,input, index...index)
362
+ end
363
+ s0 << r1
364
+ if r1
365
+ if has_terminal?('[', false, index)
366
+ r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
367
+ @index += 1
368
+ else
369
+ terminal_parse_failure('[')
370
+ r3 = nil
371
+ end
372
+ s0 << r3
373
+ if r3
374
+ r4 = _nt_w
375
+ s0 << r4
376
+ if r4
377
+ i5, s5 = index, []
378
+ if has_terminal?('\G[a-zA-Z]', true, index)
379
+ r6 = true
380
+ @index += 1
381
+ else
382
+ r6 = nil
383
+ end
384
+ s5 << r6
385
+ if r6
386
+ s7, i7 = [], index
387
+ loop do
388
+ if has_terminal?('\G[a-zA-Z_0-9-]', true, index)
389
+ r8 = true
390
+ @index += 1
391
+ else
392
+ r8 = nil
393
+ end
394
+ if r8
395
+ s7 << r8
396
+ else
397
+ break
398
+ end
399
+ end
400
+ r7 = instantiate_node(SyntaxNode,input, i7...index, s7)
401
+ s5 << r7
402
+ end
403
+ if s5.last
404
+ r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
405
+ r5.extend(Substitution0)
406
+ else
407
+ @index = i5
408
+ r5 = nil
409
+ end
410
+ s0 << r5
411
+ if r5
412
+ r9 = _nt_w
413
+ s0 << r9
414
+ if r9
415
+ i11, s11 = index, []
416
+ if has_terminal?(":", false, index)
417
+ r12 = instantiate_node(SyntaxNode,input, index...(index + 1))
418
+ @index += 1
419
+ else
420
+ terminal_parse_failure(":")
421
+ r12 = nil
422
+ end
423
+ s11 << r12
424
+ if r12
425
+ s13, i13 = [], index
426
+ loop do
427
+ if has_terminal?('\G[a-zA-Z]', true, index)
428
+ r14 = true
429
+ @index += 1
430
+ else
431
+ r14 = nil
432
+ end
433
+ if r14
434
+ s13 << r14
435
+ else
436
+ break
437
+ end
438
+ end
439
+ if s13.empty?
440
+ @index = i13
441
+ r13 = nil
442
+ else
443
+ r13 = instantiate_node(SyntaxNode,input, i13...index, s13)
444
+ end
445
+ s11 << r13
446
+ end
447
+ if s11.last
448
+ r11 = instantiate_node(SyntaxNode,input, i11...index, s11)
449
+ r11.extend(Substitution1)
450
+ else
451
+ @index = i11
452
+ r11 = nil
453
+ end
454
+ if r11
455
+ r10 = r11
456
+ else
457
+ r10 = instantiate_node(SyntaxNode,input, index...index)
458
+ end
459
+ s0 << r10
460
+ if r10
461
+ r15 = _nt_w
462
+ s0 << r15
463
+ if r15
464
+ if has_terminal?(']', false, index)
465
+ r16 = instantiate_node(SyntaxNode,input, index...(index + 1))
466
+ @index += 1
467
+ else
468
+ terminal_parse_failure(']')
469
+ r16 = nil
470
+ end
471
+ s0 << r16
472
+ end
473
+ end
474
+ end
475
+ end
476
+ end
477
+ end
478
+ end
479
+ if s0.last
480
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
481
+ r0.extend(Substitution2)
482
+ r0.extend(Substitution3)
483
+ else
484
+ @index = i0
485
+ r0 = nil
486
+ end
487
+
488
+ node_cache[:substitution][start_index] = r0
489
+
490
+ r0
491
+ end
492
+
493
+ module W0
494
+ def compose(kb = nil)
495
+ text_value
496
+ end
497
+ end
498
+
499
+ def _nt_w
500
+ start_index = index
501
+ if node_cache[:w].has_key?(index)
502
+ cached = node_cache[:w][index]
503
+ if cached
504
+ cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
505
+ @index = cached.interval.end
506
+ end
507
+ return cached
508
+ end
509
+
510
+ s0, i0 = [], index
511
+ loop do
512
+ if has_terminal?('\G[ \\t]', true, index)
513
+ r1 = true
514
+ @index += 1
515
+ else
516
+ r1 = nil
517
+ end
518
+ if r1
519
+ s0 << r1
520
+ else
521
+ break
522
+ end
523
+ end
524
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
525
+ r0.extend(W0)
526
+
527
+ node_cache[:w][start_index] = r0
528
+
529
+ r0
530
+ end
531
+
532
+ module EscapedChar0
533
+ end
534
+
535
+ module EscapedChar1
536
+ def compose(kb = nil)
537
+ text_value
538
+ end
539
+ end
540
+
541
+ def _nt_escaped_char
542
+ start_index = index
543
+ if node_cache[:escaped_char].has_key?(index)
544
+ cached = node_cache[:escaped_char][index]
545
+ if cached
546
+ cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
547
+ @index = cached.interval.end
548
+ end
549
+ return cached
550
+ end
551
+
552
+ i0, s0 = index, []
553
+ if has_terminal?('\\', false, index)
554
+ r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
555
+ @index += 1
556
+ else
557
+ terminal_parse_failure('\\')
558
+ r1 = nil
559
+ end
560
+ s0 << r1
561
+ if r1
562
+ if index < input_length
563
+ r2 = instantiate_node(SyntaxNode,input, index...(index + 1))
564
+ @index += 1
565
+ else
566
+ terminal_parse_failure("any character")
567
+ r2 = nil
568
+ end
569
+ s0 << r2
570
+ end
571
+ if s0.last
572
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
573
+ r0.extend(EscapedChar0)
574
+ r0.extend(EscapedChar1)
575
+ else
576
+ @index = i0
577
+ r0 = nil
578
+ end
579
+
580
+ node_cache[:escaped_char][start_index] = r0
581
+
582
+ r0
583
+ end
584
+
585
+ module Words0
586
+ def compose(kb = nil)
587
+ text_value
588
+ end
589
+ end
590
+
591
+ def _nt_words
592
+ start_index = index
593
+ if node_cache[:words].has_key?(index)
594
+ cached = node_cache[:words][index]
595
+ if cached
596
+ cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
597
+ @index = cached.interval.end
598
+ end
599
+ return cached
600
+ end
601
+
602
+ s0, i0 = [], index
603
+ loop do
604
+ if has_terminal?('\G[^\\[{}\\|\\\\]', true, index)
605
+ r1 = true
606
+ @index += 1
607
+ else
608
+ r1 = nil
609
+ end
610
+ if r1
611
+ s0 << r1
612
+ else
613
+ break
614
+ end
615
+ end
616
+ if s0.empty?
617
+ @index = i0
618
+ r0 = nil
619
+ else
620
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
621
+ r0.extend(Words0)
622
+ end
623
+
624
+ node_cache[:words][start_index] = r0
625
+
626
+ r0
627
+ end
628
+
629
+ module Char0
630
+ end
631
+
632
+ module Char1
633
+ def compose(kb = nil)
634
+ text_value
635
+ end
636
+ end
637
+
638
+ def _nt_char
639
+ start_index = index
640
+ if node_cache[:char].has_key?(index)
641
+ cached = node_cache[:char][index]
642
+ if cached
643
+ cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
644
+ @index = cached.interval.end
645
+ end
646
+ return cached
647
+ end
648
+
649
+ i0, s0 = index, []
650
+ i1 = index
651
+ if has_terminal?('\\\\', false, index)
652
+ r2 = instantiate_node(SyntaxNode,input, index...(index + 2))
653
+ @index += 2
654
+ else
655
+ terminal_parse_failure('\\\\')
656
+ r2 = nil
657
+ end
658
+ if r2
659
+ r1 = nil
660
+ else
661
+ @index = i1
662
+ r1 = instantiate_node(SyntaxNode,input, index...index)
663
+ end
664
+ s0 << r1
665
+ if r1
666
+ if index < input_length
667
+ r3 = instantiate_node(SyntaxNode,input, index...(index + 1))
668
+ @index += 1
669
+ else
670
+ terminal_parse_failure("any character")
671
+ r3 = nil
672
+ end
673
+ s0 << r3
674
+ end
675
+ if s0.last
676
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
677
+ r0.extend(Char0)
678
+ r0.extend(Char1)
679
+ else
680
+ @index = i0
681
+ r0 = nil
682
+ end
683
+
684
+ node_cache[:char][start_index] = r0
685
+
686
+ r0
687
+ end
688
+
689
+ end
690
+
691
+ class ConfabulatorLanguageParser < Treetop::Runtime::CompiledParser
692
+ include ConfabulatorLanguage
693
+ end
694
+
695
+ end
@@ -0,0 +1,31 @@
1
+ require "rubygems"
2
+ require "linguistics"
3
+ Linguistics::use( :en )
4
+
5
+ module Confabulator
6
+ class Parser
7
+ attr_accessor :confabulation, :kb
8
+
9
+ def initialize(str, opts = {})
10
+ self.confabulation = str
11
+ self.kb = opts[:knowledge]
12
+ end
13
+
14
+ def confabulate
15
+ if parser
16
+ parser.compose(kb).squeeze(" ")
17
+ else
18
+ ""
19
+ end
20
+ end
21
+
22
+ def parser
23
+ if !@parsed # this caches even a nil result
24
+ @cached_parser = ConfabulatorLanguageParser.new.parse(confabulation)
25
+ @parsed = true
26
+ end
27
+
28
+ @cached_parser
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module Confabulator
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,89 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ describe Confabulator do
5
+ describe "choice blocks" do
6
+ it "should work" do
7
+ 100.times.map {
8
+ Confabulator::Parser.new("{Choice one|Choice two} and stuff").confabulate
9
+ }.uniq.sort.should == [
10
+ "Choice one and stuff",
11
+ "Choice two and stuff"
12
+ ]
13
+ end
14
+
15
+ it "should be recursive" do
16
+ 100.times.map {
17
+ Confabulator::Parser.new("{Choice {1|2}|Choice 3} and stuff").confabulate
18
+ }.uniq.sort.should == [
19
+ "Choice 1 and stuff",
20
+ "Choice 2 and stuff",
21
+ "Choice 3 and stuff"
22
+ ]
23
+ end
24
+
25
+ it "should allow differential weighting" do
26
+ one = two = 0
27
+ 500.times do
28
+ case Confabulator::Parser.new("{5:Choice 1|Choice 2} and stuff").confabulate
29
+ when "Choice 1 and stuff"
30
+ one += 1
31
+ when "Choice 2 and stuff"
32
+ two += 1
33
+ end
34
+ end
35
+ one.should > two * 3
36
+ one.should < two * 7
37
+ end
38
+ end
39
+
40
+ describe "substitutions" do
41
+ it "should use the knowledge base" do
42
+ knowledge = Confabulator::Knowledge.new
43
+ knowledge.add "world", "there"
44
+ Confabulator::Parser.new("Hello, [world]!", :knowledge => knowledge).confabulate.should == "Hello, there!"
45
+ end
46
+
47
+ it "should return an empty string if it cannot be found" do
48
+ Confabulator::Parser.new("Hello, [world]!").confabulate.should == "Hello, !"
49
+ end
50
+
51
+ it "should work recursively" do
52
+ k = Confabulator::Knowledge.new
53
+ k.add "expand" => "is [recursive]",
54
+ "recursive" => "pretty cool"
55
+ k.confabulate("Hello, this [expand]!").should == "Hello, this is pretty cool!"
56
+ end
57
+
58
+ it "should work with choices too" do
59
+ k = Confabulator::Knowledge.new
60
+ k.add "expand" => "is {[recursive]|not recursive}", "recursive" => "pretty cool"
61
+ 100.times.map {
62
+ Confabulator::Parser.new("Hello, this [expand]!", :knowledge => k).confabulate
63
+ }.uniq.sort.should == [
64
+ "Hello, this is not recursive!",
65
+ "Hello, this is pretty cool!"
66
+ ]
67
+ end
68
+
69
+ it "should be able to capitalize" do
70
+ k = Confabulator::Knowledge.new
71
+ k.add "blah" => "world foo"
72
+ k.confabulate("Hello. [blah:c]!").should == "Hello. World foo!"
73
+ end
74
+
75
+ it "should be able to pluralize" do
76
+ k = Confabulator::Knowledge.new
77
+ k.add "blah" => "ancient dog"
78
+ k.confabulate("Many [blah:p]!").should == "Many ancient dogs!"
79
+ end
80
+ end
81
+
82
+ describe "general behavior" do
83
+ it "should remove repeated spaces" do
84
+ k = Confabulator::Knowledge.new
85
+ k.add "expand" => " is {[recursive]| not recursive}", "recursive" => "pretty cool "
86
+ Confabulator::Parser.new("Hello, this [expand]!", :knowledge => k).confabulate.should_not =~ / /
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format s -c
3
+ --loadby mtime
4
+ --reverse
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'rspec'
3
+ $: << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
4
+ require 'confabulator'
5
+
6
+ RSpec.configure do |c|
7
+ end
@@ -0,0 +1,82 @@
1
+ module Confabulator
2
+ grammar ConfabulatorLanguage
3
+ rule sentence
4
+ (substitution / choice / escaped_char / words)+ {
5
+ def compose(kb = nil)
6
+ elements.map {|e| e.compose(kb) }.join
7
+ end
8
+ }
9
+ end
10
+
11
+ rule choice
12
+ '{' first_sentence:(weight:weight? sentence) rest_sentences:('|' weight:weight? sentence)* '}' {
13
+ def compose(kb = nil)
14
+ elems = []
15
+ (first_sentence.weight.empty? ? 1 : first_sentence.weight.value).times { elems << first_sentence.sentence }
16
+ rest_sentences.elements.each do |s|
17
+ (s.weight.empty? ? 1 : s.weight.value).times { elems << s.sentence }
18
+ end
19
+ elems[elems.length * rand].compose(kb)
20
+ end
21
+ }
22
+ end
23
+
24
+ rule weight
25
+ w:([0-9]+) ':' {
26
+ def value
27
+ w.text_value.to_i
28
+ end
29
+ }
30
+ end
31
+
32
+ rule substitution
33
+ !'\\\\' '[' w name:( [a-zA-Z] [a-zA-Z_0-9-]* ) w options:(":" [a-zA-Z]+)? w ']' {
34
+ def compose(kb = nil)
35
+ if kb
36
+ result = kb.find(name.text_value).confabulate
37
+ if options.text_value =~ /p/
38
+ result = result.en.plural
39
+ elsif options.text_value =~ /c/
40
+ result[0] = result[0].upcase if result[0]
41
+ end
42
+ result
43
+ else
44
+ ""
45
+ end
46
+ end
47
+ }
48
+ end
49
+
50
+ rule w
51
+ [ \t]* {
52
+ def compose(kb = nil)
53
+ text_value
54
+ end
55
+ }
56
+ end
57
+
58
+ rule escaped_char
59
+ '\\' . {
60
+ def compose(kb = nil)
61
+ text_value
62
+ end
63
+ }
64
+ end
65
+
66
+ rule words
67
+ [^\[{}\|\\]+ {
68
+ def compose(kb = nil)
69
+ text_value
70
+ end
71
+ }
72
+ end
73
+
74
+ rule char
75
+ !'\\\\' . {
76
+ def compose(kb = nil)
77
+ text_value
78
+ end
79
+ }
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ $: << File.expand_path(File.join(File.dirname(__FILE__), "lib"))
3
+ require 'confabulator'
4
+
5
+ kb = Confabulator::Knowledge.new
6
+ kb.add "ok" => "{ok|sure|sure thing|no problem|gotcha}",
7
+ "time" => "{current time|time}"
8
+ puts kb.confabulate("[ok], the [time] is #{Time.now}")
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: confabulator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Andrew Cantino
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-12-06 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70312387498520 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70312387498520
25
+ - !ruby/object:Gem::Dependency
26
+ name: treetop
27
+ requirement: &70312387498100 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70312387498100
36
+ - !ruby/object:Gem::Dependency
37
+ name: linguistics
38
+ requirement: &70312387497680 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70312387497680
47
+ description: ''
48
+ email:
49
+ - andrew@iterationlabs.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - .rvmrc
56
+ - Gemfile
57
+ - README.markdown
58
+ - Rakefile
59
+ - confabulator.gemspec
60
+ - lib/confabulator.rb
61
+ - lib/confabulator/knowledge.rb
62
+ - lib/confabulator/language.rb
63
+ - lib/confabulator/parser.rb
64
+ - lib/confabulator/version.rb
65
+ - spec/confabulator_spec.rb
66
+ - spec/spec.opts
67
+ - spec/spec_helper.rb
68
+ - src/confabulator_language.treetop
69
+ - test_grammer.rb
70
+ homepage: ''
71
+ licenses: []
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project: confabulator
90
+ rubygems_version: 1.8.10
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: Ruby generative grammer for conversational text
94
+ test_files:
95
+ - spec/confabulator_spec.rb
96
+ - spec/spec.opts
97
+ - spec/spec_helper.rb