story-gen 0.0.1

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