calyx 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6c0d95f945e56c8dd61240e8868b62dfae5f4ac3
4
- data.tar.gz: 4ced529a55be7f48c54875caa37b896ae40bdc2c
3
+ metadata.gz: f131c3c4d9ce8acaa598a0f6e32b92f13da5c566
4
+ data.tar.gz: c4e81bf0f753c605946c77c4943c28f1358632b2
5
5
  SHA512:
6
- metadata.gz: 52ce90165a9e98c61f3a32849ef53d18af8c23772e5c31025a40c9edc6e3482ea896d188d56fd091485af6d22eb79fb2b58cd48096ec4c900ee802eb3402afac
7
- data.tar.gz: d25eba6f569b69261d6eb77caeae011febfbc3177e29fb66614cc6721f799dcdd0237ead1190d87fd0ae25d8df095a754a07f948a9772a931554a5a1bb4a4f88
6
+ metadata.gz: 12a3d0964f80866aae48ae67036b6e97afb1ef3d89ca005bfbcbd6a8df7f55d2400cb8774ded7f2fbefd6b64b0e8da3b9201ade2c3599670918a2fba0f9c6f29
7
+ data.tar.gz: 6bd0062c621e001f28322484dd4dd7f9762a5e12de07fbbe26500a1b9fd03655099b22ec036046c96163b479f381c37be5fb711e5eacb9efd511a983538e982e
data/.travis.yml CHANGED
@@ -4,7 +4,6 @@ rvm:
4
4
  - 2.2
5
5
  - rbx-2
6
6
  - jruby-9
7
- - ruby-head
8
7
  - jruby-head
9
8
  before_install: gem install bundler -v 1.10.6
10
9
  script: bundle exec rspec
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Calyx
2
2
 
3
+ [![Gem Version](http://img.shields.io/gem/v/yarrow.svg)](https://rubygems.org/gems/calyx)
4
+ [![Build Status](https://travis-ci.org/maetl/calyx.svg?branch=master)](https://travis-ci.org/maetl/calyx)
5
+
3
6
  Calyx provides a simple API for generating text with declarative recursive grammars.
4
7
 
5
8
  ## Install
@@ -56,6 +59,21 @@ hello.generate
56
59
  # > "Yo world."
57
60
  ```
58
61
 
62
+ ### Block Constructors
63
+
64
+ As an alternative to subclassing, you can also construct rules unique to an instance by passing a block when initializing the class:
65
+
66
+ ```ruby
67
+ hello = Calyx::Grammar.new do
68
+ start '{greeting} world.'
69
+ rule :greeting, 'Hello', 'Hi', 'Hey', 'Yo'
70
+ end
71
+
72
+ hello.generate
73
+ ```
74
+
75
+ ### Nesting and Substitution
76
+
59
77
  Rules are recursive. They can be arbitrarily nested and connected to generate larger and more complex texts.
60
78
 
61
79
  ```ruby
@@ -90,6 +108,116 @@ module HelloWorld
90
108
  end
91
109
  ```
92
110
 
111
+ ### Random Sampling
112
+
113
+ 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.
114
+
115
+ For example, you can model the triangular distribution produced by rolling 2d6:
116
+
117
+ ```ruby
118
+ class Roll2D6 < Calyx::Grammar
119
+ start(
120
+ ['2', 0.0278],
121
+ ['3', 0.556],
122
+ ['4', 0.833],
123
+ ['5', 0.1111],
124
+ ['6', 0.1389],
125
+ ['7', 0.1667],
126
+ ['8', 0.1389],
127
+ ['9', 0.1111],
128
+ ['10', 0.833],
129
+ ['11', 0.556],
130
+ ['12', 0.278]
131
+ )
132
+ end
133
+ ```
134
+
135
+ Or reproduce Gary Gygax’s famous generation table from the original Dungeon Master’s Guide (page 171):
136
+
137
+ ```ruby
138
+ class ChamberOrRoomContents < Calyx::Grammar
139
+ start(
140
+ [:empty, 0.6],
141
+ [:monster, 0.1],
142
+ [:monster_treasure, 0.15],
143
+ [:special, 0.05],
144
+ [:trick_trap, 0.05],
145
+ [:treasure, 0.05]
146
+ )
147
+
148
+ rule :empty, 'Empty'
149
+ rule :monster, 'Monster Only'
150
+ rule :monster_treasure, 'Monster and Treasure'
151
+ rule :special, 'Special'
152
+ rule :trick_trap, 'Trick/Trap.'
153
+ rule :treasure, 'Treasure'
154
+ end
155
+ ```
156
+
157
+ ### Template Expressions
158
+
159
+ Basic rule substitution uses single curly brackets as delimiters for template expressions:
160
+
161
+ ```ruby
162
+ class Fruit < Calyx::Grammar
163
+ start '{colour} {fruit}'
164
+ rule :colour, 'red', 'green', 'yellow'
165
+ rule :fruit, 'apple', 'pear', 'tomato'
166
+ end
167
+ ```
168
+
169
+ 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.
170
+
171
+ ```ruby
172
+ class Greeting < Calyx::Grammar
173
+ start '{hello.capitalize} there.', 'Why, {hello} there.'
174
+ rule :hello, 'hello'
175
+ end
176
+
177
+ # => "Hello there."
178
+ # => "Why, hello there."
179
+ ```
180
+
181
+ 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`.
182
+
183
+ ```ruby
184
+ require 'indefinite_article'
185
+
186
+ module FullStop
187
+ def full_stop
188
+ self << '.'
189
+ end
190
+ end
191
+
192
+ class String
193
+ include FullStop
194
+ end
195
+
196
+ class NounsWithArticles < Calyx::Grammar
197
+ start '{fruit.with_indefinite_article.capitalize.full_stop}'
198
+ rule :fruit, 'apple', 'orange', 'banana', 'pear'
199
+ end
200
+
201
+ # => "An apple."
202
+ # => "An orange."
203
+ # => "A banana."
204
+ # => "A pear."
205
+ ```
206
+
207
+ ## Roadmap
208
+
209
+ Rough plan for stabilising the API and features for a `1.0` release.
210
+
211
+ | Version | Features planned |
212
+ |---------|------------------|
213
+ | `0.6` | block constructor |
214
+ | `0.7` | support for template context map passed to generate |
215
+ | `0.8` | return grammar tree from evaluate/generate, with to_s being separate |
216
+ | `0.9` | support mixin/composition of rule sets rather than inheritance |
217
+ | `0.10` | support YAML format (and JSON?) |
218
+ | `0.11` | method missing metaclass API |
219
+ | `1.0` | API documentation |
220
+
93
221
  ## License
94
222
 
95
223
  Calyx is open source and provided under the terms of the MIT license. Copyright (c) 2015 Mark Rickerby
data/lib/calyx.rb CHANGED
@@ -1,34 +1,67 @@
1
1
  module Calyx
2
2
  class Grammar
3
- class << self
4
- attr_accessor :registry
3
+ class Registry
4
+ def initialize
5
+ @rules = {}
6
+ end
5
7
 
6
8
  def start(*productions, &production)
7
- registry[:start] = construct_rule(productions)
9
+ @rules[:start] = construct_rule(productions)
8
10
  end
9
11
 
10
12
  def rule(name, *productions, &production)
11
- registry[name.to_sym] = construct_rule(productions)
13
+ @rules[name.to_sym] = construct_rule(productions)
12
14
  end
13
15
 
14
- def inherit_registry(rules)
15
- @registry ||= {}
16
- @registry.merge!(rules || {})
16
+ def []=(symbol, production)
17
+ @rules[symbol] = production
17
18
  end
18
19
 
19
- def inherited(subclass)
20
- subclass.inherit_registry(@registry)
20
+ def [](symbol)
21
+ @rules[symbol]
22
+ end
23
+
24
+ def combine(rules)
25
+ @rules.merge!(rules.to_h)
26
+ end
27
+
28
+ def to_h
29
+ @rules
21
30
  end
22
31
 
32
+ private
33
+
23
34
  def construct_rule(productions)
24
35
  if productions.first.is_a?(Enumerable)
25
- Production::WeightedChoices.parse(productions, registry)
36
+ Production::WeightedChoices.parse(productions, self)
26
37
  else
27
- Production::Choices.parse(productions, registry)
38
+ Production::Choices.parse(productions, self)
28
39
  end
29
40
  end
30
41
  end
31
42
 
43
+ class << self
44
+ def registry
45
+ @registry ||= Registry.new
46
+ end
47
+
48
+ def start(*productions, &production)
49
+ registry.start(*productions)
50
+ end
51
+
52
+ def rule(name, *productions, &production)
53
+ registry.rule(name, *productions)
54
+ end
55
+
56
+ def inherit_registry(rules)
57
+ registry.combine(rules) unless rules.nil?
58
+ end
59
+
60
+ def inherited(subclass)
61
+ subclass.inherit_registry(registry)
62
+ end
63
+ end
64
+
32
65
  module Production
33
66
  class NonTerminal
34
67
  def initialize(expansion, registry)
@@ -65,14 +98,16 @@ module Calyx
65
98
  end
66
99
 
67
100
  class Concat
68
- DELIMITER = /(\{[A-Za-z0-9_\.]+\})/.freeze
69
- DEREF = '.'.freeze
101
+ EXPRESSION = /(\{[A-Za-z0-9_\.]+\})/.freeze
102
+ START_TOKEN = '{'.freeze
103
+ END_TOKEN = '}'.freeze
104
+ DEREF_TOKEN = '.'.freeze
70
105
 
71
106
  def self.parse(production, registry)
72
- expansion = production.split(DELIMITER).map do |atom|
107
+ expansion = production.split(EXPRESSION).map do |atom|
73
108
  if atom.is_a?(String)
74
- if atom.chars.first == '{' && atom.chars.last == '}'
75
- head, *tail = atom.slice(1, atom.length-2).split(DEREF)
109
+ if atom.chars.first == START_TOKEN && atom.chars.last == END_TOKEN
110
+ head, *tail = atom.slice(1, atom.length-2).split(DEREF_TOKEN)
76
111
  rule = NonTerminal.new(head, registry)
77
112
  unless tail.empty?
78
113
  Expression.new(rule, tail)
@@ -155,18 +190,21 @@ module Calyx
155
190
  end
156
191
  end
157
192
 
158
- def initialize(seed=nil)
193
+ def initialize(seed=nil, &block)
159
194
  @seed = seed
160
195
  @seed = Time.new.to_i unless @seed
161
196
  srand(@seed)
162
- end
163
197
 
164
- def registry
165
- self.class.registry
198
+ if block_given?
199
+ @registry = Registry.new
200
+ @registry.instance_eval(&block)
201
+ else
202
+ @registry = self.class.registry
203
+ end
166
204
  end
167
205
 
168
206
  def generate
169
- registry[:start].evaluate
207
+ @registry[:start].evaluate
170
208
  end
171
209
  end
172
210
  end
data/lib/calyx/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Calyx
2
- VERSION = '0.5.1'.freeze
2
+ VERSION = '0.6.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: calyx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Rickerby
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-28 00:00:00.000000000 Z
11
+ date: 2016-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -94,3 +94,4 @@ signing_key:
94
94
  specification_version: 4
95
95
  summary: Generate text with declarative recursive grammars
96
96
  test_files: []
97
+ has_rdoc: