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
@@ -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)