coffee-script 0.1.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/coffee-script.gemspec +3 -3
- data/examples/code.coffee +43 -43
- data/examples/documents.coffee +17 -17
- data/examples/poignant.coffee +22 -22
- data/examples/underscore.coffee +457 -416
- data/lib/coffee-script.rb +2 -1
- data/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +23 -4
- data/lib/coffee_script/command_line.rb +5 -4
- data/lib/coffee_script/grammar.y +99 -78
- data/lib/coffee_script/lexer.rb +91 -47
- data/lib/coffee_script/narwhal/coffee-script.coffee +16 -15
- data/lib/coffee_script/narwhal/{js → lib}/coffee-script.js +22 -17
- data/lib/coffee_script/narwhal/{js → lib/coffee-script}/loader.js +6 -4
- data/lib/coffee_script/narwhal/loader.coffee +3 -3
- data/lib/coffee_script/nodes.rb +307 -255
- data/lib/coffee_script/parse_error.rb +3 -2
- data/lib/coffee_script/parser.output +10284 -9773
- data/lib/coffee_script/parser.rb +1286 -1141
- data/lib/coffee_script/rewriter.rb +208 -0
- data/lib/coffee_script/scope.rb +15 -10
- data/package.json +9 -0
- metadata +6 -6
- data/lib/coffee_script/narwhal/js/launcher.js +0 -3
- data/lib/coffee_script/narwhal/launcher.coffee +0 -1
data/lib/coffee-script.rb
CHANGED
@@ -4,12 +4,13 @@ require "coffee_script/parser"
|
|
4
4
|
require "coffee_script/nodes"
|
5
5
|
require "coffee_script/value"
|
6
6
|
require "coffee_script/scope"
|
7
|
+
require "coffee_script/rewriter"
|
7
8
|
require "coffee_script/parse_error"
|
8
9
|
|
9
10
|
# Namespace for all CoffeeScript internal classes.
|
10
11
|
module CoffeeScript
|
11
12
|
|
12
|
-
VERSION = '0.
|
13
|
+
VERSION = '0.2.0' # Keep in sync with the gemspec.
|
13
14
|
|
14
15
|
# Compile a script (String or IO) to JavaScript.
|
15
16
|
def self.compile(script, options={})
|
@@ -39,7 +39,7 @@
|
|
39
39
|
<key>comment</key>
|
40
40
|
<string>match stuff like: funcName: => … </string>
|
41
41
|
<key>match</key>
|
42
|
-
<string>([a-zA-
|
42
|
+
<string>([a-zA-Z0-9_?.$*]*)\s*(=|:)\s*([\w,\s]*?)\s*(=>)</string>
|
43
43
|
<key>name</key>
|
44
44
|
<string>meta.function.coffee</string>
|
45
45
|
</dict>
|
@@ -60,7 +60,7 @@
|
|
60
60
|
<key>comment</key>
|
61
61
|
<string>match stuff like: a => … </string>
|
62
62
|
<key>match</key>
|
63
|
-
<string>([a-zA-
|
63
|
+
<string>([a-zA-Z0-9_?., $*]*)\s*(=>)</string>
|
64
64
|
<key>name</key>
|
65
65
|
<string>meta.inline.function.coffee</string>
|
66
66
|
</dict>
|
@@ -204,10 +204,29 @@
|
|
204
204
|
</dict>
|
205
205
|
<dict>
|
206
206
|
<key>match</key>
|
207
|
-
<string>\b(break|
|
207
|
+
<string>\b(break|by|catch|continue|else|finally|for|if|return|switch|then|throw|try|unless|when|while)\b</string>
|
208
208
|
<key>name</key>
|
209
209
|
<string>keyword.control.coffee</string>
|
210
210
|
</dict>
|
211
|
+
<dict>
|
212
|
+
<key>match</key>
|
213
|
+
<string>\b([a-zA-Z$_]\w*)(\:)\s</string>
|
214
|
+
<key>name</key>
|
215
|
+
<string>variable.assignment.coffee</string>
|
216
|
+
<key>captures</key>
|
217
|
+
<dict>
|
218
|
+
<key>1</key>
|
219
|
+
<dict>
|
220
|
+
<key>name</key>
|
221
|
+
<string>entity.name.function.coffee</string>
|
222
|
+
</dict>
|
223
|
+
<key>2</key>
|
224
|
+
<dict>
|
225
|
+
<key>name</key>
|
226
|
+
<string>keyword.operator.coffee</string>
|
227
|
+
</dict>
|
228
|
+
</dict>
|
229
|
+
</dict>
|
211
230
|
<dict>
|
212
231
|
<key>match</key>
|
213
232
|
<string>\b(true|on|yes)\b</string>
|
@@ -240,7 +259,7 @@
|
|
240
259
|
</dict>
|
241
260
|
<dict>
|
242
261
|
<key>match</key>
|
243
|
-
<string>!|\$|%|&
|
262
|
+
<string>!|\$|%|&|\*|\/|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\?|\|\||\:|\*=|(?<!\()/=|%=|\+=|\-=|&=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|isnt|not)\b</string>
|
244
263
|
<key>name</key>
|
245
264
|
<string>keyword.operator.coffee</string>
|
246
265
|
</dict>
|
@@ -19,8 +19,9 @@ Usage:
|
|
19
19
|
# Seconds to pause between checks for changed source files.
|
20
20
|
WATCH_INTERVAL = 0.5
|
21
21
|
|
22
|
-
#
|
23
|
-
|
22
|
+
# Command to execute in Narwhal
|
23
|
+
PACKAGE = File.expand_path(File.dirname(__FILE__) + '/../..')
|
24
|
+
LAUNCHER = "narwhal -p #{PACKAGE} -e 'require(\"coffee-script\").run(system.args);'"
|
24
25
|
|
25
26
|
# Run the CommandLine off the contents of ARGV.
|
26
27
|
def initialize
|
@@ -107,7 +108,7 @@ Usage:
|
|
107
108
|
|
108
109
|
# Use Narwhal to run an interactive CoffeeScript session.
|
109
110
|
def launch_repl
|
110
|
-
exec "
|
111
|
+
exec "#{LAUNCHER}"
|
111
112
|
rescue Errno::ENOENT
|
112
113
|
puts "Error: Narwhal must be installed to use the interactive REPL."
|
113
114
|
exit(1)
|
@@ -116,7 +117,7 @@ Usage:
|
|
116
117
|
# Use Narwhal to compile and execute CoffeeScripts.
|
117
118
|
def run_scripts
|
118
119
|
sources = @sources.join(' ')
|
119
|
-
exec "
|
120
|
+
exec "#{LAUNCHER} #{sources}"
|
120
121
|
rescue Errno::ENOENT
|
121
122
|
puts "Error: Narwhal must be installed in order to execute CoffeeScripts."
|
122
123
|
exit(1)
|
data/lib/coffee_script/grammar.y
CHANGED
@@ -1,24 +1,26 @@
|
|
1
1
|
class Parser
|
2
2
|
|
3
3
|
# Declare tokens produced by the lexer
|
4
|
-
token IF ELSE
|
4
|
+
token IF ELSE UNLESS
|
5
5
|
token NUMBER STRING REGEX
|
6
6
|
token TRUE FALSE YES NO ON OFF
|
7
7
|
token IDENTIFIER PROPERTY_ACCESS
|
8
|
-
token CODE PARAM NEW RETURN
|
8
|
+
token CODE PARAM PARAM_SPLAT NEW RETURN
|
9
9
|
token TRY CATCH FINALLY THROW
|
10
10
|
token BREAK CONTINUE
|
11
|
-
token FOR IN WHILE
|
12
|
-
token SWITCH
|
11
|
+
token FOR IN BY WHEN WHILE
|
12
|
+
token SWITCH LEADING_WHEN
|
13
13
|
token DELETE INSTANCEOF TYPEOF
|
14
14
|
token SUPER EXTENDS
|
15
15
|
token NEWLINE
|
16
16
|
token COMMENT
|
17
17
|
token JS
|
18
|
+
token INDENT OUTDENT
|
18
19
|
|
19
20
|
# Declare order of operations.
|
20
21
|
prechigh
|
21
|
-
|
22
|
+
left '?'
|
23
|
+
nonassoc UMINUS PARAM_SPLAT SPLAT NOT '!' '!!' '~' '++' '--'
|
22
24
|
left '*' '/' '%'
|
23
25
|
left '+' '-'
|
24
26
|
left '<<' '>>' '>>>'
|
@@ -29,51 +31,40 @@ prechigh
|
|
29
31
|
right '-=' '+=' '/=' '*=' '%='
|
30
32
|
right DELETE INSTANCEOF TYPEOF
|
31
33
|
left '.'
|
32
|
-
right
|
33
|
-
left
|
34
|
+
right INDENT
|
35
|
+
left OUTDENT
|
36
|
+
right WHEN LEADING_WHEN IN BY
|
37
|
+
right THROW FOR NEW SUPER
|
38
|
+
left EXTENDS
|
34
39
|
left ASSIGN '||=' '&&='
|
35
|
-
right RETURN
|
40
|
+
right RETURN '=>' UNLESS IF ELSE WHILE
|
36
41
|
preclow
|
37
42
|
|
38
|
-
# We expect 3 shift/reduce errors for optional syntax.
|
39
|
-
# There used to be 252 -- greatly improved.
|
40
|
-
expect 3
|
41
|
-
|
42
43
|
rule
|
43
44
|
|
44
45
|
# All parsing will end in this rule, being the trunk of the AST.
|
45
46
|
Root:
|
46
|
-
/* nothing */ { result = Expressions.new
|
47
|
-
| Terminator { result = Expressions.new
|
47
|
+
/* nothing */ { result = Expressions.new }
|
48
|
+
| Terminator { result = Expressions.new }
|
48
49
|
| Expressions { result = val[0] }
|
50
|
+
| Block Terminator { result = val[0] }
|
49
51
|
;
|
50
52
|
|
51
53
|
# Any list of expressions or method body, seperated by line breaks or semis.
|
52
54
|
Expressions:
|
53
|
-
Expression { result = Expressions.
|
55
|
+
Expression { result = Expressions.wrap(val) }
|
54
56
|
| Expressions Terminator Expression { result = val[0] << val[2] }
|
55
57
|
| Expressions Terminator { result = val[0] }
|
56
|
-
| Terminator Expressions { result = val[1] }
|
57
58
|
;
|
58
59
|
|
59
60
|
# All types of expressions in our language.
|
60
61
|
Expression:
|
61
|
-
PureExpression
|
62
|
-
| Statement
|
63
|
-
;
|
64
|
-
|
65
|
-
# The parts that are natural JavaScript expressions.
|
66
|
-
PureExpression:
|
67
62
|
Value
|
68
63
|
| Call
|
69
64
|
| Code
|
70
65
|
| Operation
|
71
66
|
| Range
|
72
|
-
|
73
|
-
|
74
|
-
# We have to take extra care to convert these statements into expressions.
|
75
|
-
Statement:
|
76
|
-
Assign
|
67
|
+
| Assign
|
77
68
|
| If
|
78
69
|
| Try
|
79
70
|
| Throw
|
@@ -82,21 +73,22 @@ rule
|
|
82
73
|
| For
|
83
74
|
| Switch
|
84
75
|
| Extends
|
76
|
+
| Splat
|
77
|
+
| Existence
|
85
78
|
| Comment
|
86
79
|
;
|
87
80
|
|
81
|
+
Block:
|
82
|
+
INDENT Expressions OUTDENT { result = val[1] }
|
83
|
+
| INDENT OUTDENT { result = Expressions.new }
|
84
|
+
;
|
85
|
+
|
88
86
|
# All tokens that can terminate an expression.
|
89
87
|
Terminator:
|
90
88
|
"\n"
|
91
89
|
| ";"
|
92
90
|
;
|
93
91
|
|
94
|
-
# All tokens that can serve to begin the second block of a multi-part expression.
|
95
|
-
Then:
|
96
|
-
THEN
|
97
|
-
| Terminator
|
98
|
-
;
|
99
|
-
|
100
92
|
# All hard-coded values.
|
101
93
|
Literal:
|
102
94
|
NUMBER { result = LiteralNode.new(val[0]) }
|
@@ -115,13 +107,13 @@ rule
|
|
115
107
|
|
116
108
|
# Assignment to a variable.
|
117
109
|
Assign:
|
118
|
-
Value ASSIGN Expression
|
110
|
+
Value ASSIGN Expression { result = AssignNode.new(val[0], val[2]) }
|
119
111
|
;
|
120
112
|
|
121
113
|
# Assignment within an object literal.
|
122
114
|
AssignObj:
|
123
|
-
IDENTIFIER ASSIGN Expression
|
124
|
-
| STRING ASSIGN Expression
|
115
|
+
IDENTIFIER ASSIGN Expression { result = AssignNode.new(ValueNode.new(val[0]), val[2], :object) }
|
116
|
+
| STRING ASSIGN Expression { result = AssignNode.new(ValueNode.new(LiteralNode.new(val[0])), val[2], :object) }
|
125
117
|
| Comment { result = val[0] }
|
126
118
|
;
|
127
119
|
|
@@ -146,6 +138,8 @@ rule
|
|
146
138
|
| '~' Expression { result = OpNode.new(val[0], val[1]) }
|
147
139
|
| '--' Expression { result = OpNode.new(val[0], val[1]) }
|
148
140
|
| '++' Expression { result = OpNode.new(val[0], val[1]) }
|
141
|
+
| DELETE Expression { result = OpNode.new(val[0], val[1]) }
|
142
|
+
| TYPEOF Expression { result = OpNode.new(val[0], val[1]) }
|
149
143
|
| Expression '--' { result = OpNode.new(val[1], val[0], nil, true) }
|
150
144
|
| Expression '++' { result = OpNode.new(val[1], val[0], nil, true) }
|
151
145
|
|
@@ -187,27 +181,32 @@ rule
|
|
187
181
|
| Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
188
182
|
| Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
189
183
|
|
190
|
-
| DELETE Expression { result = OpNode.new(val[0], val[1]) }
|
191
|
-
| TYPEOF Expression { result = OpNode.new(val[0], val[1]) }
|
192
184
|
| Expression INSTANCEOF Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
193
185
|
;
|
194
186
|
|
195
|
-
|
196
|
-
|
197
|
-
ParamList "=>" CodeBody "." { result = CodeNode.new(val[0], val[2]) }
|
198
|
-
| "=>" CodeBody "." { result = CodeNode.new([], val[1]) }
|
187
|
+
Existence:
|
188
|
+
Expression '?' { result = ExistenceNode.new(val[0]) }
|
199
189
|
;
|
200
190
|
|
201
|
-
#
|
202
|
-
|
203
|
-
|
204
|
-
|
|
191
|
+
# Function definition.
|
192
|
+
Code:
|
193
|
+
ParamList "=>" Block { result = CodeNode.new(val[0], val[2]) }
|
194
|
+
| "=>" Block { result = CodeNode.new([], val[1]) }
|
205
195
|
;
|
206
196
|
|
207
197
|
# The parameters to a function definition.
|
208
198
|
ParamList:
|
209
|
-
|
210
|
-
| ParamList ","
|
199
|
+
Param { result = val }
|
200
|
+
| ParamList "," Param { result = val[0] << val[2] }
|
201
|
+
;
|
202
|
+
|
203
|
+
Param:
|
204
|
+
PARAM
|
205
|
+
| PARAM_SPLAT PARAM { result = ParamSplatNode.new(val[1]) }
|
206
|
+
;
|
207
|
+
|
208
|
+
Splat:
|
209
|
+
'*' Value = SPLAT { result = ArgSplatNode.new(val[1]) }
|
211
210
|
;
|
212
211
|
|
213
212
|
# Expressions that can be treated as values.
|
@@ -240,10 +239,13 @@ rule
|
|
240
239
|
|
241
240
|
# Assignment within an object literal (comma or newline separated).
|
242
241
|
AssignList:
|
243
|
-
/* nothing */ { result = []}
|
242
|
+
/* nothing */ { result = [] }
|
244
243
|
| AssignObj { result = val }
|
245
244
|
| AssignList "," AssignObj { result = val[0] << val[2] }
|
246
245
|
| AssignList Terminator AssignObj { result = val[0] << val[2] }
|
246
|
+
| AssignList ","
|
247
|
+
Terminator AssignObj { result = val[0] << val[3] }
|
248
|
+
| INDENT AssignList OUTDENT { result = val[1] }
|
247
249
|
;
|
248
250
|
|
249
251
|
# All flavors of function call (instantiation, super, and regular).
|
@@ -260,8 +262,14 @@ rule
|
|
260
262
|
|
261
263
|
# A generic function invocation.
|
262
264
|
Invocation:
|
263
|
-
Value
|
264
|
-
| Invocation
|
265
|
+
Value Arguments { result = CallNode.new(val[0], val[1]) }
|
266
|
+
| Invocation Arguments { result = CallNode.new(val[0], val[1]) }
|
267
|
+
# | Invocation Code { result = val[0] << val[1] }
|
268
|
+
;
|
269
|
+
|
270
|
+
Arguments:
|
271
|
+
"(" ArgList ")" { result = val[1] }
|
272
|
+
| "(" ArgList ")" Code { result = val[1] << val[3] }
|
265
273
|
;
|
266
274
|
|
267
275
|
# Calling super.
|
@@ -271,8 +279,10 @@ rule
|
|
271
279
|
|
272
280
|
# The range literal.
|
273
281
|
Range:
|
274
|
-
"["
|
275
|
-
|
282
|
+
"[" Expression
|
283
|
+
"." "." Expression "]" { result = RangeNode.new(val[1], val[4]) }
|
284
|
+
| "[" Expression
|
285
|
+
"." "." "." Expression "]" { result = RangeNode.new(val[1], val[5], true) }
|
276
286
|
;
|
277
287
|
|
278
288
|
# The array literal.
|
@@ -284,21 +294,25 @@ rule
|
|
284
294
|
ArgList:
|
285
295
|
/* nothing */ { result = [] }
|
286
296
|
| Expression { result = val }
|
297
|
+
| INDENT Expression { result = [val[1]] }
|
287
298
|
| ArgList "," Expression { result = val[0] << val[2] }
|
288
299
|
| ArgList Terminator Expression { result = val[0] << val[2] }
|
300
|
+
| ArgList "," Terminator Expression { result = val[0] << val[3] }
|
301
|
+
| ArgList "," INDENT Expression { result = val[0] << val[3] }
|
302
|
+
| ArgList OUTDENT { result = val[0] }
|
289
303
|
;
|
290
304
|
|
291
305
|
# Try/catch/finally exception handling blocks.
|
292
306
|
Try:
|
293
|
-
TRY
|
294
|
-
| TRY
|
295
|
-
|
307
|
+
TRY Block Catch { result = TryNode.new(val[1], val[2][0], val[2][1]) }
|
308
|
+
| TRY Block FINALLY Block { result = TryNode.new(val[1], nil, nil, val[3]) }
|
309
|
+
| TRY Block Catch
|
310
|
+
FINALLY Block { result = TryNode.new(val[1], val[2][0], val[2][1], val[4]) }
|
296
311
|
;
|
297
312
|
|
298
313
|
# A catch clause.
|
299
314
|
Catch:
|
300
|
-
|
301
|
-
| CATCH IDENTIFIER Expressions { result = [val[1], val[2]] }
|
315
|
+
CATCH IDENTIFIER Block { result = [val[1], val[2]] }
|
302
316
|
;
|
303
317
|
|
304
318
|
# Throw an exception.
|
@@ -308,20 +322,20 @@ rule
|
|
308
322
|
|
309
323
|
# Parenthetical expressions.
|
310
324
|
Parenthetical:
|
311
|
-
"("
|
325
|
+
"(" Expression ")" { result = ParentheticalNode.new(val[1], val[0].line) }
|
312
326
|
;
|
313
327
|
|
314
328
|
# The while loop. (there is no do..while).
|
315
329
|
While:
|
316
|
-
WHILE Expression
|
317
|
-
Expressions "." { result = WhileNode.new(val[1], val[3]) }
|
330
|
+
WHILE Expression Block { result = WhileNode.new(val[1], val[2]) }
|
318
331
|
;
|
319
332
|
|
320
333
|
# Array comprehensions, including guard and current index.
|
321
334
|
# Looks a little confusing, check nodes.rb for the arguments to ForNode.
|
322
335
|
For:
|
323
336
|
Expression FOR
|
324
|
-
ForVariables ForSource { result = ForNode.new(val[0], val[3]
|
337
|
+
ForVariables ForSource { result = ForNode.new(val[0], val[3], val[2][0], val[2][1]) }
|
338
|
+
| FOR ForVariables ForSource Block { result = ForNode.new(val[3], val[2], val[1][0], val[1][1]) }
|
325
339
|
;
|
326
340
|
|
327
341
|
# An array comprehension has variables for the current element and index.
|
@@ -332,17 +346,19 @@ rule
|
|
332
346
|
|
333
347
|
# The source of the array comprehension can optionally be filtered.
|
334
348
|
ForSource:
|
335
|
-
IN
|
336
|
-
|
|
337
|
-
|
349
|
+
IN Expression { result = {:source => val[1]} }
|
350
|
+
| ForSource
|
351
|
+
WHEN Expression { result = val[0].merge(:filter => val[2]) }
|
352
|
+
| ForSource
|
353
|
+
BY Expression { result = val[0].merge(:step => val[2]) }
|
338
354
|
;
|
339
355
|
|
340
356
|
# Switch/When blocks.
|
341
357
|
Switch:
|
342
|
-
SWITCH Expression
|
343
|
-
Whens
|
344
|
-
| SWITCH Expression
|
345
|
-
Whens ELSE
|
358
|
+
SWITCH Expression INDENT
|
359
|
+
Whens OUTDENT { result = val[3].rewrite_condition(val[1]) }
|
360
|
+
| SWITCH Expression INDENT
|
361
|
+
Whens ELSE Block OUTDENT { result = val[3].rewrite_condition(val[1]).add_else(val[5]) }
|
346
362
|
;
|
347
363
|
|
348
364
|
# The inner list of whens.
|
@@ -353,16 +369,22 @@ rule
|
|
353
369
|
|
354
370
|
# An individual when.
|
355
371
|
When:
|
356
|
-
|
372
|
+
LEADING_WHEN Expression Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
|
373
|
+
| LEADING_WHEN Expression Block
|
374
|
+
Terminator { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
|
375
|
+
| Comment
|
357
376
|
;
|
358
377
|
|
359
378
|
# All of the following nutso if-else destructuring is to make the
|
360
379
|
# grammar expand unambiguously.
|
361
380
|
|
381
|
+
IfBlock:
|
382
|
+
IF Expression Block { result = IfNode.new(val[1], val[2]) }
|
383
|
+
;
|
384
|
+
|
362
385
|
# An elsif portion of an if-else block.
|
363
386
|
ElsIf:
|
364
|
-
ELSE
|
365
|
-
Then Expressions { result = IfNode.new(val[2], val[4]) }
|
387
|
+
ELSE IfBlock { result = val[1].force_statement }
|
366
388
|
;
|
367
389
|
|
368
390
|
# Multiple elsifs can be chained together.
|
@@ -373,8 +395,8 @@ rule
|
|
373
395
|
|
374
396
|
# Terminating else bodies are strictly optional.
|
375
397
|
ElseBody
|
376
|
-
|
377
|
-
| ELSE
|
398
|
+
/* nothing */ { result = nil }
|
399
|
+
| ELSE Block { result = val[1] }
|
378
400
|
;
|
379
401
|
|
380
402
|
# All the alternatives for ending an if-else block.
|
@@ -385,10 +407,9 @@ rule
|
|
385
407
|
|
386
408
|
# The full complement of if blocks, including postfix one-liner ifs and unlesses.
|
387
409
|
If:
|
388
|
-
|
389
|
-
|
390
|
-
| Expression
|
391
|
-
| Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true, :invert => true}) }
|
410
|
+
IfBlock IfEnd { result = val[0].add_else(val[1]) }
|
411
|
+
| Expression IF Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true}) }
|
412
|
+
| Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true, :invert => true}) }
|
392
413
|
;
|
393
414
|
|
394
415
|
end
|