tra38-calyx 0.6.2
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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +9 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +282 -0
- data/calyx.gemspec +24 -0
- data/lib/calyx.rb +217 -0
- data/lib/calyx/version.rb +3 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 85ba5ec4d74873b85ad9e7aafd4694191717b00b
|
4
|
+
data.tar.gz: 93e6e5cc487f09443953e46d334e746aff12d919
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 88d7fd63cdf3e7cd491cd001093d5762e5a0b8dff5716d493bafb0afde2d2ffef82014f5fb68a5bdfdcd94b19fd24b8ac215f1e18a4aa77f4f41ffca9b7e33f8
|
7
|
+
data.tar.gz: 0c407e12b96da714500fb2827bf62c2f28ab9e6ae5fd97fea4535f672672e6f685cfede0d5f2e36ea64e104c07f9bf0aa74650b7a7d7ffa91a6c5a7a88335976
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Mark Rickerby
|
4
|
+
Copyright (c) 2016 Tariq Ali
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
8
|
+
in the Software without restriction, including without limitation the rights
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
11
|
+
furnished to do so, subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
14
|
+
all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,282 @@
|
|
1
|
+
# Calyx
|
2
|
+
|
3
|
+
Calyx provides a simple API for generating text with declarative recursive grammars.
|
4
|
+
|
5
|
+
## Install
|
6
|
+
|
7
|
+
### Command Line
|
8
|
+
|
9
|
+
```
|
10
|
+
gem install calyx
|
11
|
+
```
|
12
|
+
|
13
|
+
## Gemfile
|
14
|
+
|
15
|
+
```
|
16
|
+
gem 'calyx'
|
17
|
+
```
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Calyx support two types of classes.
|
22
|
+
|
23
|
+
Classes that inherit from `Calyx::Grammar` are used to construct a set of rules that can generate a text. All grammars require a `start` rule, which specifies the starting point for generating the text structure.
|
24
|
+
|
25
|
+
Classes that inherit from `Calyx::DataTemplate` are used to construct a set of "meta-rules" that will invoke Grammar rules for you. All templates require a `write_narrative` method which specifies what "meta-rules" are being called.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'calyx'
|
29
|
+
|
30
|
+
class HelloWorld < Calyx::Grammar
|
31
|
+
start "Hello World."
|
32
|
+
end
|
33
|
+
|
34
|
+
class Greeting < Calyx::DataTemplate
|
35
|
+
def write_narrative
|
36
|
+
write HelloWorld
|
37
|
+
end
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
There are two ways to generate text. You can generate text using Calyx::Grammar by initializing the object and calling the `generate` method.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
hello = HelloWorld.new
|
45
|
+
hello.generate
|
46
|
+
# > "Hello World."
|
47
|
+
```
|
48
|
+
|
49
|
+
Or, you can generate text by initializing the Calyx::DataTemplate class and calling the `result` method.
|
50
|
+
```ruby
|
51
|
+
greeting = Greeting.new
|
52
|
+
greeting.result
|
53
|
+
# > "Hello World."
|
54
|
+
```
|
55
|
+
|
56
|
+
### Calyx::Grammar
|
57
|
+
Obviously, "Hello World" isn’t very interesting by itself. Possible variations can be added to the text using the `rule` constructor to provide a named set of text strings and the rule delimiter syntax (`{}`) within the text strings to substitute the generated content of the rule.
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
class HelloWorld < Calyx::Grammar
|
61
|
+
start '{greeting} world.'
|
62
|
+
rule :greeting, 'Hello', 'Hi', 'Hey', 'Yo'
|
63
|
+
end
|
64
|
+
|
65
|
+
hello = HelloWorld.new
|
66
|
+
|
67
|
+
hello.generate
|
68
|
+
# > "Hi world."
|
69
|
+
|
70
|
+
hello.generate
|
71
|
+
# > "Hello world."
|
72
|
+
|
73
|
+
hello.generate
|
74
|
+
# > "Yo world."
|
75
|
+
```
|
76
|
+
|
77
|
+
Rules are recursive. They can be arbitrarily nested and connected to generate larger and more complex texts.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
class HelloWorld < Calyx::Grammar
|
81
|
+
start '{greeting} {world_phrase}.'
|
82
|
+
rule :greeting, 'Hello', 'Hi', 'Hey', 'Yo'
|
83
|
+
rule :world_phrase, '{happy_adj} world', '{sad_adj} world', 'world'
|
84
|
+
rule :happy_adj, 'wonderful', 'amazing', 'bright', 'beautiful'
|
85
|
+
rule :sad_adj, 'cruel', 'miserable'
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
Nesting and hierarchy can be manipulated to balance consistency with variation. The exact same word atoms can be combined in different ways to produce strikingly different resulting texts.
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
module HelloWorld
|
93
|
+
Sentiment < Calyx::Grammar
|
94
|
+
start '{happy_phrase}', '{sad_phrase}'
|
95
|
+
rule :happy_phrase, '{happy_greeting} {happy_adj} world.'
|
96
|
+
rule :happy_greeting, 'Hello', 'Hi', 'Hey', 'Yo'
|
97
|
+
rule :happy_adj, 'wonderful', 'amazing', 'bright', 'beautiful'
|
98
|
+
rule :sad_phrase, '{sad_greeting} {sad_adj} world.'
|
99
|
+
rule :sad_greeting, 'Goodbye', 'So long', 'Farewell'
|
100
|
+
rule :sad_adj, 'cruel', 'miserable'
|
101
|
+
end
|
102
|
+
|
103
|
+
Mixed < Calyx::Grammar
|
104
|
+
start '{greeting} {adj} world.'
|
105
|
+
rule :greeting, 'Hello', 'Hi', 'Hey', 'Yo', 'Goodbye', 'So long', 'Farewell'
|
106
|
+
rule :adj, 'wonderful', 'amazing', 'bright', 'beautiful', 'cruel', 'miserable'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
By default, the outcomes of generated rules are selected with Ruby’s built-in random number generator (as seen in methods like `Kernel.rand` and `Array.sample`). If you want to supply a weighted probability list, you can pass in arrays to the rule constructor, with the first argument being the template text string and the second argument being a float representing the probability between `0` and `1` of this choice being selected.
|
112
|
+
|
113
|
+
For example, you can model the triangular distribution produced by rolling 2d6:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
class Roll2D6 < Calyx::Grammar
|
117
|
+
start(
|
118
|
+
['2', 0.0278],
|
119
|
+
['3', 0.556],
|
120
|
+
['4', 0.833],
|
121
|
+
['5', 0.1111],
|
122
|
+
['6', 0.1389],
|
123
|
+
['7', 0.1667],
|
124
|
+
['8', 0.1389],
|
125
|
+
['9', 0.1111],
|
126
|
+
['10', 0.833],
|
127
|
+
['11', 0.556],
|
128
|
+
['12', 0.278]
|
129
|
+
)
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
Or reproduce Gary Gygax’s famous generation table from the original Dungeon Master’s Guide (page 171):
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
class ChamberOrRoomContents < Calyx::Grammar
|
137
|
+
start(
|
138
|
+
[:empty, 0.6],
|
139
|
+
[:monster, 0.1],
|
140
|
+
[:monster_treasure, 0.15],
|
141
|
+
[:special, 0.05],
|
142
|
+
[:trick_trap, 0.05],
|
143
|
+
[:treasure, 0.05]
|
144
|
+
)
|
145
|
+
|
146
|
+
rule :empty, 'Empty'
|
147
|
+
rule :monster, 'Monster Only'
|
148
|
+
rule :monster_treasure, 'Monster and Treasure'
|
149
|
+
rule :special, 'Special'
|
150
|
+
rule :trick_trap, 'Trick/Trap.'
|
151
|
+
rule :treasure, 'Treasure'
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
Dot-notation is supported in template expressions, allowing you to call any available method on the `String` object returned from a rule. Formatting methods can be chained arbitrarily and will execute in the same way as they would in native Ruby code.
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
class Greeting < Calyx::Grammar
|
159
|
+
start '{hello.capitalize} there.', 'Why, {hello} there.'
|
160
|
+
rule :hello, 'hello'
|
161
|
+
end
|
162
|
+
|
163
|
+
# => "Hello there."
|
164
|
+
# => "Why, hello there."
|
165
|
+
```
|
166
|
+
|
167
|
+
In order to use more intricate natural language processing capabilities, you can embed additional methods onto the `String` class yourself, as well as use methods from existing Gems that monkeypatch `String`.
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
require 'indefinite_article'
|
171
|
+
|
172
|
+
module FullStop
|
173
|
+
def full_stop
|
174
|
+
self << '.'
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
class String
|
179
|
+
include FullStop
|
180
|
+
end
|
181
|
+
|
182
|
+
class NounsWithArticles < Calyx::Grammar
|
183
|
+
start '{fruit.with_indefinite_article.capitalize.full_stop}'
|
184
|
+
rule :fruit, 'apple', 'orange', 'banana', 'pear'
|
185
|
+
end
|
186
|
+
|
187
|
+
# => "An apple."
|
188
|
+
# => "An orange."
|
189
|
+
# => "A banana."
|
190
|
+
# => "A pear."
|
191
|
+
```
|
192
|
+
|
193
|
+
### Calyx::DataTemplate
|
194
|
+
Calyx::DataTemplate is useful for allowing a computer to write stories based on data stored within a Hash. The data can be plugged instantly into generated content, so long as you use erb syntax (to distingush from the rule delimiter syntax).
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
class StockReport < Calyx::Grammar
|
198
|
+
start "The price of one share of <%= name %> on <%= date %> is <%= price %> Yen."
|
199
|
+
end
|
200
|
+
|
201
|
+
class StockWriter < Calyx::DataTemplate
|
202
|
+
def write_narrative
|
203
|
+
write StockReport
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
cyberdyne = { :name => "Cyberdyne", :price => 1897.0, :date => Date.new(2015,1,14) }
|
208
|
+
|
209
|
+
stock_writer = StockWriter.new(cyberdyne)
|
210
|
+
stock_writer.result
|
211
|
+
# => "The price of one share of Cyberdyne on 2015-01-14 is 1897.0 Yen."
|
212
|
+
```
|
213
|
+
|
214
|
+
`conditional_write` allows Calyx::DataTemplate to choose what grammar rule to invoke. If the condition is true, use the first grammar; otherwise, use the second grammar.
|
215
|
+
```ruby
|
216
|
+
class GoodStock < Calyx::Grammar
|
217
|
+
start "You should buy stock in <%= name %> because this company has a low EPS."
|
218
|
+
end
|
219
|
+
|
220
|
+
class BadStock < Calyx:Grammar
|
221
|
+
start "You should sell stock in <%= name %> because this company has a high EPS."
|
222
|
+
end
|
223
|
+
|
224
|
+
class StockWriter < Calyx::DataTemplate
|
225
|
+
def write_narrative
|
226
|
+
conditional_write eps <= 20, GoodStock, BadStock
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
mitsui = { :name => "Mitsui", :eps => 15.8}
|
231
|
+
mitsui_writer = StockWriter.new(mitsui)
|
232
|
+
mitsui_writer.result
|
233
|
+
# => "You should buy stock in Mitsui because this company has a low EPS."
|
234
|
+
|
235
|
+
tokoyo_electric = { :name => "Tokyo Electric Power", :eps => 275.2 }
|
236
|
+
tokoyo_electric_writer = StockWriter.new(tokoyo_electric)
|
237
|
+
tokoyo_electric_writer.result
|
238
|
+
# => "You should sell stock in Tokoyo Electric Power because this company has a high EPS."
|
239
|
+
```
|
240
|
+
|
241
|
+
You may also only provide only one grammar for `conditional_write`. If the condition is false, then nothing will be written.
|
242
|
+
```ruby
|
243
|
+
class StockWriter < Calyx::DataTemplate
|
244
|
+
def write_narrative
|
245
|
+
conditional_write eps <= 20, GoodStock
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
tokoyo_electric = { :name => "Tokyo Electric Power", :eps => 275.2 }
|
250
|
+
tokoyo_electric_writer = StockWriter.new(tokoyo_electric)
|
251
|
+
tokoyo_electric_writer.result
|
252
|
+
# => ""
|
253
|
+
```
|
254
|
+
|
255
|
+
By simply specifying a few "meta-rules" with conditionals and Grammars, you can generate unique, readable narratives based on your data.
|
256
|
+
```ruby
|
257
|
+
class StockWriter < Calyx::DataTemplate
|
258
|
+
def write_narrative
|
259
|
+
write StockReport
|
260
|
+
conditional_write eps <= 20, GoodStock, BadStock
|
261
|
+
conditional_write eps <= 10, WonderfulStock
|
262
|
+
conditional_write eps >= 50, AbsolutelyHorribleStock
|
263
|
+
write ThanksForReading
|
264
|
+
end
|
265
|
+
end
|
266
|
+
```
|
267
|
+
|
268
|
+
###
|
269
|
+
|
270
|
+
## License
|
271
|
+
|
272
|
+
Calyx is open source and provided under the terms of the MIT license. Copyright (c) 2015 Mark Rickerby, (c) 2016 Tariq Ali
|
273
|
+
|
274
|
+
See the `LICENSE` file [included with the project distribution](https://github.com/tra38/calyx/blob/master/LICENSE) for more information.
|
275
|
+
|
276
|
+
## History
|
277
|
+
In November 2015, Mark Rickerby created Calyx and used that gem to create [choose-your-own adventure gamebooks](https://github.com/dariusk/NaNoGenMo-2015/issues/189). He later on wrote a [blog post](http://maetl.net/notes/storyboard/gamebook-of-dungeon-tropes) explaining his thought process.
|
278
|
+
|
279
|
+
In January 2016, Tariq Ali forked Calyx and started adding in new features to turn Calyx into a useful tool for generating data-driven narratives (robojournalism).
|
280
|
+
|
281
|
+
## Disclaimer
|
282
|
+
In the real world, you would probably not want to buy or sell Japanese stock based solely on EPS. [The MIT Encyclopedia of the Japanese Economy](https://books.google.com/books?id=0RS0CGUaef8C&pg=PA423&lpg=PA423&dq=high+earnings+per+share+in+japan&source=bl&ots=sR8KV0fBTk&sig=qHspeX72SmpsU25wz9AZnhaAxyU&hl=en&sa=X&ved=0ahUKEwjcnqqctrLKAhWKRiYKHdKACaoQ6AEIHDAA#v=onepage&q=high%20earnings%20per%20share%20in%20japan&f=false) can provide some reasons why.
|
data/calyx.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'calyx/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'tra38-calyx'
|
8
|
+
spec.version = Calyx::VERSION
|
9
|
+
spec.authors = ['Mark Rickerby','Tariq Ali']
|
10
|
+
spec.email = ['me@maetl.net','tra38@nau.edu']
|
11
|
+
|
12
|
+
spec.summary = %q{Generate text with declarative recursive grammars}
|
13
|
+
spec.description = %q{Calyx provides a simple API for generating text with declarative recursive grammars.}
|
14
|
+
spec.homepage = 'https://github.com/tra38/calyx'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.10'
|
22
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
23
|
+
spec.add_development_dependency 'rspec'
|
24
|
+
end
|
data/lib/calyx.rb
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Calyx
|
4
|
+
#The Grammar class and the Production module was written by Mark Rickerby in 2015, and licensed under the MIT license.
|
5
|
+
class Grammar
|
6
|
+
class << self
|
7
|
+
attr_accessor :registry
|
8
|
+
|
9
|
+
def start(*productions, &production)
|
10
|
+
registry[:start] = construct_rule(productions)
|
11
|
+
end
|
12
|
+
|
13
|
+
def rule(name, *productions, &production)
|
14
|
+
registry[name.to_sym] = construct_rule(productions)
|
15
|
+
end
|
16
|
+
|
17
|
+
def inherit_registry(rules)
|
18
|
+
@registry ||= {}
|
19
|
+
@registry.merge!(rules || {})
|
20
|
+
end
|
21
|
+
|
22
|
+
def inherited(subclass)
|
23
|
+
subclass.inherit_registry(@registry)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def construct_rule(productions)
|
29
|
+
if productions.first.is_a?(Enumerable)
|
30
|
+
Production::WeightedChoices.parse(productions, registry)
|
31
|
+
else
|
32
|
+
Production::Choices.parse(productions, registry)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module Production
|
38
|
+
class NonTerminal
|
39
|
+
def initialize(expansion, registry)
|
40
|
+
@expansion = expansion.to_sym
|
41
|
+
@registry = registry
|
42
|
+
end
|
43
|
+
|
44
|
+
def evaluate
|
45
|
+
@registry[@expansion].evaluate
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Terminal
|
50
|
+
def initialize(atom)
|
51
|
+
@atom = atom
|
52
|
+
end
|
53
|
+
|
54
|
+
def evaluate
|
55
|
+
@atom
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Expression
|
60
|
+
def initialize(production, methods)
|
61
|
+
@production = production
|
62
|
+
@methods = methods.map { |m| m.to_sym }
|
63
|
+
end
|
64
|
+
|
65
|
+
def evaluate
|
66
|
+
@methods.reduce(@production.evaluate) do |value,method|
|
67
|
+
value.send(method)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Concat
|
73
|
+
EXPRESSION = /(\{[A-Za-z0-9_\.]+\})/.freeze
|
74
|
+
START_TOKEN = '{'.freeze
|
75
|
+
END_TOKEN = '}'.freeze
|
76
|
+
DEREF_TOKEN = '.'.freeze
|
77
|
+
|
78
|
+
def self.parse(production, registry)
|
79
|
+
expansion = production.split(EXPRESSION).map do |atom|
|
80
|
+
if atom.is_a?(String)
|
81
|
+
if atom.chars.first == START_TOKEN && atom.chars.last == END_TOKEN
|
82
|
+
head, *tail = atom.slice(1, atom.length-2).split(DEREF_TOKEN)
|
83
|
+
rule = NonTerminal.new(head, registry)
|
84
|
+
unless tail.empty?
|
85
|
+
Expression.new(rule, tail)
|
86
|
+
else
|
87
|
+
rule
|
88
|
+
end
|
89
|
+
else
|
90
|
+
Terminal.new(atom)
|
91
|
+
end
|
92
|
+
elsif atom.is_a?(Symbol)
|
93
|
+
NonTerminal.new(atom, registry)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
self.new(expansion)
|
98
|
+
end
|
99
|
+
|
100
|
+
def initialize(expansion)
|
101
|
+
@expansion = expansion
|
102
|
+
end
|
103
|
+
|
104
|
+
def evaluate
|
105
|
+
@expansion.reduce('') do |exp, atom|
|
106
|
+
exp << atom.evaluate
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class WeightedChoices
|
112
|
+
def self.parse(productions, registry)
|
113
|
+
weights_sum = productions.reduce(0) do |memo, choice|
|
114
|
+
memo += choice.last
|
115
|
+
end
|
116
|
+
|
117
|
+
raise 'Weights must sum to 1' if weights_sum != 1.0
|
118
|
+
|
119
|
+
choices = productions.map do |choice, weight|
|
120
|
+
if choice.is_a?(String)
|
121
|
+
[Concat.parse(choice, registry), weight]
|
122
|
+
elsif choice.is_a?(Symbol)
|
123
|
+
[NonTerminal.new(choice, registry), weight]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
self.new(choices)
|
128
|
+
end
|
129
|
+
|
130
|
+
def initialize(collection)
|
131
|
+
@collection = collection
|
132
|
+
end
|
133
|
+
|
134
|
+
def evaluate
|
135
|
+
choice = @collection.max_by do |_, weight|
|
136
|
+
rand ** (1.0 / weight)
|
137
|
+
end.first
|
138
|
+
|
139
|
+
choice.evaluate
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class Choices
|
144
|
+
def self.parse(productions, registry)
|
145
|
+
choices = productions.map do |choice|
|
146
|
+
if choice.is_a?(String)
|
147
|
+
Concat.parse(choice, registry)
|
148
|
+
elsif choice.is_a?(Symbol)
|
149
|
+
NonTerminal.new(choice, registry)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
self.new(choices)
|
153
|
+
end
|
154
|
+
|
155
|
+
def initialize(collection)
|
156
|
+
@collection = collection
|
157
|
+
end
|
158
|
+
|
159
|
+
def evaluate
|
160
|
+
@collection.sample.evaluate
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def initialize(seed=nil)
|
166
|
+
@seed = seed
|
167
|
+
@seed = Time.new.to_i unless @seed
|
168
|
+
srand(@seed)
|
169
|
+
end
|
170
|
+
|
171
|
+
def registry
|
172
|
+
self.class.registry
|
173
|
+
end
|
174
|
+
|
175
|
+
def generate
|
176
|
+
registry[:start].evaluate
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
#The DataTemplate class was written by Tariq Ali in 2016, and licensed under the MIT License.
|
181
|
+
class DataTemplate
|
182
|
+
attr_reader :narrative
|
183
|
+
|
184
|
+
def initialize(data_hash = {})
|
185
|
+
data_hash.each do |key, value|
|
186
|
+
self.define_singleton_method(:"#{key}") do
|
187
|
+
value
|
188
|
+
end
|
189
|
+
end
|
190
|
+
@narrative = []
|
191
|
+
write_narrative
|
192
|
+
end
|
193
|
+
|
194
|
+
def write_narrative
|
195
|
+
#user writes in what should happened next
|
196
|
+
raise "There is no 'write_narrative' method present in your class."
|
197
|
+
end
|
198
|
+
|
199
|
+
def write(klass)
|
200
|
+
@narrative << klass.new.generate
|
201
|
+
end
|
202
|
+
|
203
|
+
def conditional_write(condition, klass_a, klass_b = nil)
|
204
|
+
if condition
|
205
|
+
@narrative << klass_a.new.generate
|
206
|
+
elsif klass_b
|
207
|
+
@narrative << klass_b.new.generate
|
208
|
+
else
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def result
|
213
|
+
ERB.new(@narrative.join(" ")).result(binding)
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tra38-calyx
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.6.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mark Rickerby
|
8
|
+
- Tariq Ali
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2016-01-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.10'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.10'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '10.0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '10.0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rspec
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
description: Calyx provides a simple API for generating text with declarative recursive
|
57
|
+
grammars.
|
58
|
+
email:
|
59
|
+
- me@maetl.net
|
60
|
+
- tra38@nau.edu
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- ".gitignore"
|
66
|
+
- ".rspec"
|
67
|
+
- ".travis.yml"
|
68
|
+
- Gemfile
|
69
|
+
- LICENSE
|
70
|
+
- README.md
|
71
|
+
- calyx.gemspec
|
72
|
+
- lib/calyx.rb
|
73
|
+
- lib/calyx/version.rb
|
74
|
+
homepage: https://github.com/tra38/calyx
|
75
|
+
licenses:
|
76
|
+
- MIT
|
77
|
+
metadata: {}
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 2.4.8
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: Generate text with declarative recursive grammars
|
98
|
+
test_files: []
|