story-gen 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +259 -0
- data/bin/story +82 -0
- data/gem.gemspec +13 -0
- data/lib/any.rb +11 -0
- data/lib/array/case_equal_fixed.rb +9 -0
- data/lib/array/chomp.rb +21 -0
- data/lib/array/separate.rb +10 -0
- data/lib/array/to_h.rb +9 -0
- data/lib/code.rb +123 -0
- data/lib/enumerable/empty.rb +10 -0
- data/lib/enumerable/lazy.rb +10 -0
- data/lib/enumerable/new.rb +30 -0
- data/lib/enumerable/sample.rb +8 -0
- data/lib/fact.rb +333 -0
- data/lib/hash/put.rb +14 -0
- data/lib/object/map_.rb +9 -0
- data/lib/object/to_rb.rb +9 -0
- data/lib/parse.rb +463 -0
- data/lib/story.rb +89 -0
- data/lib/story/compile.rb +926 -0
- data/lib/string/lchomp.rb +14 -0
- data/lib/string/ru_downcase.rb +21 -0
- data/lib/strscan/substr.rb +16 -0
- data/lib/unique_names.rb +269 -0
- data/sample/fight_club.sdl +58 -0
- data/sample/university.sdl +53 -0
- data/sample//320/261/320/276/320/271/321/206/320/276/320/262/321/201/320/272/320/270/320/271_/320/272/320/273/321/203/320/261.sdl +58 -0
- data/sample//321/203/320/275/320/270/320/262/320/265/321/200/321/201/320/270/321/202/320/265/321/202.sdl +54 -0
- metadata +75 -0
@@ -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
|
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
data/lib/object/map_.rb
ADDED
data/lib/object/to_rb.rb
ADDED
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
|