calyx 0.9.4 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/README.md +32 -2
- data/lib/calyx/errors.rb +3 -0
- data/lib/calyx/file_converter.rb +5 -3
- data/lib/calyx/grammar.rb +5 -1
- data/lib/calyx/production/choices.rb +30 -0
- data/lib/calyx/production/concat.rb +46 -0
- data/lib/calyx/production/expression.rb +20 -0
- data/lib/calyx/production/memo.rb +16 -0
- data/lib/calyx/production/non_terminal.rb +14 -0
- data/lib/calyx/production/terminal.rb +13 -0
- data/lib/calyx/production/weighted_choices.rb +35 -0
- data/lib/calyx/registry.rb +91 -0
- data/lib/calyx/version.rb +1 -1
- data/lib/calyx.rb +9 -8
- metadata +11 -10
- data/lib/calyx/grammar/production/choices.rb +0 -32
- data/lib/calyx/grammar/production/concat.rb +0 -48
- data/lib/calyx/grammar/production/expression.rb +0 -21
- data/lib/calyx/grammar/production/memo.rb +0 -18
- data/lib/calyx/grammar/production/non_terminal.rb +0 -16
- data/lib/calyx/grammar/production/terminal.rb +0 -15
- data/lib/calyx/grammar/production/weighted_choices.rb +0 -37
- data/lib/calyx/grammar/registry.rb +0 -64
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c30774b8a6567532e9ee613185e1f4505665e197
|
4
|
+
data.tar.gz: fd92cdc99522713e076ae9161d855c18eb42058f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de2c0e4685357546aeda4474c5b16be15c1461c517d4c1ca0210ce21a85e191eeb23be760ac7660a48518b764e4c12c88f8a8e61e5487440a6d5d40f848784e7
|
7
|
+
data.tar.gz: 459fcb3685caeb784df815592ad1e0da37d82bbf29def422c27e90b90d7ccf9e8168115f6e484b6a99c835fa7106268b8c4171c10bd3f7af3bc194e1c7cc0798
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -94,7 +94,7 @@ Nesting and hierarchy can be manipulated to balance consistency with novelty. Th
|
|
94
94
|
|
95
95
|
```ruby
|
96
96
|
module HelloWorld
|
97
|
-
Sentiment < Calyx::Grammar
|
97
|
+
class Sentiment < Calyx::Grammar
|
98
98
|
start '{happy_phrase}', '{sad_phrase}'
|
99
99
|
rule :happy_phrase, '{happy_greeting} {happy_adj} world.'
|
100
100
|
rule :happy_greeting, 'Hello', 'Hi', 'Hey', 'Yo'
|
@@ -104,7 +104,7 @@ module HelloWorld
|
|
104
104
|
rule :sad_adj, 'cruel', 'miserable'
|
105
105
|
end
|
106
106
|
|
107
|
-
Mixed < Calyx::Grammar
|
107
|
+
class Mixed < Calyx::Grammar
|
108
108
|
start '{greeting} {adj} world.'
|
109
109
|
rule :greeting, 'Hello', 'Hi', 'Hey', 'Yo', 'Goodbye', 'So long', 'Farewell'
|
110
110
|
rule :adj, 'wonderful', 'amazing', 'bright', 'beautiful', 'cruel', 'miserable'
|
@@ -219,6 +219,36 @@ end
|
|
219
219
|
# => "A pear."
|
220
220
|
```
|
221
221
|
|
222
|
+
### Memoized Rules
|
223
|
+
|
224
|
+
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.
|
225
|
+
|
226
|
+
The `@` symbol 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.
|
227
|
+
|
228
|
+
```ruby
|
229
|
+
# Without memoization
|
230
|
+
grammar = Calyx::Grammar.new do
|
231
|
+
start '{name} <{name.downcase}>'
|
232
|
+
name 'Daenerys', 'Tyrion', 'Jon'
|
233
|
+
end
|
234
|
+
|
235
|
+
3.times { grammar.generate }
|
236
|
+
# => Daenerys <jon>
|
237
|
+
# => Tyrion <daenerys>
|
238
|
+
# => Jon <tyrion>
|
239
|
+
|
240
|
+
# With memoization
|
241
|
+
grammar = Calyx::Grammar.new do
|
242
|
+
start '{@name} <{@name.downcase}>'
|
243
|
+
name 'Daenerys', 'Tyrion', 'Jon'
|
244
|
+
end
|
245
|
+
|
246
|
+
3.times { grammar.generate }
|
247
|
+
# => Tyrion <tyrion>
|
248
|
+
# => Daenerys <daenerys>
|
249
|
+
# => Jon <jon>
|
250
|
+
```
|
251
|
+
|
222
252
|
### Dynamically Constructing Rules
|
223
253
|
|
224
254
|
Template expansions can be dynamically constructed at runtime by passing a context map of rules to the `#generate` method:
|
data/lib/calyx/errors.rb
ADDED
data/lib/calyx/file_converter.rb
CHANGED
@@ -26,10 +26,12 @@ module Calyx
|
|
26
26
|
build_grammar(json)
|
27
27
|
end
|
28
28
|
|
29
|
-
|
29
|
+
private
|
30
|
+
|
31
|
+
def build_grammar(rules)
|
30
32
|
Calyx::Grammar.new do
|
31
|
-
|
32
|
-
rule(
|
33
|
+
rules.each do |label, productions|
|
34
|
+
rule(label, *productions)
|
33
35
|
end
|
34
36
|
end
|
35
37
|
end
|
data/lib/calyx/grammar.rb
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Calyx
|
2
|
+
module Production
|
3
|
+
class Choices
|
4
|
+
def self.parse(productions, registry)
|
5
|
+
choices = productions.map do |choice|
|
6
|
+
if choice.is_a?(String)
|
7
|
+
Concat.parse(choice, registry)
|
8
|
+
elsif choice.is_a?(Fixnum)
|
9
|
+
Terminal.new(choice.to_s)
|
10
|
+
elsif choice.is_a?(Symbol)
|
11
|
+
if choice[0] == Memo::SIGIL
|
12
|
+
Memo.new(choice, registry)
|
13
|
+
else
|
14
|
+
NonTerminal.new(choice, registry)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
self.new(choices)
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(collection)
|
22
|
+
@collection = collection
|
23
|
+
end
|
24
|
+
|
25
|
+
def evaluate
|
26
|
+
[:choice, @collection.sample.evaluate]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Calyx
|
2
|
+
module Production
|
3
|
+
class Concat
|
4
|
+
EXPRESSION = /(\{[A-Za-z0-9_@\.]+\})/.freeze
|
5
|
+
START_TOKEN = '{'.freeze
|
6
|
+
END_TOKEN = '}'.freeze
|
7
|
+
DEREF_TOKEN = '.'.freeze
|
8
|
+
|
9
|
+
def self.parse(production, registry)
|
10
|
+
expansion = production.split(EXPRESSION).map do |atom|
|
11
|
+
if atom.is_a?(String)
|
12
|
+
if atom.chars.first == START_TOKEN && atom.chars.last == END_TOKEN
|
13
|
+
head, *tail = atom.slice(1, atom.length-2).split(DEREF_TOKEN)
|
14
|
+
if head[0] == Memo::SIGIL
|
15
|
+
rule = Memo.new(head, registry)
|
16
|
+
else
|
17
|
+
rule = NonTerminal.new(head, registry)
|
18
|
+
end
|
19
|
+
unless tail.empty?
|
20
|
+
Expression.new(rule, tail, registry)
|
21
|
+
else
|
22
|
+
rule
|
23
|
+
end
|
24
|
+
else
|
25
|
+
Terminal.new(atom)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
self.new(expansion)
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(expansion)
|
34
|
+
@expansion = expansion
|
35
|
+
end
|
36
|
+
|
37
|
+
def evaluate
|
38
|
+
concat = @expansion.reduce([]) do |exp, atom|
|
39
|
+
exp << atom.evaluate
|
40
|
+
end
|
41
|
+
|
42
|
+
[:concat, concat]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Calyx
|
2
|
+
module Production
|
3
|
+
class Expression
|
4
|
+
def initialize(production, methods, registry)
|
5
|
+
@production = production
|
6
|
+
@methods = methods.map { |m| m.to_sym }
|
7
|
+
@registry = registry
|
8
|
+
end
|
9
|
+
|
10
|
+
def evaluate
|
11
|
+
terminal = @production.evaluate.flatten.reject { |o| o.is_a?(Symbol) }.join(''.freeze)
|
12
|
+
expression = @methods.reduce(terminal) do |value, method|
|
13
|
+
@registry.transform(method, value)
|
14
|
+
end
|
15
|
+
|
16
|
+
[:expression, expression]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Calyx
|
2
|
+
module Production
|
3
|
+
class Memo
|
4
|
+
SIGIL = '@'.freeze
|
5
|
+
|
6
|
+
def initialize(symbol, registry)
|
7
|
+
@symbol = symbol.slice(1, symbol.length-1).to_sym
|
8
|
+
@registry = registry
|
9
|
+
end
|
10
|
+
|
11
|
+
def evaluate
|
12
|
+
[@symbol, @registry.memoize_expansion(@symbol)]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Calyx
|
2
|
+
module Production
|
3
|
+
class WeightedChoices
|
4
|
+
def self.parse(productions, registry)
|
5
|
+
weights_sum = productions.reduce(0) do |memo, choice|
|
6
|
+
memo += choice.last
|
7
|
+
end
|
8
|
+
|
9
|
+
raise 'Weights must sum to 1' if weights_sum != 1.0
|
10
|
+
|
11
|
+
choices = productions.map do |choice, weight|
|
12
|
+
if choice.is_a?(String)
|
13
|
+
[Concat.parse(choice, registry), weight]
|
14
|
+
elsif choice.is_a?(Symbol)
|
15
|
+
[NonTerminal.new(choice, registry), weight]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
self.new(choices)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(collection)
|
23
|
+
@collection = collection
|
24
|
+
end
|
25
|
+
|
26
|
+
def evaluate
|
27
|
+
choice = @collection.max_by do |_, weight|
|
28
|
+
rand ** (1.0 / weight)
|
29
|
+
end.first
|
30
|
+
|
31
|
+
[:weighted_choice, choice.evaluate]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Calyx
|
2
|
+
class Registry
|
3
|
+
attr_reader :rules, :mappings
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@rules = {}
|
7
|
+
@mappings = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(name, *arguments)
|
11
|
+
rule(name, *arguments)
|
12
|
+
end
|
13
|
+
|
14
|
+
def mapping(name, pairs)
|
15
|
+
mappings[name.to_sym] = construct_mapping(pairs)
|
16
|
+
end
|
17
|
+
|
18
|
+
def rule(name, *productions)
|
19
|
+
rules[name.to_sym] = construct_rule(productions)
|
20
|
+
end
|
21
|
+
|
22
|
+
def expand(symbol)
|
23
|
+
rules[symbol] || context[symbol]
|
24
|
+
end
|
25
|
+
|
26
|
+
def transform(name, value)
|
27
|
+
if value.respond_to?(name)
|
28
|
+
value.send(name)
|
29
|
+
else
|
30
|
+
mappings[name].call(value)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def memoize_expansion(symbol)
|
35
|
+
memos[symbol] ||= expand(symbol).evaluate
|
36
|
+
end
|
37
|
+
|
38
|
+
def combine(registry)
|
39
|
+
@rules = rules.merge(registry.rules)
|
40
|
+
end
|
41
|
+
|
42
|
+
def evaluate(start_symbol=:start, rules_map={})
|
43
|
+
reset_evaluation_context
|
44
|
+
|
45
|
+
rules_map.each do |key, value|
|
46
|
+
if rules.key?(key.to_sym)
|
47
|
+
raise "Rule already declared in grammar: #{key}"
|
48
|
+
end
|
49
|
+
|
50
|
+
context[key.to_sym] = if value.is_a?(Array)
|
51
|
+
Production::Choices.parse(value, self)
|
52
|
+
else
|
53
|
+
Production::Concat.parse(value.to_s, self)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
expansion = expand(start_symbol)
|
58
|
+
|
59
|
+
if expansion.respond_to?(:evaluate)
|
60
|
+
[start_symbol, expansion.evaluate]
|
61
|
+
else
|
62
|
+
raise RuleNotFound.new(start_symbol)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
attr_reader :memos, :context
|
69
|
+
|
70
|
+
def reset_evaluation_context
|
71
|
+
@context = {}
|
72
|
+
@memos = {}
|
73
|
+
end
|
74
|
+
|
75
|
+
def construct_mapping(pairs)
|
76
|
+
mapper = -> (input) {
|
77
|
+
pairs.each { |match,target|
|
78
|
+
return input.gsub(match, target) if input =~ match
|
79
|
+
}
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def construct_rule(productions)
|
84
|
+
if productions.first.is_a?(Enumerable)
|
85
|
+
Production::WeightedChoices.parse(productions, self)
|
86
|
+
else
|
87
|
+
Production::Choices.parse(productions, self)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/calyx/version.rb
CHANGED
data/lib/calyx.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
require 'calyx/version'
|
2
2
|
require 'calyx/grammar'
|
3
|
+
require 'calyx/errors'
|
3
4
|
require 'calyx/file_converter'
|
4
|
-
require 'calyx/
|
5
|
-
require 'calyx/
|
6
|
-
require 'calyx/
|
7
|
-
require 'calyx/
|
8
|
-
require 'calyx/
|
9
|
-
require 'calyx/
|
10
|
-
require 'calyx/
|
11
|
-
require 'calyx/
|
5
|
+
require 'calyx/registry'
|
6
|
+
require 'calyx/production/memo'
|
7
|
+
require 'calyx/production/choices'
|
8
|
+
require 'calyx/production/concat'
|
9
|
+
require 'calyx/production/expression'
|
10
|
+
require 'calyx/production/non_terminal'
|
11
|
+
require 'calyx/production/terminal'
|
12
|
+
require 'calyx/production/weighted_choices'
|
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.10.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-05-
|
11
|
+
date: 2016-05-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -68,16 +68,17 @@ files:
|
|
68
68
|
- calyx.gemspec
|
69
69
|
- examples/tiny_woodland.rb
|
70
70
|
- lib/calyx.rb
|
71
|
+
- lib/calyx/errors.rb
|
71
72
|
- lib/calyx/file_converter.rb
|
72
73
|
- lib/calyx/grammar.rb
|
73
|
-
- lib/calyx/
|
74
|
-
- lib/calyx/
|
75
|
-
- lib/calyx/
|
76
|
-
- lib/calyx/
|
77
|
-
- lib/calyx/
|
78
|
-
- lib/calyx/
|
79
|
-
- lib/calyx/
|
80
|
-
- lib/calyx/
|
74
|
+
- lib/calyx/production/choices.rb
|
75
|
+
- lib/calyx/production/concat.rb
|
76
|
+
- lib/calyx/production/expression.rb
|
77
|
+
- lib/calyx/production/memo.rb
|
78
|
+
- lib/calyx/production/non_terminal.rb
|
79
|
+
- lib/calyx/production/terminal.rb
|
80
|
+
- lib/calyx/production/weighted_choices.rb
|
81
|
+
- lib/calyx/registry.rb
|
81
82
|
- lib/calyx/version.rb
|
82
83
|
homepage: https://github.com/maetl/calyx
|
83
84
|
licenses:
|
@@ -1,32 +0,0 @@
|
|
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?(Fixnum)
|
10
|
-
Terminal.new(choice.to_s)
|
11
|
-
elsif choice.is_a?(Symbol)
|
12
|
-
if choice[0] == Memo::SIGIL
|
13
|
-
Memo.new(choice, registry)
|
14
|
-
else
|
15
|
-
NonTerminal.new(choice, registry)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
self.new(choices)
|
20
|
-
end
|
21
|
-
|
22
|
-
def initialize(collection)
|
23
|
-
@collection = collection
|
24
|
-
end
|
25
|
-
|
26
|
-
def evaluate
|
27
|
-
[:choice, @collection.sample.evaluate]
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
@@ -1,48 +0,0 @@
|
|
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
|
-
if head[0] == Memo::SIGIL
|
16
|
-
rule = Memo.new(head, registry)
|
17
|
-
else
|
18
|
-
rule = NonTerminal.new(head, registry)
|
19
|
-
end
|
20
|
-
unless tail.empty?
|
21
|
-
Expression.new(rule, tail)
|
22
|
-
else
|
23
|
-
rule
|
24
|
-
end
|
25
|
-
else
|
26
|
-
Terminal.new(atom)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
self.new(expansion)
|
32
|
-
end
|
33
|
-
|
34
|
-
def initialize(expansion)
|
35
|
-
@expansion = expansion
|
36
|
-
end
|
37
|
-
|
38
|
-
def evaluate
|
39
|
-
concat = @expansion.reduce([]) do |exp, atom|
|
40
|
-
exp << atom.evaluate
|
41
|
-
end
|
42
|
-
|
43
|
-
[:concat, concat]
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
@@ -1,21 +0,0 @@
|
|
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
|
-
terminal = @production.evaluate.flatten.reject { |o| o.is_a?(Symbol) }.join(''.freeze)
|
12
|
-
expression = @methods.reduce(terminal) do |value,method|
|
13
|
-
value.send(method)
|
14
|
-
end
|
15
|
-
|
16
|
-
[:expression, expression]
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
module Calyx
|
2
|
-
class Grammar
|
3
|
-
module Production
|
4
|
-
class Memo
|
5
|
-
SIGIL = '@'.freeze
|
6
|
-
|
7
|
-
def initialize(symbol, registry)
|
8
|
-
@symbol = symbol.slice(1, symbol.length-1).to_sym
|
9
|
-
@registry = registry
|
10
|
-
end
|
11
|
-
|
12
|
-
def evaluate
|
13
|
-
[@symbol, @registry.memoize_expansion(@symbol)]
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,16 +0,0 @@
|
|
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
|
-
[@symbol, @registry.expand(@symbol).evaluate]
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
@@ -1,37 +0,0 @@
|
|
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
|
-
[:weighted_choice, choice.evaluate]
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,64 +0,0 @@
|
|
1
|
-
module Calyx
|
2
|
-
class Grammar
|
3
|
-
class Registry
|
4
|
-
attr_reader :rules, :memos
|
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] || @context[symbol]
|
20
|
-
end
|
21
|
-
|
22
|
-
def memoize_expansion(symbol)
|
23
|
-
memos[symbol] ||= expand(symbol).evaluate
|
24
|
-
end
|
25
|
-
|
26
|
-
def combine(registry)
|
27
|
-
@rules = rules.merge(registry.rules)
|
28
|
-
end
|
29
|
-
|
30
|
-
def evaluate(start_symbol=:start, rules_map={})
|
31
|
-
reset_state
|
32
|
-
|
33
|
-
rules_map.each do |key, value|
|
34
|
-
if rules.key?(key.to_sym)
|
35
|
-
raise "Rule already declared in grammar: #{key}"
|
36
|
-
end
|
37
|
-
|
38
|
-
@context[key.to_sym] = if value.is_a?(Array)
|
39
|
-
Production::Choices.parse(value, self)
|
40
|
-
else
|
41
|
-
Production::Concat.parse(value.to_s, self)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
[start_symbol, expand(start_symbol).evaluate]
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
def reset_state
|
51
|
-
@context = {}
|
52
|
-
@memos = {}
|
53
|
-
end
|
54
|
-
|
55
|
-
def construct_rule(productions)
|
56
|
-
if productions.first.is_a?(Enumerable)
|
57
|
-
Production::WeightedChoices.parse(productions, self)
|
58
|
-
else
|
59
|
-
Production::Choices.parse(productions, self)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|