calyx 0.7.2 → 0.8.0
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 +4 -4
- data/README.md +4 -4
- data/lib/calyx.rb +9 -221
- data/lib/calyx/grammar.rb +41 -0
- data/lib/calyx/grammar/production/choices.rb +26 -0
- data/lib/calyx/grammar/production/concat.rb +44 -0
- data/lib/calyx/grammar/production/expression.rb +18 -0
- data/lib/calyx/grammar/production/non_terminal.rb +16 -0
- data/lib/calyx/grammar/production/terminal.rb +15 -0
- data/lib/calyx/grammar/production/weighted_choices.rb +37 -0
- data/lib/calyx/grammar/registry.rb +55 -0
- data/lib/calyx/version.rb +1 -1
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f518207a92db50cec50c78151e39a75e0a58e93f
|
4
|
+
data.tar.gz: f5a4df32c2fa466fc76e583986cedf81ba9f4633
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8202e1e8c589ef05b98e58ae7ed82efba185c2c8473293e1fbcdba7109b69af9c37dd34b69b2457da779f3c3d6d2be3555b09f1e879d2df695d5488a5d9d98fd
|
7
|
+
data.tar.gz: 8cecb3bd92fbfac27f044cb263ae0bac1cc87253d43831744d8f36c46a1a844dc1c0ee69ecaa0eca2ede5c003f943cc06beecd928ffd7c5a7b1b18b9258a747c
|
data/README.md
CHANGED
@@ -246,10 +246,10 @@ Rough plan for stabilising the API and features for a `1.0` release.
|
|
246
246
|
|---------|------------------|
|
247
247
|
| `0.6` | ~~block constructor~~ |
|
248
248
|
| `0.7` | ~~support for template context map passed to generate~~ |
|
249
|
-
| `0.8` |
|
250
|
-
| `0.9` |
|
251
|
-
| `0.10` |
|
252
|
-
| `0.11` |
|
249
|
+
| `0.8` | ~~method missing metaclass API~~ |
|
250
|
+
| `0.9` | return grammar tree from evaluate/generate, with to_s being separate |
|
251
|
+
| `0.10` | inject custom string functions for parameterised rules, transforms and mappings |
|
252
|
+
| `0.11` | support YAML format (and JSON?) |
|
253
253
|
| `1.0` | API documentation |
|
254
254
|
|
255
255
|
## Credits
|
data/lib/calyx.rb
CHANGED
@@ -1,221 +1,9 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def method_missing(name, *arguments)
|
11
|
-
rule(name, *arguments)
|
12
|
-
end
|
13
|
-
|
14
|
-
def rule(name, *productions, &production)
|
15
|
-
@rules[name.to_sym] = construct_rule(productions)
|
16
|
-
end
|
17
|
-
|
18
|
-
def expand(symbol)
|
19
|
-
@rules[symbol]
|
20
|
-
end
|
21
|
-
|
22
|
-
def combine(registry)
|
23
|
-
@rules = rules.merge(registry.rules)
|
24
|
-
end
|
25
|
-
|
26
|
-
def evaluate(start_symbol=:start, context={})
|
27
|
-
duplicate_registry = Marshal.load(Marshal.dump(self))
|
28
|
-
duplicate_rules = duplicate_registry.rules
|
29
|
-
context.each do |key, value|
|
30
|
-
if duplicate_rules.key?(key.to_sym)
|
31
|
-
raise "Rule already declared in grammar: #{key}"
|
32
|
-
end
|
33
|
-
|
34
|
-
duplicate_rules[key.to_sym] = if value.is_a?(Array)
|
35
|
-
Production::Choices.parse(value, duplicate_registry)
|
36
|
-
else
|
37
|
-
Production::Concat.parse(value.to_s, duplicate_registry)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
duplicate_rules[start_symbol].evaluate
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
def construct_rule(productions)
|
47
|
-
if productions.first.is_a?(Enumerable)
|
48
|
-
Production::WeightedChoices.parse(productions, self)
|
49
|
-
else
|
50
|
-
Production::Choices.parse(productions, self)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
class << self
|
56
|
-
def registry
|
57
|
-
@registry ||= Registry.new
|
58
|
-
end
|
59
|
-
|
60
|
-
def start(*productions, &production)
|
61
|
-
registry.rule(:start, *productions)
|
62
|
-
end
|
63
|
-
|
64
|
-
def rule(name, *productions, &production)
|
65
|
-
registry.rule(name, *productions)
|
66
|
-
end
|
67
|
-
|
68
|
-
def inherit_registry(child_registry)
|
69
|
-
registry.combine(child_registry) unless child_registry.nil?
|
70
|
-
end
|
71
|
-
|
72
|
-
def inherited(subclass)
|
73
|
-
subclass.inherit_registry(registry)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
module Production
|
78
|
-
class NonTerminal
|
79
|
-
def initialize(symbol, registry)
|
80
|
-
@symbol = symbol.to_sym
|
81
|
-
@registry = registry
|
82
|
-
end
|
83
|
-
|
84
|
-
def evaluate
|
85
|
-
@registry.expand(@symbol).evaluate
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
class Terminal
|
90
|
-
def initialize(atom)
|
91
|
-
@atom = atom
|
92
|
-
end
|
93
|
-
|
94
|
-
def evaluate
|
95
|
-
@atom
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
class Expression
|
100
|
-
def initialize(production, methods)
|
101
|
-
@production = production
|
102
|
-
@methods = methods.map { |m| m.to_sym }
|
103
|
-
end
|
104
|
-
|
105
|
-
def evaluate
|
106
|
-
@methods.reduce(@production.evaluate) do |value,method|
|
107
|
-
value.send(method)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
class Concat
|
113
|
-
EXPRESSION = /(\{[A-Za-z0-9_\.]+\})/.freeze
|
114
|
-
START_TOKEN = '{'.freeze
|
115
|
-
END_TOKEN = '}'.freeze
|
116
|
-
DEREF_TOKEN = '.'.freeze
|
117
|
-
|
118
|
-
def self.parse(production, registry)
|
119
|
-
expansion = production.split(EXPRESSION).map do |atom|
|
120
|
-
if atom.is_a?(String)
|
121
|
-
if atom.chars.first == START_TOKEN && atom.chars.last == END_TOKEN
|
122
|
-
head, *tail = atom.slice(1, atom.length-2).split(DEREF_TOKEN)
|
123
|
-
rule = NonTerminal.new(head, registry)
|
124
|
-
unless tail.empty?
|
125
|
-
Expression.new(rule, tail)
|
126
|
-
else
|
127
|
-
rule
|
128
|
-
end
|
129
|
-
else
|
130
|
-
Terminal.new(atom)
|
131
|
-
end
|
132
|
-
elsif atom.is_a?(Symbol)
|
133
|
-
NonTerminal.new(atom, registry)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
self.new(expansion)
|
138
|
-
end
|
139
|
-
|
140
|
-
def initialize(expansion)
|
141
|
-
@expansion = expansion
|
142
|
-
end
|
143
|
-
|
144
|
-
def evaluate
|
145
|
-
@expansion.reduce('') do |exp, atom|
|
146
|
-
exp << atom.evaluate
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
class WeightedChoices
|
152
|
-
def self.parse(productions, registry)
|
153
|
-
weights_sum = productions.reduce(0) do |memo, choice|
|
154
|
-
memo += choice.last
|
155
|
-
end
|
156
|
-
|
157
|
-
raise 'Weights must sum to 1' if weights_sum != 1.0
|
158
|
-
|
159
|
-
choices = productions.map do |choice, weight|
|
160
|
-
if choice.is_a?(String)
|
161
|
-
[Concat.parse(choice, registry), weight]
|
162
|
-
elsif choice.is_a?(Symbol)
|
163
|
-
[NonTerminal.new(choice, registry), weight]
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
self.new(choices)
|
168
|
-
end
|
169
|
-
|
170
|
-
def initialize(collection)
|
171
|
-
@collection = collection
|
172
|
-
end
|
173
|
-
|
174
|
-
def evaluate
|
175
|
-
choice = @collection.max_by do |_, weight|
|
176
|
-
rand ** (1.0 / weight)
|
177
|
-
end.first
|
178
|
-
|
179
|
-
choice.evaluate
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
class Choices
|
184
|
-
def self.parse(productions, registry)
|
185
|
-
choices = productions.map do |choice|
|
186
|
-
if choice.is_a?(String)
|
187
|
-
Concat.parse(choice, registry)
|
188
|
-
elsif choice.is_a?(Symbol)
|
189
|
-
NonTerminal.new(choice, registry)
|
190
|
-
end
|
191
|
-
end
|
192
|
-
self.new(choices)
|
193
|
-
end
|
194
|
-
|
195
|
-
def initialize(collection)
|
196
|
-
@collection = collection
|
197
|
-
end
|
198
|
-
|
199
|
-
def evaluate
|
200
|
-
@collection.sample.evaluate
|
201
|
-
end
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
def initialize(seed=nil, &block)
|
206
|
-
@seed = seed || Random.new_seed
|
207
|
-
srand(@seed)
|
208
|
-
|
209
|
-
if block_given?
|
210
|
-
@registry = Registry.new
|
211
|
-
@registry.instance_eval(&block)
|
212
|
-
else
|
213
|
-
@registry = self.class.registry
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
def generate(context={})
|
218
|
-
@registry.evaluate(:start, context)
|
219
|
-
end
|
220
|
-
end
|
221
|
-
end
|
1
|
+
require_relative 'calyx/version'
|
2
|
+
require_relative 'calyx/grammar'
|
3
|
+
require_relative 'calyx/grammar/registry'
|
4
|
+
require_relative 'calyx/grammar/production/choices'
|
5
|
+
require_relative 'calyx/grammar/production/concat'
|
6
|
+
require_relative 'calyx/grammar/production/expression'
|
7
|
+
require_relative 'calyx/grammar/production/non_terminal'
|
8
|
+
require_relative 'calyx/grammar/production/terminal'
|
9
|
+
require_relative 'calyx/grammar/production/weighted_choices'
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Calyx
|
2
|
+
class Grammar
|
3
|
+
class << self
|
4
|
+
def registry
|
5
|
+
@registry ||= Registry.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def rule(name, *productions, &production)
|
9
|
+
registry.rule(name, *productions)
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(name, *productions)
|
13
|
+
registry.rule(name, *productions)
|
14
|
+
end
|
15
|
+
|
16
|
+
def inherit_registry(child_registry)
|
17
|
+
registry.combine(child_registry) unless child_registry.nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
def inherited(subclass)
|
21
|
+
subclass.inherit_registry(registry)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(seed=nil, &block)
|
26
|
+
@seed = seed || Random.new_seed
|
27
|
+
srand(@seed)
|
28
|
+
|
29
|
+
if block_given?
|
30
|
+
@registry = Registry.new
|
31
|
+
@registry.instance_eval(&block)
|
32
|
+
else
|
33
|
+
@registry = self.class.registry
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def generate(context={})
|
38
|
+
@registry.evaluate(:start, context)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Calyx
|
2
|
+
class Grammar
|
3
|
+
module Production
|
4
|
+
class Choices
|
5
|
+
def self.parse(productions, registry)
|
6
|
+
choices = productions.map do |choice|
|
7
|
+
if choice.is_a?(String)
|
8
|
+
Concat.parse(choice, registry)
|
9
|
+
elsif choice.is_a?(Symbol)
|
10
|
+
NonTerminal.new(choice, registry)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
self.new(choices)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(collection)
|
17
|
+
@collection = collection
|
18
|
+
end
|
19
|
+
|
20
|
+
def evaluate
|
21
|
+
@collection.sample.evaluate
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Calyx
|
2
|
+
class Grammar
|
3
|
+
module Production
|
4
|
+
class Concat
|
5
|
+
EXPRESSION = /(\{[A-Za-z0-9_\.]+\})/.freeze
|
6
|
+
START_TOKEN = '{'.freeze
|
7
|
+
END_TOKEN = '}'.freeze
|
8
|
+
DEREF_TOKEN = '.'.freeze
|
9
|
+
|
10
|
+
def self.parse(production, registry)
|
11
|
+
expansion = production.split(EXPRESSION).map do |atom|
|
12
|
+
if atom.is_a?(String)
|
13
|
+
if atom.chars.first == START_TOKEN && atom.chars.last == END_TOKEN
|
14
|
+
head, *tail = atom.slice(1, atom.length-2).split(DEREF_TOKEN)
|
15
|
+
rule = NonTerminal.new(head, registry)
|
16
|
+
unless tail.empty?
|
17
|
+
Expression.new(rule, tail)
|
18
|
+
else
|
19
|
+
rule
|
20
|
+
end
|
21
|
+
else
|
22
|
+
Terminal.new(atom)
|
23
|
+
end
|
24
|
+
elsif atom.is_a?(Symbol)
|
25
|
+
NonTerminal.new(atom, registry)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
self.new(expansion)
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(expansion)
|
33
|
+
@expansion = expansion
|
34
|
+
end
|
35
|
+
|
36
|
+
def evaluate
|
37
|
+
@expansion.reduce('') do |exp, atom|
|
38
|
+
exp << atom.evaluate
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Calyx
|
2
|
+
class Grammar
|
3
|
+
module Production
|
4
|
+
class Expression
|
5
|
+
def initialize(production, methods)
|
6
|
+
@production = production
|
7
|
+
@methods = methods.map { |m| m.to_sym }
|
8
|
+
end
|
9
|
+
|
10
|
+
def evaluate
|
11
|
+
@methods.reduce(@production.evaluate) do |value,method|
|
12
|
+
value.send(method)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Calyx
|
2
|
+
class Grammar
|
3
|
+
module Production
|
4
|
+
class NonTerminal
|
5
|
+
def initialize(symbol, registry)
|
6
|
+
@symbol = symbol.to_sym
|
7
|
+
@registry = registry
|
8
|
+
end
|
9
|
+
|
10
|
+
def evaluate
|
11
|
+
@registry.expand(@symbol).evaluate
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Calyx
|
2
|
+
class Grammar
|
3
|
+
module Production
|
4
|
+
class WeightedChoices
|
5
|
+
def self.parse(productions, registry)
|
6
|
+
weights_sum = productions.reduce(0) do |memo, choice|
|
7
|
+
memo += choice.last
|
8
|
+
end
|
9
|
+
|
10
|
+
raise 'Weights must sum to 1' if weights_sum != 1.0
|
11
|
+
|
12
|
+
choices = productions.map do |choice, weight|
|
13
|
+
if choice.is_a?(String)
|
14
|
+
[Concat.parse(choice, registry), weight]
|
15
|
+
elsif choice.is_a?(Symbol)
|
16
|
+
[NonTerminal.new(choice, registry), weight]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
self.new(choices)
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(collection)
|
24
|
+
@collection = collection
|
25
|
+
end
|
26
|
+
|
27
|
+
def evaluate
|
28
|
+
choice = @collection.max_by do |_, weight|
|
29
|
+
rand ** (1.0 / weight)
|
30
|
+
end.first
|
31
|
+
|
32
|
+
choice.evaluate
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Calyx
|
2
|
+
class Grammar
|
3
|
+
class Registry
|
4
|
+
attr_reader :rules
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@rules = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(name, *arguments)
|
11
|
+
rule(name, *arguments)
|
12
|
+
end
|
13
|
+
|
14
|
+
def rule(name, *productions, &production)
|
15
|
+
@rules[name.to_sym] = construct_rule(productions)
|
16
|
+
end
|
17
|
+
|
18
|
+
def expand(symbol)
|
19
|
+
@rules[symbol]
|
20
|
+
end
|
21
|
+
|
22
|
+
def combine(registry)
|
23
|
+
@rules = rules.merge(registry.rules)
|
24
|
+
end
|
25
|
+
|
26
|
+
def evaluate(start_symbol=:start, context={})
|
27
|
+
duplicate_registry = Marshal.load(Marshal.dump(self))
|
28
|
+
duplicate_rules = duplicate_registry.rules
|
29
|
+
context.each do |key, value|
|
30
|
+
if duplicate_rules.key?(key.to_sym)
|
31
|
+
raise "Rule already declared in grammar: #{key}"
|
32
|
+
end
|
33
|
+
|
34
|
+
duplicate_rules[key.to_sym] = if value.is_a?(Array)
|
35
|
+
Production::Choices.parse(value, duplicate_registry)
|
36
|
+
else
|
37
|
+
Production::Concat.parse(value.to_s, duplicate_registry)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
duplicate_rules[start_symbol].evaluate
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def construct_rule(productions)
|
47
|
+
if productions.first.is_a?(Enumerable)
|
48
|
+
Production::WeightedChoices.parse(productions, self)
|
49
|
+
else
|
50
|
+
Production::Choices.parse(productions, self)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/calyx/version.rb
CHANGED
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.
|
4
|
+
version: 0.8.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: 2016-03-
|
11
|
+
date: 2016-03-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -68,6 +68,14 @@ files:
|
|
68
68
|
- README.md
|
69
69
|
- calyx.gemspec
|
70
70
|
- lib/calyx.rb
|
71
|
+
- lib/calyx/grammar.rb
|
72
|
+
- lib/calyx/grammar/production/choices.rb
|
73
|
+
- lib/calyx/grammar/production/concat.rb
|
74
|
+
- lib/calyx/grammar/production/expression.rb
|
75
|
+
- lib/calyx/grammar/production/non_terminal.rb
|
76
|
+
- lib/calyx/grammar/production/terminal.rb
|
77
|
+
- lib/calyx/grammar/production/weighted_choices.rb
|
78
|
+
- lib/calyx/grammar/registry.rb
|
71
79
|
- lib/calyx/version.rb
|
72
80
|
homepage: https://github.com/maetl/calyx
|
73
81
|
licenses:
|