nscript 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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +35 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +6 -0
- data/bin/nscript +5 -0
- data/examples/code.ns +173 -0
- data/examples/helloworld.ns +6 -0
- data/lib/nscript/command_line.rb +235 -0
- data/lib/nscript/lexer/lexer.rb +230 -0
- data/lib/nscript/lexer/rewriter.rb +238 -0
- data/lib/nscript/parser/grammar.y +483 -0
- data/lib/nscript/parser/nodes.rb +932 -0
- data/lib/nscript/parser/parse_error.rb +22 -0
- data/lib/nscript/parser/parser.rb +2662 -0
- data/lib/nscript/scope.rb +74 -0
- data/lib/nscript/value.rb +62 -0
- data/lib/nscript/version.rb +3 -0
- data/lib/nscript.rb +15 -0
- data/nscript.gemspec +30 -0
- metadata +115 -0
@@ -0,0 +1,932 @@
|
|
1
|
+
module NScript
|
2
|
+
|
3
|
+
class Node
|
4
|
+
TAB = ' '
|
5
|
+
|
6
|
+
def self.statement
|
7
|
+
class_eval "def statement?; true; end"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.statement_only
|
11
|
+
statement
|
12
|
+
class_eval "def statement_only?; true; end"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.top_sensitive
|
16
|
+
class_eval "def top_sensitive?; true; end"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.children(*attributes)
|
20
|
+
attr_reader(*attributes)
|
21
|
+
attrs = attributes.map {|a| "[@#{a}]" }.join(', ')
|
22
|
+
class_eval "def children; [#{attrs}].flatten.compact; end"
|
23
|
+
end
|
24
|
+
|
25
|
+
def write(code)
|
26
|
+
puts "#{self.class.to_s}:\n#{@options.inspect}\n#{code}\n\n" if ENV['VERBOSE']
|
27
|
+
code
|
28
|
+
end
|
29
|
+
|
30
|
+
def compile(o={})
|
31
|
+
@options = o.dup
|
32
|
+
@indent = o[:indent]
|
33
|
+
top = self.top_sensitive? ? @options[:top] : @options.delete(:top)
|
34
|
+
closure = statement? && !statement_only? && !top && !@options[:return] && !self.is_a?(CommentNode)
|
35
|
+
closure &&= !contains? {|n| n.statement_only? }
|
36
|
+
closure ? compile_closure(@options) : compile_node(@options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def compile_closure(o={})
|
40
|
+
@indent = o[:indent]
|
41
|
+
ClosureNode.wrap(self).compile(o.merge(:shared_scope => o[:scope]))
|
42
|
+
end
|
43
|
+
|
44
|
+
def idt(tabs=0)
|
45
|
+
@indent + (TAB * tabs)
|
46
|
+
end
|
47
|
+
|
48
|
+
def contains?(&block)
|
49
|
+
children.each do |node|
|
50
|
+
return true if yield(node)
|
51
|
+
return true if node.is_a?(Node) && node.contains?(&block)
|
52
|
+
end
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
def unwrap; self; end
|
57
|
+
def children; []; end
|
58
|
+
def statement?; false; end
|
59
|
+
def statement_only?; false; end
|
60
|
+
def top_sensitive?; false; end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Expressions < Node
|
64
|
+
statement
|
65
|
+
children :expressions
|
66
|
+
attr_accessor :function
|
67
|
+
|
68
|
+
TRAILING_WHITESPACE = /\s+$/
|
69
|
+
|
70
|
+
def self.wrap(*nodes)
|
71
|
+
return nodes[0] if nodes.length == 1 && nodes[0].is_a?(Expressions)
|
72
|
+
Expressions.new(*nodes)
|
73
|
+
end
|
74
|
+
|
75
|
+
def initialize(*nodes)
|
76
|
+
@expressions = nodes.flatten
|
77
|
+
end
|
78
|
+
|
79
|
+
def <<(node)
|
80
|
+
@expressions << node
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
def unshift(node)
|
85
|
+
@expressions.unshift(node)
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
def unwrap
|
90
|
+
@expressions.length == 1 ? @expressions.first : self
|
91
|
+
end
|
92
|
+
|
93
|
+
def empty?
|
94
|
+
@expressions.empty?
|
95
|
+
end
|
96
|
+
|
97
|
+
def last?(node)
|
98
|
+
@last_index ||= @expressions.last.is_a?(CommentNode) ? -2 : -1
|
99
|
+
node == @expressions[@last_index]
|
100
|
+
end
|
101
|
+
|
102
|
+
def compile(o={})
|
103
|
+
o[:scope] ? super(o) : compile_root(o)
|
104
|
+
end
|
105
|
+
|
106
|
+
def compile_node(options={})
|
107
|
+
write(@expressions.map {|n| compile_expression(n, options.dup) }.join("\n"))
|
108
|
+
end
|
109
|
+
|
110
|
+
def compile_root(o={})
|
111
|
+
indent = o[:no_wrap] ? '' : TAB
|
112
|
+
@indent = indent
|
113
|
+
o.merge!(:indent => indent, :scope => Scope.new(nil, self, nil))
|
114
|
+
code = o[:globals] ? compile_node(o) : compile_with_declarations(o)
|
115
|
+
code.gsub!(TRAILING_WHITESPACE, '')
|
116
|
+
write(o[:no_wrap] ? code : "(function(){\n#{code}\n})();")
|
117
|
+
end
|
118
|
+
|
119
|
+
def compile_with_declarations(o={})
|
120
|
+
code = compile_node(o)
|
121
|
+
args = self.contains? {|n| n.is_a?(ValueNode) && n.arguments? }
|
122
|
+
argv = args && o[:scope].check('arguments') ? '' : 'var '
|
123
|
+
code = "#{idt}#{argv}arguments = Array.prototype.slice.call(arguments, 0);\n#{code}" if args
|
124
|
+
code = "#{idt}var #{o[:scope].compiled_assignments};\n#{code}" if o[:scope].assignments?(self)
|
125
|
+
code = "#{idt}var #{o[:scope].compiled_declarations};\n#{code}" if o[:scope].declarations?(self)
|
126
|
+
write(code)
|
127
|
+
end
|
128
|
+
|
129
|
+
def compile_expression(node, o)
|
130
|
+
@indent = o[:indent]
|
131
|
+
stmt = node.statement?
|
132
|
+
# We need to return the result if this is the last node in the expressions body.
|
133
|
+
returns = o.delete(:return) && last?(node) && !node.statement_only?
|
134
|
+
# Return the regular compile of the node, unless we need to return the result.
|
135
|
+
return "#{stmt ? '' : idt}#{node.compile(o.merge(:top => true))}#{stmt ? '' : ';'}" unless returns
|
136
|
+
# If it's a statement, the node knows how to return itself.
|
137
|
+
return node.compile(o.merge(:return => true)) if node.statement?
|
138
|
+
# If it's not part of a constructor, we can just return the value of the expression.
|
139
|
+
return "#{idt}return #{node.compile(o)};" unless o[:scope].function && o[:scope].function.constructor?
|
140
|
+
# It's the last line of a constructor, add a safety check.
|
141
|
+
temp = o[:scope].free_variable
|
142
|
+
"#{idt}#{temp} = #{node.compile(o)};\n#{idt}return #{o[:scope].function.name} === this.constructor ? this : #{temp};"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class LiteralNode < Node
|
147
|
+
children :value
|
148
|
+
|
149
|
+
STATEMENTS = ['break', 'continue']
|
150
|
+
|
151
|
+
def self.wrap(string)
|
152
|
+
self.new(Value.new(string))
|
153
|
+
end
|
154
|
+
|
155
|
+
def initialize(value)
|
156
|
+
@value = value
|
157
|
+
end
|
158
|
+
|
159
|
+
def statement?
|
160
|
+
STATEMENTS.include?(@value.to_s)
|
161
|
+
end
|
162
|
+
alias_method :statement_only?, :statement?
|
163
|
+
|
164
|
+
def compile_node(o)
|
165
|
+
indent = statement? ? idt : ''
|
166
|
+
ending = statement? ? ';' : ''
|
167
|
+
"#{indent}#{@value}#{ending}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class ReturnNode < Node
|
172
|
+
statement_only
|
173
|
+
children :expression
|
174
|
+
|
175
|
+
def initialize(expression)
|
176
|
+
@expression = expression
|
177
|
+
end
|
178
|
+
|
179
|
+
def compile_node(o)
|
180
|
+
return write(@expression.compile(o.merge(:return => true))) if @expression.statement?
|
181
|
+
compiled = @expression.compile(o)
|
182
|
+
write(@expression.statement? ? "#{compiled}\n#{idt}return null;" : "#{idt}return #{compiled};")
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
class CommentNode < Node
|
187
|
+
statement
|
188
|
+
|
189
|
+
def initialize(lines)
|
190
|
+
@lines = lines.value
|
191
|
+
end
|
192
|
+
|
193
|
+
def compile_node(o={})
|
194
|
+
delimiter = "\n#{idt}//"
|
195
|
+
write("#{delimiter}#{@lines.join(delimiter)}")
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
class CallNode < Node
|
201
|
+
children :variable, :arguments
|
202
|
+
|
203
|
+
def initialize(variable, arguments=[])
|
204
|
+
@variable, @arguments = variable, arguments
|
205
|
+
@prefix = ''
|
206
|
+
end
|
207
|
+
|
208
|
+
def new_instance
|
209
|
+
@prefix = "new "
|
210
|
+
self
|
211
|
+
end
|
212
|
+
|
213
|
+
def <<(argument)
|
214
|
+
@arguments << argument
|
215
|
+
self
|
216
|
+
end
|
217
|
+
|
218
|
+
def compile_node(o)
|
219
|
+
return write(compile_splat(o)) if @arguments.any? {|a| a.is_a?(SplatNode) }
|
220
|
+
args = @arguments.map{|a| a.compile(o) }.join(', ')
|
221
|
+
return write(compile_super(args, o)) if @variable == 'super'
|
222
|
+
write("#{@prefix}#{@variable.compile(o)}(#{args})")
|
223
|
+
end
|
224
|
+
|
225
|
+
def compile_super(args, o)
|
226
|
+
methname = o[:scope].function.name
|
227
|
+
arg_part = args.empty? ? '' : ", #{args}"
|
228
|
+
meth = o[:scope].function.proto ?
|
229
|
+
"#{o[:scope].function.proto}.__superClass__.#{methname}" :
|
230
|
+
"#{methname}.__superClass__.constructor"
|
231
|
+
"#{meth}.call(this#{arg_part})"
|
232
|
+
end
|
233
|
+
|
234
|
+
def compile_splat(o)
|
235
|
+
meth = @variable.compile(o)
|
236
|
+
obj = @variable.source || 'this'
|
237
|
+
args = @arguments.map do |arg|
|
238
|
+
code = arg.compile(o)
|
239
|
+
code = arg.is_a?(SplatNode) ? code : "[#{code}]"
|
240
|
+
arg.equal?(@arguments.first) ? code : ".concat(#{code})"
|
241
|
+
end
|
242
|
+
"#{@prefix}#{meth}.apply(#{obj}, #{args.join('')})"
|
243
|
+
end
|
244
|
+
|
245
|
+
def compile_reference(o)
|
246
|
+
reference = o[:scope].free_variable
|
247
|
+
call = ParentheticalNode.new(AssignNode.new(reference, self))
|
248
|
+
return call, reference
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
class ExtendsNode < Node
|
253
|
+
children :sub_object, :super_object
|
254
|
+
statement
|
255
|
+
|
256
|
+
def initialize(sub_object, super_object)
|
257
|
+
@sub_object, @super_object = sub_object, super_object
|
258
|
+
end
|
259
|
+
|
260
|
+
def compile_node(o={})
|
261
|
+
constructor = o[:scope].free_variable
|
262
|
+
sub, sup = @sub_object.compile(o), @super_object.compile(o)
|
263
|
+
"#{idt}#{constructor} = function(){};\n#{idt}" +
|
264
|
+
"#{constructor}.prototype = #{sup}.prototype;\n#{idt}" +
|
265
|
+
"#{sub}.__superClass__ = #{sup}.prototype;\n#{idt}" +
|
266
|
+
"#{sub}.prototype = new #{constructor}();\n#{idt}" +
|
267
|
+
"#{sub}.prototype.constructor = #{sub};"
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
class ValueNode < Node
|
272
|
+
children :base, :properties
|
273
|
+
attr_reader :last, :source
|
274
|
+
|
275
|
+
SOAK = " == undefined ? undefined : "
|
276
|
+
|
277
|
+
def initialize(base, properties=[])
|
278
|
+
@base, @properties = base, [properties].flatten
|
279
|
+
end
|
280
|
+
|
281
|
+
def <<(other)
|
282
|
+
@properties << other
|
283
|
+
self
|
284
|
+
end
|
285
|
+
|
286
|
+
def properties?
|
287
|
+
return !@properties.empty? || @base.is_a?(ThisNode)
|
288
|
+
end
|
289
|
+
|
290
|
+
def array?
|
291
|
+
@base.is_a?(ArrayNode) && !properties?
|
292
|
+
end
|
293
|
+
|
294
|
+
def object?
|
295
|
+
@base.is_a?(ObjectNode) && !properties?
|
296
|
+
end
|
297
|
+
|
298
|
+
def splice?
|
299
|
+
properties? && @properties.last.is_a?(SliceNode)
|
300
|
+
end
|
301
|
+
|
302
|
+
def arguments?
|
303
|
+
@base.to_s == 'arguments'
|
304
|
+
end
|
305
|
+
|
306
|
+
def unwrap
|
307
|
+
@properties.empty? ? @base : self
|
308
|
+
end
|
309
|
+
|
310
|
+
def statement?
|
311
|
+
@base.is_a?(Node) && @base.statement? && !properties?
|
312
|
+
end
|
313
|
+
|
314
|
+
def compile_node(o)
|
315
|
+
soaked = false
|
316
|
+
only = o.delete(:only_first)
|
317
|
+
props = only ? @properties[0...-1] : @properties
|
318
|
+
baseline = @base.compile(o)
|
319
|
+
parts = [baseline.dup]
|
320
|
+
props.each do |prop|
|
321
|
+
if prop.is_a?(AccessorNode) && prop.soak
|
322
|
+
soaked = true
|
323
|
+
if @base.is_a?(CallNode) && prop == props.first
|
324
|
+
temp = o[:scope].free_variable
|
325
|
+
parts[-1] = "(#{temp} = #{baseline})#{SOAK}#{baseline = temp.to_s + prop.compile(o)}"
|
326
|
+
else
|
327
|
+
parts[-1] << "#{SOAK}#{baseline += prop.compile(o)}"
|
328
|
+
end
|
329
|
+
else
|
330
|
+
part = prop.compile(o)
|
331
|
+
baseline += part
|
332
|
+
parts << part
|
333
|
+
end
|
334
|
+
end
|
335
|
+
@last = parts.last
|
336
|
+
@source = parts.length > 1 ? parts[0...-1].join('') : nil
|
337
|
+
code = parts.join('').gsub(')())', '()))')
|
338
|
+
write(soaked ? "(#{code})" : code)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
class AccessorNode < Node
|
343
|
+
children :name
|
344
|
+
attr_reader :soak
|
345
|
+
|
346
|
+
def initialize(name, tag=nil)
|
347
|
+
@name = name
|
348
|
+
@prototype = tag == :prototype
|
349
|
+
@soak = tag == :soak
|
350
|
+
end
|
351
|
+
|
352
|
+
def compile_node(o)
|
353
|
+
proto = @prototype ? "prototype." : ''
|
354
|
+
write(".#{proto}#{@name}")
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
class IndexNode < Node
|
359
|
+
children :index
|
360
|
+
|
361
|
+
def initialize(index)
|
362
|
+
@index = index
|
363
|
+
end
|
364
|
+
|
365
|
+
def compile_node(o)
|
366
|
+
write("[#{@index.compile(o)}]")
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
class ThisNode < Node
|
371
|
+
def initialize(property=nil)
|
372
|
+
@property = property
|
373
|
+
end
|
374
|
+
|
375
|
+
def compile_node(o)
|
376
|
+
prop = @property ? ".#{@property}" : ''
|
377
|
+
write("this#{prop}")
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
class RangeNode < Node
|
382
|
+
children :from, :to
|
383
|
+
|
384
|
+
def initialize(from, to, exclusive=false)
|
385
|
+
@from, @to, @exclusive = from, to, exclusive
|
386
|
+
end
|
387
|
+
|
388
|
+
def exclusive?
|
389
|
+
@exclusive
|
390
|
+
end
|
391
|
+
|
392
|
+
def compile_variables(o)
|
393
|
+
@indent = o[:indent]
|
394
|
+
@from_var, @to_var = o[:scope].free_variable, o[:scope].free_variable
|
395
|
+
from_val, to_val = @from.compile(o), @to.compile(o)
|
396
|
+
write("#{@from_var} = #{from_val}; #{@to_var} = #{to_val};\n#{idt}")
|
397
|
+
end
|
398
|
+
|
399
|
+
def compile_node(o)
|
400
|
+
return compile_array(o) unless o[:index]
|
401
|
+
idx, step = o.delete(:index), o.delete(:step)
|
402
|
+
vars = "#{idx}=#{@from_var}"
|
403
|
+
step = step ? step.compile(o) : '1'
|
404
|
+
equals = @exclusive ? '' : '='
|
405
|
+
compare = "(#{@from_var} <= #{@to_var} ? #{idx} <#{equals} #{@to_var} : #{idx} >#{equals} #{@to_var})"
|
406
|
+
incr = "(#{@from_var} <= #{@to_var} ? #{idx} += #{step} : #{idx} -= #{step})"
|
407
|
+
write("#{vars}; #{compare}; #{incr}")
|
408
|
+
end
|
409
|
+
|
410
|
+
def compile_array(o)
|
411
|
+
body = Expressions.wrap(LiteralNode.wrap('i'))
|
412
|
+
arr = Expressions.wrap(ForNode.new(body, {:source => ValueNode.new(self)}, Value.new('i')))
|
413
|
+
ParentheticalNode.new(CallNode.new(CodeNode.new([], arr))).compile(o)
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
class SliceNode < Node
|
418
|
+
children :range
|
419
|
+
|
420
|
+
def initialize(range)
|
421
|
+
@range = range
|
422
|
+
end
|
423
|
+
|
424
|
+
def compile_node(o)
|
425
|
+
from = @range.from.compile(o)
|
426
|
+
to = @range.to.compile(o)
|
427
|
+
plus_part = @range.exclusive? ? '' : ' + 1'
|
428
|
+
write(".slice(#{from}, #{to}#{plus_part})")
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
class AssignNode < Node
|
433
|
+
top_sensitive
|
434
|
+
children :variable, :value
|
435
|
+
|
436
|
+
PROTO_ASSIGN = /\A(\S+)\.prototype/
|
437
|
+
LEADING_DOT = /\A\.(prototype\.)?/
|
438
|
+
|
439
|
+
def initialize(variable, value, context=nil)
|
440
|
+
@variable, @value, @context = variable, value, context
|
441
|
+
end
|
442
|
+
|
443
|
+
def compile_node(o)
|
444
|
+
top = o.delete(:top)
|
445
|
+
return compile_pattern_match(o) if statement?
|
446
|
+
return compile_splice(o) if value? && @variable.splice?
|
447
|
+
stmt = o.delete(:as_statement)
|
448
|
+
name = @variable.compile(o)
|
449
|
+
last = value? ? @variable.last.to_s.sub(LEADING_DOT, '') : name
|
450
|
+
proto = name[PROTO_ASSIGN, 1]
|
451
|
+
if @value.is_a?(CodeNode)
|
452
|
+
@value.name = last if last.match(Lexer::IDENTIFIER)
|
453
|
+
@value.proto = proto if proto
|
454
|
+
end
|
455
|
+
return write("#{name}: #{@value.compile(o)}") if @context == :object
|
456
|
+
o[:scope].find(name) unless value? && @variable.properties?
|
457
|
+
val = "#{name} = #{@value.compile(o)}"
|
458
|
+
return write("#{idt}#{val};") if stmt
|
459
|
+
val = "(#{val})" if !top || o[:return]
|
460
|
+
val = "#{idt}return #{val}" if o[:return]
|
461
|
+
write(val)
|
462
|
+
end
|
463
|
+
|
464
|
+
def value?
|
465
|
+
@variable.is_a?(ValueNode)
|
466
|
+
end
|
467
|
+
|
468
|
+
def statement?
|
469
|
+
value? && (@variable.array? || @variable.object?)
|
470
|
+
end
|
471
|
+
|
472
|
+
def compile_pattern_match(o)
|
473
|
+
val_var = o[:scope].free_variable
|
474
|
+
assigns = ["#{idt}#{val_var} = #{@value.compile(o)};"]
|
475
|
+
o.merge!(:top => true, :as_statement => true)
|
476
|
+
@variable.base.objects.each_with_index do |obj, i|
|
477
|
+
obj, i = obj.value, obj.variable.base if @variable.object?
|
478
|
+
access_class = @variable.array? ? IndexNode : AccessorNode
|
479
|
+
if obj.is_a?(SplatNode)
|
480
|
+
val = LiteralNode.wrap(obj.compile_value(o, val_var, @variable.base.objects.index(obj)))
|
481
|
+
else
|
482
|
+
val = ValueNode.new(val_var, [access_class.new(Value.new(i.to_s))])
|
483
|
+
end
|
484
|
+
assigns << AssignNode.new(obj, val).compile(o)
|
485
|
+
end
|
486
|
+
write(assigns.join("\n"))
|
487
|
+
end
|
488
|
+
|
489
|
+
def compile_splice(o)
|
490
|
+
var = @variable.compile(o.merge(:only_first => true))
|
491
|
+
range = @variable.properties.last.range
|
492
|
+
plus = range.exclusive? ? '' : ' + 1'
|
493
|
+
from = range.from.compile(o)
|
494
|
+
to = "#{range.to.compile(o)} - #{from}#{plus}"
|
495
|
+
write("#{var}.splice.apply(#{var}, [#{from}, #{to}].concat(#{@value.compile(o)}))")
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
class OpNode < Node
|
500
|
+
children :first, :second
|
501
|
+
attr_reader :operator
|
502
|
+
attr_accessor :second
|
503
|
+
|
504
|
+
CONVERSIONS = {
|
505
|
+
:== => "===",
|
506
|
+
:'!=' => "!==",
|
507
|
+
:and => '&&',
|
508
|
+
:or => '||',
|
509
|
+
:is => '===',
|
510
|
+
:isnt => "!==",
|
511
|
+
:not => '!'
|
512
|
+
}
|
513
|
+
CHAINABLE = [:<, :>, :>=, :<=, :===, :'!===']
|
514
|
+
ASSIGNMENT = [:'||=', :'&&=', :'?=']
|
515
|
+
PREFIX_OPERATORS = [:typeof, :delete]
|
516
|
+
|
517
|
+
def initialize(operator, first, second=nil, flip=false)
|
518
|
+
@first, @second, @flip = first, second, flip
|
519
|
+
@operator = CONVERSIONS[operator.to_sym] || operator
|
520
|
+
end
|
521
|
+
|
522
|
+
def unary?
|
523
|
+
@second.nil?
|
524
|
+
end
|
525
|
+
|
526
|
+
def chainable?
|
527
|
+
CHAINABLE.include?(operator.to_sym)
|
528
|
+
end
|
529
|
+
|
530
|
+
def compile_node(o)
|
531
|
+
return write(compile_chain(o)) if chainable? && @first.unwrap.is_a?(OpNode) && @first.unwrap.chainable?
|
532
|
+
return write(compile_assignment(o)) if ASSIGNMENT.include?(@operator.to_sym)
|
533
|
+
return write(compile_unary(o)) if unary?
|
534
|
+
return write(compile_existence(o)) if @operator == '?'
|
535
|
+
write("#{@first.compile(o)} #{@operator} #{@second.compile(o)}")
|
536
|
+
end
|
537
|
+
|
538
|
+
def compile_chain(o)
|
539
|
+
shared = @first.unwrap.second
|
540
|
+
@first.second, shared = *shared.compile_reference(o) if shared.is_a?(CallNode)
|
541
|
+
"(#{@first.compile(o)}) && (#{shared.compile(o)} #{@operator} #{@second.compile(o)})"
|
542
|
+
end
|
543
|
+
|
544
|
+
def compile_assignment(o)
|
545
|
+
first, second = @first.compile(o), @second.compile(o)
|
546
|
+
o[:scope].find(first) if @first.unwrap.is_a?(Value)
|
547
|
+
sym = @operator[0..1]
|
548
|
+
return "#{first} = #{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}" if @operator == '?='
|
549
|
+
"#{first} = #{first} #{sym} #{second}"
|
550
|
+
end
|
551
|
+
|
552
|
+
def compile_existence(o)
|
553
|
+
first, second = @first.compile(o), @second.compile(o)
|
554
|
+
"#{ExistenceNode.compile_test(o, @first)} ? #{first} : #{second}"
|
555
|
+
end
|
556
|
+
|
557
|
+
def compile_unary(o)
|
558
|
+
space = PREFIX_OPERATORS.include?(@operator.to_sym) ? ' ' : ''
|
559
|
+
parts = [@operator.to_s, space, @first.compile(o)]
|
560
|
+
parts.reverse! if @flip
|
561
|
+
parts.join('')
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
class CodeNode < Node
|
566
|
+
top_sensitive
|
567
|
+
attr_reader :params, :body, :bound
|
568
|
+
attr_accessor :name, :proto
|
569
|
+
|
570
|
+
UPPERCASE = /[A-Z]/
|
571
|
+
|
572
|
+
def initialize(params, body, tag=nil)
|
573
|
+
@params = params
|
574
|
+
@body = body
|
575
|
+
@bound = tag == :boundfunc
|
576
|
+
end
|
577
|
+
|
578
|
+
def constructor?
|
579
|
+
@name && @name[0..0][UPPERCASE]
|
580
|
+
end
|
581
|
+
|
582
|
+
def compile_node(o)
|
583
|
+
shared_scope = o.delete(:shared_scope)
|
584
|
+
top = o.delete(:top)
|
585
|
+
o[:scope] = shared_scope || Scope.new(o[:scope], @body, self)
|
586
|
+
o[:return] = true
|
587
|
+
o[:top] = true
|
588
|
+
o[:indent] = idt(@bound ? 2 : 1)
|
589
|
+
o.delete(:no_wrap)
|
590
|
+
o.delete(:globals)
|
591
|
+
if @params.last.is_a?(SplatNode)
|
592
|
+
splat = @params.pop
|
593
|
+
splat.index = @params.length
|
594
|
+
@body.unshift(splat)
|
595
|
+
end
|
596
|
+
@params.each {|id| o[:scope].parameter(id.to_s) }
|
597
|
+
code = @body.empty? ? "" : "\n#{@body.compile_with_declarations(o)}\n"
|
598
|
+
name_part = @name ? " #{@name}" : ''
|
599
|
+
func = "function#{@bound ? '' : name_part}(#{@params.join(', ')}) {#{code}#{idt(@bound ? 1 : 0)}}"
|
600
|
+
func = "(#{func})" if top && !@bound
|
601
|
+
return write(func) unless @bound
|
602
|
+
inner = "(function#{name_part}() {\n#{idt(2)}return __func.apply(__this, arguments);\n#{idt(1)}});"
|
603
|
+
write("(function(__this) {\n#{idt(1)}var __func = #{func};\n#{idt(1)}return #{inner}\n#{idt}})(this)")
|
604
|
+
end
|
605
|
+
end
|
606
|
+
|
607
|
+
class SplatNode < Node
|
608
|
+
children :name
|
609
|
+
attr_accessor :index
|
610
|
+
|
611
|
+
def initialize(name)
|
612
|
+
@name = name
|
613
|
+
end
|
614
|
+
|
615
|
+
def compile_node(o={})
|
616
|
+
write(@index ? compile_param(o) : @name.compile(o))
|
617
|
+
end
|
618
|
+
|
619
|
+
def compile_param(o)
|
620
|
+
o[:scope].find(@name)
|
621
|
+
"#{@name} = Array.prototype.slice.call(arguments, #{@index})"
|
622
|
+
end
|
623
|
+
|
624
|
+
def compile_value(o, name, index)
|
625
|
+
"Array.prototype.slice.call(#{name}, #{index})"
|
626
|
+
end
|
627
|
+
end
|
628
|
+
|
629
|
+
class ObjectNode < Node
|
630
|
+
children :properties
|
631
|
+
alias_method :objects, :properties
|
632
|
+
|
633
|
+
def initialize(properties = [])
|
634
|
+
@properties = properties
|
635
|
+
end
|
636
|
+
|
637
|
+
def compile_node(o)
|
638
|
+
o[:indent] = idt(1)
|
639
|
+
joins = Hash.new("\n")
|
640
|
+
non_comments = @properties.select {|p| !p.is_a?(CommentNode) }
|
641
|
+
non_comments.each {|p| joins[p] = p == non_comments.last ? "\n" : ",\n" }
|
642
|
+
props = @properties.map { |prop|
|
643
|
+
join = joins[prop]
|
644
|
+
join = '' if prop == @properties.last
|
645
|
+
indent = prop.is_a?(CommentNode) ? '' : idt(1)
|
646
|
+
"#{indent}#{prop.compile(o)}#{join}"
|
647
|
+
}.join('')
|
648
|
+
write("{\n#{props}\n#{idt}}")
|
649
|
+
end
|
650
|
+
end
|
651
|
+
|
652
|
+
class ArrayNode < Node
|
653
|
+
children :objects
|
654
|
+
|
655
|
+
def initialize(objects=[])
|
656
|
+
@objects = objects
|
657
|
+
end
|
658
|
+
|
659
|
+
def compile_node(o)
|
660
|
+
o[:indent] = idt(1)
|
661
|
+
objects = @objects.map { |obj|
|
662
|
+
code = obj.compile(o)
|
663
|
+
obj.is_a?(CommentNode) ? "\n#{code}\n#{o[:indent]}" :
|
664
|
+
obj == @objects.last ? code : "#{code}, "
|
665
|
+
}.join('')
|
666
|
+
ending = objects.include?("\n") ? "\n#{idt}]" : ']'
|
667
|
+
write("[#{objects}#{ending}")
|
668
|
+
end
|
669
|
+
end
|
670
|
+
|
671
|
+
class PushNode
|
672
|
+
def self.wrap(array, expressions)
|
673
|
+
expr = expressions.unwrap
|
674
|
+
return expressions if expr.statement_only? || expr.contains? {|n| n.statement_only? }
|
675
|
+
Expressions.wrap(CallNode.new(
|
676
|
+
ValueNode.new(LiteralNode.new(array), [AccessorNode.new(Value.new('push'))]),
|
677
|
+
[expr]
|
678
|
+
))
|
679
|
+
end
|
680
|
+
end
|
681
|
+
|
682
|
+
class ClosureNode
|
683
|
+
def self.wrap(expressions, statement=false)
|
684
|
+
func = ParentheticalNode.new(CodeNode.new([], Expressions.wrap(expressions)))
|
685
|
+
call = CallNode.new(ValueNode.new(func, AccessorNode.new(Value.new('call'))), [Value.new('this')])
|
686
|
+
statement ? Expressions.wrap(call) : call
|
687
|
+
end
|
688
|
+
end
|
689
|
+
|
690
|
+
class WhileNode < Node
|
691
|
+
top_sensitive
|
692
|
+
children :condition, :body
|
693
|
+
statement
|
694
|
+
|
695
|
+
def initialize(condition, body)
|
696
|
+
@condition, @body = condition, body
|
697
|
+
end
|
698
|
+
|
699
|
+
def compile_node(o)
|
700
|
+
returns = o.delete(:return)
|
701
|
+
top = o.delete(:top) && !returns
|
702
|
+
o[:indent] = idt(1)
|
703
|
+
o[:top] = true
|
704
|
+
cond = @condition.compile(o)
|
705
|
+
set = ''
|
706
|
+
if !top
|
707
|
+
rvar = o[:scope].free_variable
|
708
|
+
set = "#{idt}#{rvar} = [];\n"
|
709
|
+
@body = PushNode.wrap(rvar, @body)
|
710
|
+
end
|
711
|
+
post = returns ? "\n#{idt}return #{rvar};" : ''
|
712
|
+
return write("#{set}#{idt}while (#{cond}) null;#{post}") if @body.nil?
|
713
|
+
write("#{set}#{idt}while (#{cond}) {\n#{@body.compile(o)}\n#{idt}}#{post}")
|
714
|
+
end
|
715
|
+
end
|
716
|
+
|
717
|
+
class ForNode < Node
|
718
|
+
top_sensitive
|
719
|
+
children :body, :source, :filter
|
720
|
+
attr_reader :name, :index, :step
|
721
|
+
statement
|
722
|
+
|
723
|
+
def initialize(body, source, name, index=nil)
|
724
|
+
@body, @name, @index = body, name, index
|
725
|
+
@source = source[:source]
|
726
|
+
@filter = source[:filter]
|
727
|
+
@step = source[:step]
|
728
|
+
@object = !!source[:object]
|
729
|
+
@name, @index = @index, @name if @object
|
730
|
+
end
|
731
|
+
|
732
|
+
def compile_node(o)
|
733
|
+
top_level = o.delete(:top) && !o[:return]
|
734
|
+
range = @source.is_a?(ValueNode) && @source.base.is_a?(RangeNode) && @source.properties.empty?
|
735
|
+
source = range ? @source.base : @source
|
736
|
+
scope = o[:scope]
|
737
|
+
name_found = @name && scope.find(@name)
|
738
|
+
index_found = @index && scope.find(@index)
|
739
|
+
body_dent = idt(1)
|
740
|
+
rvar = scope.free_variable unless top_level
|
741
|
+
svar = scope.free_variable
|
742
|
+
ivar = range ? name : @index ? @index : scope.free_variable
|
743
|
+
var_part = ''
|
744
|
+
body = Expressions.wrap(@body)
|
745
|
+
if range
|
746
|
+
index_var = scope.free_variable
|
747
|
+
source_part = source.compile_variables(o)
|
748
|
+
for_part = "#{index_var}=0, #{source.compile(o.merge(:index => ivar, :step => @step))}, #{index_var}++"
|
749
|
+
else
|
750
|
+
index_var = nil
|
751
|
+
source_part = "#{svar} = #{@source.compile(o)};\n#{idt}"
|
752
|
+
step_part = @step ? "#{ivar} += #{@step.compile(o)}" : "#{ivar}++"
|
753
|
+
for_part = @object ? "#{ivar} in #{svar}" : "#{ivar} = 0; #{ivar} < #{svar}.length; #{step_part}"
|
754
|
+
var_part = "#{body_dent}#{@name} = #{svar}[#{ivar}];\n" if @name
|
755
|
+
# body.unshift(AssignNode.new(@name, ValueNode.new(svar, [IndexNode.new(ivar)]))) if @name
|
756
|
+
end
|
757
|
+
set_result = rvar ? "#{idt}#{rvar} = []; " : idt
|
758
|
+
return_result = rvar || ''
|
759
|
+
body = ClosureNode.wrap(body, true) if top_level && contains? {|n| n.is_a? CodeNode }
|
760
|
+
body = PushNode.wrap(rvar, body) unless top_level
|
761
|
+
if o[:return]
|
762
|
+
return_result = "return #{return_result}" if o[:return]
|
763
|
+
o.delete(:return)
|
764
|
+
body = IfNode.new(@filter, body, nil, :statement => true) if @filter
|
765
|
+
elsif @filter
|
766
|
+
body = Expressions.wrap(IfNode.new(@filter, body))
|
767
|
+
end
|
768
|
+
if @object
|
769
|
+
o[:scope].assign("__hasProp", "Object.prototype.hasOwnProperty", true)
|
770
|
+
body = Expressions.wrap(IfNode.new(
|
771
|
+
CallNode.new(
|
772
|
+
ValueNode.new(LiteralNode.wrap("__hasProp"), [AccessorNode.new(Value.new('call'))]),
|
773
|
+
[LiteralNode.wrap(svar), LiteralNode.wrap(ivar)]
|
774
|
+
),
|
775
|
+
Expressions.wrap(body), nil, {:statement => true}
|
776
|
+
))
|
777
|
+
end
|
778
|
+
|
779
|
+
return_result = "\n#{idt}#{return_result};" unless top_level
|
780
|
+
body = body.compile(o.merge(:indent => body_dent, :top => true))
|
781
|
+
vars = range ? @name : "#{@name}, #{ivar}"
|
782
|
+
return write(set_result + source_part + "for (#{for_part}) {\n#{var_part}#{body}\n#{idt}}\n#{idt}#{return_result}")
|
783
|
+
end
|
784
|
+
end
|
785
|
+
|
786
|
+
class TryNode < Node
|
787
|
+
children :try, :recovery, :finally
|
788
|
+
attr_reader :error
|
789
|
+
statement
|
790
|
+
|
791
|
+
def initialize(try, error, recovery, finally=nil)
|
792
|
+
@try, @error, @recovery, @finally = try, error, recovery, finally
|
793
|
+
end
|
794
|
+
|
795
|
+
def compile_node(o)
|
796
|
+
o[:indent] = idt(1)
|
797
|
+
o[:top] = true
|
798
|
+
error_part = @error ? " (#{@error}) " : ' '
|
799
|
+
catch_part = @recovery && " catch#{error_part}{\n#{@recovery.compile(o)}\n#{idt}}"
|
800
|
+
finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:return => nil))}\n#{idt}}"
|
801
|
+
write("#{idt}try {\n#{@try.compile(o)}\n#{idt}}#{catch_part}#{finally_part}")
|
802
|
+
end
|
803
|
+
end
|
804
|
+
|
805
|
+
class ThrowNode < Node
|
806
|
+
children :expression
|
807
|
+
statement_only
|
808
|
+
|
809
|
+
def initialize(expression)
|
810
|
+
@expression = expression
|
811
|
+
end
|
812
|
+
|
813
|
+
def compile_node(o)
|
814
|
+
write("#{idt}throw #{@expression.compile(o)};")
|
815
|
+
end
|
816
|
+
end
|
817
|
+
|
818
|
+
class ExistenceNode < Node
|
819
|
+
children :expression
|
820
|
+
|
821
|
+
def self.compile_test(o, variable)
|
822
|
+
first, second = variable, variable
|
823
|
+
first, second = *variable.compile_reference(o) if variable.is_a?(CallNode)
|
824
|
+
"(typeof #{first.compile(o)} !== \"undefined\" && #{second.compile(o)} !== null)"
|
825
|
+
end
|
826
|
+
|
827
|
+
def initialize(expression)
|
828
|
+
@expression = expression
|
829
|
+
end
|
830
|
+
|
831
|
+
def compile_node(o)
|
832
|
+
write(ExistenceNode.compile_test(o, @expression))
|
833
|
+
end
|
834
|
+
end
|
835
|
+
|
836
|
+
class ParentheticalNode < Node
|
837
|
+
children :expressions
|
838
|
+
|
839
|
+
def initialize(expressions, line=nil)
|
840
|
+
@expressions = expressions.unwrap
|
841
|
+
@line = line
|
842
|
+
end
|
843
|
+
|
844
|
+
def compile_node(o)
|
845
|
+
compiled = @expressions.compile(o)
|
846
|
+
compiled = compiled[0...-1] if compiled[-1..-1] == ';'
|
847
|
+
write("(#{compiled})")
|
848
|
+
end
|
849
|
+
end
|
850
|
+
|
851
|
+
class IfNode < Node
|
852
|
+
children :condition, :body, :else_body
|
853
|
+
|
854
|
+
def initialize(condition, body, else_body=nil, tags={})
|
855
|
+
@condition = condition
|
856
|
+
@body = body && body.unwrap
|
857
|
+
@else_body = else_body && else_body.unwrap
|
858
|
+
@tags = tags
|
859
|
+
@multiple = true if @condition.is_a?(Array)
|
860
|
+
@condition = OpNode.new("!", ParentheticalNode.new(@condition)) if @tags[:invert]
|
861
|
+
end
|
862
|
+
|
863
|
+
def <<(else_body)
|
864
|
+
eb = else_body.unwrap
|
865
|
+
@else_body ? @else_body << eb : @else_body = eb
|
866
|
+
self
|
867
|
+
end
|
868
|
+
|
869
|
+
def add_comment(comment)
|
870
|
+
@comment = comment
|
871
|
+
self
|
872
|
+
end
|
873
|
+
|
874
|
+
def force_statement
|
875
|
+
@tags[:statement] = true
|
876
|
+
self
|
877
|
+
end
|
878
|
+
|
879
|
+
def rewrite_condition(expression)
|
880
|
+
@condition = @multiple ? @condition.map {|c| OpNode.new("is", expression, c) } :
|
881
|
+
OpNode.new("is", expression, @condition)
|
882
|
+
@else_body.rewrite_condition(expression) if chain?
|
883
|
+
self
|
884
|
+
end
|
885
|
+
|
886
|
+
def add_else(exprs)
|
887
|
+
chain? ? @else_body.add_else(exprs) : @else_body = (exprs && exprs.unwrap)
|
888
|
+
self
|
889
|
+
end
|
890
|
+
|
891
|
+
def chain?
|
892
|
+
@chain ||= @else_body && @else_body.is_a?(IfNode)
|
893
|
+
end
|
894
|
+
|
895
|
+
def statement?
|
896
|
+
@is_statement ||= !!(@comment || @tags[:statement] || @body.statement? || (@else_body && @else_body.statement?))
|
897
|
+
end
|
898
|
+
|
899
|
+
def compile_condition(o)
|
900
|
+
[@condition].flatten.map {|c| c.compile(o) }.join(' || ')
|
901
|
+
end
|
902
|
+
|
903
|
+
def compile_node(o)
|
904
|
+
write(statement? ? compile_statement(o) : compile_ternary(o))
|
905
|
+
end
|
906
|
+
|
907
|
+
def compile_statement(o)
|
908
|
+
child = o.delete(:chain_child)
|
909
|
+
cond_o = o.dup
|
910
|
+
cond_o.delete(:return)
|
911
|
+
o[:indent] = idt(1)
|
912
|
+
o[:top] = true
|
913
|
+
if_dent = child ? '' : idt
|
914
|
+
com_dent = child ? idt : ''
|
915
|
+
prefix = @comment ? @comment.compile(cond_o) + "\n#{com_dent}" : ''
|
916
|
+
body = Expressions.wrap(@body).compile(o)
|
917
|
+
if_part = "#{prefix}#{if_dent}if (#{compile_condition(cond_o)}) {\n#{body}\n#{idt}}"
|
918
|
+
return if_part unless @else_body
|
919
|
+
else_part = chain? ?
|
920
|
+
" else #{@else_body.compile(o.merge(:indent => idt, :chain_child => true))}" :
|
921
|
+
" else {\n#{Expressions.wrap(@else_body).compile(o)}\n#{idt}}"
|
922
|
+
if_part + else_part
|
923
|
+
end
|
924
|
+
|
925
|
+
def compile_ternary(o)
|
926
|
+
if_part = "#{@condition.compile(o)} ? #{@body.compile(o)}"
|
927
|
+
else_part = @else_body ? "#{@else_body.compile(o)}" : 'null'
|
928
|
+
"#{if_part} : #{else_part}"
|
929
|
+
end
|
930
|
+
end
|
931
|
+
|
932
|
+
end # end - NScript
|