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.
- 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
|