twostroke 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/lib/twostroke/ast/case.rb +1 -1
  2. data/lib/twostroke/ast/function.rb +2 -2
  3. data/lib/twostroke/ast/label.rb +20 -0
  4. data/lib/twostroke/ast/switch.rb +1 -0
  5. data/lib/twostroke/ast/try.rb +3 -3
  6. data/lib/twostroke/ast/unary_operators.rb +1 -1
  7. data/lib/twostroke/ast.rb +1 -0
  8. data/lib/twostroke/compiler/javascript.rb +20 -2
  9. data/lib/twostroke/compiler/tsasm.rb +89 -35
  10. data/lib/twostroke/compiler.rb +1 -1
  11. data/lib/twostroke/error.rb +3 -0
  12. data/lib/twostroke/lexer.rb +3 -2
  13. data/lib/twostroke/parser.rb +309 -227
  14. data/lib/twostroke/runtime/lib/array.js +9 -3
  15. data/lib/twostroke/runtime/lib/array.rb +15 -7
  16. data/lib/twostroke/runtime/lib/console.rb +3 -2
  17. data/lib/twostroke/runtime/lib/error.rb +1 -1
  18. data/lib/twostroke/runtime/lib/etc.js +38 -0
  19. data/lib/twostroke/runtime/lib/etc.rb +29 -0
  20. data/lib/twostroke/runtime/lib/math.rb +31 -0
  21. data/lib/twostroke/runtime/lib/number.rb +7 -1
  22. data/lib/twostroke/runtime/lib/object.rb +1 -1
  23. data/lib/twostroke/runtime/lib/regexp.rb +1 -0
  24. data/lib/twostroke/runtime/lib/string.rb +62 -22
  25. data/lib/twostroke/runtime/scope.rb +23 -2
  26. data/lib/twostroke/runtime/types/array.rb +5 -0
  27. data/lib/twostroke/runtime/types/function.rb +5 -2
  28. data/lib/twostroke/runtime/types/object.rb +3 -4
  29. data/lib/twostroke/runtime/types/regexp.rb +45 -2
  30. data/lib/twostroke/runtime/types.rb +1 -1
  31. data/lib/twostroke/runtime/vm.rb +2 -2
  32. data/lib/twostroke/runtime/vm_frame.rb +47 -8
  33. data/lib/twostroke/tokens.rb +32 -16
  34. metadata +5 -5
  35. data/lib/twostroke/ast/delete.rb +0 -15
  36. data/lib/twostroke/ast/unsorted_binop.rb +0 -97
  37. data/lib/twostroke/runtime/lib/function.js +0 -0
@@ -12,7 +12,7 @@ module Twostroke::AST
12
12
 
13
13
  def walk(&bk)
14
14
  if yield self
15
- expression.walk &bk
15
+ expression.walk &bk if expression
16
16
  statements.each { |s| s.walk &bk }
17
17
  end
18
18
  end
@@ -1,6 +1,6 @@
1
1
  module Twostroke::AST
2
2
  class Function < Base
3
- attr_accessor :name, :arguments, :statements
3
+ attr_accessor :name, :arguments, :statements, :fnid
4
4
 
5
5
  def initialize(*args)
6
6
  @arguments = []
@@ -9,7 +9,7 @@ module Twostroke::AST
9
9
  end
10
10
 
11
11
  def collapse
12
- self.class.new name: name, arguments: arguments, statements: statements.reject(&:nil?).map(&:collapse)
12
+ self.class.new name: name, arguments: arguments, statements: statements.reject(&:nil?).map(&:collapse), fnid: fnid
13
13
  end
14
14
 
15
15
  def walk(&bk)
@@ -0,0 +1,20 @@
1
+ module Twostroke::AST
2
+ class Label < Base
3
+ attr_accessor :name, :statement
4
+
5
+ def initialize(*args)
6
+ @statements = []
7
+ super *args
8
+ end
9
+
10
+ def collapse
11
+ self.class.new name: name, statement: statement
12
+ end
13
+
14
+ def walk(&bk)
15
+ if yield self
16
+ statement.walk &bk
17
+ end
18
+ end
19
+ end
20
+ end
@@ -11,6 +11,7 @@ module Twostroke::AST
11
11
 
12
12
  def walk(&bk)
13
13
  if yield self
14
+
14
15
  expression.walk &bk
15
16
  cases.each { |c| c.walk &bk }
16
17
  end
@@ -3,9 +3,9 @@ module Twostroke::AST
3
3
  attr_accessor :try_statements, :catch_variable, :catch_statements, :finally_statements
4
4
 
5
5
  def collapse
6
- self.class.new try_statements: try_statements.map(&:collapse), catch_variable: catch_variable,
7
- catch_statements: (catch_statements && catch_statements.map(&:collapse)),
8
- finally_statements: (finally_statements && finally_statements.map(&:collapse))
6
+ self.class.new try_statements: try_statements.reject(&:nil?).map(&:collapse), catch_variable: catch_variable,
7
+ catch_statements: (catch_statements && catch_statements.reject(&:nil?).map(&:collapse)),
8
+ finally_statements: (finally_statements && finally_statements.reject(&:nil?).map(&:collapse))
9
9
  end
10
10
 
11
11
  def walk(&bk)
@@ -1,5 +1,5 @@
1
1
  module Twostroke::AST
2
- [ :PostIncrement, :PreIncrement, :PostDecrement, :PreDecrement,
2
+ [ :PostIncrement, :PreIncrement, :PostDecrement, :PreDecrement, :Delete,
3
3
  :BinaryNot, :UnaryPlus, :Negation, :TypeOf, :Not, :Void, :BracketedExpression ].each do |op|
4
4
  klass = Class.new Base do
5
5
  attr_accessor :value
data/lib/twostroke/ast.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  module Twostroke
2
2
  module AST
3
3
  class Base
4
+ attr_accessor :line
4
5
  def initialize(hash = {})
5
6
  hash.each do |k,v|
6
7
  send "#{k}=", v
@@ -80,13 +80,31 @@ private
80
80
  "\"#{escaped}\""
81
81
  end
82
82
 
83
+ def lval(node)
84
+ if type(left) == :Variable
85
+ left.name
86
+ elsif type(left) == :Declaration
87
+ left.name
88
+ elsif type(left) == :MemberAccess
89
+ compile left.object
90
+ "#{stack}.pop().#{left.member}"
91
+ elsif type(left) == :Index
92
+ compile left.object
93
+ compile left.index
94
+ output "var __A = stack.pop(), __B = stack.pop();"
95
+ "__B[__A]"
96
+ else
97
+ "bad lval!"
98
+ end
99
+ end
100
+
83
101
  def mutate(left, fmt)
84
102
  lval = nil
85
103
  if type(left) == :Variable
86
- lval = "#{left.name}"
104
+ lval = left.name
87
105
  elsif type(left) == :Declaration
88
106
  compile left
89
- lval = "#{left.name}"
107
+ lval = left.name
90
108
  elsif type(left) == :MemberAccess
91
109
  compile left.object
92
110
  lval = "#{stack}.pop().#{left.member}"
@@ -17,7 +17,10 @@ class Twostroke::Compiler::TSASM
17
17
  send node
18
18
  else
19
19
  if @methods[type(node)]
20
+ output :".line", @current_line = node.line if node.line and node.line > @current_line
21
+ @node_stack.push node
20
22
  send type(node), node if node
23
+ @node_stack.pop
21
24
  else
22
25
  error! "#{type node} not implemented"
23
26
  end
@@ -30,6 +33,8 @@ class Twostroke::Compiler::TSASM
30
33
  @sections = [:"#{prefix}main"]
31
34
  @break_stack = []
32
35
  @continue_stack = []
36
+ @node_stack = []
37
+ @current_line = 0
33
38
 
34
39
  ast.each { |node| hoist node }
35
40
  ast.each { |node| compile node }
@@ -66,8 +71,11 @@ private
66
71
  node.walk do |node|
67
72
  if node.is_a? Twostroke::AST::Declaration
68
73
  output :".local", node.name.intern
74
+ false
69
75
  elsif node.is_a? Twostroke::AST::Function
70
76
  output :".local", node.name.intern if node.name
77
+ # because javascript is odd, entire function bodies need to be hoisted, not just their declarations
78
+ Function(node, true)
71
79
  false
72
80
  else
73
81
  true
@@ -76,7 +84,7 @@ private
76
84
  end
77
85
 
78
86
  def error!(msg)
79
- raise Twostroke::Compiler::CompileError, msg
87
+ raise Twostroke::Compiler::CompileError, "#{msg} at line #{@current_line}"
80
88
  end
81
89
 
82
90
  def type(node)
@@ -108,7 +116,7 @@ private
108
116
  compile left.object
109
117
  compile right
110
118
  output :setprop, left.member.intern
111
- elsif type(left) == :Index
119
+ elsif type(left) == :Index
112
120
  compile left.object
113
121
  compile left.index
114
122
  compile right
@@ -136,7 +144,7 @@ private
136
144
  output :set, node.left.name.intern
137
145
  elsif type(node.left) == :MemberAccess
138
146
  compile node.left.object
139
- dup
147
+ output :dup
140
148
  output :member, node.left.member.intern
141
149
  compile node.right
142
150
  output op
@@ -180,15 +188,23 @@ private
180
188
  compile left.object
181
189
  output :dup
182
190
  output :member, left.member.intern
191
+ output :dup
192
+ output :tst
183
193
  output op
184
194
  output :setprop, left.member.intern
195
+ output :pop
196
+ output :tld
185
197
  elsif type(left) == :Index
186
198
  compile left.object
187
199
  compile left.index
188
200
  output :dup, 2
189
201
  output :index
202
+ output :dup
203
+ output :tst
190
204
  output op
191
205
  output :setindex
206
+ output :pop
207
+ output :tld
192
208
  else
193
209
  error! "Bad lval in post-mutation"
194
210
  end
@@ -214,7 +230,12 @@ private
214
230
  output op
215
231
  output :setprop, left.member.intern
216
232
  elsif type(left) == :Index
217
- error! "pre-mutatation of array index not supported yet" # @TODO
233
+ compile left.object
234
+ compile left.index
235
+ output :dup, 2
236
+ output :index
237
+ output op
238
+ output :setindex
218
239
  else
219
240
  error! "Bad lval in post-mutation"
220
241
  end
@@ -238,7 +259,7 @@ private
238
259
  elsif type(node.callee) == :Index
239
260
  compile node.callee.object
240
261
  output :dup
241
- output node.callee.index
262
+ compile node.callee.index
242
263
  output :index
243
264
  node.arguments.each { |n| compile n }
244
265
  output :thiscall, node.arguments.size
@@ -275,27 +296,30 @@ private
275
296
  output :regexp, node.regexp
276
297
  end
277
298
 
278
- def Function(node)
279
- fnid = :"#{@prefix}fn_#{uniqid}"
299
+ def Function(node, in_hoist_stage = false)
300
+ fnid = node.fnid ||= :"#{@prefix}fn_#{uniqid}"
280
301
 
281
- section fnid
282
- output :".name", node.name if node.name
283
- node.arguments.each do |arg|
284
- output :".arg", arg.intern
285
- end
286
- output :".local", node.name.intern if node.name
287
- node.statements.each { |s| hoist s }
288
- if node.name
289
- output :callee
290
- output :set, node.name.intern
302
+ if !node.name or in_hoist_stage
303
+ section fnid
304
+ output :".name", node.name if node.name
305
+ node.arguments.each do |arg|
306
+ output :".arg", arg.intern
307
+ end
308
+ output :".local", node.name.intern if node.name
309
+ node.statements.each { |s| hoist s }
310
+ if node.name
311
+ output :callee
312
+ output :set, node.name.intern
313
+ end
314
+ node.statements.each { |s| compile s }
315
+ output :undefined
316
+ output :ret
317
+ pop_section
318
+ output :close, fnid
319
+ output :set, node.name.intern if node.name
320
+ else
321
+ output :close, fnid
291
322
  end
292
- node.statements.each { |s| compile s }
293
- output :undefined
294
- output :ret
295
- pop_section
296
-
297
- output :close, fnid
298
- output :set, node.name.intern if node.name
299
323
  end
300
324
 
301
325
  def Declaration(node)
@@ -327,15 +351,18 @@ private
327
351
  end
328
352
 
329
353
  def Delete(node)
330
- if node.expression.is_a?(Twostroke::AST::Variable)
331
- output :deleteg, node.expression.name
332
- elsif node.expression.is_a?(Twostroke::AST::MemberAccess)
333
- compile node.expression.object
334
- output :delete, node.expression.member
335
- elsif node.expression.is_a?(Twostroke::AST::Index)
336
- compile node.expression.object
337
- output :delete, node.expression.index
354
+ if node.value.is_a?(Twostroke::AST::Variable)
355
+ output :deleteg, node.value.name.intern
356
+ elsif node.value.is_a?(Twostroke::AST::MemberAccess)
357
+ compile node.value.object
358
+ output :delete, node.value.member
359
+ elsif node.value.is_a?(Twostroke::AST::Index)
360
+ compile node.value.index
361
+ compile node.value.object
362
+ output :deleteindex
338
363
  else
364
+ compile node.value
365
+ output :pop
339
366
  output :true
340
367
  end
341
368
  end
@@ -358,7 +385,6 @@ private
358
385
  compile node.try_statements
359
386
  # no exceptions? clean up
360
387
  output :popcatch if node.catch_variable
361
- output :popfinally if node.catch_variable
362
388
  output :jmp, finally_label
363
389
 
364
390
  if node.catch_variable
@@ -371,6 +397,7 @@ private
371
397
  if node.finally_statements
372
398
  output :popfinally
373
399
  compile node.finally_statements
400
+ output :endfinally
374
401
  end
375
402
  end
376
403
 
@@ -437,7 +464,11 @@ private
437
464
  end
438
465
 
439
466
  def Number(node)
440
- output :push, node.number
467
+ if node.number.is_a? Bignum
468
+ output :push, node.number.to_f
469
+ else
470
+ output :push, node.number
471
+ end
441
472
  end
442
473
 
443
474
  def UnaryPlus(node)
@@ -517,16 +548,32 @@ private
517
548
  node.statements.each { |s| compile s }
518
549
  end
519
550
 
551
+ def DoWhile(node)
552
+ start_label = uniqid
553
+ next_label = uniqid
554
+ end_label = uniqid
555
+ @continue_stack.push next_label
556
+ @break_stack.push end_label
557
+ output :".label", start_label
558
+ compile node.body
559
+ output :".label", next_label
560
+ compile node.condition
561
+ output :jit, start_label
562
+ output :".label", end_label
563
+ end
564
+
520
565
  def ForLoop(node)
521
566
  compile node.initializer if node.initializer
522
567
  start_label = uniqid
568
+ next_label = uniqid
523
569
  end_label = uniqid
524
- @continue_stack.push start_label
570
+ @continue_stack.push next_label
525
571
  @break_stack.push end_label
526
572
  output :".label", start_label
527
573
  compile node.condition if node.condition
528
574
  output :jif, end_label
529
575
  compile node.body if node.body
576
+ output :".label", next_label
530
577
  compile node.increment if node.increment
531
578
  output :jmp, start_label
532
579
  output :".label", end_label
@@ -556,16 +603,23 @@ private
556
603
  @continue_stack.pop
557
604
  end
558
605
 
606
+ def BinaryNot(node)
607
+ compile node.value
608
+ output :bnot
609
+ end
610
+
559
611
  def Not(node)
560
612
  compile node.value
561
613
  output :not
562
614
  end
563
615
 
564
616
  def Break(node)
617
+ raise Twostroke::Compiler::CompileError, "Break not allowed outside of loop" unless @break_stack.any?
565
618
  output :jmp, @break_stack.last
566
619
  end
567
620
 
568
621
  def Continue(node)
622
+ raise Twostroke::Compiler::CompileError, "Continue not allowed outside of loop" unless @continue_stack.any?
569
623
  output :jmp, @continue_stack.last
570
624
  end
571
625
 
@@ -1,5 +1,5 @@
1
1
  module Twostroke::Compiler
2
- class CompileError < Twostroke::Error
2
+ class CompileError < Twostroke::SyntaxError
3
3
  end
4
4
 
5
5
  Dir.glob File.expand_path("../compiler/*", __FILE__) do |f|
@@ -1,4 +1,7 @@
1
1
  module Twostroke
2
2
  class Error < StandardError
3
3
  end
4
+
5
+ class SyntaxError < Error
6
+ end
4
7
  end
@@ -1,5 +1,5 @@
1
1
  module Twostroke
2
- class LexError < Twostroke::Error
2
+ class LexError < SyntaxError
3
3
  end
4
4
 
5
5
  class Token
@@ -27,6 +27,7 @@ module Twostroke
27
27
  @str = str
28
28
  @col = 1
29
29
  @line = 1
30
+ @line_terminator = false
30
31
  end
31
32
 
32
33
  def read_token(allow_regexp = true)
@@ -39,7 +40,7 @@ module Twostroke
39
40
  @col = 1 if !newlines.zero?
40
41
  @line += newlines
41
42
  @col += m[0].length - (m[0].rindex("\n") || 0)
42
- if [:WHITESPACE, :MULTI_COMMENT, :SINGLE_COMMENT].include? token[0]
43
+ if token[0] == :LINE_TERMINATOR or [:WHITESPACE, :MULTI_COMMENT, :SINGLE_COMMENT].include? token[0]
43
44
  return read_token(allow_regexp)
44
45
  else
45
46
  return tok