confabulator 0.0.1
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/.gitignore +4 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/README.markdown +34 -0
- data/Rakefile +13 -0
- data/confabulator.gemspec +25 -0
- data/lib/confabulator.rb +6 -0
- data/lib/confabulator/knowledge.rb +36 -0
- data/lib/confabulator/language.rb +695 -0
- data/lib/confabulator/parser.rb +31 -0
- data/lib/confabulator/version.rb +3 -0
- data/spec/confabulator_spec.rb +89 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +7 -0
- data/src/confabulator_language.treetop +82 -0
- data/test_grammer.rb +8 -0
- metadata +97 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.2@confabulator --create
|
data/Gemfile
ADDED
data/README.markdown
ADDED
@@ -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!"
|
data/Rakefile
ADDED
@@ -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
|
data/lib/confabulator.rb
ADDED
@@ -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,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
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -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
|
data/test_grammer.rb
ADDED
@@ -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
|