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
data/lib/story.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'enumerable/empty'
|
3
|
+
require 'enumerable/sample'
|
4
|
+
|
5
|
+
# To remove YARD warnings:
|
6
|
+
# @!parse
|
7
|
+
# class Proc
|
8
|
+
# end
|
9
|
+
|
10
|
+
class Story
|
11
|
+
|
12
|
+
# @param [IO] out
|
13
|
+
# @return [void]
|
14
|
+
# @raise [Story::Error]
|
15
|
+
def write(out = STDOUT)
|
16
|
+
@out = out
|
17
|
+
write0()
|
18
|
+
end
|
19
|
+
|
20
|
+
# @!method relations
|
21
|
+
# @abstract
|
22
|
+
# @return [Array<String>] relations mentioned in this {Story}.
|
23
|
+
|
24
|
+
Position = Struct.new :file, :line, :column
|
25
|
+
|
26
|
+
class Error < Exception
|
27
|
+
|
28
|
+
# @param [Story::Position] pos
|
29
|
+
# @param [String] message
|
30
|
+
def initialize(pos, message)
|
31
|
+
super(message)
|
32
|
+
@pos = pos
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Story::Position]
|
36
|
+
attr_reader :pos
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
# @!method write0
|
43
|
+
# @abstract
|
44
|
+
#
|
45
|
+
# Implementation of {#write}.
|
46
|
+
#
|
47
|
+
# @return [void]
|
48
|
+
# @raise [Story::Error]
|
49
|
+
|
50
|
+
# chooses a random non-empty {Enumerable} from +enumerable_and_action_s+,
|
51
|
+
# then chooses a random {Array} from that {Enumerable} and calls
|
52
|
+
# the appropriate {Proc}, passing the chosen {Array} as its *parameters.
|
53
|
+
# Or calls +else_action+ if all of the {Enumerable}s are empty.
|
54
|
+
#
|
55
|
+
# @param [Array<Enumerable<Array<Object>>, Proc>] enumerable_and_action_s
|
56
|
+
# @param [Proc<*Object>] else_action
|
57
|
+
# @return [void]
|
58
|
+
#
|
59
|
+
def if_(enumerable_and_action_s, else_action)
|
60
|
+
x = enumerable_and_action_s.
|
61
|
+
map do |enumerable_and_action|
|
62
|
+
enumerable, action = *enumerable_and_action
|
63
|
+
[enumerable.sample, action]
|
64
|
+
end.
|
65
|
+
reject do |sample, action|
|
66
|
+
sample.nil?
|
67
|
+
end.
|
68
|
+
sample
|
69
|
+
if x.nil? then
|
70
|
+
else_action.() if else_action
|
71
|
+
else
|
72
|
+
data, action = *x
|
73
|
+
action.(*data)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# writes +msg+ to +out+ passed to {#write}.
|
78
|
+
#
|
79
|
+
# @param [String] msg
|
80
|
+
# @return [void]
|
81
|
+
def tell(msg)
|
82
|
+
@out.write(msg)
|
83
|
+
end
|
84
|
+
|
85
|
+
def must_be(x, clazz)
|
86
|
+
raise "#{x.inspect} is not #{if /^[aeiou]/ === clazz.to_s.downcase then "an" else "a" end} #{clazz}" unless x.is_a? clazz
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,926 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'parse'
|
3
|
+
require 'code'
|
4
|
+
require 'string/ru_downcase'
|
5
|
+
require 'object/to_rb'
|
6
|
+
require 'array/case_equal_fixed'
|
7
|
+
require 'array/to_h'
|
8
|
+
require 'array/chomp'
|
9
|
+
require 'any'
|
10
|
+
|
11
|
+
# To remove YARD warnings:
|
12
|
+
# @!parse
|
13
|
+
# class Class
|
14
|
+
# end
|
15
|
+
# module Kernel
|
16
|
+
# def eval(str)
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
|
20
|
+
class Story
|
21
|
+
|
22
|
+
# @param [String] text
|
23
|
+
# @param [String] file a file the +text+ is taken from.
|
24
|
+
# @return [String] a {String} which {Kernel#eval}s to a {Class} which
|
25
|
+
# inherits {Story}.
|
26
|
+
# @raise [Parse::Error]
|
27
|
+
def self.compile(text, file = "-")
|
28
|
+
#
|
29
|
+
c = Parse.new.(text, file).to_code
|
30
|
+
#
|
31
|
+
relation_id_to_var_arg_valuesss_var = Hash.new do |h, relation_id|
|
32
|
+
h[relation_id] = "@#{INTERNAL_VAR_PREFIX}var_arg_valuesss#{h.size}"
|
33
|
+
end
|
34
|
+
#
|
35
|
+
c = c.map_non_code_parts do |part|
|
36
|
+
if part.is_a? VarArgValuesssVar
|
37
|
+
relation_id_to_var_arg_valuesss_var[part.relation_id]
|
38
|
+
else
|
39
|
+
raise "unknown non-code part type: #{part.inspect}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
#
|
43
|
+
relation_ids_code = relation_id_to_var_arg_valuesss_var.keys.
|
44
|
+
map { |relation_id| relation_id.join(" ") }.
|
45
|
+
sort.
|
46
|
+
to_rb
|
47
|
+
#
|
48
|
+
init_var_arg_valuesss_vars = relation_id_to_var_arg_valuesss_var.
|
49
|
+
map do |relation_id, var|
|
50
|
+
"#{var} = Set.new # #{relation_id}\n"
|
51
|
+
end.
|
52
|
+
join_code
|
53
|
+
#
|
54
|
+
c = code <<
|
55
|
+
"require 'enumerable/empty'\n"<<
|
56
|
+
"require 'enumerable/lazy'\n"<<
|
57
|
+
"require 'set'\n"<<
|
58
|
+
"require 'story'\n"<<
|
59
|
+
"require 'enumerable/new'\n"<<
|
60
|
+
"\n"<<
|
61
|
+
"Class.new(Story) do\n"<<
|
62
|
+
"def relations\n"<<
|
63
|
+
relation_ids_code << "\n"<<
|
64
|
+
"end\n"<<
|
65
|
+
"protected\n"<<
|
66
|
+
"def write0(io = STDOUT)\n"<<
|
67
|
+
init_var_arg_valuesss_vars << "\n"<<
|
68
|
+
c << "\n"<<
|
69
|
+
"end\n"<<
|
70
|
+
"end\n"
|
71
|
+
#
|
72
|
+
c.to_s
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
INTERNAL_VAR_PREFIX = "__internal__"
|
78
|
+
|
79
|
+
# @!visibility private
|
80
|
+
class ::Array
|
81
|
+
|
82
|
+
# @param [Code, String] delimiter
|
83
|
+
# @return [Code]
|
84
|
+
def join_code(delimiter = "")
|
85
|
+
self.reduce do |r, self_i|
|
86
|
+
code << r << delimiter << self_i
|
87
|
+
end or
|
88
|
+
code
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
# @!visibility private
|
94
|
+
module ::ASTNode
|
95
|
+
|
96
|
+
INTERNAL_VAR_PREFIX = Story::INTERNAL_VAR_PREFIX
|
97
|
+
|
98
|
+
protected
|
99
|
+
|
100
|
+
# macro
|
101
|
+
def internal_var(base_name)
|
102
|
+
"#{INTERNAL_VAR_PREFIX}#{base_name}"
|
103
|
+
end
|
104
|
+
|
105
|
+
# wraps result of +f+ to:
|
106
|
+
#
|
107
|
+
# (
|
108
|
+
# begin
|
109
|
+
# (f result)
|
110
|
+
# rescue Exception => e
|
111
|
+
# raise Story::Error.new(pos, e.message)
|
112
|
+
# end
|
113
|
+
# )
|
114
|
+
#
|
115
|
+
# Here +e+ is some internal variable (see {INTERNAL_VAR_PREFIX}) and
|
116
|
+
# +pos+ is converted to {Story::Position}.
|
117
|
+
#
|
118
|
+
# @param [Parse::Position] pos
|
119
|
+
# @yieldreturn [Code]
|
120
|
+
# @return [Code]
|
121
|
+
#
|
122
|
+
def at(pos, &f)
|
123
|
+
e = internal_var(:e)
|
124
|
+
code << "(begin\n" <<
|
125
|
+
f.() << "\n" <<
|
126
|
+
"rescue Exception => #{e}\n" <<
|
127
|
+
"raise Story::Error.new(Story::Position.new(#{pos.file.to_rb}, #{pos.line.to_rb}, #{pos.column.to_rb}), #{e}.message)\n" <<
|
128
|
+
"end)"
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
# @!visibility private
|
134
|
+
VarArgValuesssVar = Struct.new :relation_id, :pos
|
135
|
+
|
136
|
+
Var = ASTNode.new :name, :original_name do
|
137
|
+
|
138
|
+
alias name_super name
|
139
|
+
|
140
|
+
def name
|
141
|
+
raise Parse::Error.new(pos, "variables with names starting with `#{ASTNode::INTERNAL_VAR_PREFIX}' are reserved for internal use") if name_super.start_with? ASTNode::INTERNAL_VAR_PREFIX
|
142
|
+
name_super
|
143
|
+
end
|
144
|
+
|
145
|
+
def == other
|
146
|
+
self.class == other.class and
|
147
|
+
self.name == other.name
|
148
|
+
end
|
149
|
+
|
150
|
+
def === other
|
151
|
+
self.class == other.class and
|
152
|
+
self.name === other.name
|
153
|
+
end
|
154
|
+
|
155
|
+
def hash
|
156
|
+
name.hash
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
Asterisk = ASTNode.new
|
162
|
+
|
163
|
+
# @!visibility private
|
164
|
+
module Statement
|
165
|
+
end
|
166
|
+
|
167
|
+
Statement::Tell = ASTNode.new :parts do
|
168
|
+
|
169
|
+
def to_code
|
170
|
+
code << "(begin\n" <<
|
171
|
+
parts.map do |part|
|
172
|
+
code << "print(" <<
|
173
|
+
case part
|
174
|
+
when String then part.to_rb
|
175
|
+
when Var then at(part.pos) { part.name }
|
176
|
+
end <<
|
177
|
+
")"
|
178
|
+
end.join_code("\n") << "\n" <<
|
179
|
+
"end)"
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
Statement::SetFact = ASTNode.new :subexpr do
|
185
|
+
|
186
|
+
def to_code
|
187
|
+
#
|
188
|
+
var_arg_valuess = internal_var(:var_arg_valuess)
|
189
|
+
#
|
190
|
+
case subexpr
|
191
|
+
when FactExpr::Not then
|
192
|
+
args = subexpr.subexpr.args
|
193
|
+
reject = args.zip(0...args.size).
|
194
|
+
map do |arg, i|
|
195
|
+
case arg
|
196
|
+
when Asterisk then
|
197
|
+
nil
|
198
|
+
when Var then
|
199
|
+
"#{at(arg.pos) { arg.name }} == #{var_arg_valuess}[#{i}]"
|
200
|
+
else
|
201
|
+
"#{arg.to_rb} == #{var_arg_valuess}[#{i}]"
|
202
|
+
end
|
203
|
+
end.
|
204
|
+
compact
|
205
|
+
reject =
|
206
|
+
if reject.empty?
|
207
|
+
then ".clear()"
|
208
|
+
else ".reject! { |#{var_arg_valuess}| #{reject.join(" and ")} }"
|
209
|
+
end
|
210
|
+
code <<
|
211
|
+
non_code(VarArgValuesssVar[subexpr.subexpr.relation_id, pos]) <<
|
212
|
+
reject
|
213
|
+
else
|
214
|
+
args = subexpr.args
|
215
|
+
code <<
|
216
|
+
non_code(VarArgValuesssVar[subexpr.relation_id, pos]) <<
|
217
|
+
".add([" <<
|
218
|
+
args.zip(0...args.size).
|
219
|
+
map do |arg, i|
|
220
|
+
case arg
|
221
|
+
when Asterisk then
|
222
|
+
raise Parse::Error.new(arg.pos, "not supported")
|
223
|
+
when Var then
|
224
|
+
at(arg.pos) { arg.name }
|
225
|
+
else
|
226
|
+
arg.to_rb
|
227
|
+
end
|
228
|
+
end.
|
229
|
+
join_code(", ") <<
|
230
|
+
"])"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
|
236
|
+
Statement::If = ASTNode.new :condition_and_then_s, :else_ do
|
237
|
+
|
238
|
+
def to_code
|
239
|
+
code << "if_(\n"<<
|
240
|
+
"[\n" <<
|
241
|
+
condition_and_then_s.map do |condition, then_|
|
242
|
+
code << "[\n" <<
|
243
|
+
condition.to_code << ",\n" <<
|
244
|
+
"lambda do |" << condition.var_args.map(&:name).join_code(", ") << "|\n" <<
|
245
|
+
then_.to_code << "\n" <<
|
246
|
+
"end\n" <<
|
247
|
+
"]"
|
248
|
+
end.join_code(",\n") << "\n" <<
|
249
|
+
"],\n" <<
|
250
|
+
if else_ then
|
251
|
+
code << "lambda { " << else_.to_code << " }\n"
|
252
|
+
else
|
253
|
+
"nil\n"
|
254
|
+
end <<
|
255
|
+
")"
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
259
|
+
|
260
|
+
Statement::Compound = ASTNode.new :statements do
|
261
|
+
|
262
|
+
def to_code
|
263
|
+
code << "(begin\n" <<
|
264
|
+
statements.map { |statement| statement.to_code << "\n" }.reduce(:<<) <<
|
265
|
+
"end)"
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
|
270
|
+
Statement::While = ASTNode.new :condition, :body do
|
271
|
+
|
272
|
+
def to_code
|
273
|
+
o =
|
274
|
+
case condition
|
275
|
+
when FactExpr::Not then code << "while " << condition.subexpr.to_code << ".empty?"
|
276
|
+
else code << "until " << condition.to_code << ".empty?"
|
277
|
+
end
|
278
|
+
code << "(" << o << "\n" <<
|
279
|
+
body.to_code << "\n" <<
|
280
|
+
"end)"
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
|
285
|
+
Statement::ForAll = ASTNode.new :fact_expr, :body do
|
286
|
+
|
287
|
+
def to_code
|
288
|
+
ignored = internal_var(:ignored)
|
289
|
+
code << "(" << fact_expr.to_code << ".\n" <<
|
290
|
+
"to_a.\n" <<
|
291
|
+
"each do |" << fact_expr.var_args.map(&:name).join_code(", ") << ", " << ignored << " = nil|\n" <<
|
292
|
+
body.to_code << "\n" <<
|
293
|
+
"end)"
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|
297
|
+
|
298
|
+
Statement::NTimes = ASTNode.new :range_begin, :range_end, :body do
|
299
|
+
|
300
|
+
def to_code
|
301
|
+
n_code =
|
302
|
+
if range_begin == range_end
|
303
|
+
then to_code_(range_begin)
|
304
|
+
else code << "rand(" << to_code_(range_begin) << ".." << to_code_(range_end) << ")"
|
305
|
+
end
|
306
|
+
code << "(" << n_code << ".times do\n" <<
|
307
|
+
body.to_code << "\n" <<
|
308
|
+
"end)"
|
309
|
+
end
|
310
|
+
|
311
|
+
private
|
312
|
+
|
313
|
+
def to_code_(range_part)
|
314
|
+
case range_part
|
315
|
+
when Integer then range_part.to_rb
|
316
|
+
when Var then at(range_part.pos) { code << "(must_be(" << range_part.name << ", Integer); " << range_part.name << ")" }
|
317
|
+
else raise Parse::Error.new(pos, "what?")
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
end
|
322
|
+
|
323
|
+
Statement::Or = ASTNode.new :substatements do
|
324
|
+
|
325
|
+
def to_code
|
326
|
+
code << "[\n" <<
|
327
|
+
substatements.
|
328
|
+
map { |substatement| code << "lambda { " << substatement.to_code << " }" }.
|
329
|
+
join_code(",\n") << "\n" <<
|
330
|
+
"].sample.()"
|
331
|
+
end
|
332
|
+
|
333
|
+
end
|
334
|
+
|
335
|
+
Statement::Code = ASTNode.new :body do
|
336
|
+
|
337
|
+
def to_code
|
338
|
+
at(pos) { body }
|
339
|
+
end
|
340
|
+
|
341
|
+
end
|
342
|
+
|
343
|
+
Program = ASTNode.new :statements do
|
344
|
+
|
345
|
+
def to_code
|
346
|
+
statements.
|
347
|
+
map { |statement| statement.to_code << "\n" }.
|
348
|
+
reduce(:<<)
|
349
|
+
end
|
350
|
+
|
351
|
+
end
|
352
|
+
|
353
|
+
# @!visibility private
|
354
|
+
module FactExpr
|
355
|
+
|
356
|
+
# @method to_code
|
357
|
+
# @abstract
|
358
|
+
# @return [Code] a {Code} which evaluates to "var arg valuess" -
|
359
|
+
# {Enumerable} of values of {Var} arguments ({Enumerable} of
|
360
|
+
# {Array}s of {Object}s).
|
361
|
+
|
362
|
+
# @return [Array<Story::Var>] {Var} args attached to {#to_code} with
|
363
|
+
# {#with_var_args}.
|
364
|
+
def var_args
|
365
|
+
self.to_code.metadata or raise "no var args attached to #{self.to_code.inspect}"
|
366
|
+
end
|
367
|
+
|
368
|
+
# @return [Hash<Story::Var, Integer>] {#var_args} along with their indexes.
|
369
|
+
def var_args_indexes
|
370
|
+
(var_args).zip(0...var_args.size).to_h
|
371
|
+
end
|
372
|
+
|
373
|
+
protected
|
374
|
+
|
375
|
+
# @param [Array<Story::Var>] var_args
|
376
|
+
# @param [Code] code
|
377
|
+
# @return [Code] +code+ with +var_args+ attached to it as {Code#metadata}.
|
378
|
+
def with_var_args(var_args, code)
|
379
|
+
code.metadata(var_args)
|
380
|
+
end
|
381
|
+
|
382
|
+
end
|
383
|
+
|
384
|
+
FactExpr::Primary = ASTNode.new :relation_id, :args do
|
385
|
+
|
386
|
+
include FactExpr
|
387
|
+
|
388
|
+
def to_code
|
389
|
+
var_arg_valuess = internal_var(:var_arg_valuess)
|
390
|
+
select = []; map = []; begin
|
391
|
+
known_var_index = {}
|
392
|
+
args.zip(0...args.size).each do |arg, i|
|
393
|
+
case arg
|
394
|
+
when Asterisk then
|
395
|
+
# do nothing
|
396
|
+
when Var then
|
397
|
+
var = arg
|
398
|
+
if known_var_index.has_key? var then
|
399
|
+
select << "#{var_arg_valuess}[#{known_var_index[var]}] == #{var_arg_valuess}[#{i}]"
|
400
|
+
else
|
401
|
+
map << "#{var_arg_valuess}[#{i}]"
|
402
|
+
known_var_index[var] = i
|
403
|
+
end
|
404
|
+
else
|
405
|
+
select << "#{arg.to_rb} == #{var_arg_valuess}[#{i}]"
|
406
|
+
end
|
407
|
+
end
|
408
|
+
select =
|
409
|
+
if select.empty?
|
410
|
+
then ""
|
411
|
+
else ".select { |#{var_arg_valuess}| #{select.join(" and ")} }"
|
412
|
+
end
|
413
|
+
map =
|
414
|
+
".map { |#{var_arg_valuess}| [#{map.join(", ")}] }"
|
415
|
+
end
|
416
|
+
with_var_args(
|
417
|
+
args.select { |arg| arg.is_a? Var },
|
418
|
+
code << "(" <<
|
419
|
+
non_code(VarArgValuesssVar[relation_id, pos]) <<
|
420
|
+
select <<
|
421
|
+
map <<
|
422
|
+
")"
|
423
|
+
)
|
424
|
+
end
|
425
|
+
|
426
|
+
end
|
427
|
+
|
428
|
+
FactExpr::Not = ASTNode.new :subexpr do
|
429
|
+
|
430
|
+
include FactExpr
|
431
|
+
|
432
|
+
def to_code
|
433
|
+
raise Parse::Error.new(pos, "not supported")
|
434
|
+
end
|
435
|
+
|
436
|
+
end
|
437
|
+
|
438
|
+
FactExpr::And = ASTNode.new :e1, :e2 do
|
439
|
+
|
440
|
+
include FactExpr
|
441
|
+
|
442
|
+
def to_code
|
443
|
+
#
|
444
|
+
var_arg_valuess = internal_var(:var_arg_valuess)
|
445
|
+
var_arg_valuess1 = internal_var(:var_arg_valuess1)
|
446
|
+
var_arg_valuess2 = internal_var(:var_arg_valuess2)
|
447
|
+
block = internal_var(:block)
|
448
|
+
#
|
449
|
+
case [e1, e2]
|
450
|
+
when [any, FactExpr::Not]
|
451
|
+
common_var_args = e1.var_args & e2.subexpr.var_args
|
452
|
+
if common_var_args.empty? then
|
453
|
+
with_var_args(
|
454
|
+
e1.var_args,
|
455
|
+
code << "("<<
|
456
|
+
"if " << e2.subexpr.to_code << ".empty? "<<
|
457
|
+
"then " << e1.to_code << " "<<
|
458
|
+
"else [] "<<
|
459
|
+
"end"<<
|
460
|
+
")"
|
461
|
+
)
|
462
|
+
else
|
463
|
+
condition = common_var_args.
|
464
|
+
map do |var_arg|
|
465
|
+
"#{var_arg_valuess1}[#{e1.var_args_indexes[var_arg]}] == #{var_arg_valuess2}[#{e2.subexpr.var_args_indexes[var_arg]}]"
|
466
|
+
end.
|
467
|
+
join(" and ")
|
468
|
+
with_var_args(
|
469
|
+
e1.var_args,
|
470
|
+
code << "(" <<
|
471
|
+
e1.to_code << ".select do |#{var_arg_valuess1}| "<<
|
472
|
+
"not (" << e2.subexpr.to_code << ".any? { |#{var_arg_valuess2}| " << condition << " }) " <<
|
473
|
+
"end" <<
|
474
|
+
")"
|
475
|
+
)
|
476
|
+
end
|
477
|
+
when [any, FactExpr::Select]
|
478
|
+
[e2.e1, e2.e2].
|
479
|
+
select { |operand| operand.is_a? Var }.
|
480
|
+
each do |e2_var_arg|
|
481
|
+
if not e1.var_args.include? e2_var_arg then
|
482
|
+
raise Parse::Error.new(e2_var_arg.pos, "#{e2_var_arg.original_name} is not known in the left part")
|
483
|
+
end
|
484
|
+
end
|
485
|
+
to_code = lambda do |operand|
|
486
|
+
case operand
|
487
|
+
when Var then "#{var_arg_valuess}[#{e1.var_args_indexes[operand]}]"
|
488
|
+
else operand.to_rb
|
489
|
+
end
|
490
|
+
end
|
491
|
+
rb_op =
|
492
|
+
case e2.op
|
493
|
+
when "==", "!=", "<=", ">=", "<", ">" then e2.op
|
494
|
+
when "<>" then "!="
|
495
|
+
when "=" then "=="
|
496
|
+
end
|
497
|
+
with_var_args(
|
498
|
+
e1.var_args,
|
499
|
+
code << "(" <<
|
500
|
+
e1.to_code << ".select do |#{var_arg_valuess}| "<<
|
501
|
+
at(e2.pos) { to_code.(e2.e1) << rb_op << to_code.(e2.e2) } << " "<<
|
502
|
+
"end " <<
|
503
|
+
")"
|
504
|
+
)
|
505
|
+
when [any, any]
|
506
|
+
var_args = e1.var_args | e2.var_args
|
507
|
+
select = []; map = []; begin
|
508
|
+
var_args.each do |var_arg|
|
509
|
+
if e1.var_args.include?(var_arg) and e2.var_args.include?(var_arg) then
|
510
|
+
select << "#{var_arg_valuess1}[#{e1.var_args_indexes[var_arg]}] == #{var_arg_valuess2}[#{e2.var_args_indexes[var_arg]}]"
|
511
|
+
map << "#{var_arg_valuess1}[#{e1.var_args_indexes[var_arg]}]"
|
512
|
+
elsif e1.var_args.include?(var_arg) then
|
513
|
+
map << "#{var_arg_valuess1}[#{e1.var_args_indexes[var_arg]}]"
|
514
|
+
elsif e2.var_args.include?(var_arg) then
|
515
|
+
map << "#{var_arg_valuess2}[#{e2.var_args_indexes[var_arg]}]"
|
516
|
+
end
|
517
|
+
end
|
518
|
+
select =
|
519
|
+
if select.empty?
|
520
|
+
then "true"
|
521
|
+
else "#{select.join(" and ")}"
|
522
|
+
end
|
523
|
+
map =
|
524
|
+
"[#{map.join(", ")}]"
|
525
|
+
end
|
526
|
+
with_var_args(
|
527
|
+
var_args,
|
528
|
+
code << "(" <<
|
529
|
+
"Enumerable.new do |#{block}| "<<
|
530
|
+
e1.to_code << ".each do |#{var_arg_valuess1}| "<<
|
531
|
+
e2.to_code << ".each do |#{var_arg_valuess2}| "<<
|
532
|
+
"if #{select} then "<<
|
533
|
+
"#{block}.(#{map}) "<<
|
534
|
+
"end "<<
|
535
|
+
"end "<<
|
536
|
+
"end "<<
|
537
|
+
"end"<<
|
538
|
+
")"
|
539
|
+
)
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
end
|
544
|
+
|
545
|
+
FactExpr::Select = ASTNode.new :e1, :op, :e2
|
546
|
+
|
547
|
+
FactExpr::Or = ASTNode.new :e1, :e2 do
|
548
|
+
|
549
|
+
include FactExpr
|
550
|
+
|
551
|
+
def to_code
|
552
|
+
#
|
553
|
+
var_arg_valuess = internal_var(:var_arg_valuess)
|
554
|
+
block = internal_var(:block)
|
555
|
+
#
|
556
|
+
var_args = e1.var_args | e2.var_args
|
557
|
+
enum_var_arg_valuess = lambda do |e|
|
558
|
+
new_var_arg_valuess = var_args.map do |var_arg|
|
559
|
+
if (index = e.var_args_indexes[var_arg])
|
560
|
+
then "#{var_arg_valuess}[#{index}]"
|
561
|
+
else "nil"
|
562
|
+
end
|
563
|
+
end
|
564
|
+
e.to_code << ".map { |#{var_arg_valuess}| [#{new_var_arg_valuess.join(", ")}] }.each(&#{block})\n"
|
565
|
+
end
|
566
|
+
with_var_args(
|
567
|
+
var_args,
|
568
|
+
code << "(" <<
|
569
|
+
"Enumerable.new do |#{block}| "<<
|
570
|
+
enum_var_arg_valuess.(e1) <<
|
571
|
+
enum_var_arg_valuess.(e2) <<
|
572
|
+
"end" <<
|
573
|
+
")"
|
574
|
+
)
|
575
|
+
end
|
576
|
+
|
577
|
+
end
|
578
|
+
|
579
|
+
# @!visibility private
|
580
|
+
class Parse < ::Parse
|
581
|
+
|
582
|
+
# --------------
|
583
|
+
# @!group Syntax
|
584
|
+
# --------------
|
585
|
+
|
586
|
+
rule :start do
|
587
|
+
no_errors { wsc } and
|
588
|
+
ss = many {
|
589
|
+
s = statement and opt { _{ semicolon } or _{ dot } } and s
|
590
|
+
} and
|
591
|
+
Program[ss]
|
592
|
+
end
|
593
|
+
|
594
|
+
rule :statement do
|
595
|
+
_{ statement_or } or
|
596
|
+
_{ statement_if } or
|
597
|
+
_{ statement_while } or
|
598
|
+
_{ statement_for_all } or
|
599
|
+
_{ statement_n_times } or
|
600
|
+
_{ statement_code } or
|
601
|
+
_{ statement_set_fact } or
|
602
|
+
_{ statement_tell } or
|
603
|
+
_{ statement_compound }
|
604
|
+
end
|
605
|
+
|
606
|
+
rule :statement_tell do
|
607
|
+
parts = many1 {
|
608
|
+
_{ string } or _{ var }
|
609
|
+
} and
|
610
|
+
_(Statement::Tell[parts])
|
611
|
+
end
|
612
|
+
|
613
|
+
rule :statement_set_fact do
|
614
|
+
f = fact_expr_primary and _(Statement::SetFact[f])
|
615
|
+
end
|
616
|
+
|
617
|
+
rule :statement_if do
|
618
|
+
if_then = lambda {
|
619
|
+
_if_ and c = fact_expr and opt { comma } and _then_ and s = statement and [c, s]
|
620
|
+
}
|
621
|
+
or_delimiter = lambda {
|
622
|
+
opt { _{ comma } or _{ semicolon } or _{ dot } } and _or_
|
623
|
+
}
|
624
|
+
#
|
625
|
+
c_and_s1 = if_then.() and
|
626
|
+
c_and_sn = many { or_delimiter.() and if_then.() } and
|
627
|
+
else_a = opt { or_delimiter.() and statement } and
|
628
|
+
_(Statement::If[[c_and_s1, *c_and_sn], else_a.first])
|
629
|
+
end
|
630
|
+
|
631
|
+
rule :statement_while do
|
632
|
+
_while_ and f = fact_expr and opt { comma } and s = statement and
|
633
|
+
_(Statement::While[f, s])
|
634
|
+
end
|
635
|
+
|
636
|
+
rule :statement_for_all do
|
637
|
+
_for_ and all and f = fact_expr and opt { comma } and s = statement and
|
638
|
+
_(Statement::ForAll[f, s])
|
639
|
+
end
|
640
|
+
|
641
|
+
rule :statement_n_times do
|
642
|
+
val = lambda {
|
643
|
+
_{ number } or _{ var }
|
644
|
+
}
|
645
|
+
#
|
646
|
+
range_begin = range_end = val.() and opt { ellipsis and range_end = val.() } and times and s = statement and
|
647
|
+
_(Statement::NTimes[range_begin, range_end, s])
|
648
|
+
end
|
649
|
+
|
650
|
+
rule :statement_or do
|
651
|
+
_or_ and s1 = statement and
|
652
|
+
ss = many { opt { comma } and _or_ and statement } and
|
653
|
+
Statement::Or[[s1, *ss]]
|
654
|
+
end
|
655
|
+
|
656
|
+
rule :statement_code do
|
657
|
+
c = _code_ and
|
658
|
+
act {
|
659
|
+
begin
|
660
|
+
RubyVM::InstructionSequence.compile(c)
|
661
|
+
rescue SyntaxError => e
|
662
|
+
raise Parse::Error.new(rule_start_pos, e.message)
|
663
|
+
end
|
664
|
+
} and
|
665
|
+
_(Statement::Code[c])
|
666
|
+
end
|
667
|
+
|
668
|
+
rule :statement_compound do
|
669
|
+
body = lambda {
|
670
|
+
ss = many {
|
671
|
+
opt { dash } and s = statement and opt { semicolon } and s
|
672
|
+
} and
|
673
|
+
_(Statement::Compound[ss])
|
674
|
+
}
|
675
|
+
#
|
676
|
+
_{
|
677
|
+
colon and b = body.() and dot and b
|
678
|
+
} or
|
679
|
+
_{
|
680
|
+
lparen and b = body.() and rparen and b
|
681
|
+
}
|
682
|
+
end
|
683
|
+
|
684
|
+
rule :fact_expr do
|
685
|
+
fact_expr110
|
686
|
+
end
|
687
|
+
|
688
|
+
rule :fact_expr110 do
|
689
|
+
f = fact_expr100 and
|
690
|
+
many { _or_ and f2 = fact_expr100 and f = _(FactExpr::Or[f, f2]) } and
|
691
|
+
f
|
692
|
+
end
|
693
|
+
|
694
|
+
rule :fact_expr100 do
|
695
|
+
f = fact_expr90 and
|
696
|
+
many { _and_ and f2 = fact_expr90 and f = _(FactExpr::And[f, f2]) } and
|
697
|
+
f
|
698
|
+
end
|
699
|
+
|
700
|
+
rule :fact_expr90 do
|
701
|
+
_{ fact_expr80 } or
|
702
|
+
_{ _not_ and f = fact_expr90 and _(FactExpr::Not[f]) }
|
703
|
+
end
|
704
|
+
|
705
|
+
rule :fact_expr80 do
|
706
|
+
_{ lparen and f = fact_expr110 and rparen and f } or
|
707
|
+
_{ fact_expr_select } or
|
708
|
+
_{ fact_expr_primary }
|
709
|
+
end
|
710
|
+
|
711
|
+
rule :fact_expr_select do
|
712
|
+
#
|
713
|
+
operand = lambda { _{ var } or _{ value } }
|
714
|
+
#
|
715
|
+
v1 = operand.() and op = comparison_op and v2 = operand.() and
|
716
|
+
_(FactExpr::Select[v1, op, v2])
|
717
|
+
end
|
718
|
+
|
719
|
+
rule :fact_expr_primary do
|
720
|
+
relation_id = []
|
721
|
+
args = []
|
722
|
+
negated = false
|
723
|
+
#
|
724
|
+
one_or_more {
|
725
|
+
_{ s = (_{dash} or _{other_char}) and act { relation_id << s } } or
|
726
|
+
_{ a = asterisk and act { args << a; relation_id << :* } } or
|
727
|
+
_{ _not_ and act { negated = !negated } } or
|
728
|
+
_{ v = value and act { args << v; relation_id << :* } } or
|
729
|
+
_{ v = var and act { args << v; relation_id << :* } } or
|
730
|
+
_{ w = word and act { relation_id << w.ru_downcase } }
|
731
|
+
} and
|
732
|
+
# act { relation_id.chomp!(",") } and
|
733
|
+
# this is not a "statement_tell" and
|
734
|
+
not relation_id.all? { |p| p == :* } and
|
735
|
+
e = _(FactExpr::Primary[relation_id, args]) and
|
736
|
+
if negated then _(FactExpr::Not[e]); else e; end
|
737
|
+
end
|
738
|
+
|
739
|
+
rule :value do
|
740
|
+
_{ string } or
|
741
|
+
_{ number }
|
742
|
+
end
|
743
|
+
|
744
|
+
# ----------
|
745
|
+
# @!endgroup
|
746
|
+
# ----------
|
747
|
+
|
748
|
+
# ------------------------
|
749
|
+
# @!group Lexical Analysis
|
750
|
+
# ------------------------
|
751
|
+
|
752
|
+
# macro
|
753
|
+
def self.token(method_id, human_readable_description, &body)
|
754
|
+
rule method_id do
|
755
|
+
expect(human_readable_description) {
|
756
|
+
no_errors {
|
757
|
+
r = instance_eval(&body) and wsc and r
|
758
|
+
}
|
759
|
+
}
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|
763
|
+
# macro
|
764
|
+
def self.simple_token(method_id, str)
|
765
|
+
token method_id, "`#{str}'" do
|
766
|
+
scan(str)
|
767
|
+
end
|
768
|
+
end
|
769
|
+
|
770
|
+
simple_token :lparen, "("
|
771
|
+
simple_token :rparen, ")"
|
772
|
+
simple_token :comma, ","
|
773
|
+
simple_token :semicolon, ";"
|
774
|
+
simple_token :colon, ":"
|
775
|
+
simple_token :dot, "."
|
776
|
+
simple_token :dash, "-"
|
777
|
+
simple_token :ellipsis, "..."
|
778
|
+
|
779
|
+
token :comparison_op, "comparison operator" do
|
780
|
+
scan(/=|!=|<>|<=|>=|<|>/)
|
781
|
+
end
|
782
|
+
|
783
|
+
token :asterisk, "`*'" do
|
784
|
+
scan("*") and _(Asterisk.new)
|
785
|
+
end
|
786
|
+
|
787
|
+
token :other_char, "any of `#№@$%^/'" do
|
788
|
+
scan(/[\#\№\@\$\%\^\/]/)
|
789
|
+
end
|
790
|
+
|
791
|
+
token :_for_, "`for'" do
|
792
|
+
t = word_ and /^([Ff]or|[Дд]ля)$/ === t
|
793
|
+
end
|
794
|
+
|
795
|
+
token :all, "`all'" do
|
796
|
+
t = word_ and /^(all|всех)$/ === t
|
797
|
+
end
|
798
|
+
|
799
|
+
token :_while_, "`while'" do
|
800
|
+
t = word_ and /^([Ww]hile|[Пп]ока)$/ === t
|
801
|
+
end
|
802
|
+
|
803
|
+
token :_then_, "`then'" do
|
804
|
+
t = word_ and /^(then|то)$/ === t
|
805
|
+
end
|
806
|
+
|
807
|
+
token :_if_, "`if'" do
|
808
|
+
t = word_ and /^([Ii]f|[Ее]сли)$/ === t
|
809
|
+
end
|
810
|
+
|
811
|
+
token :_not_, "`not'" do
|
812
|
+
t = word_ and /^(not|не)$/ === t
|
813
|
+
end
|
814
|
+
|
815
|
+
token :_or_, "`or'" do
|
816
|
+
t = word_ and /^([Ee]ither|[Oo]r|[Ии]ли|[Лл]ибо)$/ === t
|
817
|
+
end
|
818
|
+
|
819
|
+
token :_and_, "`and'" do
|
820
|
+
t = word_ and /^(and|и)$/ === t
|
821
|
+
end
|
822
|
+
|
823
|
+
token :times, "`times'" do
|
824
|
+
t = word_ and /^(times|раза?)$/ === t
|
825
|
+
end
|
826
|
+
|
827
|
+
token :number, "number" do
|
828
|
+
n = scan(/\d+/) and n.to_i
|
829
|
+
end
|
830
|
+
|
831
|
+
token :string, "string" do
|
832
|
+
_{
|
833
|
+
scan('"') and
|
834
|
+
s = scan(/[^"]*/) and
|
835
|
+
(scan('"') or raise Error.new(rule_start_pos, "`\"' missing at the end")) and
|
836
|
+
s
|
837
|
+
} or
|
838
|
+
_{
|
839
|
+
scan("'") and
|
840
|
+
s = scan(/[^']*/) and
|
841
|
+
(scan("'") or raise Error.new(rule_start_pos, %(<<'>> missing at the end))) and
|
842
|
+
s
|
843
|
+
}
|
844
|
+
end
|
845
|
+
|
846
|
+
token :_code_, "code" do
|
847
|
+
p = pos and
|
848
|
+
scan("```") and c = scan(/((?!```).)*/m) and (scan("```") or raise Parse::Error.new(p, "\"```\" missing at the end")) and
|
849
|
+
c
|
850
|
+
end
|
851
|
+
|
852
|
+
token :var, "variable" do
|
853
|
+
n = word_ and
|
854
|
+
/^[_A-ZА-ЯЁ][_A-ZА-ЯЁ0-9]*$/ === n and
|
855
|
+
_(Var[n.ru_downcase, n])
|
856
|
+
end
|
857
|
+
|
858
|
+
token :word, "word" do
|
859
|
+
not_follows(
|
860
|
+
:_not_, :_and_, :_or_, :_if_, :_then_, :var, :_while_, :_for_, :all,
|
861
|
+
:times
|
862
|
+
) and
|
863
|
+
word_
|
864
|
+
end
|
865
|
+
|
866
|
+
rule :word_ do
|
867
|
+
scan(/[a-zA-Zа-яёА-ЯЁ_](['\-]?[a-zA-Zа-яёА-ЯЁ0-9_]+)*/)
|
868
|
+
end
|
869
|
+
|
870
|
+
rule :whitespace_and_comments do
|
871
|
+
many {
|
872
|
+
_{ ws } or
|
873
|
+
_{
|
874
|
+
p = pos and
|
875
|
+
scan("/*") and scan(/((?!\*\/).)*/m) and (scan("*/") or raise Error.new(p, "`*/' missing at the end"))
|
876
|
+
} or
|
877
|
+
_{
|
878
|
+
p = pos and
|
879
|
+
scan("(") and ws_opt and scan(/note|прим.|примечание/) and ws_opt and scan(":") and
|
880
|
+
note_comment_body and
|
881
|
+
(scan(")") or raise Error.new(p, "`)' missing at the end"))
|
882
|
+
}
|
883
|
+
}
|
884
|
+
end
|
885
|
+
|
886
|
+
alias wsc whitespace_and_comments
|
887
|
+
|
888
|
+
rule :whitespace do
|
889
|
+
scan(/\s+/)
|
890
|
+
end
|
891
|
+
|
892
|
+
alias ws whitespace
|
893
|
+
|
894
|
+
rule :whitespace_opt do
|
895
|
+
opt { ws }
|
896
|
+
end
|
897
|
+
|
898
|
+
alias ws_opt whitespace_opt
|
899
|
+
|
900
|
+
rule :note_comment_body do
|
901
|
+
many {
|
902
|
+
_{ scan(/[^\(\)]+/m) } or
|
903
|
+
_{ p = pos and scan("(") and note_comment_body and (scan(")") or raise Error.new(p, "`)' missing at the end")) }
|
904
|
+
}
|
905
|
+
end
|
906
|
+
|
907
|
+
# ----------
|
908
|
+
# @!endgroup
|
909
|
+
# ----------
|
910
|
+
|
911
|
+
end
|
912
|
+
|
913
|
+
end
|
914
|
+
|
915
|
+
# begin
|
916
|
+
# puts Story.compile(<<-STORY)
|
917
|
+
#
|
918
|
+
# ```puts(a)```
|
919
|
+
# Either "John" loves "Liza",
|
920
|
+
# or ("Hello, world, again!"; "John" kisses "Liza";).
|
921
|
+
# STORY
|
922
|
+
# rescue Parse::Error => e
|
923
|
+
# puts "error: #{e.pos.file}:#{e.pos.line}:#{e.pos.column}: #{e.message}"
|
924
|
+
# # rescue Story::Error => e
|
925
|
+
# # puts "error: #{e.pos.file}:#{e.pos.line}:#{e.pos.column}: #{e.message}"
|
926
|
+
# end
|