story-gen 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/NEWS +13 -0
- data/README.md +56 -79
- data/bin/story +1 -6
- data/lib/parse.rb +7 -2
- data/lib/story/compile.rb +103 -121
- data/sample/fight_club.sdl +12 -11
- data/sample/university.sdl +12 -13
- 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 +8 -7
- 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 +12 -12
- data/sample//321/203/320/275/320/270/320/262/320/265/321/200/321/201/320/270/321/202/320/265/321/202_2.sdl +13 -13
- metadata +3 -2
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 "
|
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
|
-
|
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
|
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
|
129
|
+
You may use `otherwise` keyword:
|
125
130
|
|
126
|
-
If X is a boy then
|
127
|
-
"
|
128
|
-
|
129
|
-
"
|
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
|
-
|
136
|
+
You may combine several conditions and `then` statements using colon (":") and dashes ("-"):
|
132
137
|
|
133
|
-
If
|
134
|
-
"We
|
135
|
-
|
136
|
-
"We
|
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 `
|
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
|
-
|
145
|
+
Look at the trailing semicolons (";") by the way - they resolve ambiguity!
|
143
146
|
|
144
|
-
|
145
|
-
or if <condition> [,] then <statement> [,]
|
146
|
-
or if <condition> [,] then <statement> [,]
|
147
|
-
...
|
148
|
-
or <statement>
|
147
|
+
Another form of `if`:
|
149
148
|
|
150
|
-
|
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
|
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
|
-
|
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 <fact expression>
|
193
|
+
While <fact expression> <statement>
|
192
194
|
</code></pre>
|
193
|
-
|
195
|
+
|
194
196
|
Here <fact expression> 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 <fact expression> are not available in <statement>
|
202
|
-
|
202
|
+
|
203
|
+
The variables from <fact expression> are not available in <statement>.
|
204
|
+
|
205
|
+
Note that usually there is no way to delimit <fact expression> from <statement> except by wrapping the <statement> 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
|
-
|
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 <fact expression>
|
220
|
+
For all <fact expression> <statement>
|
219
221
|
</code></pre>
|
220
|
-
|
222
|
+
|
221
223
|
<statement> is executed for all combinations of variables in <fact expression>. The <fact expression> 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
|
-
|
240
|
-
or <statement> [
|
241
|
-
or <statement>
|
241
|
+
Either <statement> [;|,]
|
242
|
+
or <statement> [;|,]
|
243
|
+
or <statement> [;|,]
|
242
244
|
...
|
245
|
+
or <statement> [;|,]
|
243
246
|
</code></pre>
|
244
|
-
|
247
|
+
|
248
|
+
`[;|,]` means optional character which is either semicolon (";") or comma (",").
|
249
|
+
|
245
250
|
A random <statement> 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
|
-
|
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
|
-
|
264
|
-
|
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 || "-"
|
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
|
-
|
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 = "-"
|
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::
|
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
|
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 :
|
634
|
-
|
635
|
-
|
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
|
-
|
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
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
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
|
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
|
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
|
-
|
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 { _{
|
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
|
-
|
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
|
-
_{
|
759
|
-
|
760
|
-
|
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
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
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(
|
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 =
|
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 :
|
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
|
data/sample/fight_club.sdl
CHANGED
@@ -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
|
-
|
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
|
41
|
+
If X is in and Y is in and X != Y then:
|
42
42
|
(note: fight!)
|
43
|
-
either (X won; Y lost)
|
43
|
+
either (X won; Y lost) or (Y won; X lost);
|
44
44
|
if A won and B lost then:
|
45
|
-
if
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
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
|
data/sample/university.sdl
CHANGED
@@ -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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
если:
|
46
|
+
- ^A использует "руки", то ""A" избил "B". ";
|
47
|
+
- ^A использует "ноги", то ""A" запинал "B". ";
|
48
|
+
- ^A использует "копыта", то ""A" лягнул "B" и вырубил его. ";
|
49
|
+
- ^A использует "когти", то ""A" исцарапал всего "B". ";
|
49
50
|
A не победил; B не проиграл;
|
50
|
-
|
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 -
|
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 -
|
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
|
-
"И жили они долго и счастливо!"
|
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
|
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-
|
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
|