calyx 0.7.2 → 0.8.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: 6e0af61453ee9d96b61daf9255a322023510eda0
4
- data.tar.gz: 1beb02a6c87b667587fc3cae9e05852e52d253b1
3
+ metadata.gz: f518207a92db50cec50c78151e39a75e0a58e93f
4
+ data.tar.gz: f5a4df32c2fa466fc76e583986cedf81ba9f4633
5
5
  SHA512:
6
- metadata.gz: d844ef847d5a1e7fa00f75d252f666ad183740a39537ff0441df3def2e2768c2daff09399364e794a08c64e03ded3b06ddb1b9fce14fd72d8ec2edfe40b4ea39
7
- data.tar.gz: e10899e3abac20855a2a10f616d53ffe75b2f72d9751503cf9e6a17d2e19595c28924ed289b01c1734f7d32e9d53065f83c391842e3680af7784acbb88aec6f2
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` | return grammar tree from evaluate/generate, with to_s being separate |
250
- | `0.9` | support mixin/composition of rule sets rather than inheritance |
251
- | `0.10` | support YAML format (and JSON?) |
252
- | `0.11` | method missing metaclass API |
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
- 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
-
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,15 @@
1
+ module Calyx
2
+ class Grammar
3
+ module Production
4
+ class Terminal
5
+ def initialize(atom)
6
+ @atom = atom
7
+ end
8
+
9
+ def evaluate
10
+ @atom
11
+ end
12
+ end
13
+ end
14
+ end
15
+ 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
@@ -1,3 +1,3 @@
1
1
  module Calyx
2
- VERSION = '0.7.2'.freeze
2
+ VERSION = '0.8.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.7.2
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-25 00:00:00.000000000 Z
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: