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
@@ -3,25 +3,11 @@ module Twostroke::Runtime
3
3
  evaled = 0
4
4
  eval = Types::Function.new(->(_scope, this, args) {
5
5
  src = Types.to_string(args[0] || Types::Undefined.new).string + ";"
6
-
7
6
  begin
8
- parser = Twostroke::Parser.new Twostroke::Lexer.new src
9
- parser.parse
10
-
11
- evaled += 1
12
- compiler = Twostroke::Compiler::TSASM.new parser.statements, "evaled_#{evaled}_"
13
- compiler.compile
7
+ scope.global_scope.vm.eval src, _scope, this
14
8
  rescue Twostroke::SyntaxError => e
15
9
  Lib.throw_syntax_error e.to_s
16
10
  end
17
-
18
- vm = scope.global_scope.vm
19
- compiler.bytecode.each do |k,v|
20
- vm.bytecode[k] = v
21
- end
22
-
23
- vm.bytecode[:"evaled_#{evaled}_main"][-2] = [:ret]
24
- vm.execute :"evaled_#{evaled}_main", _scope, this
25
11
  }, nil, "eval", [])
26
12
  eval.inherits_caller_this = true
27
13
  scope.set_var "eval", eval
@@ -4,13 +4,6 @@ module Twostroke::Runtime
4
4
  scope.set_var "Function", obj
5
5
 
6
6
  proto = Types::Object.new
7
- proto.proto_put "toString", Types::Function.new(->(scope, this, args) {
8
- if this.is_a?(Types::Function)
9
- this.primitive_value
10
- else
11
- Lib.throw_type_error "Function.prototype.toString is not generic"
12
- end
13
- }, nil, "toString", [])
14
7
  proto.proto_put "valueOf", Types::Function.new(->(scope, this, args) { this }, nil, "valueOf", [])
15
8
  proto.define_own_property "arity", get: ->(this) { this.arguments.size }, writable: false
16
9
  proto.define_own_property "length", get: ->(this) { this.arguments.size }, writable: false
@@ -38,7 +31,8 @@ module Twostroke::Runtime
38
31
  end, nil, "call", [])
39
32
  # Function.prototype.toString
40
33
  proto.proto_put "toString", Types::Function.new(->(scope, this, args) do
41
- this.primitive_value
34
+ Lib.throw_type_error "Function.prototype.toString is not generic" unless this.is_a? Types::Function
35
+ Types::String.new "function #{this.name}(#{this.arguments.join ","}) { #{this.source || "[native code]"} }"
42
36
  end, nil, "toString", [])
43
37
  obj.proto_put "prototype", proto
44
38
  end
@@ -32,9 +32,14 @@ module Twostroke::Runtime
32
32
  }, nil, "parseInt", [])
33
33
 
34
34
  # one argument functions
35
- %w(sqrt sin cos tan).each do |method|
35
+ %w(sqrt sin cos tan exp).each do |method|
36
36
  obj.proto_put method, Types::Function.new(->(scope, this, args) {
37
- Types::Number.new(Math.send method, Types.to_number(args[0] || Undefined.new).number)
37
+ ans = begin
38
+ Math.send method, Types.to_number(args[0] || Undefined.new).number
39
+ rescue Math::DomainError
40
+ Float::NAN
41
+ end
42
+ Types::Number.new(ans)
38
43
  }, nil, method, [])
39
44
  end
40
45
 
@@ -63,5 +68,12 @@ module Twostroke::Runtime
63
68
  obj.proto_put "min", Types::Function.new(->(scope, this, args) {
64
69
  Types::Number.new [Float::INFINITY, *args.map { |a| Types.to_number(a).number }].min
65
70
  }, nil, "min", [])
71
+
72
+ obj.proto_put "pow", Types::Function.new(->(scope, this, args) {
73
+ a = Types.to_number(args[0] || Types::Undefined.new).number
74
+ b = Types.to_number(args[1] || Types::Undefined.new).number
75
+ ans = a ** b
76
+ Types::Number.new(ans.is_a?(Complex) ? Float::NAN : ans)
77
+ }, nil, "random", [])
66
78
  end
67
79
  end
@@ -0,0 +1,3 @@
1
+ Math.round = function(x) {
2
+ return Math.floor(Number(x) + 0.5);
3
+ };
@@ -22,7 +22,7 @@ module Twostroke::Runtime
22
22
  proto.proto_put "toExponential", Types::Function.new(->(scope, this, args) do
23
23
  n = Types.to_number(this)
24
24
  if n.nan? || n.infinite?
25
- Types::String.new n.to_s
25
+ Types::String.new n.number.to_s
26
26
  else
27
27
  places = Math.log(n.number, 10).floor
28
28
  significand = n.number / (10 ** places).to_f
@@ -41,25 +41,32 @@ module Twostroke::Runtime
41
41
  end, nil, "toExponential", [])
42
42
  # Number.prototype.toFixed
43
43
  proto.proto_put "toFixed", Types::Function.new(->(scope, this, args) do
44
- digits = Types.to_number(args[0] || Undefined.new)
44
+ digits = Types.to_number(args[0] || Types::Undefined.new)
45
45
  if digits.nan? || digits.infinite?
46
46
  digits = 0
47
47
  else
48
48
  digits = digits.number
49
49
  end
50
- Types::String.new sprintf("%.#{[[0,digits].max,20].min}f", Types.to_number(this).number)
50
+ digits = [[0,digits].max,20].min
51
+ Types::String.new sprintf("%.#{digits}f", Types.to_number(this).number.round(digits))
51
52
  end, nil, "toFixed", [])
52
53
  # Number.prototype.toLocaleString
53
54
  proto.proto_put "toLocaleString", proto.get("toString")
54
55
  # Number.prototype.toString
55
56
  proto.proto_put "toPrecision", Types::Function.new(->(scope, this, args) do
56
- digits = Types.to_number(args[0] || Undefined.new)
57
+ n = Types.to_number(this).number
58
+ return Types::String.new(n.to_s) unless args[0]
59
+ digits = Types.to_number(args[0] || Types::Undefined.new)
57
60
  if digits.nan? || digits.infinite?
58
61
  digits = 0
59
62
  else
60
63
  digits = digits.number
61
64
  end
62
- Types::Number.new Types.to_number(this).number.round([[digits,0].max, 100].min)
65
+ Lib.throw_range_error "toPrecision() argument must be between 1 and 21" unless (1..21).include? digits
66
+ fixup = 10 ** Math.log(n, 10).floor
67
+ n /= fixup.to_f
68
+ n = n.round digits - fixup
69
+ Types::String.new (n * fixup).to_s
63
70
  end, nil, "toString", [])
64
71
  obj.proto_put "prototype", proto
65
72
  obj.proto_put "MAX_VALUE", Types::Number.new(Float::MAX)
@@ -30,11 +30,11 @@ module Twostroke::Runtime
30
30
  proto = args[0].prototype
31
31
  this = Types.to_object(this || Types::Undefined.new)
32
32
  while proto.is_a?(Types::Object)
33
- return Types::Boolean.new(true) if this == proto
33
+ return Types::Boolean.true if this == proto
34
34
  proto = proto.prototype
35
35
  end
36
36
  end
37
- Types::Boolean.new false
37
+ Types::Boolean.false
38
38
  }, nil, "isPrototypeOf", [])
39
39
  proto.proto_put "propertyIsEnumerable", Types::Function.new(->(scope, this, args) {
40
40
  this = Types.to_object(this || Types::Undefined.new)
@@ -42,9 +42,9 @@ module Twostroke::Runtime
42
42
  if this.has_accessor(prop)
43
43
  Types::Boolean.new this.accessors[prop][:enumerable]
44
44
  elsif this.has_property(prop)
45
- Types::Boolean.new true
45
+ Types::Boolean.true
46
46
  else
47
- Types::Boolean.new false
47
+ Types::Boolean.false
48
48
  end
49
49
  }, nil, "propertyIsEnumerable", [])
50
50
 
@@ -4,8 +4,8 @@ module Twostroke::Runtime
4
4
  scope.set_var "RegExp", regexp
5
5
  proto = Types::Object.new
6
6
  proto.proto_put "toString", Types::Function.new(->(scope, this, args) {
7
- Lib.throw_type_error "RegExp.prototype.toString is not generic" unless this.is_a?(Types::RegExp)
8
- this.primitive_value
7
+ Lib.throw_type_error "RegExp.prototype.toString is not generic" unless this.is_a? Types::RegExp
8
+ Types::String.new this.regexp.inspect + (this.global ? "g" : "")
9
9
  }, nil, "toString", [])
10
10
  proto.define_own_property "global", get: ->(this) {
11
11
  Types::Boolean.new this.global
@@ -32,11 +32,7 @@ module Twostroke::Runtime
32
32
  end
33
33
 
34
34
  def delete(var)
35
- if has_var var
36
- @locals.delete var
37
- else
38
- parent.delete var
39
- end
35
+ parent.delete var
40
36
  end
41
37
 
42
38
  def close
@@ -34,7 +34,7 @@ module Twostroke::Runtime::Types
34
34
  elsif object.is_a?(String)
35
35
  Number.new(Float(object.string)) rescue Number.new(Float::NAN)
36
36
  else # object is Object
37
- to_number to_primitive(object)
37
+ to_number to_primitive(object, "Number")
38
38
  end
39
39
  end
40
40
 
@@ -70,7 +70,7 @@ module Twostroke::Runtime::Types
70
70
  elsif object.is_a?(String)
71
71
  object
72
72
  else
73
- to_string to_primitive(object)
73
+ to_string to_primitive(object, "String")
74
74
  end
75
75
  end
76
76
 
@@ -128,6 +128,21 @@ module Twostroke::Runtime::Types
128
128
  end
129
129
  end
130
130
 
131
+ def self.marshal(ruby_object)
132
+ case ruby_object
133
+ when ::String; String.new ruby_object
134
+ when Fixnum, Float; Number.new ruby_object
135
+ when ::Array; Array.new ruby_object.map { |el| box el }
136
+ when Hash; o = Object.new
137
+ ruby_object.each { |k,v| o.put k.to_s, box(v) }
138
+ o
139
+ when nil; Null.new
140
+ when true; Boolean.true
141
+ when false; Boolean.false
142
+ else Undefined.new
143
+ end
144
+ end
145
+
131
146
  require File.expand_path("../types/value.rb", __FILE__)
132
147
  require File.expand_path("../types/object.rb", __FILE__)
133
148
  Dir.glob(File.expand_path("../types/*", __FILE__)).each do |f|
@@ -26,6 +26,10 @@ module Twostroke::Runtime
26
26
  end, writable: true, enumerable: false
27
27
  end
28
28
 
29
+ def to_ruby
30
+ items.map &:to_ruby
31
+ end
32
+
29
33
  def length
30
34
  items.size
31
35
  end
@@ -4,7 +4,7 @@ module Twostroke::Runtime::Types
4
4
  @@true ||= Boolean.new(true)
5
5
  end
6
6
  def self.false
7
- @@false ||= Null.new(false)
7
+ @@false ||= Boolean.new(false)
8
8
  end
9
9
 
10
10
  attr_reader :boolean
@@ -12,6 +12,10 @@ module Twostroke::Runtime::Types
12
12
  @boolean = boolean
13
13
  end
14
14
 
15
+ def to_ruby
16
+ boolean
17
+ end
18
+
15
19
  def ===(other)
16
20
  other.is_a?(Boolean) && boolean == other.boolean
17
21
  end
@@ -18,8 +18,8 @@ module Twostroke::Runtime::Types
18
18
  @boolean = boolean
19
19
  end
20
20
 
21
- def primitive_value
22
- Boolean.new boolean
21
+ def to_ruby
22
+ boolean
23
23
  end
24
24
  end
25
25
  end
@@ -50,6 +50,10 @@ module Twostroke::Runtime::Types
50
50
  put "prototype", proto
51
51
  end
52
52
 
53
+ def to_ruby
54
+ ->(this, *args) { call(nil, this, args.map(&Twostroke::Runtime::Types.method(:marshal))).to_ruby }
55
+ end
56
+
53
57
  def prototype
54
58
  @prototype ||= Function.constructor_function.get("prototype") if Function.constructor_function
55
59
  end
@@ -69,10 +73,6 @@ module Twostroke::Runtime::Types
69
73
  "function"
70
74
  end
71
75
 
72
- def primitive_value
73
- String.new "function #{name}(#{arguments.join ","}) { #{source || "[native code]"} }"
74
- end
75
-
76
76
  def call(upper_scope, this, args)
77
77
  retn_val = function.(upper_scope, this || upper_scope.global_scope.root_object, args)
78
78
  # prevent non-Value objects being returned to javascript
@@ -5,6 +5,10 @@ module Twostroke::Runtime::Types
5
5
  @number = number
6
6
  end
7
7
 
8
+ def to_ruby
9
+ number
10
+ end
11
+
8
12
  def ===(other)
9
13
  if number.zero? && other.is_a?(Number) && other.number.zero?
10
14
  # in javascript, -0 and 0 are not equal
@@ -15,15 +19,19 @@ module Twostroke::Runtime::Types
15
19
  other.is_a?(Number) && number == other.number
16
20
  end
17
21
  end
22
+
18
23
  def typeof
19
24
  "number"
20
25
  end
26
+
21
27
  def zero?
22
28
  number.zero?
23
29
  end
30
+
24
31
  def nan?
25
32
  number.is_a?(Float) && number.nan?
26
33
  end
34
+
27
35
  def infinite?
28
36
  number.is_a?(Float) && number.infinite?
29
37
  end
@@ -18,8 +18,8 @@ module Twostroke::Runtime::Types
18
18
  super()
19
19
  end
20
20
 
21
- def primitive_value
22
- Number.new number
21
+ def to_ruby
22
+ number
23
23
  end
24
24
  end
25
25
  end
@@ -13,6 +13,10 @@ module Twostroke::Runtime::Types
13
13
  proto_put "constructor", @_class
14
14
  end
15
15
 
16
+ def to_ruby
17
+ Twostroke::Context::ObjectProxy.new self
18
+ end
19
+
16
20
  def _class=(c)
17
21
  proto_put "constructor", (@_class = c)
18
22
  end
@@ -41,13 +45,7 @@ module Twostroke::Runtime::Types
41
45
 
42
46
  def construct(opts = {})
43
47
  @constructing = true
44
- opts.each do |k,v|
45
- if respond_to? "#{k}="
46
- send "#{k}=", v
47
- else
48
- instance_variable_set "@#{k}", v
49
- end
50
- end
48
+ opts.each { |k,v| send "#{k}=", v }
51
49
  yield if block_given?
52
50
  @constructing = false
53
51
  end
@@ -24,8 +24,8 @@ module Twostroke::Runtime::Types
24
24
  super()
25
25
  end
26
26
 
27
- def primitive_value
28
- String.new(regexp.inspect + (@global ? "g" : ""))
27
+ def to_ruby
28
+ regexp
29
29
  end
30
30
 
31
31
  def self.to_ruby_regexp(src)
@@ -36,7 +36,7 @@ module Twostroke::Runtime::Types
36
36
  gsub(/([^\[]|\A)\^/,"\\1\\A").gsub(/((\]|\A)([^\[]*))\$/,"\\1\\z").
37
37
 
38
38
  # javascript supports \cA through \cZ for control characters
39
- gsub(/\\c[a-z]/i) { |m| (m.last.downcase.ord - 'a'.ord).chr }
39
+ gsub(/\\c[a-z]/i) { |m| (m[-1].downcase.ord - 'a'.ord + 1).chr }
40
40
  end
41
41
 
42
42
  def self.exec(scope, this, args)
@@ -5,6 +5,10 @@ module Twostroke::Runtime::Types
5
5
  @string = string
6
6
  end
7
7
 
8
+ def to_ruby
9
+ string
10
+ end
11
+
8
12
  def ===(other)
9
13
  other.is_a?(String) && string == other.string
10
14
  end
@@ -12,14 +12,17 @@ module Twostroke::Runtime
12
12
  @string = string
13
13
  super()
14
14
  end
15
-
16
- def primitive_value
17
- String.new string
15
+
16
+ def to_ruby
17
+ string
18
18
  end
19
19
 
20
20
  def get(prop, this = self)
21
21
  if prop =~ /\A\d+\z/
22
- String.new string[prop.to_i]
22
+ idx = prop.to_i
23
+ unless idx < 0 or idx >= string.length
24
+ String.new string[idx]
25
+ end
23
26
  else
24
27
  super prop, this
25
28
  end
@@ -1,12 +1,12 @@
1
1
  module Twostroke::Runtime::Types
2
2
  class Value
3
- def typeof
4
- "VALUE"
5
- end
6
-
7
3
  def has_instance(obj)
8
4
  Twostroke::Runtime::Lib.throw_type_error "Expected a function in instanceof check"
9
5
  end
6
+
7
+ def to_ruby
8
+ nil
9
+ end
10
10
  end
11
11
 
12
12
  class Primitive < Value
@@ -8,6 +8,7 @@ module Twostroke::Runtime
8
8
  @global_scope = GlobalScope.new self
9
9
  @lib = {}
10
10
  @name_args = {}
11
+ @vm_eval_counter = 0
11
12
  end
12
13
 
13
14
  def execute(section = :main, scope = nil, this = nil)
@@ -29,6 +30,17 @@ module Twostroke::Runtime
29
30
  @name_args[section]
30
31
  end
31
32
  end
33
+
34
+ def eval(source, scope = nil, this = nil)
35
+ parser = Twostroke::Parser.new Twostroke::Lexer.new source
36
+ parser.parse
37
+ prefix = "#{@vm_eval_counter += 1}_"
38
+ compiler = Twostroke::Compiler::TSASM.new parser.statements, prefix
39
+ compiler.compile
40
+ compiler.bytecode[:"#{prefix}main"][-2] = [:ret]
41
+ bytecode.merge! compiler.bytecode
42
+ execute :"#{prefix}main", scope, this
43
+ end
32
44
 
33
45
  private
34
46
  def error!(msg)