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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 129a9b8ddec5530b5f427780572e1e0a3a667bd4
4
- data.tar.gz: 445ce73db118256bbf9ad748b82421235bb8573d
3
+ metadata.gz: ba09252aa5dedcc03c190a14c68be4eb446fc01c
4
+ data.tar.gz: cf7c1287f4dcfbc35682d74a1f83d3f7eeb8b2a4
5
5
  SHA512:
6
- metadata.gz: 5b31b154440e67f0bde8ba9cd49784aece4ffa971a3b15731f83b05696f44e4db3fc43cdf78ffba990c962c46ac7d71c4964339133a43c202bd651a9066113bd
7
- data.tar.gz: 3aded7ee2f7104244b0d47dc0a06a98607f32bdc05e17a1eabf153b1595cca60e17f708cbc2873eb09bb27b16ec6bb445980f23c3b679a76feb669ecb1bcd601
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
- MyGrammar.new(12345)
218
- Calyx::Grammar.new(12345, &rules)
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 value isn’t supplied, `Time.new.to_i` is used as the default seed, which makes each run of the generator relatively unique.
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
 
@@ -1,4 +1,5 @@
1
1
  require 'calyx/version'
2
+ require 'calyx/rule'
2
3
  require 'calyx/grammar'
3
4
  require 'calyx/errors'
4
5
  require 'calyx/format'
@@ -9,10 +9,31 @@ module Calyx
9
9
  # end
10
10
  #
11
11
  # grammar.evaluate
12
- # # => Calyx::Errors::MissingRule: :blank is not registered
13
- class MissingRule < RuntimeError
14
- def initialize(msg)
15
- super(":#{msg} is not registered")
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
 
@@ -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(file)
72
+ self.load_yml(filename)
17
73
  elsif extension == ".json"
18
- self.load_json(file)
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] data
27
- # @return [Calyx::Grammar]
28
- def self.load_yml(data)
29
- require 'yaml'
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] data
36
- # @return [Calyx::Grammar]
37
- def self.load_json(data)
38
- require 'json'
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(rules)
98
+ def self.build_grammar(grammar_format)
45
99
  Calyx::Grammar.new do
46
- rules.each do |label, productions|
47
- rule(label, *productions)
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
@@ -59,7 +59,7 @@ module Calyx
59
59
  # @param [Symbol] name
60
60
  # @param [Array] productions
61
61
  def rule(name, *productions)
62
- registry.rule(name, *productions)
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.rule(name, *productions)
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
- expansion = @registry.expand(@symbol)
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
@@ -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
- rules[name.to_sym] = construct_rule(productions)
54
+ define_rule(name, caller_locations.first, productions)
55
55
  end
56
56
 
57
- # Expands the given rule symbol to its production.
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
- context[key.to_sym] = if value.is_a?(Array)
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
- expansion = expand(start_symbol)
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Calyx
2
- VERSION = '0.14.2'.freeze
2
+ VERSION = '0.15.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.14.2
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-21 00:00:00.000000000 Z
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: