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