twostroke 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/twostroke.rb +2 -1
- data/lib/twostroke/ast/break.rb +2 -0
- data/lib/twostroke/ast/continue.rb +2 -0
- data/lib/twostroke/compiler/tsasm.rb +45 -18
- data/lib/twostroke/context.rb +58 -0
- data/lib/twostroke/context/object_proxy.rb +42 -0
- data/lib/twostroke/lexer.rb +10 -2
- data/lib/twostroke/parser.rb +36 -8
- data/lib/twostroke/runtime/lib.rb +3 -1
- data/lib/twostroke/runtime/lib/boolean.rb +4 -10
- data/lib/twostroke/runtime/lib/console.rb +1 -1
- data/lib/twostroke/runtime/lib/date.rb +18 -4
- data/lib/twostroke/runtime/lib/etc.rb +1 -15
- data/lib/twostroke/runtime/lib/function.rb +2 -8
- data/lib/twostroke/runtime/lib/math.rb +14 -2
- data/lib/twostroke/runtime/lib/number.js +3 -0
- data/lib/twostroke/runtime/lib/number.rb +12 -5
- data/lib/twostroke/runtime/lib/object.rb +4 -4
- data/lib/twostroke/runtime/lib/regexp.rb +2 -2
- data/lib/twostroke/runtime/scope.rb +1 -5
- data/lib/twostroke/runtime/types.rb +17 -2
- data/lib/twostroke/runtime/types/array.rb +4 -0
- data/lib/twostroke/runtime/types/boolean.rb +5 -1
- data/lib/twostroke/runtime/types/boolean_object.rb +2 -2
- data/lib/twostroke/runtime/types/function.rb +4 -4
- data/lib/twostroke/runtime/types/number.rb +8 -0
- data/lib/twostroke/runtime/types/number_object.rb +2 -2
- data/lib/twostroke/runtime/types/object.rb +5 -7
- data/lib/twostroke/runtime/types/regexp.rb +3 -3
- data/lib/twostroke/runtime/types/string.rb +4 -0
- data/lib/twostroke/runtime/types/string_object.rb +7 -4
- data/lib/twostroke/runtime/types/value.rb +4 -4
- data/lib/twostroke/runtime/vm.rb +12 -0
- data/lib/twostroke/runtime/vm_frame.rb +11 -19
- data/lib/twostroke/tokens.rb +2 -2
- metadata +5 -3
- data/lib/twostroke/compiler/javascript.rb +0 -414
data/lib/twostroke.rb
CHANGED
data/lib/twostroke/ast/break.rb
CHANGED
@@ -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 @
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
618
|
-
|
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
|
-
|
623
|
-
|
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
|
data/lib/twostroke/lexer.rb
CHANGED
@@ -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
|
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
|
data/lib/twostroke/parser.rb
CHANGED
@@ -559,23 +559,49 @@ module Twostroke
|
|
559
559
|
end
|
560
560
|
|
561
561
|
def return
|
562
|
-
|
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
|
-
|
569
|
-
|
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
|
-
|
574
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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) {
|
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
|
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])
|
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
|
-
|
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
|