twostroke 0.2.2 → 0.2.3

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