calyx 0.14.2 → 0.15.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 +14 -3
- data/lib/calyx.rb +1 -0
- data/lib/calyx/errors.rb +25 -4
- data/lib/calyx/format.rb +71 -16
- data/lib/calyx/grammar.rb +10 -2
- data/lib/calyx/production/non_terminal.rb +1 -6
- data/lib/calyx/registry.rb +34 -23
- data/lib/calyx/rule.rb +17 -0
- data/lib/calyx/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba09252aa5dedcc03c190a14c68be4eb446fc01c
|
4
|
+
data.tar.gz: cf7c1287f4dcfbc35682d74a1f83d3f7eeb8b2a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 670da35d3148a5aaaba3eb81edd7ba9635f3667cc2c1f6288b64cf2f79cafbc2649f752799911e8156609887f9fa260a95bb29765e9342b23bf5ca4f1fa87bca
|
7
|
+
data.tar.gz: 013ab7c2761b8db39e3748fd80602cecbcefe547c1802ccf703a4e459f880d54c0104b502c0798bb9048bcee63e7cf380bf1e6f4d434a04c2bf422e72bfd4200
|
data/README.md
CHANGED
@@ -214,11 +214,22 @@ end
|
|
214
214
|
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:
|
215
215
|
|
216
216
|
```ruby
|
217
|
-
|
218
|
-
|
217
|
+
grammar = Calyx::Grammar.new(seed: 12345) do
|
218
|
+
# rules...
|
219
|
+
end
|
220
|
+
```
|
221
|
+
|
222
|
+
Alternatively, you can pass a preconfigured instance of Ruby’s stdlib `Random` class:
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
random = Random.new(12345)
|
226
|
+
|
227
|
+
grammar = Calyx::Grammar.new(rng: random) do
|
228
|
+
# rules...
|
229
|
+
end
|
219
230
|
```
|
220
231
|
|
221
|
-
When a seed
|
232
|
+
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.
|
222
233
|
|
223
234
|
### Weighted Selection
|
224
235
|
|
data/lib/calyx.rb
CHANGED
data/lib/calyx/errors.rb
CHANGED
@@ -9,10 +9,31 @@ module Calyx
|
|
9
9
|
# end
|
10
10
|
#
|
11
11
|
# grammar.evaluate
|
12
|
-
# # => Calyx::Errors::
|
13
|
-
class
|
14
|
-
def initialize(
|
15
|
-
|
12
|
+
# # => Calyx::Errors::UndefinedRule: :blank is not defined
|
13
|
+
class UndefinedRule < RuntimeError
|
14
|
+
def initialize(rule, symbol)
|
15
|
+
@trace = if rule
|
16
|
+
rule.trace
|
17
|
+
else
|
18
|
+
trace_api_boundary(caller_locations)
|
19
|
+
end
|
20
|
+
|
21
|
+
super("undefined rule :#{symbol} in #{@trace.path}:#{@trace.lineno}:`#{source_line}`")
|
22
|
+
end
|
23
|
+
|
24
|
+
def trace_api_boundary(trace)
|
25
|
+
(trace.count - 1).downto(0) do |index|
|
26
|
+
if trace[index].to_s.include?('lib/calyx/')
|
27
|
+
return trace[index + 1]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def source_line
|
33
|
+
File.open(@trace.absolute_path) do |source|
|
34
|
+
(@trace.lineno-1).times { source.gets }
|
35
|
+
source.gets
|
36
|
+
end.strip
|
16
37
|
end
|
17
38
|
end
|
18
39
|
|
data/lib/calyx/format.rb
CHANGED
@@ -1,7 +1,63 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
1
3
|
module Calyx
|
2
4
|
# Helper methods for loading and initializing grammars from static files
|
3
5
|
# on disk.
|
4
6
|
module Format
|
7
|
+
class Trace
|
8
|
+
def initialize(match_symbol, filename, contents)
|
9
|
+
@match_symbol = match_symbol
|
10
|
+
@filename = Pathname.new(filename)
|
11
|
+
@contents = contents
|
12
|
+
end
|
13
|
+
|
14
|
+
def path
|
15
|
+
@filename.basename
|
16
|
+
end
|
17
|
+
|
18
|
+
def absolute_path
|
19
|
+
@filename.expand_path
|
20
|
+
end
|
21
|
+
|
22
|
+
def lineno
|
23
|
+
line_number = 0
|
24
|
+
@contents.each_line do |line|
|
25
|
+
line_number += 1
|
26
|
+
return line_number if line =~ @match_symbol
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class JSONGrammar
|
32
|
+
def initialize(filename)
|
33
|
+
require 'json'
|
34
|
+
@filename = filename
|
35
|
+
@contents = File.read(@filename)
|
36
|
+
@rules = JSON.parse(@contents)
|
37
|
+
end
|
38
|
+
|
39
|
+
def each_rule(&block)
|
40
|
+
@rules.each do |rule, productions|
|
41
|
+
yield rule, productions, Trace.new(/("#{rule}")(\s*)(:)/, @filename, @contents)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class YAMLGrammar
|
47
|
+
def initialize(filename)
|
48
|
+
require 'yaml'
|
49
|
+
@filename = filename
|
50
|
+
@contents = File.read(@filename)
|
51
|
+
@rules = YAML.load(@contents)
|
52
|
+
end
|
53
|
+
|
54
|
+
def each_rule(&block)
|
55
|
+
@rules.each do |rule, productions|
|
56
|
+
yield rule, productions, Trace.new(/#{rule}:/, @filename, @contents)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
5
61
|
# Reads a file and parses its format, based on the given extension.
|
6
62
|
#
|
7
63
|
# Accepts a JSON or YAML file path, identified by its extension (`.json`
|
@@ -10,12 +66,12 @@ module Calyx
|
|
10
66
|
# @param [String] filename
|
11
67
|
# @return [Calyx::Grammar]
|
12
68
|
def self.load(filename)
|
13
|
-
file = File.read(filename)
|
14
69
|
extension = File.extname(filename)
|
70
|
+
|
15
71
|
if extension == ".yml"
|
16
|
-
self.load_yml(
|
72
|
+
self.load_yml(filename)
|
17
73
|
elsif extension == ".json"
|
18
|
-
self.load_json(
|
74
|
+
self.load_json(filename)
|
19
75
|
else
|
20
76
|
raise Errors::UnsupportedFormat.new(filename)
|
21
77
|
end
|
@@ -23,28 +79,27 @@ module Calyx
|
|
23
79
|
|
24
80
|
# Converts the given string of YAML data to a grammar instance.
|
25
81
|
#
|
26
|
-
# @param [String]
|
27
|
-
# @return [Calyx::
|
28
|
-
def self.load_yml(
|
29
|
-
|
30
|
-
self.build_grammar(YAML.load(data))
|
82
|
+
# @param [String] filename
|
83
|
+
# @return [Calyx::Format::YAMLGrammar]
|
84
|
+
def self.load_yml(filename)
|
85
|
+
self.build_grammar(YAMLGrammar.new(filename))
|
31
86
|
end
|
32
87
|
|
33
88
|
# Converts the given string of JSON data to a grammar instance.
|
34
89
|
#
|
35
|
-
# @param [String]
|
36
|
-
# @return [Calyx::
|
37
|
-
def self.load_json(
|
38
|
-
|
39
|
-
self.build_grammar(JSON.parse(data))
|
90
|
+
# @param [String] filename
|
91
|
+
# @return [Calyx::Format::JSONGrammar]
|
92
|
+
def self.load_json(filename)
|
93
|
+
self.build_grammar(JSONGrammar.new(filename))
|
40
94
|
end
|
41
95
|
|
42
96
|
private
|
43
97
|
|
44
|
-
def self.build_grammar(
|
98
|
+
def self.build_grammar(grammar_format)
|
45
99
|
Calyx::Grammar.new do
|
46
|
-
|
47
|
-
|
100
|
+
grammar_format.each_rule do |label, productions, trace|
|
101
|
+
productions = [productions] unless productions.is_a?(Enumerable)
|
102
|
+
define_rule(label, trace, productions)
|
48
103
|
end
|
49
104
|
end
|
50
105
|
end
|
data/lib/calyx/grammar.rb
CHANGED
@@ -59,7 +59,7 @@ module Calyx
|
|
59
59
|
# @param [Symbol] name
|
60
60
|
# @param [Array] productions
|
61
61
|
def rule(name, *productions)
|
62
|
-
registry.
|
62
|
+
registry.define_rule(name, caller_locations.first, productions)
|
63
63
|
end
|
64
64
|
|
65
65
|
# Augument the grammar with a method missing hook that treats class
|
@@ -71,7 +71,7 @@ module Calyx
|
|
71
71
|
# @param [Symbol] name
|
72
72
|
# @param [Array] productions
|
73
73
|
def method_missing(name, *productions)
|
74
|
-
registry.
|
74
|
+
registry.define_rule(name, caller_locations.first, productions)
|
75
75
|
end
|
76
76
|
|
77
77
|
# Hook for combining the registry of a parent grammar into the child that
|
@@ -103,8 +103,16 @@ module Calyx
|
|
103
103
|
unless options.is_a?(Hash)
|
104
104
|
config_opts = {}
|
105
105
|
if options.is_a?(Numeric)
|
106
|
+
warn [
|
107
|
+
"NOTE: Passing a numeric seed arg directly is deprecated. ",
|
108
|
+
"Use the options hash instead: `Calyx::Grammar.new(seed: 1234)`"
|
109
|
+
].join
|
106
110
|
config_opts[:seed] = options
|
107
111
|
elsif options.is_a?(Random)
|
112
|
+
warn [
|
113
|
+
"NOTE: Passing a Random object directly is deprecated. ",
|
114
|
+
"Use the options hash instead: `Calyx::Grammar.new(rng: Random.new)`"
|
115
|
+
].join
|
108
116
|
config_opts[:rng] = options
|
109
117
|
end
|
110
118
|
else
|
@@ -18,12 +18,7 @@ module Calyx
|
|
18
18
|
# @param [Random] random
|
19
19
|
# @return [Array]
|
20
20
|
def evaluate(random)
|
21
|
-
|
22
|
-
if expansion.nil?
|
23
|
-
raise Errors::MissingRule.new(@symbol)
|
24
|
-
else
|
25
|
-
[@symbol, expansion.evaluate(random)]
|
26
|
-
end
|
21
|
+
[@symbol, @registry.expand(@symbol).evaluate(random)]
|
27
22
|
end
|
28
23
|
end
|
29
24
|
end
|
data/lib/calyx/registry.rb
CHANGED
@@ -11,14 +11,6 @@ module Calyx
|
|
11
11
|
@modifiers = Modifiers.new
|
12
12
|
end
|
13
13
|
|
14
|
-
# Hook for defining rules without explicitly calling the `#rule` method.
|
15
|
-
#
|
16
|
-
# @param [Symbol] name
|
17
|
-
# @param [Array] productions
|
18
|
-
def method_missing(name, *arguments)
|
19
|
-
rule(name, *arguments)
|
20
|
-
end
|
21
|
-
|
22
14
|
# Attaches a modifier module to this instance.
|
23
15
|
#
|
24
16
|
# @param [Module] module_name
|
@@ -46,19 +38,48 @@ module Calyx
|
|
46
38
|
end
|
47
39
|
end
|
48
40
|
|
41
|
+
# Registers a new grammar rule without explicitly calling the `#rule` method.
|
42
|
+
#
|
43
|
+
# @param [Symbol] name
|
44
|
+
# @param [Array] productions
|
45
|
+
def method_missing(name, *productions)
|
46
|
+
define_rule(name, caller_locations.first, productions)
|
47
|
+
end
|
48
|
+
|
49
49
|
# Registers a new grammar rule.
|
50
50
|
#
|
51
51
|
# @param [Symbol] name
|
52
52
|
# @param [Array] productions
|
53
53
|
def rule(name, *productions)
|
54
|
-
|
54
|
+
define_rule(name, caller_locations.first, productions)
|
55
55
|
end
|
56
56
|
|
57
|
-
#
|
57
|
+
# Defines a static rule in the grammar.
|
58
|
+
#
|
59
|
+
# @param [Symbol] name
|
60
|
+
# @param [Array] productions
|
61
|
+
def define_rule(name, trace, productions)
|
62
|
+
rules[name.to_sym] = Rule.new(name.to_sym, construct_rule(productions), trace)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Defines a rule in the temporary evaluation context.
|
66
|
+
#
|
67
|
+
# @param [Symbol] name
|
68
|
+
# @param [Array] productions
|
69
|
+
def define_context_rule(name, trace, productions)
|
70
|
+
productions = [productions] unless productions.is_a?(Enumerable)
|
71
|
+
context[name.to_sym] = Rule.new(name.to_sym, construct_rule(productions), trace)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Expands the given symbol to its rule.
|
58
75
|
#
|
59
76
|
# @param [Symbol] symbol
|
77
|
+
# @return [Calyx::Rule]
|
60
78
|
def expand(symbol)
|
61
|
-
rules[symbol] || context[symbol]
|
79
|
+
expansion = rules[symbol] || context[symbol]
|
80
|
+
raise Errors::UndefinedRule.new(@last_expansion, symbol) if expansion.nil?
|
81
|
+
@last_expansion = expansion
|
82
|
+
expansion
|
62
83
|
end
|
63
84
|
|
64
85
|
# Applies the given modifier function to the given value to transform it.
|
@@ -129,20 +150,10 @@ module Calyx
|
|
129
150
|
raise Errors::DuplicateRule.new(key)
|
130
151
|
end
|
131
152
|
|
132
|
-
|
133
|
-
Production::Choices.parse(value, self)
|
134
|
-
else
|
135
|
-
Production::Concat.parse(value.to_s, self)
|
136
|
-
end
|
153
|
+
define_context_rule(key, caller_locations.last, value)
|
137
154
|
end
|
138
155
|
|
139
|
-
|
140
|
-
|
141
|
-
if expansion.respond_to?(:evaluate)
|
142
|
-
[start_symbol, expansion.evaluate(random)]
|
143
|
-
else
|
144
|
-
raise Errors::MissingRule.new(start_symbol)
|
145
|
-
end
|
156
|
+
[start_symbol, expand(start_symbol).evaluate(random)]
|
146
157
|
end
|
147
158
|
|
148
159
|
private
|
data/lib/calyx/rule.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Calyx
|
2
|
+
# Represents a named rule connected to a tree of productions that can be
|
3
|
+
# evaluated and a trace which represents where the rule was declared.
|
4
|
+
class Rule
|
5
|
+
attr_reader :name, :productions, :trace
|
6
|
+
|
7
|
+
def initialize(name, productions, trace)
|
8
|
+
@name = name.to_sym
|
9
|
+
@productions = productions
|
10
|
+
@trace = trace
|
11
|
+
end
|
12
|
+
|
13
|
+
def evaluate(random)
|
14
|
+
productions.evaluate(random)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
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.15.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: 2017-08-
|
11
|
+
date: 2017-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -85,6 +85,7 @@ files:
|
|
85
85
|
- lib/calyx/production/unique.rb
|
86
86
|
- lib/calyx/production/weighted_choices.rb
|
87
87
|
- lib/calyx/registry.rb
|
88
|
+
- lib/calyx/rule.rb
|
88
89
|
- lib/calyx/version.rb
|
89
90
|
homepage: https://github.com/maetl/calyx
|
90
91
|
licenses:
|