story-gen 0.0.6 → 0.1.0

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/NEWS ADDED
@@ -0,0 +1,13 @@
1
+ 0.1.0:
2
+ "-m" or "--allow-comma-in-facts" is removed
3
+ Comma (",") in Facts is always allowed
4
+ $DEBUG variable is considered in Parse
5
+ Some bugs fixed
6
+ "Either" statement is changed
7
+ Dot (".") is now allowed at the end of the story only
8
+ "If" statement syntax is completely rewritten
9
+ No more commas in "times", "while", "for all" statements
10
+ Alternate form of compound statement (with "-") is removed
11
+
12
+ 0.0.4:
13
+ Like, first release
data/README.md CHANGED
@@ -55,7 +55,7 @@ A story is based on Facts. To state a Fact just write it:
55
55
 
56
56
  "John" loves "Liza"
57
57
 
58
- In the Fact expression you may use english and russian words (except keywords, see below), characters from "#№@$%/-", integer numbers (e.g., `18`) and arbitrary double- or single-quoted strings (`"..."` or `'...'`). Trailing commas are ignored (`xxx yyy zzz,` is the same as `xxx yyy zzz`). The words are case-insensitive (so `loves` and `Loves` mean the same), quoted strings are case-sensitive (so `"John"`≠`"JOHN"`).
58
+ In the Fact expression you may use english and russian words (except keywords, see below), characters from "#№@$%/-,[]{}", integer numbers (e.g., `18`) and arbitrary double- or single-quoted strings (`"..."` or `'...'`). Trailing commas are ignored (`xxx yyy zzz,` is the same as `xxx yyy zzz`). The words are case-insensitive (so `loves` and `Loves` mean the same), quoted strings are case-sensitive (so `"John"`≠`"JOHN"`).
59
59
 
60
60
  Let's state some Facts:
61
61
 
@@ -77,7 +77,11 @@ What can Facts be used for? You may form conditions with them:
77
77
 
78
78
  If X is a boy then "Let's meet " X;
79
79
 
80
- Here `X` is a variable. Variable names consist of underscores ("_") and capital letters only (e.g., `LONG_VARIABLE_NAME`). You may only capture quoted strings or numbers as variables, so the following condition is invalid:
80
+ Or, with a comma (",") before `then`:
81
+
82
+ If X is a boy, then "Let's meet " X;
83
+
84
+ Here `X` is a variable. Variable names consist of underscores ("_") and capital letters only (e.g., `LONG_VARIABLE_NAME`). You may only capture quoted strings or numbers as variables, so the following is invalid:
81
85
 
82
86
  If "John" A "Liza" then ... /* ERROR! */
83
87
 
@@ -107,47 +111,45 @@ Not all combinations of `and`, `or` and `not` are available, though. Use common
107
111
 
108
112
  If X is not a boy then "How can I determine "X"?" /* ERROR! */
109
113
 
114
+ <!-- TODO: Allow `not` in top-level but disallow to capture variables from it. -->
110
115
  You may also compare variables in the condition:
111
116
 
112
- If X is a boy and Y is a boy and X != Y then
117
+ If X is a boy and Y is a boy and X <> Y then
113
118
  X" and "Y" are two different boys!"
114
119
 
115
120
  There are limitations on the comparison: the comparison must be after the `and` keyword and all variables must be mentioned in the left part of `and`.
116
121
 
117
- You may use `=`, `!=`, `<>`, `<=`, `<`, `>` and `>=` as comparison operators. Take types of the comparands into account, though!
122
+ You may use `=`, `!=`, `<>`, `=/=`, `<=`, `<`, `>` and `>=` as comparison operators. Take types of the comparands into account, though!
118
123
 
119
124
  You may use asterisk ("*") instead of the variable to avoid capturing:
120
125
 
121
126
  If X is a boy and X not loves * then
122
127
  X" is a lonely boy."
123
128
 
124
- You may combine several `if`-s with `or` keyword:
129
+ You may use `otherwise` keyword:
125
130
 
126
- If X is a boy then
127
- "We have found a boy "X"!"
128
- or if Y is a girl then
129
- "We have found a girl "Y"!"
131
+ If X is a boy and X not loves * then
132
+ X" is a lonely boy."
133
+ otherwise
134
+ X" has found someone!"
130
135
 
131
- Or combine `if`-s with some statement:
136
+ You may combine several conditions and `then` statements using colon (":") and dashes ("-"):
132
137
 
133
- If X is a boy then
134
- "We know a boy "X"!"
135
- or if Y is a girl then
136
- "We know a girl "Y"!"
137
- or
138
- "We do not know anyone."
138
+ If:
139
+ - X is a boy then "We have found a boy "X;
140
+ - Y is a girl then "We have found a girl "Y;
141
+ - otherwise "We do not know anyone";
139
142
 
140
- This is like a classical `if ... else if ... else ...` but if multiple conditions are true then random one is chosen (instead of the first one, like in the classical `if-else`). The last `or` is the same as `else` in the classical `if-else` - it is chosen if all conditions are false.
143
+ This is like a classical `if ... else if ... else ...` but if multiple conditions are true then the random one is chosen (instead of the first one, like in the classical `if-else`). The last `otherwise` is the same as `else` in the classical `if-else` - it is chosen if all other conditions are false.
141
144
 
142
- The full form of `if`:
145
+ Look at the trailing semicolons (";") by the way - they resolve ambiguity!
143
146
 
144
- If <condition> [,] then <statement> [,]
145
- or if <condition> [,] then <statement> [,]
146
- or if <condition> [,] then <statement> [,]
147
- ...
148
- or <statement>
147
+ Another form of `if`:
149
148
 
150
- Here `[,]` means "optional comma".
149
+ If:
150
+ a) X is a boy then "We have found a boy "X;
151
+ b) Y is a girl then "We have found a girl "Y;
152
+ c) otherwise "We do not know anyone";
151
153
 
152
154
  You may use captured variables to state a Fact:
153
155
 
@@ -164,11 +166,11 @@ Set multiple Facts false:
164
166
  If X is a boy then
165
167
  X not loves *
166
168
 
167
- You may use the captured variables in another conditions (by prefixing them with hat "^"):
169
+ You may use the captured variables in another conditions by prefixing them with hat ("^"):
168
170
 
169
171
  If X is a boy then
170
172
  if ^X loves * then "We found "X" which is in love!"
171
- or "We found "X" which is stll single."
173
+ otherwise "We found "X" which is stll single."
172
174
 
173
175
  To combine several statements into one use a colon (":") with the final dot ("."):
174
176
 
@@ -186,38 +188,38 @@ or parentheses:
186
188
  There are other statements you can use:
187
189
 
188
190
  - "While":
189
-
191
+
190
192
  <pre><code>
191
- While &lt;fact expression&gt; [,] &lt;statement&gt;
193
+ While &lt;fact expression&gt; &lt;statement&gt;
192
194
  </code></pre>
193
-
195
+
194
196
  Here &lt;fact expression&gt; is the same as in `if` statement except that you may use `not` in top level:
195
197
 
196
198
  <pre><code>
197
199
  While X not loves *:
198
- If X is a boy Y is a girl then X loves Y.
200
+ If X is a boy and Y is a girl then X loves Y.
199
201
  </code></pre>
200
-
201
- The variables from &lt;fact expression&gt; are not available in &lt;statement&gt;
202
-
202
+
203
+ The variables from &lt;fact expression&gt; are not available in &lt;statement&gt;.
204
+
205
+ Note that usually there is no way to delimit &lt;fact expression&gt; from &lt;statement&gt; except by wrapping the &lt;statement&gt; into ":" and "." or into parentheses.
206
+
203
207
  - "Repeat n times":
204
-
208
+
205
209
  <pre><code>
206
210
  10 times "Hello!" (note: print "Hello!" 10 times)
207
- 10...20 times "Hello!" (note: random number between 10 and 20 is
208
- chosen)
209
- X times "Hello!" (note: the value of the captured variable X
210
- is used)
211
+ 10...20 times "Hello!" (note: random number between 10 and 20 is chosen)
212
+ X times "Hello!" (note: the value of the captured variable X is used)
211
213
  X...Y times "Hello!" (note: the value between two captured variables
212
214
  is used)
213
215
  </code></pre>
214
-
216
+
215
217
  - "For all":
216
-
218
+
217
219
  <pre><code>
218
- For all &lt;fact expression&gt; [,] &lt;statement&gt;
220
+ For all &lt;fact expression&gt; &lt;statement&gt;
219
221
  </code></pre>
220
-
222
+
221
223
  &lt;statement&gt; is executed for all combinations of variables in &lt;fact expression&gt;. The &lt;fact expression&gt; is like in `if` statement.
222
224
 
223
225
  - Ruby code:
@@ -227,21 +229,24 @@ There are other statements you can use:
227
229
  </code></pre>
228
230
 
229
231
  Inside the code you can access the captured variables by their lowercase names:
230
-
232
+
231
233
  <pre><code>
232
234
  If X loves Y then
233
235
  ```puts(x); puts(y)```
234
236
  </code></pre>
235
-
237
+
236
238
  - "Either ... or ...":
237
239
 
238
240
  <pre><code>
239
- either &lt;statement&gt; [,]
240
- or &lt;statement&gt; [,]
241
- or &lt;statement&gt;
241
+ Either &lt;statement&gt; [;|,]
242
+ or &lt;statement&gt; [;|,]
243
+ or &lt;statement&gt; [;|,]
242
244
  ...
245
+ or &lt;statement&gt; [;|,]
243
246
  </code></pre>
244
-
247
+
248
+ `[;|,]` means optional character which is either semicolon (";") or comma (",").
249
+
245
250
  A random &lt;statement&gt; is chosen and executed.
246
251
 
247
252
  ### Notes ###
@@ -250,43 +255,15 @@ There are other statements you can use:
250
255
 
251
256
  (note: this is a comment (with nested parentheses)!)
252
257
 
253
- Top-level statements may also be delimited with dot ("."):
254
-
255
- "John" is a boy.
256
- "Sam" is a boy.
257
- "Liza" is a girl.
258
- "Sabrina" is a girl.
259
- "John" loves "Liza".
260
-
261
- There is another form of the statements combination:
258
+ You may end the story with a dot ("."):
262
259
 
263
- if X is a boy then:
264
- - "We know "X;
265
- - X" is a good boy";
266
- - X" is glad to meet you".
260
+ "John" loves "Liza";
261
+ "Sam" loves "Sophia".
267
262
 
268
263
  Keywords `if`, `either`, `or`, `for` (in `for all`) and `while` may start with a capital letter: `If`, `Either` etc.
269
264
 
270
265
  You may also use russian keywords! ;)
271
266
 
272
- ### Mods ###
273
-
274
- When `-m` or `--allow-comma-in-facts` is passed to `story`, the optional comma in "while" and "for all" becomes disallowed:
275
-
276
- For all A, B, C loves D, "Love quadrangle!"; /* ERROR, ambiguity! */
277
- For all A, B, C loves D: "Love quadrangle!". /* OK */
278
- For all A, B, C loves D ("Love quadrangle!"); /* OK too */
279
-
280
- Trailing comma in Fact expression is ignored:
281
-
282
- For all X loves Y, ("Someone loves other one!")
283
- /* is the same as */
284
- For all X loves Y ("Someone loves other one!")
285
-
286
- If (A loves B,) and (B loves C,) then ...
287
- /* is the same as */
288
- If (A loves B) and (B loves C) then ...
289
-
290
267
  Examples
291
268
  --------
292
269
 
data/bin/story CHANGED
@@ -6,7 +6,6 @@ require 'story/samples_dir'
6
6
  $output_file = nil
7
7
  $input_file = nil
8
8
  $story_class_name = nil
9
- $allow_comma_in_facts = false
10
9
  $process = lambda do |story_class_code, output|
11
10
  # execute the story
12
11
  story_class = eval story_class_code
@@ -35,10 +34,6 @@ OptionParser.new do |opts|
35
34
  "instead of an anonymous class" do |name|
36
35
  $story_class_name = name
37
36
  end
38
- opts.on "-m", "--allow-comma-in-facts", "Allow `,' in Fact expressions",
39
- "(see `Mods' in README)" do
40
- $allow_comma_in_facts = true
41
- end
42
37
  opts.on "-r", "--show-relations", "Show relations mentioned in the story",
43
38
  "(useful for debug)" do
44
39
  $process = lambda do |story_class_code, output|
@@ -67,7 +62,7 @@ begin
67
62
  end
68
63
  story_class_code =
69
64
  begin
70
- Story.compile(input_text, $input_file || "-", $allow_comma_in_facts)
65
+ Story.compile(input_text, $input_file || "-")
71
66
  rescue Parse::Error => e
72
67
  abort "error: #{e.pos.file}:#{e.pos.line+1}:#{e.pos.column+1}: #{e.message}"
73
68
  end
data/lib/parse.rb CHANGED
@@ -17,6 +17,8 @@ class Parse
17
17
  #
18
18
  # parses +text+.
19
19
  #
20
+ # If $DEBUG == true then it prints {#rule} calls to stdout.
21
+ #
20
22
  # @param [String] text
21
23
  # @param [String] file file the +text+ is taken from.
22
24
  # @raise [Parse::Error, IOError]
@@ -32,7 +34,7 @@ class Parse
32
34
  if r.nil? or not @text.eos? then
33
35
  if @most_probable_error
34
36
  then raise @most_probable_error
35
- else raise Error.new(Position.new(@text.pos, @text), "syntax error")
37
+ else raise Error.new(Position.new(@text.pos, @text, @file), "syntax error")
36
38
  end
37
39
  end
38
40
  return r
@@ -195,7 +197,10 @@ class Parse
195
197
  old_rule_start_pos = @rule_start_pos
196
198
  @rule_start_pos = pos
197
199
  begin
198
- instance_eval(&body)
200
+ if $DEBUG then STDERR.puts("#{pos.file}:#{pos.line+1}:#{pos.column+1}: entering rule :#{name}"); end
201
+ r = instance_eval(&body)
202
+ if $DEBUG then STDERR.puts("#{pos.file}:#{pos.line+1}:#{pos.column+1}: exiting rule :#{name} #{if r.nil? then "(with nil)" end}"); end
203
+ r
199
204
  ensure
200
205
  @rule_start_pos = old_rule_start_pos
201
206
  end
data/lib/story/compile.rb CHANGED
@@ -21,16 +21,12 @@ class Story
21
21
 
22
22
  # @param [String] text
23
23
  # @param [String] file a file the +text+ is taken from.
24
- # @param [Boolean] allow_comma_in_fact_expr_primary
25
24
  # @return [String] a {String} which {Kernel#eval}s to a {Class} which
26
25
  # inherits {Story}.
27
26
  # @raise [Parse::Error]
28
- def self.compile(text, file = "-", allow_comma_in_fact_expr_primary = false)
27
+ def self.compile(text, file = "-")
29
28
  #
30
- c = Parse.new.
31
- tap { |p| p.allow_comma_in_fact_expr_primary = allow_comma_in_fact_expr_primary }.
32
- (text, file).
33
- to_code
29
+ c = Parse.new.(text, file).to_code
34
30
  #
35
31
  relation_id_to_var_arg_valuesss_var = Hash.new do |h, relation_id|
36
32
  h[relation_id] = "@#{INTERNAL_VAR_PREFIX}var_arg_valuesss#{h.size}"
@@ -336,7 +332,7 @@ class Story
336
332
 
337
333
  end
338
334
 
339
- Statement::Or = ASTNode.new :substatements do
335
+ Statement::Either = ASTNode.new :substatements do
340
336
 
341
337
  def to_code
342
338
  code << "[\n" <<
@@ -510,7 +506,7 @@ class Story
510
506
  rb_op =
511
507
  case e2.op
512
508
  when "==", "!=", "<=", ">=", "<", ">" then e2.op
513
- when "<>" then "!="
509
+ when "<>", "=/=" then "!="
514
510
  when "=" then "=="
515
511
  end
516
512
  with_var_args(
@@ -598,14 +594,6 @@ class Story
598
594
  # @!visibility private
599
595
  class Parse < ::Parse
600
596
 
601
- # ---------------
602
- # @!group Options
603
- # ---------------
604
-
605
- # @return [Boolean] is "," in {#fact_expr_primary} allowed? Default is
606
- # false.
607
- attr_accessor :allow_comma_in_fact_expr_primary
608
-
609
597
  # --------------
610
598
  # @!group Syntax
611
599
  # --------------
@@ -613,13 +601,13 @@ class Story
613
601
  rule :start do
614
602
  no_errors { wsc } and
615
603
  ss = many {
616
- s = statement and opt { _{ semicolon } or _{ dot } } and s
604
+ s = statement and opt{semicolon} and s
617
605
  } and
606
+ opt{dot} and
618
607
  Program[ss]
619
608
  end
620
609
 
621
610
  rule :statement do
622
- _{ statement_or } or
623
611
  _{ statement_if } or
624
612
  _{ statement_while } or
625
613
  _{ statement_for_all } or
@@ -627,14 +615,28 @@ class Story
627
615
  _{ statement_code } or
628
616
  _{ statement_set_fact } or
629
617
  _{ statement_tell } or
630
- _{ statement_compound }
618
+ _{ statement_compound } or
619
+ _{ statement_either }
631
620
  end
632
621
 
633
- rule :statement_tell do
634
- parts = many1 {
635
- _{ string } or _{ newline } or _{ var }
622
+ rule :statement_either do
623
+ either and s1 = statement and opt_c_or_s and sn = many {
624
+ _or_ and s = statement and opt_c_or_s and s
636
625
  } and
637
- _(Statement::Tell[parts])
626
+ if sn.empty?
627
+ then s1
628
+ else _(Statement::Either[[s1, *sn]])
629
+ end
630
+ end
631
+
632
+ def opt_c_or_s
633
+ opt{ _{semicolon} or _{comma} }
634
+ end
635
+
636
+ rule :statement_tell do
637
+ expect("\"smth\"") { no_errors {
638
+ e = fact_expr_primary_or_statement_tell and e.is_a?(Statement::Tell) and e
639
+ } }
638
640
  end
639
641
 
640
642
  rule :statement_set_fact do
@@ -642,44 +644,41 @@ class Story
642
644
  end
643
645
 
644
646
  rule :statement_if do
645
- if_then = lambda {
646
- _if_ and c = fact_expr and opt { comma } and _then_ and s = statement and [c, s]
647
- }
648
- or_delimiter = lambda {
649
- opt { comma } and _or_
647
+ # single `then' form
648
+ _{
649
+ _if_ and c = fact_expr and opt{comma} and _then_ and s1 = statement and opt{semicolon} and
650
+ s2 = opt { otherwise and s = statement and opt{semicolon} and s } and
651
+ _(Statement::If[ [[c, s1]], s2.first ])
652
+ } or
653
+ # multiple `then' form
654
+ _{
655
+ _if_ and colon and c_and_sn = many {
656
+ (_{dash} or _{ word and rparen }) and c = fact_expr and opt{comma} and _then_ and s = statement and opt{semicolon} and [c, s]
657
+ } and
658
+ otherwise_s = opt {
659
+ otherwise and s = statement and opt{semicolon} and s
660
+ } and
661
+ _(Statement::If[c_and_sn, otherwise_s.first])
650
662
  }
651
- #
652
- c_and_s1 = if_then.() and
653
- c_and_sn = many { or_delimiter.() and if_then.() } and
654
- else_a = opt { or_delimiter.() and statement } and
655
- _(Statement::If[[c_and_s1, *c_and_sn], else_a.first])
656
663
  end
657
664
 
658
665
  rule :statement_while do
659
- _while_ and f = fact_expr and opt { comma } and s = statement and
666
+ _while_ and f = fact_expr and s = statement and
660
667
  _(Statement::While[f, s])
661
668
  end
662
669
 
663
670
  rule :statement_for_all do
664
- _for_ and all and f = fact_expr and opt { comma } and s = statement and
671
+ _for_ and all and f = fact_expr and s = statement and
665
672
  _(Statement::ForAll[f, s])
666
673
  end
667
674
 
668
675
  rule :statement_n_times do
669
- val = lambda {
670
- _{ number } or _{ var }
671
- }
676
+ val = lambda { _{number} or _{var} or _{above_var} }
672
677
  #
673
678
  range_begin = range_end = val.() and opt { ellipsis and range_end = val.() } and times and s = statement and
674
679
  _(Statement::NTimes[range_begin, range_end, s])
675
680
  end
676
681
 
677
- rule :statement_or do
678
- _or_ and s1 = statement and
679
- ss = many { opt { comma } and _or_ and statement } and
680
- Statement::Or[[s1, *ss]]
681
- end
682
-
683
682
  rule :statement_code do
684
683
  c = _code_ and
685
684
  act {
@@ -695,7 +694,7 @@ class Story
695
694
  rule :statement_compound do
696
695
  body = lambda {
697
696
  ss = many {
698
- opt { dash } and s = statement and opt { semicolon } and s
697
+ s = statement and opt{semicolon} and s
699
698
  } and
700
699
  _(Statement::Compound[ss])
701
700
  }
@@ -737,37 +736,50 @@ class Story
737
736
 
738
737
  rule :fact_expr_select do
739
738
  #
740
- operand = lambda { _{ var } or _{ value } or _{ above_var } }
739
+ operand = lambda { _{var} or _{value} or _{above_var} }
741
740
  #
742
741
  v1 = operand.() and op = comparison_op and v2 = operand.() and
743
742
  _(FactExpr::Select[v1, op, v2])
744
743
  end
745
744
 
746
745
  rule :fact_expr_primary do
747
- allow_comma = allow_comma_in_fact_expr_primary
746
+ expect("Fact expression") { no_errors {
747
+ f = fact_expr_primary_or_statement_tell and f.is_a?(FactExpr) and f
748
+ } }
749
+ end
750
+
751
+ rule :fact_expr_primary_or_statement_tell do
748
752
  relation_id = []
749
753
  args = []
750
754
  negated = false
751
755
  #
752
756
  one_or_more {
753
- _{ s = (_{dash} or _{other_char}) and act { relation_id << s } } or
754
- _{ allow_comma and s = comma and act { relation_id << s } } or
757
+ _{ s = (_{dash} or _{other_char} or _{comma}) and act { relation_id << s } } or
755
758
  _{ a = asterisk and act { args << a; relation_id << :* } } or
756
759
  _{ _not_ and act { negated = !negated } } or
757
- _{ v = value and act { args << v; relation_id << :* } } or
758
- _{ v = var and act { args << v; relation_id << :* } } or
759
- _{ v = above_var and act { args << v; relation_id << :* } } or
760
- _{ w = word and act { relation_id << w.ru_downcase } }
760
+ _{ v = (_{value} or _{var} or _{above_var}) and act { args << v; relation_id << :* } } or
761
+ _{
762
+ w = (
763
+ _{ word } or
764
+ _{ _for_ and not_follows {all} } or
765
+ _{ all }
766
+ ) and
767
+ act { relation_id << w.ru_downcase }
768
+ }
761
769
  } and
762
770
  act { relation_id.chomp!(",") } and
763
- # this is not a "statement_tell" and
764
- not relation_id.all? { |p| p == :* } and
765
- e = _(FactExpr::Primary[relation_id, args]) and
766
- if negated then _(FactExpr::Not[e]); else e; end
771
+ if not negated and relation_id.all? { |p| p == :* } then
772
+ _(Statement::Tell[args])
773
+ else
774
+ e = _(FactExpr::Primary[relation_id, args])
775
+ if negated then e = _(FactExpr::Not[e]); end
776
+ e
777
+ end
767
778
  end
768
779
 
769
780
  rule :value do
770
781
  _{ string } or
782
+ _{ nl } or
771
783
  _{ number }
772
784
  end
773
785
 
@@ -779,6 +791,10 @@ class Story
779
791
  # @!group Lexical Analysis
780
792
  # ------------------------
781
793
 
794
+ begin
795
+ @@keyword_method_ids = []
796
+ end
797
+
782
798
  # macro
783
799
  def self.token(method_id, human_readable_description, &body)
784
800
  rule method_id do
@@ -797,6 +813,15 @@ class Story
797
813
  end
798
814
  end
799
815
 
816
+ # macro
817
+ def self.keyword(method_id, human_readable_description, check_regexp, &return_value_f)
818
+ return_value_f ||= lambda { |captured_string| captured_string }
819
+ @@keyword_method_ids.push(method_id)
820
+ token method_id, human_readable_description do
821
+ t = word_keyword_or_var and check_regexp === t and return_value_f.(t)
822
+ end
823
+ end
824
+
800
825
  simple_token :lparen, "("
801
826
  simple_token :rparen, ")"
802
827
  simple_token :comma, ","
@@ -806,60 +831,34 @@ class Story
806
831
  simple_token :dash, "-"
807
832
  simple_token :ellipsis, "..."
808
833
 
834
+ keyword :_for_, "`for'", /^([Ff]or|[Дд]ля)$/
835
+ keyword :all, "`all'", /^(all|всех)$/
836
+ keyword :_while_, "`while'", /^([Ww]hile|[Пп]ока)$/
837
+ keyword :_if_, "`if'", /^([Ii]f|[Ее]сли)$/
838
+ keyword :_then_, "`then'", /^(then|то)$/
839
+ keyword :otherwise, "`otherwise'", /^([Oo]therwise|[Ии]наче)$/
840
+ keyword :_not_, "`not'", /^(not|не)$/
841
+ keyword :either, "`either'", /^([Ee]ither|[Ии]ли|[Лл]ибо)$/
842
+ keyword :_or_, "`or'", /^([Oo]r|[Ии]ли|[Лл]ибо)$/
843
+ keyword :_and_, "`and'", /^(and|и)$/
844
+ keyword :times, "`times'", /^(times|раза?)$/
845
+ keyword :newline, "`newline'", /^(newline|nl)$/, &proc { "\n" }
846
+ alias nl newline
847
+
809
848
  token :comparison_op, "comparison operator" do
810
- scan(/=|!=|<>|<=|>=|<|>/)
849
+ scan(/=\/=|=|!=|<>|<=|>=|<|>/)
811
850
  end
812
851
 
813
852
  token :asterisk, "`*'" do
814
853
  scan("*") and _(Asterisk.new)
815
854
  end
816
855
 
817
- token :other_char, "any of `#№@$%/'" do
818
- scan(/[\#\№\@\$\%\/]/)
819
- end
820
-
821
- token :_for_, "`for'" do
822
- t = word_ and /^([Ff]or|[Дд]ля)$/ === t
823
- end
824
-
825
- token :all, "`all'" do
826
- t = word_ and /^(all|всех)$/ === t
827
- end
828
-
829
- token :_while_, "`while'" do
830
- t = word_ and /^([Ww]hile|[Пп]ока)$/ === t
831
- end
832
-
833
- token :_then_, "`then'" do
834
- t = word_ and /^(then|то)$/ === t
835
- end
836
-
837
- token :_if_, "`if'" do
838
- t = word_ and /^([Ii]f|[Ее]сли)$/ === t
839
- end
840
-
841
- token :_not_, "`not'" do
842
- t = word_ and /^(not|не)$/ === t
843
- end
844
-
845
- token :_or_, "`or'" do
846
- t = word_ and /^([Ee]ither|[Oo]r|[Ии]ли|[Лл]ибо)$/ === t
847
- end
848
-
849
- token :_and_, "`and'" do
850
- t = word_ and /^(and|и)$/ === t
851
- end
852
-
853
- token :times, "`times'" do
854
- t = word_ and /^(times|раза?)$/ === t
855
- end
856
-
857
- token :newline, "`newline'" do
858
- t = word_ and /^(newline|nl)$/ === t and "\n"
856
+ token :other_char, "any of `#№@$%/[]{}'" do
857
+ scan(/[\#\№\@\$\%\/\[\]\{\}]/)
859
858
  end
860
859
 
861
860
  token :number, "number" do
862
- n = scan(/\d+/) and n.to_i
861
+ n = scan(/-?\d+/) and Integer(n)
863
862
  end
864
863
 
865
864
  token :string, "string" do
@@ -884,7 +883,7 @@ class Story
884
883
  end
885
884
 
886
885
  token :var, "variable" do
887
- n = word_ and
886
+ n = word_keyword_or_var and
888
887
  /^[_A-ZА-ЯЁ][_A-ZА-ЯЁ0-9]*$/ === n and
889
888
  _(Var[n.ru_downcase, n])
890
889
  end
@@ -894,14 +893,10 @@ class Story
894
893
  end
895
894
 
896
895
  token :word, "word" do
897
- not_follows(
898
- :_not_, :_and_, :_or_, :_if_, :_then_, :var, :_while_, :_for_, :all,
899
- :times, :newline
900
- ) and
901
- word_
896
+ not_follows(:var, *@@keyword_method_ids) and word_keyword_or_var
902
897
  end
903
898
 
904
- rule :word_ do
899
+ rule :word_keyword_or_var do
905
900
  scan(/[a-zA-Zа-яёА-ЯЁ_](['\-]?[a-zA-Zа-яёА-ЯЁ0-9_]+)*/)
906
901
  end
907
902
 
@@ -949,16 +944,3 @@ class Story
949
944
  end
950
945
 
951
946
  end
952
-
953
- # begin
954
- # eval(Story.compile(<<-STORY)).new.write
955
- #
956
- # "Hello, world!" newline
957
- # Either "John" loves "Liza",
958
- # or ("Hello, world, again!" nl; "John" kisses "Liza";).
959
- # STORY
960
- # rescue Parse::Error => e
961
- # puts "error: #{e.pos.file}:#{e.pos.line}:#{e.pos.column}: #{e.message}"
962
- # # rescue Story::Error => e
963
- # # puts "error: #{e.pos.file}:#{e.pos.line}:#{e.pos.column}: #{e.message}"
964
- # end
@@ -20,13 +20,13 @@ names = UniqueNames.english_male
20
20
  "- "N", the ";
21
21
  either "tiger", or "fox", or "bear", or ("horse"; N has hooves);
22
22
  " ";
23
- either ("with powerful hands"; N uses "hands"),
24
- or ("with powerful feet"; N uses "feet"),
23
+ either ("with powerful hands"; N uses "hands");
24
+ or ("with powerful feet"; N uses "feet");
25
25
  or:
26
26
  if ^N has hooves then (
27
27
  "with powerful hooves"; N uses "hooves"
28
28
  )
29
- or (
29
+ otherwise (
30
30
  "with sharp claws"; N uses "claws"
31
31
  )
32
32
  .
@@ -38,20 +38,21 @@ nl
38
38
  nl
39
39
 
40
40
  While the tournament not complete:
41
- If X is in and Y is in and X != Y, then:
41
+ If X is in and Y is in and X != Y then:
42
42
  (note: fight!)
43
- either (X won; Y lost), or (Y won; X lost);
43
+ either (X won; Y lost) or (Y won; X lost);
44
44
  if A won and B lost then:
45
- if ^A uses "hands" then ""A" has beaten "B". "
46
- or if ^A uses "feet" then ""A" has kicked "B". "
47
- or if ^A uses "hooves" then ""A" has kicked "B" and knocked him out. "
48
- or if ^A uses "claws" then ""A" has scratched the whole "B". ";
45
+ if:
46
+ - ^A uses "hands" then ""A" has beaten "B". ";
47
+ - ^A uses "feet" then ""A" has kicked "B". ";
48
+ - ^A uses "hooves" then ""A" has kicked "B" and knocked him out. ";
49
+ - ^A uses "claws" then ""A" has scratched the whole "B". ";
49
50
  A not won; B not lost;
50
51
  B is not in;
51
52
  .
52
53
  .
53
- Or the tournament complete;
54
+ Otherwise the tournament complete;
54
55
  .
55
56
  If X is in then ""X" is the winner!"
56
57
  nl
57
- nl
58
+ nl
@@ -1,15 +1,15 @@
1
1
  (note: this is a sample story written in SDL - Story Description Language)
2
2
 
3
- "Oliver" is a boy.
4
- "Sam" is a boy.
5
- "Nathan" is a boy.
6
- "John" is a boy.
7
- "Jack" is a boy.
8
- "Katie" is a girl.
9
- "Abigail" is a girl.
10
- "Rose" is a girl.
11
- "Madeleine" is a girl.
12
- "Liza" is a girl.
3
+ "Oliver" is a boy;
4
+ "Sam" is a boy;
5
+ "Nathan" is a boy;
6
+ "John" is a boy;
7
+ "Jack" is a boy;
8
+ "Katie" is a girl;
9
+ "Abigail" is a girl;
10
+ "Rose" is a girl;
11
+ "Madeleine" is a girl;
12
+ "Liza" is a girl;
13
13
 
14
14
  "
15
15
  University
@@ -17,8 +17,7 @@ University
17
17
 
18
18
  "
19
19
 
20
- "Once upon a time in the University went "; for all X is a boy or X is a girl,
21
- X", "; "that's it. They quickly became friends and went out often. "
20
+ "Once upon a time in the University went "; for all X is a boy or X is a girl (X", "); "that's it. They quickly became friends and went out often. "
22
21
 
23
22
  While X is a boy and X not loves *:
24
23
  if X is a boy and X not loves * and Y is a girl and * not loves Y then:
@@ -53,4 +52,4 @@ nl
53
52
  nl
54
53
  "Then they all lived long and happy!"
55
54
  nl
56
- nl
55
+ nl
@@ -26,7 +26,7 @@ names = UniqueNames.russian_male
26
26
  если ^N имеет копыта, то (
27
27
  "с мощными копытами"; N использует "копыта"
28
28
  )
29
- или (
29
+ иначе (
30
30
  "с острыми когтями"; N использует "когти"
31
31
  )
32
32
  .
@@ -42,15 +42,16 @@ nl
42
42
  (note: бой!)
43
43
  либо (X победил; Y проиграл), либо (Y победил; X проиграл);
44
44
  если A победил и B проиграл, то:
45
- если ^A использует "руки", то ""A" избил "B". "
46
- или если ^A использует "ноги", то ""A" запинал "B". "
47
- или если ^A использует "копыта", то ""A" лягнул "B" и вырубил его. "
48
- или если ^A использует "когти", то ""A" исцарапал всего "B". ";
45
+ если:
46
+ - ^A использует "руки", то ""A" избил "B". ";
47
+ - ^A использует "ноги", то ""A" запинал "B". ";
48
+ - ^A использует "копыта", то ""A" лягнул "B" и вырубил его. ";
49
+ - ^A использует "когти", то ""A" исцарапал всего "B". ";
49
50
  A не победил; B не проиграл;
50
- A не участвует в турнире;
51
+ B не участвует в турнире;
51
52
  .
52
53
  .
53
- Или конец турнира;
54
+ Иначе конец турнира;
54
55
  .
55
56
  Если X участвует в турнире, то ""X" - победитель!"
56
57
  nl
@@ -1,15 +1,15 @@
1
1
  (прим.: это пример стории, написанной на SDL - Языке Описания Историй)
2
2
 
3
- "Слава" - самец.
4
- "Вова" - самец.
5
- "Юрий" - самец.
6
- "Борис" - самец.
7
- "Гриша" - самец.
8
- "Зоя" - самка.
9
- "Вика" - самка.
10
- "Света" - самка.
11
- "Лиля" - самка.
12
- "Юля" - самка.
3
+ "Слава" - самец;
4
+ "Вова" - самец;
5
+ "Юрий" - самец;
6
+ "Борис" - самец;
7
+ "Гриша" - самец;
8
+ "Зоя" - самка;
9
+ "Вика" - самка;
10
+ "Света" - самка;
11
+ "Лиля" - самка;
12
+ "Юля" - самка;
13
13
 
14
14
  "
15
15
  Университет
@@ -17,7 +17,7 @@
17
17
 
18
18
  "
19
19
 
20
- "Как-то раз в университет поступили "; для всех X - самец или X - самка, X", ";
20
+ "Как-то раз в университет поступили "; для всех X - самец или X - самка (X", ");
21
21
  "вот. Они быстро стали друзьями и часто гуляли все вместе. "
22
22
 
23
23
  Пока X - самец и X не любит *:
@@ -54,4 +54,4 @@ nl
54
54
  nl
55
55
  "И жили они долго и счастливо!"
56
56
  nl
57
- nl
57
+ nl
@@ -1,15 +1,15 @@
1
1
  (прим.: это пример стории, написанной на SDL - Языке Описания Историй)
2
2
 
3
- "Слава" - самец.
4
- "Вова" - самец.
5
- "Юрий" - самец.
6
- "Борис" - самец.
7
- "Гриша" - самец.
8
- "Зоя" - самка.
9
- "Вика" - самка.
10
- "Света" - самка.
11
- "Лиля" - самка.
12
- "Юля" - самка.
3
+ "Слава" - самец;
4
+ "Вова" - самец;
5
+ "Юрий" - самец;
6
+ "Борис" - самец;
7
+ "Гриша" - самец;
8
+ "Зоя" - самка;
9
+ "Вика" - самка;
10
+ "Света" - самка;
11
+ "Лиля" - самка;
12
+ "Юля" - самка;
13
13
 
14
14
  "
15
15
  Университет
@@ -17,7 +17,7 @@
17
17
  "
18
18
  nl
19
19
 
20
- "Как-то раз в университет поступили "; для всех X - самец или X - самка, X", ";
20
+ "Как-то раз в университет поступили "; для всех X - самец или X - самка (X", ");
21
21
  "вот. Они быстро стали друзьями и часто гуляли все вместе. "
22
22
 
23
23
  Для всех X - самец или X - самка: X свободен.
@@ -66,6 +66,6 @@ nl
66
66
 
67
67
  nl
68
68
  nl
69
- "И жили они долго и счастливо!". Если X любит Y и ((X - самец и Y - самец) или (X - самка и Y - самка)), то " Правда, роскомнадзорненько как-то получилось.";
69
+ "И жили они долго и счастливо!"; если X любит Y и ((X - самец и Y - самец) или (X - самка и Y - самка)), то " Правда, роскомнадзорненько как-то получилось.";
70
+ nl
70
71
  nl
71
- nl
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: story-gen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-02-04 00:00:00.000000000 Z
12
+ date: 2016-02-17 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Generate stories from descriptions based on Facts!
15
15
  email: various.furriness@gmail.com
@@ -43,6 +43,7 @@ files:
43
43
  - README.md
44
44
  - .yardopts
45
45
  - yardopts_extra.rb
46
+ - NEWS
46
47
  - sample/fight_club.sdl
47
48
  - sample/университет.sdl
48
49
  - sample/бойцовский_клуб.sdl