calyx 0.17.0 → 0.17.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CODE_OF_CONDUCT.md +46 -0
- data/CONTRIBUTING.md +6 -18
- data/README.md +1 -1
- data/docs/404.html +3 -20
- data/docs/_config.yml +2 -0
- data/docs/_layouts/default.html +21 -4
- data/docs/assets/calyx-docs.css +640 -6042
- data/docs/calyx-docs.scss +65 -0
- data/docs/guide/context.md +45 -0
- data/docs/guide/examples.md +59 -0
- data/docs/guide/expressions.md +175 -0
- data/docs/guide/formats.md +37 -0
- data/docs/guide/getting-started.md +129 -0
- data/docs/guide/installation.md +19 -0
- data/docs/guide/random.md +100 -0
- data/docs/guide/results.md +62 -0
- data/docs/index.html +24 -2
- data/docs/package-lock.json +9 -9
- data/docs/package.json +1 -0
- data/lib/calyx/grammar.rb +19 -10
- data/lib/calyx/options.rb +2 -2
- data/lib/calyx/registry.rb +3 -3
- data/lib/calyx/result.rb +5 -4
- data/lib/calyx/version.rb +1 -1
- metadata +11 -3
data/docs/calyx-docs.scss
CHANGED
@@ -1 +1,66 @@
|
|
1
|
+
$green: #218764;
|
2
|
+
|
3
|
+
$family-monospace: "Oxygen Mono", "Monaco", Inconsolata, monospace;
|
4
|
+
$family-sans-serif: "Libre Franklin", sans-serif;
|
5
|
+
|
6
|
+
$background: #fafafa;
|
7
|
+
|
8
|
+
$content-pre-padding: 0;
|
9
|
+
$content-heading-color: #218764;
|
10
|
+
$content-heading-weight: 700;
|
11
|
+
|
12
|
+
$navbar-item-color: #fff;
|
13
|
+
$navbar-background-color: #1BDE81;
|
14
|
+
$navbar-item-hover-color: $navbar-item-color;
|
15
|
+
$navbar-item-hover-background-color: darken($navbar-background-color, 6%);
|
16
|
+
$navbar-item-active-background-color: #1BDE81;
|
17
|
+
|
1
18
|
@import "bulma";
|
19
|
+
@import "syntax";
|
20
|
+
@import "styleguide";
|
21
|
+
|
22
|
+
body {
|
23
|
+
font-weight: 500;
|
24
|
+
height: 100%;
|
25
|
+
background-color: $background;
|
26
|
+
}
|
27
|
+
|
28
|
+
.sidebar {
|
29
|
+
background-color: #eee;
|
30
|
+
height: calc(100vh - 3.25rem);
|
31
|
+
}
|
32
|
+
|
33
|
+
.navbar-item {
|
34
|
+
font-weight: 500;
|
35
|
+
}
|
36
|
+
|
37
|
+
.navbar-brand {
|
38
|
+
.navbar-item {
|
39
|
+
font-family: "Work Sans";
|
40
|
+
font-weight: 800;
|
41
|
+
font-size: 24px;
|
42
|
+
line-height: 24px;
|
43
|
+
|
44
|
+
span {
|
45
|
+
margin-left: 6px;
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
.content {
|
51
|
+
h1, h2, h3 {
|
52
|
+
letter-spacing: -1px;
|
53
|
+
font-family: "Work Sans";
|
54
|
+
font-weight: 700;
|
55
|
+
}
|
56
|
+
|
57
|
+
.highlighter-rouge {
|
58
|
+
margin-bottom: 1rem;
|
59
|
+
}
|
60
|
+
|
61
|
+
pre {
|
62
|
+
code {
|
63
|
+
font-size: 1em;
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
@@ -0,0 +1,45 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Context
|
4
|
+
permalink: /context/
|
5
|
+
---
|
6
|
+
|
7
|
+
j,jnnnnnnjjj
|
8
|
+
|
9
|
+
## Constructing rules
|
10
|
+
|
11
|
+
To achieve particular effects, structured repetition of grammar rules can be extremely useful, but these sets of rules can be boring and error-prone to write by hand.
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
row "{point}{point}{point}{point}{point}{point}{point}{point}{point}{point}{point}{point}"
|
15
|
+
```
|
16
|
+
|
17
|
+
An easier
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
row (0..12).map { "{point}" }.join
|
21
|
+
```
|
22
|
+
|
23
|
+
## Injecting rules
|
24
|
+
|
25
|
+
Sometimes it’s useful to calculate template expansions outside of Calyx and inject them where they’re needed, rather than rely on the hard-coded rules defined in grammar classes and object instances.
|
26
|
+
|
27
|
+
The `#generate` method accepts an optional context map parameter which can be used to dynamically combine rules with an existing grammar during the generation process. This feature is generally used like a traditional template engine to substitute individual values that aren’t known ahead of time.
|
28
|
+
|
29
|
+
The context map is a hash, with the keys being symbols defining the rules and the values being template expansion strings.
|
30
|
+
|
31
|
+
The following example demonstrates how a field returned from a database query could be injected into a grammar to produce a randomized welcome message for users of an app.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
greeting = Calyx::Grammar.new do
|
35
|
+
start 'Hi {username}', 'Welcome back {username}', 'Hola {username}'
|
36
|
+
end
|
37
|
+
|
38
|
+
user = User.find(id: user_id)
|
39
|
+
|
40
|
+
context = {
|
41
|
+
username: user.name
|
42
|
+
}
|
43
|
+
|
44
|
+
greeting.generate(context)
|
45
|
+
```
|
@@ -0,0 +1,59 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Examples
|
4
|
+
permalink: /examples/
|
5
|
+
---
|
6
|
+
|
7
|
+
# Examples
|
8
|
+
|
9
|
+
The best way to get started quickly is to install the gem and run the examples locally.
|
10
|
+
|
11
|
+
## Any Gradient
|
12
|
+
|
13
|
+
Requires Roda and Rack to be available.
|
14
|
+
|
15
|
+
```
|
16
|
+
gem install roda
|
17
|
+
```
|
18
|
+
|
19
|
+
Demonstrates how to use Calyx to construct SVG graphics. **Any Gradient** generates a rectangle with a linear gradient of random colours.
|
20
|
+
|
21
|
+
Run as a web server and preview the output in a browser (`http://localhost:9292`):
|
22
|
+
|
23
|
+
```
|
24
|
+
ruby examples/any_gradient.rb
|
25
|
+
```
|
26
|
+
|
27
|
+
Or generate SVG files via a command line pipe:
|
28
|
+
|
29
|
+
```
|
30
|
+
ruby examples/any_gradient > gradient1.xml
|
31
|
+
```
|
32
|
+
|
33
|
+
## Tiny Woodland Bot
|
34
|
+
|
35
|
+
Requires the Twitter client gem and API access configured for a specific Twitter handle.
|
36
|
+
|
37
|
+
```
|
38
|
+
gem install twitter
|
39
|
+
```
|
40
|
+
|
41
|
+
Demonstrates how to use Calyx to make a minimal Twitter bot that periodically posts unique tweets. See [@tiny_woodland on Twitter](https://twitter.com/tiny_woodland) and the [writeup here](http://maetl.net/notes/storyboard/tiny-woodlands).
|
42
|
+
|
43
|
+
```
|
44
|
+
TWITTER_CONSUMER_KEY=XXX-XXX
|
45
|
+
TWITTER_CONSUMER_SECRET=XXX-XXX
|
46
|
+
TWITTER_ACCESS_TOKEN=XXX-XXX
|
47
|
+
TWITTER_CONSUMER_SECRET=XXX-XXX
|
48
|
+
ruby examples/tiny_woodland_bot.rb
|
49
|
+
```
|
50
|
+
|
51
|
+
## Faker
|
52
|
+
|
53
|
+
[Faker](https://github.com/stympy/faker) is a popular library for generating fake names and associated sample data like internet addresses, company names and locations.
|
54
|
+
|
55
|
+
This example demonstrates how to use Calyx to reproduce the same functionality using custom lists defined in a YAML configuration file.
|
56
|
+
|
57
|
+
```
|
58
|
+
ruby examples/faker.rb
|
59
|
+
```
|
@@ -0,0 +1,175 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Template Expressions
|
4
|
+
permalink: /expressions/
|
5
|
+
---
|
6
|
+
|
7
|
+
# Template Expressions
|
8
|
+
|
9
|
+
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.
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
greeting = Calyx::Grammar.new do
|
13
|
+
start '{hello.capitalize} there.', 'Why, {hello} there.'
|
14
|
+
hello 'hello', 'hi'
|
15
|
+
end
|
16
|
+
|
17
|
+
4.times { greeting.generate }
|
18
|
+
# => "Hello there."
|
19
|
+
# => "Hi there."
|
20
|
+
# => "Why, hello there."
|
21
|
+
# => "Why, hi there."
|
22
|
+
```
|
23
|
+
|
24
|
+
You can also extend the grammar with custom modifiers that provide useful formatting functions.
|
25
|
+
|
26
|
+
### Filters
|
27
|
+
|
28
|
+
Filters accept an input string and return the transformed output:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
greeting = Calyx::Grammar.new do
|
32
|
+
filter :shoutycaps do |input|
|
33
|
+
input.upcase
|
34
|
+
end
|
35
|
+
|
36
|
+
start '{hello.shoutycaps} there.', 'Why, {hello.shoutycaps} there.'
|
37
|
+
hello 'hello', 'hi'
|
38
|
+
end
|
39
|
+
|
40
|
+
4.times { greeting.generate }
|
41
|
+
# => "HELLO there."
|
42
|
+
# => "HI there."
|
43
|
+
# => "Why, HELLO there."
|
44
|
+
# => "Why, HI there."
|
45
|
+
```
|
46
|
+
|
47
|
+
### Mappings
|
48
|
+
|
49
|
+
The mapping shortcut allows you to specify a map of regex patterns pointing to their resulting substitution strings:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
green_bottle = Calyx::Grammar.new do
|
53
|
+
mapping :pluralize, /(.+)/ => '\\1s'
|
54
|
+
start 'One green {bottle}.', 'Two green {bottle.pluralize}.'
|
55
|
+
bottle 'bottle'
|
56
|
+
end
|
57
|
+
|
58
|
+
2.times { green_bottle.generate }
|
59
|
+
# => "One green bottle."
|
60
|
+
# => "Two green bottles."
|
61
|
+
```
|
62
|
+
|
63
|
+
### Modifier Mixins
|
64
|
+
|
65
|
+
In order to use more intricate rewriting and formatting methods in a modifier chain, you can add methods to a module and embed it in a grammar using the `modifier` classmethod.
|
66
|
+
|
67
|
+
Modifier methods accept a single argument representing the input string from the previous step in the expression chain and must return a string, representing the modified output.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
module FullStop
|
71
|
+
def full_stop(input)
|
72
|
+
input << '.'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
hello = Calyx::Grammar.new do
|
77
|
+
modifier FullStop
|
78
|
+
start '{hello.capitalize.full_stop}'
|
79
|
+
hello 'hello'
|
80
|
+
end
|
81
|
+
|
82
|
+
hello.generate
|
83
|
+
# => "Hello."
|
84
|
+
```
|
85
|
+
|
86
|
+
To share custom modifiers across multiple grammars, you can include the module in `Calyx::Modifiers`. This will make the methods available to all subsequent instances:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
module FullStop
|
90
|
+
def full_stop(input)
|
91
|
+
input << '.'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class Calyx::Modifiers
|
96
|
+
include FullStop
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
### Monkeypatching String
|
101
|
+
|
102
|
+
Alternatively, you can combine methods from existing Gems that monkeypatch `String`:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
require 'indefinite_article'
|
106
|
+
|
107
|
+
module FullStop
|
108
|
+
def full_stop
|
109
|
+
self << '.'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class String
|
114
|
+
include FullStop
|
115
|
+
end
|
116
|
+
|
117
|
+
noun_articles = Calyx::Grammar.new do
|
118
|
+
start '{fruit.with_indefinite_article.capitalize.full_stop}'
|
119
|
+
fruit 'apple', 'orange', 'banana', 'pear'
|
120
|
+
end
|
121
|
+
|
122
|
+
4.times { noun_articles.generate }
|
123
|
+
# => "An apple."
|
124
|
+
# => "An orange."
|
125
|
+
# => "A banana."
|
126
|
+
# => "A pear."
|
127
|
+
```
|
128
|
+
|
129
|
+
### Memoized Rules
|
130
|
+
|
131
|
+
Rule expansions can be ‘memoized’ so that multiple references to the same rule return the same value. This is useful for picking a noun from a list and reusing it in multiple places within a text.
|
132
|
+
|
133
|
+
The `@` sigil is used to mark memoized rules. This evaluates the rule and stores it in memory the first time it’s referenced. All subsequent references to the memoized rule use the same stored value.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
# Without memoization
|
137
|
+
grammar = Calyx::Grammar.new do
|
138
|
+
start '{name} <{name.downcase}>'
|
139
|
+
name 'Daenerys', 'Tyrion', 'Jon'
|
140
|
+
end
|
141
|
+
|
142
|
+
3.times { grammar.generate }
|
143
|
+
# => Daenerys <jon>
|
144
|
+
# => Tyrion <daenerys>
|
145
|
+
# => Jon <tyrion>
|
146
|
+
|
147
|
+
# With memoization
|
148
|
+
grammar = Calyx::Grammar.new do
|
149
|
+
start '{@name} <{@name.downcase}>'
|
150
|
+
name 'Daenerys', 'Tyrion', 'Jon'
|
151
|
+
end
|
152
|
+
|
153
|
+
3.times { grammar.generate }
|
154
|
+
# => Tyrion <tyrion>
|
155
|
+
# => Daenerys <daenerys>
|
156
|
+
# => Jon <jon>
|
157
|
+
```
|
158
|
+
|
159
|
+
Note that the memoization symbol can only be used on the right hand side of a production rule.
|
160
|
+
|
161
|
+
### Unique Rules
|
162
|
+
|
163
|
+
Rule expansions can be marked as ‘unique’, meaning that multiple references to the same rule always return a different value. This is useful for situations where the same result appearing twice would appear awkward and messy.
|
164
|
+
|
165
|
+
Unique rules are marked by the `$` sigil.
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
grammar = Calyx::Grammar.new do
|
169
|
+
start "{$medal}, {$medal}, {$medal}"
|
170
|
+
medal 'Gold', 'Silver', 'Bronze'
|
171
|
+
end
|
172
|
+
|
173
|
+
grammar.generate
|
174
|
+
# => Silver, Bronze, Gold
|
175
|
+
```
|
@@ -0,0 +1,37 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: File Formats
|
4
|
+
permalink: /formats/
|
5
|
+
---
|
6
|
+
|
7
|
+
# File Formats
|
8
|
+
|
9
|
+
In addition to defining grammars in pure Ruby, you can load them from external JSON and YAML files:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
hello = Calyx::Grammar.load('hello.yml')
|
13
|
+
hello.generate
|
14
|
+
```
|
15
|
+
|
16
|
+
The format requires a flat map with keys representing the left-hand side named symbols and the values representing the right hand side substitution rules.
|
17
|
+
|
18
|
+
In JSON:
|
19
|
+
|
20
|
+
```json
|
21
|
+
{
|
22
|
+
"start": "{greeting} world.",
|
23
|
+
"greeting": ["Hello", "Hi", "Hey", "Yo"]
|
24
|
+
}
|
25
|
+
```
|
26
|
+
|
27
|
+
In YAML:
|
28
|
+
|
29
|
+
```yaml
|
30
|
+
---
|
31
|
+
start: "{greeting} world."
|
32
|
+
greeting:
|
33
|
+
- Hello
|
34
|
+
- Hi
|
35
|
+
- Hey
|
36
|
+
- Yo
|
37
|
+
```
|
@@ -0,0 +1,129 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Getting Started
|
4
|
+
permalink: /getting-started/
|
5
|
+
---
|
6
|
+
|
7
|
+
# Getting Started
|
8
|
+
|
9
|
+
Require the library and inherit from `Calyx::Grammar` to construct a set of rules to generate a text.
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
require 'calyx'
|
13
|
+
|
14
|
+
class HelloWorld < Calyx::Grammar
|
15
|
+
start 'Hello world.'
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
To generate the text itself, initialize the object and call the `generate` method.
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
hello = HelloWorld.new
|
23
|
+
hello.generate
|
24
|
+
# > "Hello world."
|
25
|
+
```
|
26
|
+
|
27
|
+
Obviously, this hardcoded sentence isn’t very interesting by itself. Possible variations can be added to the text by adding additional rules which provide a named set of text strings. The rule delimiter syntax (`{}`) can be used to substitute the generated content of other rules.
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class HelloWorld < Calyx::Grammar
|
31
|
+
start '{greeting} world.'
|
32
|
+
greeting 'Hello', 'Hi', 'Hey', 'Yo'
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
Each time `#generate` runs, it evaluates the tree and randomly selects variations of rules to construct a resulting string.
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
hello = HelloWorld.new
|
40
|
+
|
41
|
+
hello.generate
|
42
|
+
# > "Hi world."
|
43
|
+
|
44
|
+
hello.generate
|
45
|
+
# > "Hello world."
|
46
|
+
|
47
|
+
hello.generate
|
48
|
+
# > "Yo world."
|
49
|
+
```
|
50
|
+
|
51
|
+
By convention, the `start` rule specifies the default starting point for generating the final text. You can start from any other named rule by passing it explicitly to the generate method.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class HelloWorld < Calyx::Grammar
|
55
|
+
hello 'Hello world.'
|
56
|
+
end
|
57
|
+
|
58
|
+
hello = HelloWorld.new
|
59
|
+
hello.generate(:hello)
|
60
|
+
```
|
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
|
+
greeting 'Hello', 'Hi', 'Hey', 'Yo'
|
70
|
+
end
|
71
|
+
|
72
|
+
hello.generate
|
73
|
+
```
|
74
|
+
|
75
|
+
## Template Expressions
|
76
|
+
|
77
|
+
Basic rule substitution uses single curly brackets as delimiters for template expressions:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
fruit = Calyx::Grammar.new do
|
81
|
+
start '{colour} {fruit}'
|
82
|
+
colour 'red', 'green', 'yellow'
|
83
|
+
fruit 'apple', 'pear', 'tomato'
|
84
|
+
end
|
85
|
+
|
86
|
+
6.times { fruit.generate }
|
87
|
+
# => "yellow pear"
|
88
|
+
# => "red apple"
|
89
|
+
# => "green tomato"
|
90
|
+
# => "red pear"
|
91
|
+
# => "yellow tomato"
|
92
|
+
# => "green apple"
|
93
|
+
```
|
94
|
+
|
95
|
+
## Nesting and Substitution
|
96
|
+
|
97
|
+
Rules are recursive. They can be arbitrarily nested and connected to generate larger and more complex texts.
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
class HelloWorld < Calyx::Grammar
|
101
|
+
start '{greeting} {world_phrase}.'
|
102
|
+
greeting 'Hello', 'Hi', 'Hey', 'Yo'
|
103
|
+
world_phrase '{happy_adj} world', '{sad_adj} world', 'world'
|
104
|
+
happy_adj 'wonderful', 'amazing', 'bright', 'beautiful'
|
105
|
+
sad_adj 'cruel', 'miserable'
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
Nesting and hierarchy can be manipulated to balance consistency with novelty. The exact same word atoms can be combined in a variety of ways to produce strikingly different resulting texts.
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
module HelloWorld
|
113
|
+
class Sentiment < Calyx::Grammar
|
114
|
+
start '{happy_phrase}', '{sad_phrase}'
|
115
|
+
happy_phrase '{happy_greeting} {happy_adj} world.'
|
116
|
+
happy_greeting 'Hello', 'Hi', 'Hey', 'Yo'
|
117
|
+
happy_adj 'wonderful', 'amazing', 'bright', 'beautiful'
|
118
|
+
sad_phrase '{sad_greeting} {sad_adj} world.'
|
119
|
+
sad_greeting 'Goodbye', 'So long', 'Farewell'
|
120
|
+
sad_adj 'cruel', 'miserable'
|
121
|
+
end
|
122
|
+
|
123
|
+
class Mixed < Calyx::Grammar
|
124
|
+
start '{greeting} {adj} world.'
|
125
|
+
greeting 'Hello', 'Hi', 'Hey', 'Yo', 'Goodbye', 'So long', 'Farewell'
|
126
|
+
adj 'wonderful', 'amazing', 'bright', 'beautiful', 'cruel', 'miserable'
|
127
|
+
end
|
128
|
+
end
|
129
|
+
```
|
@@ -0,0 +1,100 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Random Sampling
|
4
|
+
permalink: /random/
|
5
|
+
---
|
6
|
+
|
7
|
+
# Random Sampling
|
8
|
+
|
9
|
+
By default, the outcomes of generated rules are selected with Ruby’s built-in pseudorandom number generator (as seen in methods like `Kernel.rand` and `Array.sample`). To seed the random number generator, pass in an integer seed value as the first argument to the constructor:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
grammar = Calyx::Grammar.new(seed: 12345) do
|
13
|
+
# rules...
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
Alternatively, you can pass a preconfigured instance of Ruby’s stdlib `Random` class:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
random = Random.new(12345)
|
21
|
+
|
22
|
+
grammar = Calyx::Grammar.new(rng: random) do
|
23
|
+
# rules...
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
When a random seed isn’t supplied, `Time.new.to_i` is used as the default seed, which makes each run of the generator relatively unique.
|
28
|
+
|
29
|
+
## Weighted Choices
|
30
|
+
|
31
|
+
Choices can be weighted so that some rules have a greater probability of expanding than others.
|
32
|
+
|
33
|
+
Weights are defined by passing a hash instead of a list of rules where the keys are strings or symbols representing the grammar rules and the values are weights.
|
34
|
+
|
35
|
+
Weights can be represented as floats, integers or ranges.
|
36
|
+
|
37
|
+
- Floats must be in the interval 0..1 and the given weights for a production must sum to 1.
|
38
|
+
- Ranges must be contiguous and cover the entire interval from 1 to the maximum value of the largest range.
|
39
|
+
- Integers (Fixnums) will produce a distribution based on the sum of all given numbers, with each number being a fraction of that sum.
|
40
|
+
|
41
|
+
The following definitions produce an equivalent weighting of choices:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
Calyx::Grammar.new do
|
45
|
+
start 'heads' => 1, 'tails' => 1
|
46
|
+
end
|
47
|
+
|
48
|
+
Calyx::Grammar.new do
|
49
|
+
start 'heads' => 0.5, 'tails' => 0.5
|
50
|
+
end
|
51
|
+
|
52
|
+
Calyx::Grammar.new do
|
53
|
+
start 'heads' => 1..5, 'tails' => 6..10
|
54
|
+
end
|
55
|
+
|
56
|
+
Calyx::Grammar.new do
|
57
|
+
start 'heads' => 50, 'tails' => 50
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
There’s a lot of interesting things you can do with this. For example, you can model the triangular distribution produced by rolling 2d6:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
Calyx::Grammar.new do
|
65
|
+
start(
|
66
|
+
'2' => 1,
|
67
|
+
'3' => 2,
|
68
|
+
'4' => 3,
|
69
|
+
'5' => 4,
|
70
|
+
'6' => 5,
|
71
|
+
'7' => 6,
|
72
|
+
'8' => 5,
|
73
|
+
'9' => 4,
|
74
|
+
'10' => 3,
|
75
|
+
'11' => 2,
|
76
|
+
'12' => 1
|
77
|
+
)
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
Or reproduce Gary Gygax’s famous generation table from the original [Dungeon Master’s Guide](https://en.wikipedia.org/wiki/Dungeon_Master%27s_Guide#Advanced_Dungeons_.26_Dragons) (page 171):
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
Calyx::Grammar.new do
|
85
|
+
start(
|
86
|
+
:empty => 0.6,
|
87
|
+
:monster => 0.1,
|
88
|
+
:monster_treasure => 0.15,
|
89
|
+
:special => 0.05,
|
90
|
+
:trick_trap => 0.05,
|
91
|
+
:treasure => 0.05
|
92
|
+
)
|
93
|
+
empty 'Empty'
|
94
|
+
monster 'Monster Only'
|
95
|
+
monster_treasure 'Monster and Treasure'
|
96
|
+
special 'Special'
|
97
|
+
trick_trap 'Trick/Trap.'
|
98
|
+
treasure 'Treasure'
|
99
|
+
end
|
100
|
+
```
|
@@ -0,0 +1,62 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Results
|
4
|
+
permalink: /results/
|
5
|
+
---
|
6
|
+
|
7
|
+
# Generating Results
|
8
|
+
|
9
|
+
## Result objects
|
10
|
+
|
11
|
+
Results are generated by calling `#generate` on the grammar instance. Results are immutable objects representing the unique randomized output from evaluating the grammar.
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
grammar = Calyx::Grammar.new do
|
15
|
+
start "Where is the green sheep?"
|
16
|
+
end
|
17
|
+
|
18
|
+
result = grammar.generate
|
19
|
+
# => Calyx::Result
|
20
|
+
```
|
21
|
+
|
22
|
+
The core accessor methods on the result are:
|
23
|
+
|
24
|
+
- `#text`
|
25
|
+
- `#tree`
|
26
|
+
|
27
|
+
Results can only be deterministically generated by seeding the random number generator.
|
28
|
+
|
29
|
+
## Accessing the flattened text string
|
30
|
+
|
31
|
+
The `#text` method of the result provides access to the flattened text string generated by the grammar.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
result = grammar.generate
|
35
|
+
|
36
|
+
result.text
|
37
|
+
# => "Where is the green sheep?"
|
38
|
+
```
|
39
|
+
|
40
|
+
`#text` is aliased to the `#to_s` method so result objects can be concatenated and interpolated within other strings directly.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
result = grammar.generate
|
44
|
+
|
45
|
+
templated = "Q: #{result}"
|
46
|
+
# => "Q: Where is the green sheep?"
|
47
|
+
```
|
48
|
+
|
49
|
+
## Accessing the generated tree
|
50
|
+
|
51
|
+
The `#tree` method of the result provides access to the raw generated tree structure without being flattened into a string.
|
52
|
+
|
53
|
+
The tree is encoded as an array of nested arrays, with the leading symbols labeling the choices and rules selected, and the trailing terminal leaves encoding string values.
|
54
|
+
|
55
|
+
This is related to the concept of [s-expressions](https://en.wikipedia.org/wiki/S-expression). It’s a fairly speculative feature at this stage, but it leads to some interesting possibilities.
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
result = grammar.generate
|
59
|
+
|
60
|
+
result.tree
|
61
|
+
# => [:start, [:choice, [:concat, [[:atom, "Where is the green sheep?"]]]]]
|
62
|
+
```
|