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