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 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: