story-gen 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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