confabulator 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,34 +1,81 @@
1
1
  # Confabulator
2
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!"
3
+ A recursive Ruby tempting language for the procedural generation of random sentences.
4
+
5
+ ## Install
6
+
7
+ gem install confabulator
8
+
9
+ ## Use
10
+
11
+ require 'confabulator'
12
+
13
+ ### Choice blocks
14
+
15
+ Choice blocks let the parser make a random choice.
16
+
17
+ > 5.times { puts Confabulator::Parser.new("{Choice one|Choice two} and stuff").confabulate }
18
+ Choice two and stuff
19
+ Choice one and stuff
20
+ Choice two and stuff
21
+ ...
22
+
23
+ Recursion is fine (just try to avoid loops):
24
+
25
+ > 5.times { puts Confabulator::Parser.new("{Choice {1|2}|Choice 3} and stuff").confabulate }
26
+ Choice 3 and stuff
27
+ Choice 1 and stuff
28
+ Choice 2 and stuff
29
+ ...
30
+
31
+ You can differentially weight the options: {5:This is 5 times more likely|than this}
32
+
33
+ ### Substitutions
34
+
35
+ Substitutions let you re-use common templates.
36
+
37
+ > knowledge = Confabulator::Knowledge.new
38
+ > knowledge.add "world", "there" # a hash is also acceptable
39
+ > Confabulator::Parser.new("Hello, [world]!", :knowledge => knowledge).confabulate
40
+ => "Hello, there!"
41
+
42
+ Equivalently:
43
+
44
+ > knowledge.confabulate("Hello, [world]!")
45
+ => "Hello, there!"
46
+
47
+ You can ask a substitution to be capitalized:
48
+
49
+ > knowledge.confabulate("Hello, [world:c]!")
50
+ => "Hello, There!"
51
+
52
+ Or pluralized:
53
+
54
+ > knowledge.add "dude" => "friend"
55
+ > knowledge.confabulate("Hello, [dude:p]!")
56
+ => "Hello, friends!"
57
+
58
+ Substitutions can contain other substitutions in choice nodes inside of substitutions, etc., ad infinitum.
59
+
60
+ ### Escaping
61
+
62
+ You must escape the special characters {, [, `, and | with backslashes:
63
+
64
+ > knowledge.add "dude" => "friend"
65
+ > knowledge.confabulate("Hello, \\{friend\\|something\\} \\`\\`stuff\\`\\` \\[dude:p]!")
66
+ => "Hello, {friend|something} ``stuff`` [dude:p]!!"
67
+
68
+ ### Protected regions
69
+
70
+ Sometimes you want to insert user generated content without having to escape every {, [, `, and |. For this you use protected regions.
71
+
72
+ > knowledge.add "dude" => "friend"
73
+ > user_content = "protect regions [and stuff] with double backticks (`)!"
74
+ > knowledge.confabulate("Hello, ``#{user_content}``")
75
+ => "Hello, protect regions [and stuff] with double backticks (`)!"
76
+
77
+ At the moment, sequences of more than one backtick are never allowed inside of a protected region.
78
+
79
+ ## Helping out
80
+
81
+ Fork, write specs, add a feature, send me a pull request!
@@ -29,24 +29,29 @@ module Confabulator
29
29
  s0, i0 = [], index
30
30
  loop do
31
31
  i1 = index
32
- r2 = _nt_substitution
32
+ r2 = _nt_protected
33
33
  if r2
34
34
  r1 = r2
35
35
  else
36
- r3 = _nt_choice
36
+ r3 = _nt_substitution
37
37
  if r3
38
38
  r1 = r3
39
39
  else
40
- r4 = _nt_escaped_char
40
+ r4 = _nt_choice
41
41
  if r4
42
42
  r1 = r4
43
43
  else
44
- r5 = _nt_words
44
+ r5 = _nt_escaped_char
45
45
  if r5
46
46
  r1 = r5
47
47
  else
48
- @index = i1
49
- r1 = nil
48
+ r6 = _nt_words
49
+ if r6
50
+ r1 = r6
51
+ else
52
+ @index = i1
53
+ r1 = nil
54
+ end
50
55
  end
51
56
  end
52
57
  end
@@ -289,6 +294,127 @@ module Confabulator
289
294
  r0
290
295
  end
291
296
 
297
+ module Protected0
298
+ end
299
+
300
+ module Protected1
301
+ def words
302
+ elements[1]
303
+ end
304
+
305
+ end
306
+
307
+ module Protected2
308
+ def compose(kb = nil)
309
+ words.elements.map { |element|
310
+ element.text_value == "\\`" ? "`" : element.text_value
311
+ }.join
312
+ end
313
+ end
314
+
315
+ def _nt_protected
316
+ start_index = index
317
+ if node_cache[:protected].has_key?(index)
318
+ cached = node_cache[:protected][index]
319
+ if cached
320
+ cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
321
+ @index = cached.interval.end
322
+ end
323
+ return cached
324
+ end
325
+
326
+ i0, s0 = index, []
327
+ if has_terminal?("``", false, index)
328
+ r1 = instantiate_node(SyntaxNode,input, index...(index + 2))
329
+ @index += 2
330
+ else
331
+ terminal_parse_failure("``")
332
+ r1 = nil
333
+ end
334
+ s0 << r1
335
+ if r1
336
+ s2, i2 = [], index
337
+ loop do
338
+ i3 = index
339
+ i4, s4 = index, []
340
+ if has_terminal?("`", false, index)
341
+ r5 = instantiate_node(SyntaxNode,input, index...(index + 1))
342
+ @index += 1
343
+ else
344
+ terminal_parse_failure("`")
345
+ r5 = nil
346
+ end
347
+ s4 << r5
348
+ if r5
349
+ if has_terminal?('\G[^`]', true, index)
350
+ r6 = true
351
+ @index += 1
352
+ else
353
+ r6 = nil
354
+ end
355
+ s4 << r6
356
+ end
357
+ if s4.last
358
+ r4 = instantiate_node(SyntaxNode,input, i4...index, s4)
359
+ r4.extend(Protected0)
360
+ else
361
+ @index = i4
362
+ r4 = nil
363
+ end
364
+ if r4
365
+ r3 = r4
366
+ else
367
+ if has_terminal?('\G[^`]', true, index)
368
+ r7 = true
369
+ @index += 1
370
+ else
371
+ r7 = nil
372
+ end
373
+ if r7
374
+ r3 = r7
375
+ else
376
+ @index = i3
377
+ r3 = nil
378
+ end
379
+ end
380
+ if r3
381
+ s2 << r3
382
+ else
383
+ break
384
+ end
385
+ end
386
+ if s2.empty?
387
+ @index = i2
388
+ r2 = nil
389
+ else
390
+ r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
391
+ end
392
+ s0 << r2
393
+ if r2
394
+ if has_terminal?("``", false, index)
395
+ r8 = instantiate_node(SyntaxNode,input, index...(index + 2))
396
+ @index += 2
397
+ else
398
+ terminal_parse_failure("``")
399
+ r8 = nil
400
+ end
401
+ s0 << r8
402
+ end
403
+ end
404
+ if s0.last
405
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
406
+ r0.extend(Protected1)
407
+ r0.extend(Protected2)
408
+ else
409
+ @index = i0
410
+ r0 = nil
411
+ end
412
+
413
+ node_cache[:protected][start_index] = r0
414
+
415
+ r0
416
+ end
417
+
292
418
  module Substitution0
293
419
  end
294
420
 
@@ -530,11 +656,14 @@ module Confabulator
530
656
  end
531
657
 
532
658
  module EscapedChar0
659
+ def character
660
+ elements[1]
661
+ end
533
662
  end
534
663
 
535
664
  module EscapedChar1
536
665
  def compose(kb = nil)
537
- text_value
666
+ character.text_value
538
667
  end
539
668
  end
540
669
 
@@ -601,7 +730,7 @@ module Confabulator
601
730
 
602
731
  s0, i0 = [], index
603
732
  loop do
604
- if has_terminal?('\G[^\\[{}\\|\\\\]', true, index)
733
+ if has_terminal?('\G[^\\[{}`\\|\\\\]', true, index)
605
734
  r1 = true
606
735
  @index += 1
607
736
  else
@@ -626,66 +755,6 @@ module Confabulator
626
755
  r0
627
756
  end
628
757
 
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
758
  end
690
759
 
691
760
  class ConfabulatorLanguageParser < Treetop::Runtime::CompiledParser
@@ -4,6 +4,7 @@ Linguistics::use( :en )
4
4
 
5
5
  module Confabulator
6
6
  class Parser
7
+ REMOVE_SPACES = /(\S+) {2,}(\S+)/
7
8
  attr_accessor :confabulation, :kb
8
9
 
9
10
  def initialize(str, opts = {})
@@ -13,7 +14,9 @@ module Confabulator
13
14
 
14
15
  def confabulate
15
16
  if parser
16
- parser.compose(kb).squeeze(" ")
17
+ result = parser.compose(kb)
18
+ result.gsub!(REMOVE_SPACES, '\1 \2') while result =~ REMOVE_SPACES
19
+ result
17
20
  else
18
21
  ""
19
22
  end
@@ -1,3 +1,3 @@
1
1
  module Confabulator
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -80,10 +80,33 @@ describe Confabulator do
80
80
  end
81
81
 
82
82
  describe "general behavior" do
83
- it "should remove repeated spaces" do
83
+ before do
84
+ @k = Confabulator::Knowledge.new
85
+ @k.add "expand" => " is {[recursive]| not recursive}", "recursive" => "pretty cool "
86
+ end
87
+
88
+ it "should remove repeated spaces between words" do
89
+ @k.confabulate("Hello, this [expand]!").should_not =~ / /
90
+ end
91
+
92
+ it "should not remove leading spaces" do
93
+ @k.confabulate(" Hello, this [expand]!").should =~ /^ Hello/
94
+ @k.confabulate("Foo bar\n\n baz").should == "Foo bar\n\n baz"
95
+ @k.confabulate("List:\n * foo\n * bar baz bing blah").should == "List:\n * foo\n * bar baz bing blah"
96
+ end
97
+ end
98
+
99
+ describe "protected regions" do
100
+ it "should not parse content inside of the region" do
84
101
  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 =~ / /
102
+ k.add "expand" => "is ``{[recursive]|not recursive} single ticks (`) are ok``"
103
+ Confabulator::Parser.new("Hello, this [expand]!", :knowledge => k).confabulate.should == "Hello, this is {[recursive]|not recursive} single ticks (`) are ok!"
104
+ end
105
+ end
106
+
107
+ describe "escaped characters" do
108
+ it "should show up as unescaped" do
109
+ Confabulator::Parser.new("Hi \\[foo]\\{bar\\|foo\\}\\`baz\\` and stuff").confabulate.should == "Hi [foo]{bar|foo}`baz` and stuff"
87
110
  end
88
111
  end
89
112
  end
@@ -1,7 +1,7 @@
1
1
  module Confabulator
2
2
  grammar ConfabulatorLanguage
3
3
  rule sentence
4
- (substitution / choice / escaped_char / words)+ {
4
+ (protected / substitution / choice / escaped_char / words)+ {
5
5
  def compose(kb = nil)
6
6
  elements.map {|e| e.compose(kb) }.join
7
7
  end
@@ -29,6 +29,16 @@ module Confabulator
29
29
  }
30
30
  end
31
31
 
32
+ rule protected
33
+ "``" words:(("`" [^`]) / [^`])+ "``" {
34
+ def compose(kb = nil)
35
+ words.elements.map { |element|
36
+ element.text_value == "\\`" ? "`" : element.text_value
37
+ }.join
38
+ end
39
+ }
40
+ end
41
+
32
42
  rule substitution
33
43
  !'\\\\' '[' w name:( [a-zA-Z] [a-zA-Z_0-9-]* ) w options:(":" [a-zA-Z]+)? w ']' {
34
44
  def compose(kb = nil)
@@ -56,23 +66,15 @@ module Confabulator
56
66
  end
57
67
 
58
68
  rule escaped_char
59
- '\\' . {
69
+ '\\' character:. {
60
70
  def compose(kb = nil)
61
- text_value
71
+ character.text_value
62
72
  end
63
73
  }
64
74
  end
65
75
 
66
76
  rule words
67
- [^\[{}\|\\]+ {
68
- def compose(kb = nil)
69
- text_value
70
- end
71
- }
72
- end
73
-
74
- rule char
75
- !'\\\\' . {
77
+ [^\[{}`\|\\]+ {
76
78
  def compose(kb = nil)
77
79
  text_value
78
80
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: confabulator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-06 00:00:00.000000000Z
12
+ date: 2011-12-07 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &70312387498520 !ruby/object:Gem::Requirement
16
+ requirement: &70215715712220 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70312387498520
24
+ version_requirements: *70215715712220
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: treetop
27
- requirement: &70312387498100 !ruby/object:Gem::Requirement
27
+ requirement: &70215715711800 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70312387498100
35
+ version_requirements: *70215715711800
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: linguistics
38
- requirement: &70312387497680 !ruby/object:Gem::Requirement
38
+ requirement: &70215715711380 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70312387497680
46
+ version_requirements: *70215715711380
47
47
  description: ''
48
48
  email:
49
49
  - andrew@iterationlabs.com