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