story-gen 0.0.1

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.
@@ -0,0 +1,10 @@
1
+
2
+ module Enumerable
3
+
4
+ # @return [Boolean]
5
+ def empty?
6
+ each { |_| return false }
7
+ return true
8
+ end
9
+
10
+ end
@@ -0,0 +1,10 @@
1
+
2
+ module Enumerable
3
+
4
+ if RUBY_VERSION <= '2.0.0' then
5
+ def lazy
6
+ self # just for compatibility :-[
7
+ end
8
+ end
9
+
10
+ end
@@ -0,0 +1,30 @@
1
+
2
+ module Enumerable
3
+
4
+ # @param [Proc<Proc<Object, void>, void>] each
5
+ # @return [Enumerable] an {Enumerable} which has +each+ as implementation
6
+ # of its {Enumerable#each}.
7
+ def self.new(&each)
8
+ ProcAsEnumerable.new(&each)
9
+ end
10
+
11
+ private
12
+
13
+ # @!visibility private
14
+ class ProcAsEnumerable
15
+
16
+ include Enumerable
17
+
18
+ # @param [Proc<Proc<Object, void>, void>] each
19
+ def initialize(&each)
20
+ @each = each
21
+ end
22
+
23
+ # (see Enumerable#each)
24
+ def each(&block)
25
+ @each.(block)
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,8 @@
1
+
2
+ module Enumerable
3
+
4
+ def sample
5
+ to_a.sample
6
+ end
7
+
8
+ end
data/lib/fact.rb ADDED
@@ -0,0 +1,333 @@
1
+ require 'set'
2
+ require 'enumerable/lazy'
3
+ require 'enumerable/new'
4
+ require 'hash/put'
5
+
6
+ class Fact
7
+
8
+ # @param [Object] relation_id
9
+ # @param [*(:'*' | Symbol | Object)] args
10
+ # @return [Fact]
11
+ def self.[](relation_id, *args)
12
+ select = []; remove = []; map = []; arg_names = []; can_add = true
13
+ begin
14
+ known_var_index = {}
15
+ args.zip(0...args.size).
16
+ each do |arg, i|
17
+ case arg
18
+ when :'*' then
19
+ can_add = false
20
+ when Symbol then
21
+ var = arg
22
+ if known_var_index.has_key? var then
23
+ select << (x = "arg_values[#{known_var_index[var]}] == arg_values[#{i}]")
24
+ remove << x
25
+ else
26
+ map << "arg_values[#{i}]"
27
+ arg_names << var
28
+ known_var_index[var] = i
29
+ end
30
+ can_add = false
31
+ when Object then
32
+ select << (x = "args[#{i}] == arg_values[#{i}]")
33
+ remove << x
34
+ end
35
+ end
36
+ select =
37
+ if select.empty?
38
+ then ""
39
+ else "select { |arg_values| #{select.join(" and ")} }."
40
+ end
41
+ remove =
42
+ if remove.empty? then ".clear"
43
+ else ".reject! { |arg_values| #{reject.join(" and ")} }"
44
+ end
45
+ map =
46
+ "map { |arg_values| [#{map.join(", ")}] }"
47
+ end
48
+ Fact.new(
49
+ arg_names,
50
+ eval(
51
+ "lambda do |context| "+
52
+ "context[relation_id]."+
53
+ "lazy."+
54
+ select +
55
+ map +
56
+ " end "
57
+ ),
58
+ if can_add then
59
+ lambda { |context| context[relation_id].add(args) }
60
+ else
61
+ nil
62
+ end,
63
+ eval("lambda { |context| context[relation_id]#{remove} }")
64
+ )
65
+ end
66
+
67
+ # @!visibility private
68
+ def initialize(arg_names, arg_valuess_f, true_f = nil, false_f = nil)
69
+ @arg_names = arg_names
70
+ @arg_valuess_f = arg_valuess_f
71
+ @true_f = true_f
72
+ @false_f = false_f
73
+ end
74
+
75
+ # @return [Array<Symbol>]
76
+ attr_reader :arg_names
77
+
78
+ # @param [Context] context
79
+ # @return [Enumerable<Array<Object>>] values of named arguments passed to
80
+ # {Fact::[]} which this {Fact} is {#true?} for (in the +context+).
81
+ def arg_valuess(context)
82
+ @arg_valuess_f.(context)
83
+ end
84
+
85
+ # makes this {Fact} to be {#true?} in +context+.
86
+ #
87
+ # It modifies +context+. It is not applicable to some {Fact}s.
88
+ #
89
+ # @param [Context] context
90
+ # @return [void]
91
+ #
92
+ def true!(context)
93
+ raise "not applicable" unless @true_f
94
+ @true_f.(context)
95
+ end
96
+
97
+ # @param [Context] context
98
+ # @return [Boolean]
99
+ def true?(context)
100
+ not false?(context)
101
+ end
102
+
103
+ # makes this {Fact} to be {#false?} in +context+.
104
+ #
105
+ # It modifies +context+. It is not applicable to some {Fact}s.
106
+ #
107
+ # @param [Context] context
108
+ # @return [void]
109
+ #
110
+ def false!(context)
111
+ raise "not applicable" unless @false_f
112
+ @false_f.(context)
113
+ end
114
+
115
+ # @param [Context] context
116
+ # @return [Boolean]
117
+ def false?(context)
118
+ arg_valuess.(context).empty?
119
+ end
120
+
121
+ # -------------------
122
+ # @!group Combinators
123
+ # -------------------
124
+
125
+ # @param [Fact, Fact::Not, Fact::Select] f2
126
+ # @return [Fact]
127
+ def & f2
128
+ case f2
129
+ when Fact::Select
130
+ if not (s = f2.arg_names - f1.arg_names).empty? then
131
+ raise "#{s.map(&:to_s).join(", ")} in the right part is/are not known in the left part"
132
+ end
133
+ Fact.new(
134
+ f1.arg_names,
135
+ lambda { |context| f1.arg_valuess(context).select(&f2.criteria) }
136
+ )
137
+ when Fact::Not
138
+ common_arg_names = f1.arg_names & f2.subfact.arg_names
139
+ if common_arg_names.empty? then
140
+ Fact.new(
141
+ f1.arg_names,
142
+ lambda do |context|
143
+ if f2.subfact.true?(context) then []
144
+ else f1.arg_valuess(context)
145
+ end
146
+ end
147
+ )
148
+ else
149
+ Fact.new(
150
+ f1.arg_names,
151
+ eval(
152
+ "lambda do |context| "+
153
+ "f1.arg_valuess(context).select do |f1_arg_values| "+
154
+ "not (" +
155
+ "f2.subfact.arg_valuess(context).any? do |f2_subfact_arg_values| "+
156
+ common_arg_names.
157
+ map do |arg_name|
158
+ "f1_arg_values[#{f1.arg_indexes[arg_name]}] == f2_subfact_arg_values[#{f2.subfact.arg_indexes[arg_name]}]"
159
+ end.
160
+ join(" and ") + " " +
161
+ "end "+
162
+ ") "+
163
+ "end " +
164
+ "end "
165
+ )
166
+ )
167
+ end
168
+ when Fact
169
+ arg_names = f1.arg_names | f2.arg_names
170
+ select = []; map = []; begin
171
+ arg_names.each do |arg_name|
172
+ if f1.arg_indexes.key?(arg_name) and f2.arg_indexes.key?(arg_name) then
173
+ select << "f1_arg_values[#{f1.arg_indexes[arg_name]}] == f2_arg_values[#{f2.arg_indexes[arg_name]}]"
174
+ map << "f1_arg_values[#{f1.arg_indexes[arg_name]}]"
175
+ elsif f1.arg_indexes.key?(arg_name) then
176
+ map << "f1_arg_values[#{f1.arg_indexes[arg_name]}]"
177
+ elsif f2.arg_indexes.key?(arg_name) then
178
+ map << "f2_arg_values[#{f2.arg_indexes[arg_name]}]"
179
+ end
180
+ end
181
+ select =
182
+ if select.empty? then "true"
183
+ else "#{select.join(" and ")}"
184
+ end
185
+ map =
186
+ "[#{map.join(", ")}]"
187
+ end
188
+ Fact.new(
189
+ arg_names,
190
+ eval(
191
+ "lambda do |context| "+
192
+ "Enumerable.new do |block| "+
193
+ "f1.arg_valuess(context).each do |f1_arg_values| "+
194
+ "f2.arg_valuess(context).each do |f2_arg_values| "+
195
+ "if #{select} then "+
196
+ "block.(#{map}) "+
197
+ "end "+
198
+ "end "+
199
+ "end "+
200
+ "end "+
201
+ "end "
202
+ )
203
+ )
204
+ end
205
+ end
206
+
207
+ # @param [Fact] f2
208
+ # @return [Fact]
209
+ def | f2
210
+ arg_names = f1.arg_names | f2.arg_names
211
+ map1 = nil; map2 = nil; begin
212
+ map = lambda do |arg_indexes|
213
+ eval "lambda do |arg_values|
214
+ [#{
215
+ arg_names.map do |arg_name|
216
+ if arg_indexes.key?(arg_name) then
217
+ "arg_values[#{arg_indexes[arg_name]}]"
218
+ else
219
+ "nil"
220
+ end
221
+ end.
222
+ join(", ")
223
+ }]
224
+ end"
225
+ end
226
+ map1 = map.(f1.arg_indexes)
227
+ map2 = map.(f2.arg_indexes)
228
+ end
229
+ Fact.new(
230
+ arg_names,
231
+ lambda do |context|
232
+ Enumerable.new do |block|
233
+ f1.arg_valuess(context).map(&map1).each(&block)
234
+ f2.arg_valuess(context).map(&map2).each(&block)
235
+ end
236
+ end
237
+ )
238
+ end
239
+
240
+ # @return [Fact::Not]
241
+ def !@
242
+ Fact::Not.new(self)
243
+ end
244
+
245
+ class Not
246
+
247
+ # @param [Fact] subfact
248
+ def initialize(subfact)
249
+ raise "#{Fact} is expected" unless subfact.is_a? Fact
250
+ @subfact = subfact
251
+ end
252
+
253
+ # @return [Fact]
254
+ attr_reader :subfact
255
+
256
+ end
257
+
258
+ class Select
259
+
260
+ # @param [*Symbol] arg_names
261
+ # @param [Proc<*Object, Boolean>] criteria
262
+ def initialize(*arg_names, &criteria)
263
+ @arg_names = arg_names
264
+ @criteria = criteria
265
+ end
266
+
267
+ # @return [Array<Symbol>]
268
+ attr_reader :arg_names
269
+
270
+ # @return [Proc<*Object, Boolean>]
271
+ attr_reader :criteria
272
+
273
+ end
274
+
275
+ # ----------
276
+ # @!endgroup
277
+ # ----------
278
+
279
+ #
280
+ class Context
281
+
282
+ def initialize()
283
+ @arg_valuesss = Hash.new { |arg_valuesss, name| arg_valuesss[name] = Set.new }
284
+ end
285
+
286
+ # @api private
287
+ # @note used by {Fact} only
288
+ #
289
+ # @param [Object] relation_id
290
+ # @return [Set<Array<Object>>]
291
+ def [](relation_id)
292
+ @arg_valuesss[relation_id]
293
+ end
294
+
295
+ end
296
+
297
+ protected
298
+
299
+ # @!visibility private
300
+ #
301
+ # @return [Hash<Symbol, Integer>]
302
+ #
303
+ def arg_indexes
304
+ arg_names.zip(0...arg_names.size).reduce({}) do |h, arg_name_and_arg_index|
305
+ arg_name, arg_index = *arg_name_and_arg_index
306
+ h.put(arg_name, arg_index)
307
+ end
308
+ end
309
+
310
+ private
311
+
312
+ def f1
313
+ self
314
+ end
315
+
316
+ end
317
+
318
+ # ctx = Fact::Context.new
319
+ # Fact["loves", "John", "Liza"].true!(ctx)
320
+ # Fact["yiffs", "Sam", "Liza"].true!(ctx)
321
+ # Fact["yiffs", "John", "Liza"].true!(ctx)
322
+ # Fact["loves", "John", "Johanna"].true!(ctx)
323
+ # p((Fact["loves", :x, :y] & Fact["yiffs", :x, :'*']).arg_valuess(ctx).to_a)
324
+ # p((Fact["loves", :x, :y] | Fact["yiffs", :x, :y]).arg_valuess(ctx).to_a)
325
+ # p((Fact["loves", :x, :y] & !Fact["yiffs", :'*', :y]).arg_valuess(ctx).to_a)
326
+ # x = Fact["loves", :x, :y] & Fact::Select.new(:x, :y) { |x, y| x != "John" }
327
+ # p x.arg_names
328
+ # p x.arg_valuess(ctx).to_a
329
+ # Fact["loves", "Kris", "Diana"].true!(ctx)
330
+ # Fact["loves", "John", :'*'].false!(ctx)
331
+ # p x.arg_names
332
+ # p x.arg_valuess(ctx).to_a
333
+ # Fact["loves", :x, :y].arg_valuess(ctx).to_a
data/lib/hash/put.rb ADDED
@@ -0,0 +1,14 @@
1
+
2
+ class Hash
3
+
4
+ #
5
+ # Alias for {Hash#[]=}.
6
+ #
7
+ # @return [Hash] self.
8
+ #
9
+ def put(key, value)
10
+ self[key] = value
11
+ self
12
+ end
13
+
14
+ end
@@ -0,0 +1,9 @@
1
+
2
+ class Object
3
+
4
+ # @return [Object] result of +f+ applied to self.
5
+ def map_(&f)
6
+ f.(self)
7
+ end
8
+
9
+ end
@@ -0,0 +1,9 @@
1
+
2
+ class Object
3
+
4
+ # @return [String] a {String} x which {Kernel#eval}(x).equal?(self).
5
+ def to_rb
6
+ inspect
7
+ end
8
+
9
+ end
data/lib/parse.rb ADDED
@@ -0,0 +1,463 @@
1
+ require 'strscan'
2
+ require 'strscan/substr'
3
+
4
+ # To disable YARD warnings:
5
+ # @!parse
6
+ # class Exception
7
+ # def message
8
+ # end
9
+ # end
10
+ # class Array
11
+ # end
12
+ # class String
13
+ # end
14
+
15
+ class Parse
16
+
17
+ #
18
+ # parses +text+.
19
+ #
20
+ # @param [String] text
21
+ # @param [String] file file the +text+ is taken from.
22
+ # @raise [Parse::Error, IOError]
23
+ # @return [Object] what {#start} returns (non-nil).
24
+ #
25
+ def call(text, file = "-")
26
+ @text = StringScanner.new(text)
27
+ @file = file
28
+ @most_probable_error = nil
29
+ @allow_errors = true
30
+ @rule_start_pos = nil
31
+ r = start()
32
+ if r.nil? or not @text.eos? then
33
+ if @most_probable_error
34
+ then raise @most_probable_error
35
+ else raise Error.new(Position.new(@text.pos, @text), "syntax error")
36
+ end
37
+ end
38
+ return r
39
+ end
40
+
41
+ class Position
42
+
43
+ include Comparable
44
+
45
+ # @api private
46
+ # @note used by {Parse#pos} only
47
+ # @param [Integer] text_pos
48
+ # @param [StringScanner] text
49
+ # @param [String] file
50
+ def initialize(text_pos, text, file)
51
+ @text = text
52
+ @text_pos = text_pos
53
+ @file = file
54
+ end
55
+
56
+ # @return [String]
57
+ attr_reader :file
58
+
59
+ # @return [Integer]
60
+ def line
61
+ line_and_column[0]
62
+ end
63
+
64
+ # @return [Integer]
65
+ def column
66
+ line_and_column[1]
67
+ end
68
+
69
+ # @param [Object] other
70
+ # @return [-1, 0, 1, nil]
71
+ def <=> other
72
+ return nil unless other.is_a? Position and self.text.equal? other.text
73
+ self.text_pos <=> other.text_pos
74
+ end
75
+
76
+ # @param [Object] other
77
+ # @return [Boolean]
78
+ def == other
79
+ (self <=> other) == 0
80
+ end
81
+
82
+ # @!visibility private
83
+ attr_reader :text_pos
84
+
85
+ # @!visibility private
86
+ attr_reader :text
87
+
88
+ private
89
+
90
+ def line_and_column
91
+ @line_and_column ||= begin
92
+ s = @text.substr(0, @text_pos)
93
+ lines = s.split("\n", -1).to_a
94
+ [
95
+ line = if lines.size == 0 then 0 else lines.size - 1 end,
96
+ column = (lines.last || "").size
97
+ ].tap do
98
+ @text = nil
99
+ end
100
+ end
101
+ end
102
+
103
+ end
104
+
105
+ class Error < Exception
106
+
107
+ # @param [Position] pos
108
+ # @param [String] message
109
+ def initialize(pos, message)
110
+ super(message)
111
+ @pos = pos
112
+ end
113
+
114
+ # @return [Position]
115
+ attr_reader :pos
116
+
117
+ # @param [Error] other +self+.{#pos} must be equal to +other+.{#pos}.
118
+ # @return [Error] an {Error} with {Exception#message} combined from
119
+ # {Exception#message}s of +self+ and +other+ (using "or" word).
120
+ def or other
121
+ raise "#{self.pos} == #{other.pos} is false" unless self.pos == other.pos
122
+ Error.new(pos, "#{self.message} or #{other.message}")
123
+ end
124
+
125
+ end
126
+
127
+ class Expected < Error
128
+
129
+ # @param [Position] pos
130
+ # @param [String] what_1
131
+ # @param [*String] what_2_n
132
+ def initialize(pos, what_1, *what_2_n)
133
+ @what = [what_1, *what_2_n]
134
+ super(pos, "#{@what.join(", ")} expected")
135
+ end
136
+
137
+ # (see Error#or)
138
+ def or other
139
+ if other.is_a? Expected
140
+ raise "#{self.pos} == #{other.pos} is false" unless self.pos == other.pos
141
+ Expected.new(pos, *(self.what + other.what).uniq)
142
+ else
143
+ super(other)
144
+ end
145
+ end
146
+
147
+ protected
148
+
149
+ # @!visibility private
150
+ # @return [Array<String>]
151
+ attr_reader :what
152
+
153
+ end
154
+
155
+ protected
156
+
157
+ # @!method start()
158
+ # @abstract
159
+ #
160
+ # Implementation of {#call}. Or the starting rule.
161
+ #
162
+ # @return [Object, nil] a result of parsing or nil if the parsing failed.
163
+ # @raise [Parse::Error] if the parsing failed.
164
+ # @raise [IOError]
165
+ #
166
+
167
+ # @return [Position] current {Position} in +text+ passed to {#call}.
168
+ def pos
169
+ Position.new(@text.pos, @text, @file)
170
+ end
171
+
172
+ #
173
+ # scans +arg+ in +text+ passed to {#call} starting from {#pos} and,
174
+ # if scanned successfully, advances {#pos} and returns the scanned
175
+ # sub-{String}. Otherwise it calls {#expected} and returns nil.
176
+ #
177
+ # @param [String, Regexp] arg
178
+ # @return [String, nil]
179
+ #
180
+ def scan(arg)
181
+ case arg
182
+ when Regexp then @text.scan(arg) or expected(pos, %(regexp "#{arg.source}"))
183
+ when String then @text.scan(Regexp.new(Regexp.escape(arg))) or expected(pos, %("#{arg}"))
184
+ end
185
+ end
186
+
187
+ # defines method +name+ with body +body+. Inside +body+ {#rule_start_pos}
188
+ # is the value of {#pos} right before the defined method's call.
189
+ #
190
+ # @param [Symbol] name
191
+ # @return [void]
192
+ #
193
+ def self.rule(name, &body)
194
+ define_method(name) do
195
+ old_rule_start_pos = @rule_start_pos
196
+ @rule_start_pos = pos
197
+ begin
198
+ instance_eval(&body)
199
+ ensure
200
+ @rule_start_pos = old_rule_start_pos
201
+ end
202
+ end
203
+ end
204
+
205
+ # See {Parse::rule}.
206
+ #
207
+ # @return [Position]
208
+ #
209
+ attr_reader :rule_start_pos
210
+
211
+ # @param [ASTNode] node
212
+ # @return [ASTNode] +node+ which is
213
+ # {ASTNode#initialize_pos}({#rule_start_pos})-ed.
214
+ def _1(node)
215
+ node.initialize_pos(rule_start_pos)
216
+ end
217
+
218
+ # is {#pos} at the end of the text?
219
+ def end?
220
+ @text.eos?
221
+ end
222
+
223
+ alias eos? end?
224
+
225
+ # is {#pos} at the beginning of the text?
226
+ def begin?
227
+ @text.pos == 0
228
+ end
229
+
230
+ # alias for {#_1} and {#_2}
231
+ def _(arg = nil, &block)
232
+ if arg
233
+ then _1(arg)
234
+ else _2(&block)
235
+ end
236
+ end
237
+
238
+ # --------------------------
239
+ # @!group Parser Combinators
240
+ # --------------------------
241
+
242
+ # calls +f+ and returns true.
243
+ #
244
+ # @return [true]
245
+ def act(&f)
246
+ f.()
247
+ true
248
+ end
249
+
250
+ #
251
+ # calls +f+. If +f+ results in nil then it restores {#pos} to the
252
+ # value before the call.
253
+ #
254
+ # @return what +f+ returns.
255
+ #
256
+ def _2(&f)
257
+ old_text_pos = @text.pos
258
+ f.() or begin
259
+ @text.pos = old_text_pos
260
+ nil
261
+ end
262
+ end
263
+
264
+ # calls +f+ using {#_2} many times until it returns nil.
265
+ #
266
+ # @return [Array] an {Array} of non-nil results of +f+.
267
+ #
268
+ def many(&f)
269
+ r = []
270
+ while true
271
+ f0 = _(&f)
272
+ if f0
273
+ then r.push(f0)
274
+ else break
275
+ end
276
+ end
277
+ r
278
+ end
279
+
280
+ # calls +f+ using {#_2}.
281
+ #
282
+ # @return [Array] an empty {Array} if +f+ results in nil and an {Array}
283
+ # containing the single result of +f+ otherwise.
284
+ #
285
+ def opt(&f)
286
+ [_(&f)].compact
287
+ end
288
+
289
+ # The same as <code>f.() and many(&f)</code>.
290
+ #
291
+ # @return [Array, nil] an {Array} of results of +f+ or nil if the first call
292
+ # to +f+ returned nil.
293
+ #
294
+ def one_or_more(&f)
295
+ f1 = f.() and f2_n = many(&f) and [f1, *f2_n]
296
+ end
297
+
298
+ alias many1 one_or_more
299
+
300
+ # @overload not_follows(*method_ids)
301
+ #
302
+ # calls methods specified by +method_ids+. If any of them returns non-nil
303
+ # then this method returns nil, otherwise it returns true. The methods
304
+ # are called inside {#no_errors}. {#pos} is restored after each method's
305
+ # call.
306
+ #
307
+ # @param [Array<Symbol>] method_ids
308
+ # @return [true, nil]
309
+ #
310
+ # @overload not_follows(&f)
311
+ #
312
+ # calls +f+. If +f+ returns non-nil then this method returns nil,
313
+ # otherwise it returns true. +f+ is called inside {#no_errors}.
314
+ # {#pos} is restored after +f+'s call.
315
+ #
316
+ # @return [true, nil]
317
+ #
318
+ def not_follows(*method_ids, &f)
319
+ if f then
320
+ if no_errors { _{ f.() } }
321
+ then nil
322
+ else true
323
+ end
324
+ else # if not method_ids.empty? then
325
+ if no_errors { method_ids.any? { |method_id| _{ __send__(method_id) } } }
326
+ then nil
327
+ else true
328
+ end
329
+ end
330
+ end
331
+
332
+ # ----------
333
+ # @!endgroup
334
+ # ----------
335
+
336
+ # --------------
337
+ # @!group Errors
338
+ # --------------
339
+
340
+ #
341
+ # sets {#most_probable_error} to +error+ if it is more probable than
342
+ # {#most_probable_error} or if {#most_probable_error} is nil.
343
+ #
344
+ # @param [Error] error
345
+ # @return [nil]
346
+ #
347
+ def error(error)
348
+ if @allow_errors then
349
+ if @most_probable_error.nil? or @most_probable_error.pos < error.pos then
350
+ @most_probable_error = error
351
+ elsif @most_probable_error and @most_probable_error.pos == error.pos then
352
+ @most_probable_error = @most_probable_error.or error
353
+ else
354
+ # do nothing
355
+ end
356
+ end
357
+ return nil
358
+ end
359
+
360
+ # macro
361
+ def expected(pos, what_1, *what_2_n)
362
+ error(Expected.new(pos, what_1, *what_2_n))
363
+ end
364
+
365
+ # macro
366
+ def expect(what_1, *what_2_n, &body)
367
+ p = pos and body.() or expected(p, what_1, *what_2_n)
368
+ end
369
+
370
+ # calls +block+. Inside the +block+ {#error} has no effect.
371
+ #
372
+ # @return what +block+ returns.
373
+ #
374
+ def no_errors(&block)
375
+ old_allow_errors = @allow_errors
376
+ begin
377
+ @allow_errors = false
378
+ block.()
379
+ ensure
380
+ @allow_errors = old_allow_errors
381
+ end
382
+ end
383
+
384
+ # @return [Error, nil]
385
+ attr_reader :most_probable_error
386
+
387
+ # ----------
388
+ # @!endgroup
389
+ # ----------
390
+
391
+ end
392
+
393
+ module ASTNode
394
+
395
+ # macro
396
+ def self.new(*properties, &body)
397
+ (if properties.empty? then Class.new else Struct.new(*properties); end).tap do |c|
398
+ c.class_eval do
399
+
400
+ include ASTNode
401
+
402
+ # @return [Boolean]
403
+ def === other
404
+ self.class == other.class and
405
+ self.members.all? { |member| self.__send__(member) === other.__send__(member) }
406
+ end
407
+
408
+ if properties.empty? then
409
+ # @return [Array<Symbol>]
410
+ def members
411
+ []
412
+ end
413
+ end
414
+
415
+ end
416
+ c.class_eval(&body) if body
417
+ end
418
+ end
419
+
420
+ # @param [Parse::Position] pos
421
+ # @return [self]
422
+ def initialize_pos(pos)
423
+ @pos = pos
424
+ self
425
+ end
426
+
427
+ # @note {#initialize_pos} must be called before this method can be used.
428
+ # @return [Parse::Position]
429
+ def pos
430
+ raise "initialize_pos() must be called before this method can be used" unless @pos
431
+ @pos
432
+ end
433
+
434
+ end
435
+
436
+ # Alias for {ASTNode#new}.
437
+ def node(*properties, &body)
438
+ ASTNode.new(*properties, &body)
439
+ end
440
+
441
+ # class Calc < Parse
442
+ #
443
+ # Number = ASTNode.new :val
444
+ #
445
+ # rule :start do
446
+ # x1 = number and x2 = many { comma and number } and [x1, *x2]
447
+ # end
448
+ #
449
+ # rule :number do
450
+ # s = scan(/\d+/) and _(Number[s])
451
+ # end
452
+ #
453
+ # rule :comma do
454
+ # scan(",")
455
+ # end
456
+ #
457
+ # end
458
+ #
459
+ # begin
460
+ # p Calc.new.("10, 20", __FILE__)
461
+ # rescue Parse::Error => e
462
+ # puts "error: #{e.pos.file}:#{e.pos.line}:#{e.pos.column}: #{e.message}"
463
+ # end