twostroke 0.2.2 → 0.2.3

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.rb +2 -1
  2. data/lib/twostroke/ast/break.rb +2 -0
  3. data/lib/twostroke/ast/continue.rb +2 -0
  4. data/lib/twostroke/compiler/tsasm.rb +45 -18
  5. data/lib/twostroke/context.rb +58 -0
  6. data/lib/twostroke/context/object_proxy.rb +42 -0
  7. data/lib/twostroke/lexer.rb +10 -2
  8. data/lib/twostroke/parser.rb +36 -8
  9. data/lib/twostroke/runtime/lib.rb +3 -1
  10. data/lib/twostroke/runtime/lib/boolean.rb +4 -10
  11. data/lib/twostroke/runtime/lib/console.rb +1 -1
  12. data/lib/twostroke/runtime/lib/date.rb +18 -4
  13. data/lib/twostroke/runtime/lib/etc.rb +1 -15
  14. data/lib/twostroke/runtime/lib/function.rb +2 -8
  15. data/lib/twostroke/runtime/lib/math.rb +14 -2
  16. data/lib/twostroke/runtime/lib/number.js +3 -0
  17. data/lib/twostroke/runtime/lib/number.rb +12 -5
  18. data/lib/twostroke/runtime/lib/object.rb +4 -4
  19. data/lib/twostroke/runtime/lib/regexp.rb +2 -2
  20. data/lib/twostroke/runtime/scope.rb +1 -5
  21. data/lib/twostroke/runtime/types.rb +17 -2
  22. data/lib/twostroke/runtime/types/array.rb +4 -0
  23. data/lib/twostroke/runtime/types/boolean.rb +5 -1
  24. data/lib/twostroke/runtime/types/boolean_object.rb +2 -2
  25. data/lib/twostroke/runtime/types/function.rb +4 -4
  26. data/lib/twostroke/runtime/types/number.rb +8 -0
  27. data/lib/twostroke/runtime/types/number_object.rb +2 -2
  28. data/lib/twostroke/runtime/types/object.rb +5 -7
  29. data/lib/twostroke/runtime/types/regexp.rb +3 -3
  30. data/lib/twostroke/runtime/types/string.rb +4 -0
  31. data/lib/twostroke/runtime/types/string_object.rb +7 -4
  32. data/lib/twostroke/runtime/types/value.rb +4 -4
  33. data/lib/twostroke/runtime/vm.rb +12 -0
  34. data/lib/twostroke/runtime/vm_frame.rb +11 -19
  35. data/lib/twostroke/tokens.rb +2 -2
  36. metadata +5 -3
  37. data/lib/twostroke/compiler/javascript.rb +0 -414
data/lib/twostroke.rb CHANGED
@@ -4,4 +4,5 @@ require "twostroke/lexer"
4
4
  require "twostroke/parser"
5
5
  require "twostroke/ast"
6
6
  require "twostroke/compiler"
7
- require "twostroke/runtime"
7
+ require "twostroke/runtime"
8
+ require "twostroke/context"
@@ -1,5 +1,7 @@
1
1
  module Twostroke::AST
2
2
  class Break < Base
3
+ attr_accessor :label
4
+
3
5
  def collapse
4
6
  self
5
7
  end
@@ -1,5 +1,7 @@
1
1
  module Twostroke::AST
2
2
  class Continue < Base
3
+ attr_accessor :label
4
+
3
5
  def collapse
4
6
  self
5
7
  end
@@ -2,12 +2,11 @@ class Twostroke::Compiler::TSASM
2
2
  attr_accessor :bytecode, :ast, :prefix
3
3
 
4
4
  def initialize(ast, prefix = nil)
5
- @methods = Hash[self.class.private_instance_methods(false).map { |name| [name, true] }]
6
5
  @ast = ast
7
6
  @prefix = prefix
8
7
  end
9
8
 
10
- def compile(node = nil)
9
+ def compile(node = nil, *opt_args)
11
10
  if node
12
11
  if node.respond_to? :each
13
12
  # hoist named functions to top
@@ -16,14 +15,10 @@ class Twostroke::Compiler::TSASM
16
15
  elsif node.is_a? Symbol
17
16
  send node
18
17
  else
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
22
- send type(node), node if node
23
- @node_stack.pop
24
- else
25
- error! "#{type node} not implemented"
26
- end
18
+ output :".line", @current_line = node.line if node.line and node.line > @current_line
19
+ @node_stack.push node
20
+ send type(node), node, *opt_args if node
21
+ @node_stack.pop
27
22
  end
28
23
  else
29
24
  @indent = 0
@@ -34,6 +29,7 @@ class Twostroke::Compiler::TSASM
34
29
  @break_stack = []
35
30
  @continue_stack = []
36
31
  @node_stack = []
32
+ @labels = {}
37
33
  @current_line = 0
38
34
 
39
35
  ast.each { |node| hoist node }
@@ -382,7 +378,9 @@ private
382
378
  output :pushfinally, finally_label
383
379
  end
384
380
  end_label = uniqid
381
+
385
382
  compile node.try_statements
383
+
386
384
  # no exceptions? clean up
387
385
  output :popcatch if node.catch_variable
388
386
  output :jmp, finally_label
@@ -437,12 +435,13 @@ private
437
435
  output :array, node.items.size
438
436
  end
439
437
 
440
- def While(node)
438
+ def While(node, continue_label = nil)
441
439
  start_label = uniqid
442
440
  end_label = uniqid
443
441
  @continue_stack.push start_label
444
442
  @break_stack.push end_label
445
443
  output :".label", start_label
444
+ output :".label", continue_label if continue_label
446
445
  compile node.condition
447
446
  output :jif, end_label
448
447
  compile node.body
@@ -544,11 +543,26 @@ private
544
543
  @break_stack.pop
545
544
  end
546
545
 
546
+ def Label(node)
547
+ error! "Label '#{node.name}' has already been declared" if @labels[node.name]
548
+ end_label = uniqid
549
+ continue_label = uniqid
550
+ @labels[node.name] = { break: end_label }
551
+ if [:While, :DoWhile, :ForLoop, :ForIn].include? type(node.statement)
552
+ @labels[node.name][:continue] = continue_label
553
+ compile node.statement, continue_label
554
+ else
555
+ compile node.statement
556
+ end
557
+ output :".label", end_label
558
+ @labels.delete node.name
559
+ end
560
+
547
561
  def Body(node)
548
562
  node.statements.each { |s| compile s }
549
563
  end
550
564
 
551
- def DoWhile(node)
565
+ def DoWhile(node, continue_label = nil)
552
566
  start_label = uniqid
553
567
  next_label = uniqid
554
568
  end_label = uniqid
@@ -557,12 +571,13 @@ private
557
571
  output :".label", start_label
558
572
  compile node.body
559
573
  output :".label", next_label
574
+ output :".label", continue_label if continue_label
560
575
  compile node.condition
561
576
  output :jit, start_label
562
577
  output :".label", end_label
563
578
  end
564
579
 
565
- def ForLoop(node)
580
+ def ForLoop(node, continue_label = nil)
566
581
  compile node.initializer if node.initializer
567
582
  start_label = uniqid
568
583
  next_label = uniqid
@@ -574,6 +589,7 @@ private
574
589
  output :jif, end_label
575
590
  compile node.body if node.body
576
591
  output :".label", next_label
592
+ output :".label", continue_label if continue_label
577
593
  compile node.increment if node.increment
578
594
  output :jmp, start_label
579
595
  output :".label", end_label
@@ -585,7 +601,7 @@ private
585
601
  output :enumnext
586
602
  end
587
603
 
588
- def ForIn(node)
604
+ def ForIn(node, continue_label = nil)
589
605
  end_label = uniqid
590
606
  loop_label = uniqid
591
607
  @break_stack.push end_label
@@ -593,6 +609,7 @@ private
593
609
  compile node.object
594
610
  output :enum
595
611
  output :".label", loop_label
612
+ output :".label", continue_label if continue_label
596
613
  output :jiee, end_label
597
614
  mutate node.lval, :_enum_next
598
615
  compile node.body
@@ -614,13 +631,23 @@ private
614
631
  end
615
632
 
616
633
  def Break(node)
617
- raise Twostroke::Compiler::CompileError, "Break not allowed outside of loop" unless @break_stack.any?
618
- output :jmp, @break_stack.last
634
+ if node.label
635
+ error! "Undefined label '#{node.label}'" unless @labels[node.label]
636
+ output :jmp, @labels[node.label][:break]
637
+ else
638
+ error! "Break not allowed outside of loop" unless @break_stack.any?
639
+ output :jmp, @break_stack.last
640
+ end
619
641
  end
620
642
 
621
643
  def Continue(node)
622
- raise Twostroke::Compiler::CompileError, "Continue not allowed outside of loop" unless @continue_stack.any?
623
- output :jmp, @continue_stack.last
644
+ if node.label
645
+ error! "Undefined label '#{node.label}'" unless @labels[node.label]
646
+ output :jmp, @labels[node.label][:continue]
647
+ else
648
+ error! "Continue not allowed outside of loop" unless @continue_stack.any?
649
+ output :jmp, @continue_stack.last
650
+ end
624
651
  end
625
652
 
626
653
  def TypeOf(node)
@@ -0,0 +1,58 @@
1
+ module Twostroke
2
+ class Context
3
+ attr_reader :vm
4
+
5
+ def initialize
6
+ @ai = 0
7
+ @vm = Twostroke::Runtime::VM.new({})
8
+ Twostroke::Runtime::Lib.setup_environment vm
9
+ end
10
+
11
+ def [](var)
12
+ vm.global_scope.get_var(var.intern).to_ruby
13
+ end
14
+
15
+ def raw_eval(src)
16
+ prefix = make_prefix
17
+ bytecode = compile src, prefix
18
+ main = :"#{prefix}main"
19
+ bytecode[main][-2] = [:ret]
20
+ vm.bytecode.merge! bytecode
21
+ vm.execute main, vm.global_scope.close
22
+ end
23
+
24
+ def eval(src)
25
+ raw_eval(src + ";").to_ruby
26
+ end
27
+
28
+ def raw_exec(src)
29
+ prefix = make_prefix
30
+ bytecode = compile src, prefix
31
+ vm.bytecode.merge! bytecode
32
+ vm.execute :"#{prefix}_main"
33
+ end
34
+
35
+ def exec(src)
36
+ raw_exec(src).to_ruby
37
+ end
38
+
39
+ def inspect
40
+ to_s
41
+ end
42
+
43
+ private
44
+ def make_prefix
45
+ "#{@ai += 1}_".intern
46
+ end
47
+
48
+ def compile(src, prefix)
49
+ parser = Twostroke::Parser.new Twostroke::Lexer.new src
50
+ parser.parse
51
+ compiler = Twostroke::Compiler::TSASM.new parser.statements, prefix
52
+ compiler.compile
53
+ compiler.bytecode
54
+ end
55
+ end
56
+ end
57
+
58
+ require "twostroke/context/object_proxy"
@@ -0,0 +1,42 @@
1
+ module Twostroke
2
+ class Context
3
+ class ObjectProxy < BasicObject
4
+ def initialize(object)
5
+ @object = object
6
+ end
7
+
8
+ def [](prop)
9
+ o = @object.get(prop.to_s) and o.to_ruby
10
+ end
11
+
12
+ def []=(prop, val)
13
+ unless val.is_a? ::Twostroke::Runtime::Types::Value
14
+ val = ::Twostroke::Runtime::Types.marshal val
15
+ end
16
+ @object.put prop.to_s, val
17
+ end
18
+
19
+ def method_missing(prop, *args, &block)
20
+ return self[prop] = args[0] if prop =~ /=\z/
21
+ val = self[prop]
22
+ if val.respond_to? :call
23
+ val.call(*args)
24
+ elsif args.size > 0
25
+ ::Kernel.p args
26
+ ::Kernel.raise "Cannot call non-callable"
27
+ else
28
+ val
29
+ end
30
+ end
31
+
32
+ def inspect
33
+ if toString = @object.get("toString") and toString.respond_to? :call
34
+ s = toString.call(nil, @object, [])
35
+ if s.is_a? ::Twostroke::Runtime::Types::Primitive
36
+ ::Twostroke::Runtime::Types.to_string(s).string
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -12,7 +12,7 @@ module Twostroke
12
12
  end
13
13
 
14
14
  class Lexer
15
- attr_accessor :str, :col, :line
15
+ attr_accessor :str, :col, :line, :restricted
16
16
 
17
17
  def state
18
18
  { str: str, col: col, line: line }
@@ -28,6 +28,14 @@ module Twostroke
28
28
  @col = 1
29
29
  @line = 1
30
30
  @line_terminator = false
31
+ @restricted = false
32
+ end
33
+
34
+ def restrict
35
+ @restricted = true
36
+ retn = yield
37
+ @restricted = false
38
+ retn
31
39
  end
32
40
 
33
41
  def read_token(allow_regexp = true)
@@ -40,7 +48,7 @@ module Twostroke
40
48
  @col = 1 if !newlines.zero?
41
49
  @line += newlines
42
50
  @col += m[0].length - (m[0].rindex("\n") || 0)
43
- if token[0] == :LINE_TERMINATOR or [:WHITESPACE, :MULTI_COMMENT, :SINGLE_COMMENT].include? token[0]
51
+ if [:WHITESPACE, :MULTI_COMMENT, :SINGLE_COMMENT].include?(token[0]) or (!restricted && token[0] == :LINE_TERMINATOR)
44
52
  return read_token(allow_regexp)
45
53
  else
46
54
  return tok
@@ -559,23 +559,49 @@ module Twostroke
559
559
  end
560
560
 
561
561
  def return
562
- assert_type next_token, :RETURN
562
+ tok = @lexer.restrict do
563
+ assert_type next_token, :RETURN
564
+ peek_token true
565
+ end
566
+ if tok.type == :LINE_TERMINATOR
567
+ next_token true
568
+ return AST::Return.new line: token.line
569
+ end
563
570
  expr = expression unless peek_token.type == :SEMICOLON || peek_token.type == :CLOSE_BRACE
564
571
  AST::Return.new line: token.line, expression: expr
565
572
  end
566
573
 
567
574
  def break
568
- assert_type next_token, :BREAK
569
- AST::Break.new line: token.line
575
+ tok = @lexer.restrict do
576
+ assert_type next_token, :BREAK
577
+ peek_token
578
+ end
579
+ if tok.type == :LINE_TERMINATOR
580
+ next_token
581
+ return AST::Break.new line: token.line
582
+ end
583
+ label = next_token.val if try_peek_token and peek_token.type == :BAREWORD
584
+ AST::Break.new line: token.line, label: label
570
585
  end
571
586
 
572
587
  def continue
573
- assert_type next_token, :CONTINUE
574
- AST::Continue.new line: token.line
588
+ tok = @lexer.restrict do
589
+ assert_type next_token, :CONTINUE
590
+ peek_token
591
+ end
592
+ if tok.type == :LINE_TERMINATOR
593
+ next_token
594
+ return AST::Continue.new line: token.line
595
+ end
596
+ label = next_token.val if try_peek_token and peek_token.type == :BAREWORD
597
+ AST::Continue.new line: token.line, label: label
575
598
  end
576
599
 
577
600
  def throw
578
- assert_type next_token, :THROW
601
+ tok = @lexer.restrict do
602
+ assert_type next_token, :THROW
603
+ error! "illegal newline after throw" if peek_token(true).type == :LINE_TERMINATOR
604
+ end
579
605
  AST::Throw.new line: token.line, expression: expression
580
606
  end
581
607
 
@@ -634,9 +660,10 @@ module Twostroke
634
660
  key = token
635
661
  assert_type next_token, :COLON
636
662
  obj.items.push [key, assignment_expression]
663
+ assert_type peek_token, :COMMA, :CLOSE_BRACE
637
664
  if peek_token.type == :COMMA
638
665
  next_token
639
- redo
666
+ next
640
667
  end
641
668
  end
642
669
  next_token
@@ -648,9 +675,10 @@ module Twostroke
648
675
  ary = AST::Array.new line: token.line
649
676
  while peek_token(true).type != :CLOSE_BRACKET
650
677
  ary.items.push assignment_expression
678
+ assert_type peek_token, :COMMA, :CLOSE_BRACKET
651
679
  if peek_token.type == :COMMA
652
680
  next_token
653
- redo
681
+ next
654
682
  end
655
683
  end
656
684
  next_token
@@ -3,7 +3,9 @@ module Twostroke::Runtime
3
3
  INITIALIZERS = []
4
4
 
5
5
  def self.setup_environment(vm)
6
- INITIALIZERS.each { |i| i.arity == 1 ? i.(vm.global_scope) : i.(vm.global_scope, vm) }
6
+ scope = vm.global_scope
7
+ INITIALIZERS.each { |i| i.arity == 1 ? i.(scope) : i.(scope, vm) }
8
+ scope.get_var("Object").prototype = Types::Function.constructor_function.get("prototype")
7
9
  end
8
10
 
9
11
  def self.register(&bk)
@@ -5,19 +5,13 @@ module Twostroke::Runtime
5
5
 
6
6
  proto = Types::Object.new
7
7
  proto.proto_put "toString", Types::Function.new(->(scope, this, args) {
8
- if this.is_a?(Types::BooleanObject)
8
+ Lib.throw_type_error "Boolean.prototype.valueOf is not generic" unless this.is_a?(Types::BooleanObject)
9
9
  Types::String.new(this.boolean.to_s)
10
- else
11
- Lib.throw_type_error "Boolean.prototype.toString is not generic"
12
- end
13
- }, nil, "toString", [])
10
+ }, nil, "toString", [])
14
11
  proto.proto_put "valueOf", Types::Function.new(->(scope, this, args) {
15
- if this.is_a?(Types::BooleanObject)
12
+ Lib.throw_type_error "Boolean.prototype.valueOf is not generic" unless this.is_a?(Types::BooleanObject)
16
13
  Types::Boolean.new(this.boolean)
17
- else
18
- Types.to_primitive(this)
19
- end
20
- }, nil, "valueOf", [])
14
+ }, nil, "valueOf", [])
21
15
  obj.proto_put "prototype", proto
22
16
  end
23
17
  end
@@ -1,6 +1,6 @@
1
1
  module Twostroke::Runtime
2
2
  Lib.register do |scope|
3
- log = Types::Function.new(->(scope, this, args) { print args.map { |o| Types.to_string(o).string }.join(" ") + "\n" }, nil, "log", ["string"])
3
+ log = Types::Function.new(->(scope, this, args) { puts args.map { |o| Types.to_string(o).string }.join(" ") }, nil, "log", ["string"])
4
4
 
5
5
  console = Types::Object.new
6
6
  ["log", "info", "warn", "error"].each do |m|
@@ -1,3 +1,5 @@
1
+ require "time"
2
+
1
3
  module Twostroke::Runtime
2
4
  Lib.register do |scope|
3
5
  obj = Types::Function.new(->(scope, this, args) {
@@ -9,7 +11,7 @@ module Twostroke::Runtime
9
11
  if args[0].is_a?(Types::Number)
10
12
  this.data[:time] = Time.at *(args[0].number*1000).divmod(1000_000)
11
13
  else
12
- this.data[:time] = Time.parse Types.to_string(args[0]).string rescue this.data[:time] = :invalid
14
+ this.data[:time] = Time.parse(Types.to_string(args[0]).string) rescue this.data[:time] = :invalid
13
15
  end
14
16
  else
15
17
  args = args.take(7)
@@ -26,10 +28,13 @@ module Twostroke::Runtime
26
28
  obj.proto_put "parse", Types::Function.new(->(scope, this, args) {
27
29
  Types::Number.new (Time.parse(Types.to_string(args[0]).string).to_f * 1000).floor
28
30
  }, nil, "parse", [])
29
- obj.proto_put "UTC", Types::Function.new(->(scope, this, args) {
31
+ obj.proto_put "UTC", Types::Function.new(->(scope, this, args) {
32
+ return Types::Number.new Float::NAN if args.size < 2
30
33
  args = args.take(7)
31
34
  .map { |a| Types.to_uint32(a) }
32
- .zip([9999, 12, 31, 23, 59, 59, 999]) { |a,b| [a || 0, b].min }
35
+ .zip([9999, 12, 31, 23, 59, 59, 999])
36
+ .map { |a,b| [a || 0, b].min }
37
+ args[1] += 1 if args[1]
33
38
  args[6] *= 1000 if args[6]
34
39
  Types::Number.new (Time.utc(*args).to_f * 1000).floor
35
40
  }, nil, "UTC", [])
@@ -37,7 +42,16 @@ module Twostroke::Runtime
37
42
  proto = Types::Object.new
38
43
  obj.proto_put "prototype", proto
39
44
 
40
- { "Date" => :day, "Day" => :wday, "FullYear" => :year, "Hours" => :hour,
45
+ proto.proto_put "toString", Types::Function.new(->(scope, this, args) {
46
+ Lib.throw_type_error "this is not a Date object" unless this.data[:time]
47
+ Types::String.new this.data[:time].strftime("%a %b %d %Y %H:%M:%S GMT%z (%Z)")
48
+ }, nil, "toString", [])
49
+ proto.proto_put "valueOf", Types::Function.new(->(scope, this, args) {
50
+ Lib.throw_type_error "this is not a Date object" unless this.data[:time]
51
+ Types::Number.new (this.data[:time].to_f * 1000).to_i
52
+ }, nil, "valueOf", [])
53
+
54
+ { "Date" => :day, "Day" => :wday, "FullYear" => :year, "Year" => ->t{ t.year - 1900 }, "Hours" => :hour,
41
55
  "Milliseconds" => ->t{ (t.usec / 1000).to_i }, "Minutes" => :min, "Month" => :month,
42
56
  "Seconds" => :sec, "Time" => ->t{ (t.to_f * 1000).floor }, "TimezoneOffset" => ->t{ t.utc_offset / 60 } }.each do |prop,method|
43
57
  # Date.prototype.getXXX