furnace-avm2 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (129) hide show
  1. data/Gemfile.lock +2 -2
  2. data/bin/furnace-avm2 +5 -1
  3. data/bin/furnace-avm2-decompiler +80 -13
  4. data/bin/furnace-avm2-shell +32 -0
  5. data/furnace-avm2.gemspec +1 -1
  6. data/lib/furnace-avm2/abc.rb +1 -7
  7. data/lib/furnace-avm2/abc/metadata/file.rb +65 -0
  8. data/lib/furnace-avm2/abc/metadata/instance_info.rb +17 -11
  9. data/lib/furnace-avm2/abc/metadata/metadata_info.rb +14 -4
  10. data/lib/furnace-avm2/abc/metadata/method_body_info.rb +11 -3
  11. data/lib/furnace-avm2/abc/metadata/method_info.rb +3 -4
  12. data/lib/furnace-avm2/abc/metadata/multiname_kind_genericname.rb +3 -2
  13. data/lib/furnace-avm2/abc/metadata/multiname_kind_multiname.rb +11 -2
  14. data/lib/furnace-avm2/abc/metadata/multiname_kind_multinamel.rb +4 -0
  15. data/lib/furnace-avm2/abc/metadata/multiname_kind_qname.rb +10 -2
  16. data/lib/furnace-avm2/abc/metadata/multiname_kind_rtqname.rb +4 -0
  17. data/lib/furnace-avm2/abc/metadata/multiname_kind_rtqnamel.rb +4 -0
  18. data/lib/furnace-avm2/abc/metadata/script_info.rb +11 -7
  19. data/lib/furnace-avm2/abc/metadata/trait_class.rb +2 -2
  20. data/lib/furnace-avm2/abc/metadata/trait_info.rb +1 -1
  21. data/lib/furnace-avm2/abc/metadata/trait_method.rb +4 -3
  22. data/lib/furnace-avm2/abc/metadata/trait_slot.rb +2 -2
  23. data/lib/furnace-avm2/abc/opcodes/alchemy/alchemy_extend_1.rb +8 -0
  24. data/lib/furnace-avm2/abc/opcodes/alchemy/alchemy_extend_16.rb +8 -0
  25. data/lib/furnace-avm2/abc/opcodes/alchemy/alchemy_extend_8.rb +8 -0
  26. data/lib/furnace-avm2/abc/opcodes/alchemy/alchemy_load_float32.rb +8 -0
  27. data/lib/furnace-avm2/abc/opcodes/alchemy/alchemy_load_float64.rb +8 -0
  28. data/lib/furnace-avm2/abc/opcodes/alchemy/alchemy_load_int16.rb +8 -0
  29. data/lib/furnace-avm2/abc/opcodes/alchemy/alchemy_load_int32.rb +8 -0
  30. data/lib/furnace-avm2/abc/opcodes/alchemy/alchemy_load_int8.rb +8 -0
  31. data/lib/furnace-avm2/abc/opcodes/alchemy/alchemy_store_float32.rb +8 -0
  32. data/lib/furnace-avm2/abc/opcodes/alchemy/alchemy_store_float64.rb +8 -0
  33. data/lib/furnace-avm2/abc/opcodes/alchemy/alchemy_store_int16.rb +8 -0
  34. data/lib/furnace-avm2/abc/opcodes/alchemy/alchemy_store_int32.rb +8 -0
  35. data/lib/furnace-avm2/abc/opcodes/alchemy/alchemy_store_int8.rb +8 -0
  36. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_add.rb +1 -1
  37. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_add_i.rb +1 -1
  38. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_declocal.rb +1 -1
  39. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_declocal_i.rb +1 -1
  40. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_decrement.rb +1 -1
  41. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_decrement_i.rb +1 -1
  42. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_divide.rb +1 -1
  43. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_equals.rb +1 -1
  44. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_greaterequals.rb +1 -1
  45. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_greaterthan.rb +1 -1
  46. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_inclocal.rb +1 -1
  47. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_inclocal_i.rb +1 -1
  48. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_increment.rb +1 -1
  49. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_increment_i.rb +1 -1
  50. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_lessequals.rb +1 -1
  51. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_lessthan.rb +1 -1
  52. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_modulo.rb +1 -1
  53. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_multiply.rb +1 -1
  54. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_multiply_i.rb +1 -1
  55. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_negate.rb +1 -1
  56. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_negate_i.rb +1 -1
  57. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_not.rb +1 -1
  58. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_strictequals.rb +1 -1
  59. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_subtract.rb +1 -1
  60. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_subtract_i.rb +1 -1
  61. data/lib/furnace-avm2/abc/opcodes/bitwise/as3_bitand.rb +1 -1
  62. data/lib/furnace-avm2/abc/opcodes/bitwise/as3_bitnot.rb +1 -1
  63. data/lib/furnace-avm2/abc/opcodes/bitwise/as3_bitor.rb +1 -1
  64. data/lib/furnace-avm2/abc/opcodes/bitwise/as3_bitxor.rb +1 -1
  65. data/lib/furnace-avm2/abc/opcodes/bitwise/as3_lshift.rb +1 -1
  66. data/lib/furnace-avm2/abc/opcodes/bitwise/as3_rshift.rb +1 -1
  67. data/lib/furnace-avm2/abc/opcodes/bitwise/as3_urshift.rb +1 -1
  68. data/lib/furnace-avm2/abc/opcodes/control_transfer/as3_lookupswitch.rb +1 -1
  69. data/lib/furnace-avm2/abc/opcodes/control_transfer_opcode.rb +0 -4
  70. data/lib/furnace-avm2/abc/opcodes/exception/as3_newcatch.rb +1 -1
  71. data/lib/furnace-avm2/abc/opcodes/function_invocation/as3_callproplex.rb +4 -0
  72. data/lib/furnace-avm2/abc/opcodes/function_invocation_opcode.rb +4 -0
  73. data/lib/furnace-avm2/abc/opcodes/function_return/as3_returnvalue.rb +1 -1
  74. data/lib/furnace-avm2/abc/opcodes/function_return/as3_returnvoid.rb +1 -1
  75. data/lib/furnace-avm2/abc/opcodes/generic/as3_escxattr.rb +1 -1
  76. data/lib/furnace-avm2/abc/opcodes/generic/as3_escxelem.rb +8 -0
  77. data/lib/furnace-avm2/abc/opcodes/generic/as3_getlex.rb +4 -0
  78. data/lib/furnace-avm2/abc/opcodes/object_manipulation/as3_newfunction.rb +5 -1
  79. data/lib/furnace-avm2/abc/opcodes/property/as3_constructprop.rb +4 -0
  80. data/lib/furnace-avm2/abc/opcodes/property_opcode.rb +4 -0
  81. data/lib/furnace-avm2/abc/opcodes/push_literal/as3_pushuint.rb +11 -0
  82. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_applytype.rb +1 -1
  83. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_coerce.rb +5 -1
  84. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_coerce_a.rb +1 -1
  85. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_coerce_b.rb +1 -1
  86. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_coerce_s.rb +1 -1
  87. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_d.rb +1 -1
  88. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_i.rb +1 -1
  89. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_o.rb +1 -1
  90. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_s.rb +1 -1
  91. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_u.rb +1 -1
  92. data/lib/furnace-avm2/abc/primitives/opcode_sequence.rb +35 -23
  93. data/lib/furnace-avm2/abc/primitives/record.rb +11 -0
  94. data/lib/furnace-avm2/binary/record.rb +9 -7
  95. data/lib/furnace-avm2/source/declaration_tokens/callee_token.rb +8 -5
  96. data/lib/furnace-avm2/source/declaration_tokens/class_implementations_token.rb +5 -1
  97. data/lib/furnace-avm2/source/declaration_tokens/class_specifiers_token.rb +2 -1
  98. data/lib/furnace-avm2/source/declaration_tokens/class_token.rb +5 -1
  99. data/lib/furnace-avm2/source/declaration_tokens/metadata_token.rb +25 -0
  100. data/lib/furnace-avm2/source/declaration_tokens/method_specifiers_token.rb +4 -3
  101. data/lib/furnace-avm2/source/declaration_tokens/method_token.rb +1 -0
  102. data/lib/furnace-avm2/source/declaration_tokens/multiname_token.rb +28 -13
  103. data/lib/furnace-avm2/source/declaration_tokens/package_token.rb +9 -8
  104. data/lib/furnace-avm2/source/declaration_tokens/scope_token.rb +2 -0
  105. data/lib/furnace-avm2/source/declaration_tokens/script_token.rb +4 -1
  106. data/lib/furnace-avm2/source/declaration_tokens/slot_token.rb +1 -0
  107. data/lib/furnace-avm2/source/declaration_tokens/specifiers_token.rb +11 -9
  108. data/lib/furnace-avm2/source/decompiler.rb +372 -101
  109. data/lib/furnace-avm2/source/implementation_tokens/closure_name_token.rb +7 -0
  110. data/lib/furnace-avm2/source/implementation_tokens/closure_token.rb +9 -0
  111. data/lib/furnace-avm2/source/implementation_tokens/instance_of_token.rb +9 -0
  112. data/lib/furnace-avm2/source/implementation_tokens/supplementary_comment_token.rb +16 -0
  113. data/lib/furnace-avm2/source/implementation_tokens/xml_literal_token.rb +14 -0
  114. data/lib/furnace-avm2/transform/ast_build.rb +3 -2
  115. data/lib/furnace-avm2/transform/ast_normalize.rb +39 -7
  116. data/lib/furnace-avm2/transform/cfg_build.rb +16 -6
  117. data/lib/furnace-avm2/transform/cfg_reduce.rb +4 -3
  118. data/lib/furnace-avm2/transform/nf_normalize.rb +15 -7
  119. data/lib/furnace-avm2/version.rb +1 -1
  120. data/test/basic.as +15 -0
  121. data/test/global.as +11 -0
  122. metadata +46 -29
  123. data/bin/furnace-avm2-benchmark +0 -38
  124. data/lib/furnace-avm2/abc/opcodes/arithmetic_opcode.rb +0 -4
  125. data/lib/furnace-avm2/abc/opcodes/bitwise_opcode.rb +0 -4
  126. data/lib/furnace-avm2/abc/opcodes/exception_opcode.rb +0 -4
  127. data/lib/furnace-avm2/abc/opcodes/function_return_opcode.rb +0 -4
  128. data/lib/furnace-avm2/abc/opcodes/object_manipulation_opcode.rb +0 -4
  129. data/lib/furnace-avm2/abc/opcodes/type_conversion_opcode.rb +0 -4
@@ -6,11 +6,13 @@ module Furnace::AVM2::Tokens
6
6
  super(origin, [
7
7
  *header,
8
8
  *declaration(origin, options),
9
- (Furnace::AVM2::Decompiler.new(@body, options).decompile if @body),
10
- Furnace::Code::NewlineToken.new(origin, options)
9
+ (Furnace::AVM2::Decompiler.new(@body,
10
+ options).decompile if @body),
11
+ (Furnace::Code::NewlineToken.new(origin,
12
+ options) if @body && !options[:closure])
11
13
  ], options)
12
14
 
13
- if options[:debug_funids]
15
+ if options[:debug_funids] && !options[:closure]
14
16
  @children.unshift \
15
17
  CommentToken.new(origin,
16
18
  "Function ##{options[:index]}",
@@ -53,14 +55,15 @@ module Furnace::AVM2::Tokens
53
55
 
54
56
  if @method.needs_rest?
55
57
  args << ArgumentDeclarationToken.new(origin, [
56
- RestArgumentToken.new(origin, "rest", options)
58
+ RestArgumentToken.new(origin, "local0", options)
57
59
  ], options)
58
60
  end
59
61
 
60
62
  tokens = []
61
63
 
62
64
  tokens << ArgumentsToken.new(origin, args, options)
63
- if @method.return_type
65
+
66
+ if @method.return_type && !options[:closure]
64
67
  tokens << TypeToken.new(origin, [
65
68
  MultinameToken.new(origin, @method.return_type, options)
66
69
  ], options)
@@ -7,7 +7,11 @@ module Furnace::AVM2::Tokens
7
7
  end
8
8
 
9
9
  def text_before
10
- "implements "
10
+ if @options[:environment] == :interface
11
+ "extends "
12
+ else
13
+ "implements "
14
+ end
11
15
  end
12
16
 
13
17
  def text_between
@@ -3,9 +3,10 @@ require_relative 'specifiers_token'
3
3
  module Furnace::AVM2::Tokens
4
4
  class ClassSpecifiersToken < SpecifiersToken
5
5
  def specifiers
6
- list = super
6
+ list = []
7
7
  list << "final" if @origin.final?
8
8
  list << "dynamic" unless @origin.sealed?
9
+ list << "public"
9
10
  list
10
11
  end
11
12
  end
@@ -3,7 +3,11 @@ module Furnace::AVM2::Tokens
3
3
  include TokenWithTraits
4
4
 
5
5
  def initialize(origin, options={})
6
- options = options.merge(environment: :class)
6
+ if origin.interface?
7
+ options = options.merge(environment: :interface)
8
+ else
9
+ options = options.merge(environment: :class)
10
+ end
7
11
 
8
12
  super(origin, [
9
13
  ClassSpecifiersToken.new(origin, options),
@@ -0,0 +1,25 @@
1
+ module Furnace::AVM2::Tokens
2
+ class MetadataToken < Furnace::Code::TerminalToken
3
+ def initialize(origin, options={})
4
+ super(origin, options)
5
+ end
6
+
7
+ def to_text
8
+ if @origin.metadata?
9
+ elements = []
10
+
11
+ @origin.metadata.each do |datum|
12
+ values = datum.to_hash.map do |key, value|
13
+ %Q[#{key || '*'}="#{value}"]
14
+ end
15
+
16
+ elements << "#{datum.name}(#{values.join(",")})"
17
+ end
18
+
19
+ "[#{elements.join(",")}]\n"
20
+ else
21
+ ""
22
+ end
23
+ end
24
+ end
25
+ end
@@ -3,10 +3,11 @@ require_relative 'specifiers_token'
3
3
  module Furnace::AVM2::Tokens
4
4
  class MethodSpecifiersToken < SpecifiersToken
5
5
  def specifiers
6
- list = []
6
+ list, super_list = [], super
7
7
  list << "final" if @origin.final? && !@options[:static]
8
- list << "override" if @origin.override?
9
- list.concat super
8
+ list << "override" if @origin.override? && !super_list.include?("private")
9
+ list.concat super_list
10
+ list << "native" if @origin.body.nil? && @options[:environment] != :interface
10
11
  list
11
12
  end
12
13
  end
@@ -4,6 +4,7 @@ module Furnace::AVM2::Tokens
4
4
  class MethodToken < CalleeToken
5
5
  def initialize(origin, options={})
6
6
  super(origin, [
7
+ MetadataToken.new(origin, options),
7
8
  MethodSpecifiersToken.new(origin, options),
8
9
  FunctionNameToken.new(origin, [
9
10
  MultinameToken.new(origin, origin.name, options.merge(omit_ns: true))
@@ -6,32 +6,47 @@ module Furnace::AVM2::Tokens
6
6
  end
7
7
 
8
8
  def to_text
9
- if @multiname
9
+ name_to_text(@multiname)
10
+ end
11
+
12
+ protected
13
+
14
+ def name_to_text(multiname)
15
+ if multiname
10
16
  if @options[:debug_names]
11
- debug = "/* #{@multiname.to_astlet.to_sexp} */ "
17
+ debug = "/* #{multiname.to_astlet.to_sexp} */ "
12
18
  end
13
19
 
14
- qualified_name = ->(ns) {
15
- if @options[:ns].include?(ns) || ["*", ""].include?(ns.name.to_s) || @options[:omit_ns]
16
- "#{debug}#{@multiname.name}"
20
+ qualified_name = ->(ns, name) {
21
+ if @options[:ns].include?(ns) ||
22
+ ["*", ""].include?(ns.name) ||
23
+ @options[:omit_ns]
24
+ "#{debug}#{name}"
17
25
  else
18
- "#{debug}#{ns.name}.#{@multiname.name}"
26
+ "#{debug}#{ns.name}.#{name}"
19
27
  end
20
28
  }
21
29
 
22
- case @multiname.kind
30
+ case multiname.kind
23
31
  when :QName, :QNameA
24
- qualified_name.(@multiname.ns)
32
+ qualified_name.(multiname.ns, multiname.name)
25
33
  when :GenericName
26
- "#{debug}#{@multiname.name.name}.<#{@multiname.parameters.map(&:name).join}>"
34
+ parameters = multiname.parameters.map do |param|
35
+ subname = name_to_text(param)
36
+ # Fuck you.
37
+ subname = "#{subname} " if subname.end_with? ">"
38
+ subname
39
+ end
40
+ "#{debug}#{multiname.name.name}.<#{parameters.join(", ")}>"
27
41
  when :Multiname
28
- if @multiname.ns_set.ns.count == 1
29
- qualified_name.(@multiname.ns_set.ns[0])
42
+ if multiname.ns_set.ns.count == 1
43
+ qualified_name.(multiname.ns_set.ns[0], multiname.name)
30
44
  else
31
- "#{debug}%%Multiname"
45
+ "/* #{multiname.to_astlet.to_sexp} */ " +
46
+ qualified_name.(multiname.ns_set.ns[0], multiname.name)
32
47
  end
33
48
  else
34
- "#{debug}%%#{@multiname.kind}"
49
+ "#{debug}%%#{multiname.kind}"
35
50
  end
36
51
  else
37
52
  "%%nil"
@@ -1,15 +1,15 @@
1
1
  module Furnace::AVM2::Tokens
2
2
  class PackageToken < Furnace::Code::NonterminalToken
3
3
  def initialize(origin, options={})
4
- options[:ns] = options[:ns].uniq.reject { |ns|
4
+ options[:ns] = options[:ns].reject { |ns|
5
5
  ns.name == "" || ns.name == "*"
6
6
  }
7
7
 
8
- import_ns = options[:ns]
9
-
10
- if options[:package_name]
11
- options[:ns] = (options[:ns] + [ options[:package_name].ns ]).uniq
12
- end
8
+ import_ns = options[:ns].reject { |ns|
9
+ ns.name == options[:package_name].ns.name ||
10
+ ns.name == "__AS3__.vec" ||
11
+ ns.name !~ /^[A-Za-z0-9_$.]+$/
12
+ }
13
13
 
14
14
  super(origin, [
15
15
  (PackageNameToken.new(origin, options[:package_name].ns.name, options) if options[:package_name]),
@@ -19,8 +19,9 @@ module Furnace::AVM2::Tokens
19
19
  },
20
20
  (Furnace::Code::NewlineToken.new(origin, options) if import_ns.any?),
21
21
  (case options[:package_type]
22
- when :class; ClassToken.new(origin, options)
23
- when :script; ScriptToken.new(origin, options)
22
+ when :class; ClassToken.new(origin, options)
23
+ when :interface; ClassToken.new(origin, options)
24
+ when :script; ScriptToken.new(origin, options)
24
25
  end)
25
26
  ], options)
26
27
  ], options)
@@ -11,6 +11,8 @@ module Furnace::AVM2::Tokens
11
11
  def text_after
12
12
  if @options[:continuation]
13
13
  "} "
14
+ elsif @options[:closure]
15
+ "}"
14
16
  else
15
17
  "}\n"
16
18
  end
@@ -5,9 +5,12 @@ module Furnace::AVM2::Tokens
5
5
  def initialize(origin, options={})
6
6
  options = options.merge(environment: :script)
7
7
 
8
+ global_code = Furnace::AVM2::Decompiler.new(origin.initializer_body,
9
+ options.merge(global_code: true)).decompile
10
+
8
11
  super(origin, [
9
12
  *transform_traits(origin, options.merge(static: false)),
10
- Furnace::AVM2::Decompiler.new(origin.initializer_body, options).decompile
13
+ (global_code if global_code.children.any?)
11
14
  ], options)
12
15
  end
13
16
  end
@@ -2,6 +2,7 @@ module Furnace::AVM2::Tokens
2
2
  class SlotToken < Furnace::Code::SurroundedToken
3
3
  def initialize(origin, options={})
4
4
  super(origin, [
5
+ MetadataToken.new(origin, options),
5
6
  SpecifiersToken.new(origin, options),
6
7
  SlotNameToken.new(origin, options),
7
8
  (TypeToken.new(origin, [
@@ -4,15 +4,17 @@ module Furnace::AVM2::Tokens
4
4
  list = []
5
5
  list << "static" if @options[:static]
6
6
 
7
- if @origin.name.ns.name == ""
8
- list << "public"
9
- elsif @options[:instance]
10
- if @options[:instance].protected_ns? &&
11
- # protected_ns is not singleton. Why? Fuck me if I know.
12
- @origin.name.ns.name == @options[:instance].protected_ns.name
13
- list << "protected"
14
- elsif @origin.name.ns != @options[:instance].name.ns
15
- list << "private"
7
+ unless @options[:environment] == :interface
8
+ if @origin.name.ns.name == ""
9
+ list << "public"
10
+ elsif @options[:instance]
11
+ if @options[:instance].protected_ns? &&
12
+ # protected_ns is not singleton. Why? Fuck me if I know.
13
+ @origin.name.ns.name == @options[:instance].protected_ns.name
14
+ list << "protected"
15
+ elsif @origin.name.ns != @options[:instance].name.ns
16
+ list << "private"
17
+ end
16
18
  end
17
19
  end
18
20
 
@@ -13,17 +13,65 @@ module Furnace::AVM2
13
13
  end
14
14
 
15
15
  def initialize(body, options)
16
- @body, @method, @options = body, body.method, options
16
+ @body, @method, @options = body, body.method, options.dup
17
+ @closure = @options.delete(:closure)
18
+ end
19
+
20
+ ActivationPrologue = Matcher.new do
21
+ [:begin,
22
+ [:push_scope,
23
+ [:get_local, 0]],
24
+ [:set_local, -1,
25
+ [:new_activation]],
26
+ [:set_local, capture(:activation_local),
27
+ [:get_local, -1]],
28
+ [:push_scope,
29
+ [:get_local, -1]],
30
+ skip
31
+ ]
32
+ end
33
+
34
+ RegularPrologue = Matcher.new do
35
+ [:begin,
36
+ [:push_scope,
37
+ [:get_local, 0]],
38
+ skip
39
+ ]
17
40
  end
18
41
 
19
42
  def decompile
20
43
  begin
21
44
  @locals = Set.new([0]) + (1..@method.param_count).to_a
45
+ @scopes = []
22
46
 
23
47
  @nf = @body.code_to_nf
24
48
 
25
- stmt_block @nf, function: true
49
+ if captures = ActivationPrologue.match(@nf)
50
+ @closure_slots = {}
51
+ @body.slot_traits.each do |trait|
52
+ @closure_slots[trait.idx] = trait
53
+ end
54
+
55
+ @closure_locals = Set.new
56
+
57
+ @nf.children.slice! 0...4
58
+ else
59
+ if RegularPrologue.match @nf
60
+ @nf.children.slice! 0...1
61
+ else
62
+ # No prologue; probably a closure.
63
+ end
64
+ end
65
+
66
+ @global_slots = @options[:global_slots]
67
+
68
+ stmt_block @nf,
69
+ function: !@options[:global_code],
70
+ closure: @closure
71
+
26
72
  rescue Exception => e
73
+ @failed = true
74
+
27
75
  comment = "'Ouch!' cried I, then died.\n" +
28
76
  "#{e.class}: #{e.message}\n" +
29
77
  "#{e.backtrace[0..5].map { |l| " #{l}\n"}.join}" +
@@ -31,103 +79,144 @@ module Furnace::AVM2
31
79
 
32
80
  token(ScopeToken, [
33
81
  token(CommentToken, comment)
34
- ], function: true)
82
+ ], function: !@options[:global_code],
83
+ closure: @closure)
84
+
85
+ ensure
86
+ if stat = @options[:stat]
87
+ stat[:total] += 1
88
+
89
+ if @failed
90
+ stat[:failed] += 1
91
+ elsif @partial
92
+ stat[:partial] += 1
93
+ else
94
+ stat[:success] += 1
95
+ end
96
+ end
35
97
  end
36
98
  end
37
99
 
38
100
  # Statements
39
101
 
40
- Prologue = Matcher.new do
41
- [:push_scope,
42
- [:get_local, 0]]
43
- end
44
-
45
102
  def stmt_block(block, options={})
46
103
  nodes = []
47
104
 
48
105
  block.children.each do |opcode|
49
- if (opcode.type == :push_scope && Prologue.match(opcode))
50
- # Ignore these
51
- next
106
+ if respond_to?(:"stmt_#{opcode.type}")
107
+ send :"stmt_#{opcode.type}", opcode, nodes
108
+ else
109
+ catch(:skip) do
110
+ nodes << token(StatementToken, [
111
+ handle_expression(opcode)
112
+ ])
113
+ end
52
114
  end
115
+ end
53
116
 
54
- case opcode.type
55
- when :if
56
- condition, if_true, if_false = opcode.children
117
+ rescue ExpressionNotRecognized => e
118
+ @partial = true
57
119
 
58
- nodes << token(IfToken, handle_expression(condition),
59
- stmt_block(if_true, continuation: !if_false.nil?))
60
- nodes << token(ElseToken,
61
- stmt_block(if_false)) if if_false
120
+ comment = "Well, this is embarassing.\n\n" +
121
+ "Expression recognizer failed at:\n" +
122
+ "#{e.opcode.inspect}\n"
62
123
 
63
- when :label
64
- name, = opcode.children
124
+ if e.context != e.opcode
125
+ comment << "\nOpcode at the top of stack:\n" +
126
+ "#{e.context.inspect}\n"
127
+ end
65
128
 
66
- nodes << token(LabelDeclarationToken, name)
129
+ nodes << CommentToken.new(@body, comment, @options)
67
130
 
68
- when :while
69
- condition, body = opcode.children
131
+ ensure
132
+ if $!.nil? || $!.is_a?(ExpressionNotRecognized)
133
+ return token(ScopeToken, nodes, options)
134
+ end
135
+ end
70
136
 
71
- nodes << token(WhileToken, handle_expression(condition),
72
- stmt_block(body))
137
+ def stmt_if(opcode, nodes)
138
+ condition, if_true, if_false = opcode.children
73
139
 
74
- when :for_in, :for_each_in
75
- value_reg, value_type, object_reg, body = opcode.children
140
+ nodes << token(IfToken, handle_expression(condition),
141
+ stmt_block(if_true, continuation: !if_false.nil?))
142
+ nodes << token(ElseToken,
143
+ stmt_block(if_false)) if if_false
144
+ end
76
145
 
77
- @locals.add(value_reg)
146
+ def stmt_label(opcode, nodes)
147
+ name, = opcode.children
78
148
 
79
- if opcode.type == :for_in
80
- klass = ForToken
81
- elsif opcode.type == :for_each_in
82
- klass = ForEachToken
83
- end
149
+ nodes << token(LabelDeclarationToken, name)
150
+ end
84
151
 
85
- nodes << token(klass,
86
- token(InToken, [
87
- token(LocalVariableToken, [
88
- token(VariableNameToken, local_name(value_reg)),
89
- type_token(value_type)
90
- ]),
91
- token(VariableNameToken, local_name(object_reg)),
92
- ]),
93
- stmt_block(body))
152
+ def stmt_while(opcode, nodes)
153
+ condition, body = opcode.children
94
154
 
95
- when :break
96
- nodes << token(ReturnToken, exprs(opcode.children))
155
+ nodes << token(WhileToken, handle_expression(condition),
156
+ stmt_block(body))
157
+ end
97
158
 
98
- when :continue
99
- nodes << token(ContinueToken, exprs(opcode.children))
159
+ def stmt_for(opcode, nodes)
160
+ value_reg, value_type, object_reg, body = opcode.children
100
161
 
101
- when :throw
102
- nodes << token(ThrowToken, exprs(opcode.children))
162
+ @locals.add(value_reg)
103
163
 
104
- when :return_value, :return_void
105
- nodes << token(ReturnToken, exprs(opcode.children))
164
+ if opcode.type == :for_in
165
+ klass = ForToken
166
+ elsif opcode.type == :for_each_in
167
+ klass = ForEachToken
168
+ end
106
169
 
107
- else
108
- node = handle_expression(opcode)
109
- node = token(StatementToken, [node])
110
- nodes << node
111
- end
170
+ if @closure_slots
171
+ name = token(VariableNameToken, @closure_slots[value_reg].name.name)
172
+ else
173
+ name = token(VariableNameToken, local_name(value_reg))
112
174
  end
113
175
 
114
- rescue ExpressionNotRecognized => e
115
- comment = "Well, this is embarassing.\n\n" +
116
- "Expression recognizer failed at:\n" +
117
- "#{e.opcode.inspect}\n"
176
+ nodes << token(klass,
177
+ token(InToken, [
178
+ token(LocalVariableToken, [
179
+ name,
180
+ type_token(value_type)
181
+ ]),
182
+ token(VariableNameToken, local_name(object_reg)),
183
+ ]),
184
+ stmt_block(body))
185
+ end
186
+ alias :stmt_for_in :stmt_for
187
+ alias :stmt_for_each_in :stmt_for
118
188
 
119
- if e.context != e.opcode
120
- comment << "\nOpcode at the top of stack:\n" +
121
- "#{e.context.inspect}\n"
122
- end
189
+ def stmt_break(opcode, nodes)
190
+ nodes << token(ReturnToken, exprs(opcode.children))
191
+ end
123
192
 
124
- nodes << CommentToken.new(@body, comment, @options)
193
+ def stmt_continue(opcode, nodes)
194
+ nodes << token(ContinueToken, exprs(opcode.children))
195
+ end
125
196
 
126
- ensure
127
- if $!.nil? || $!.is_a?(ExpressionNotRecognized)
128
- return token(ScopeToken, nodes,
129
- continuation: options[:continuation],
130
- function: options[:function])
197
+ def stmt_throw(opcode, nodes)
198
+ nodes << token(ThrowToken, exprs(opcode.children))
199
+ end
200
+
201
+ def stmt_return(opcode, nodes)
202
+ nodes << token(ReturnToken, exprs(opcode.children))
203
+ end
204
+ alias :stmt_return_value :stmt_return
205
+ alias :stmt_return_void :stmt_return
206
+
207
+ def stmt_push_scope(opcode, nodes)
208
+ if @options[:global_code]
209
+ @scopes.push opcode.children.first
210
+ else
211
+ raise "pushscope in nonglobal code"
212
+ end
213
+ end
214
+
215
+ def stmt_pop_scope(opcode, nodes)
216
+ if @options[:global_code]
217
+ @scopes.pop
218
+ else
219
+ raise "popscope in nonglobal code"
131
220
  end
132
221
  end
133
222
 
@@ -202,7 +291,11 @@ module Furnace::AVM2
202
291
  if index < 0
203
292
  "sp#{-index}"
204
293
  elsif index == 0
205
- "this"
294
+ if @options[:static]
295
+ @options[:instance].name.name
296
+ else
297
+ "this"
298
+ end
206
299
  elsif index <= @method.param_count
207
300
  if @method.has_param_names?
208
301
  @method.param_names[index - 1]
@@ -219,6 +312,38 @@ module Furnace::AVM2
219
312
  token(VariableNameToken, local_name(index))
220
313
  end
221
314
 
315
+ ActivationGetSlot = Matcher.new do
316
+ [:get_slot,
317
+ capture(:index),
318
+ [:get_scope_object, 1]]
319
+ end
320
+
321
+ GlobalGetSlot = Matcher.new do
322
+ [:get_slot,
323
+ capture(:index),
324
+ either[
325
+ [:get_global_scope], # normalized form
326
+ [:get_scope_object, 0] # not emitted by ASC
327
+ ]]
328
+ end
329
+
330
+ def expr_get_slot(opcode)
331
+ if @closure_slots && captures = ActivationGetSlot.match(opcode)
332
+ # treat as a local variable
333
+ slot = @closure_slots[captures[:index]]
334
+ token(VariableNameToken, slot.name.name)
335
+ elsif captures = GlobalGetSlot.match(opcode)
336
+ # treat as a global property
337
+ if @global_slots
338
+ slot = @global_slots[captures[:index]]
339
+ get_name(nil, slot.name.to_astlet)
340
+ else
341
+ token(PropertyNameToken,
342
+ "$__GLOBAL_#{captures[:index]}")
343
+ end
344
+ end
345
+ end
346
+
222
347
  IMMEDIATE_TYPE_MAP = {
223
348
  :any => '*',
224
349
  :integer => 'int',
@@ -243,42 +368,94 @@ module Furnace::AVM2
243
368
  end
244
369
  end
245
370
 
246
- def expr_set_var(name, value, declare)
247
- if IMMEDIATE_TYPE_MAP.include?(value.type)
248
- inside = value
249
- type = token(TypeToken, [
250
- token(ImmediateTypenameToken, IMMEDIATE_TYPE_MAP[value.type])
251
- ])
252
- elsif value.type == :coerce || value.type == :convert
253
- inside_type, inside = value.children
254
- type = type_token(inside_type)
255
- else
256
- inside = value
257
- type = nil
258
- end
371
+ XmlLiteralPreMatcher = Matcher.new do
372
+ [:coerce, [:q, "XML"], any]
373
+ end
259
374
 
375
+ def expr_set_var(name, value, type, declare)
260
376
  if declare
261
377
  token(LocalVariableToken, [
262
378
  token(VariableNameToken, name),
263
379
  type,
264
380
  token(InitializationToken, [
265
- expr(inside)
381
+ expr(value)
266
382
  ])
267
383
  ])
268
384
  else
269
385
  token(AssignmentToken, [
270
386
  token(VariableNameToken, name),
271
- expr(inside)
387
+ expr(value)
272
388
  ])
273
389
  end
274
390
  end
275
391
 
276
392
  def expr_set_local(opcode)
277
393
  index, value = opcode.children
394
+ if IMMEDIATE_TYPE_MAP.include?(value.type)
395
+ type = token(TypeToken, [
396
+ token(ImmediateTypenameToken, IMMEDIATE_TYPE_MAP[value.type])
397
+ ])
398
+ elsif XmlLiteralPreMatcher.match value
399
+ # XML literals work through expr_coerce
400
+ type = type_token(value.children.first)
401
+ value = value
402
+ elsif value.type == :coerce || value.type == :convert
403
+ # Don't emit spurious coercion; typed variables already
404
+ # imply it
405
+ type = type_token(value.children.first)
406
+ value = value.children.last
407
+ end
278
408
 
279
- expr_set_var(local_name(index), value, !@locals.include?(index))
409
+ expr_set_var(local_name(index), value, type, !@locals.include?(index))
280
410
  ensure
281
- @locals << index
411
+ @locals.add index if index
412
+ end
413
+
414
+ ActivationSetSlot = Matcher.new do
415
+ [:set_slot,
416
+ capture(:index),
417
+ [:get_scope_object, 1],
418
+ capture(:value)]
419
+ end
420
+
421
+ GlobalSetSlot = Matcher.new do
422
+ [:set_slot,
423
+ capture(:index),
424
+ either[
425
+ [:get_global_scope], # normalized form
426
+ [:get_scope_object, 0] # not emitted by ASC
427
+ ],
428
+ capture(:value)]
429
+ end
430
+
431
+ def expr_set_slot(opcode)
432
+ if @closure_slots && captures = ActivationSetSlot.match(opcode)
433
+ # treat as a local variable
434
+ index, value = captures.values_at(:index, :value)
435
+ slot = @closure_slots[index]
436
+
437
+ type = type_token(slot.type.to_astlet) if slot.type
438
+ expr = expr_set_var(slot.name.name, value, type,
439
+ !@closure_locals.include?(index))
440
+ @closure_locals.add index
441
+
442
+ expr
443
+ elsif captures = GlobalSetSlot.match(opcode)
444
+ # treat as a global property
445
+ index, value = captures.values_at(:index, :value)
446
+
447
+ if @global_slots
448
+ slot = @global_slots[index]
449
+ name = get_name(nil, slot.name.to_astlet)
450
+ else
451
+ token(PropertyNameToken, "$__GLOBAL_#{index}")
452
+ end
453
+
454
+ token(AssignmentToken, [
455
+ name,
456
+ expr(value)
457
+ ])
458
+ end
282
459
  end
283
460
 
284
461
  ## Arithmetics
@@ -416,9 +593,22 @@ module Furnace::AVM2
416
593
 
417
594
  PropertyGlobal = Matcher.new do
418
595
  [any,
419
- [either[:find_property, :find_property_strict],
420
- capture(:multiname)],
421
- backref(:multiname),
596
+ either_multi[
597
+ [
598
+ [ either[:find_property, :find_property_strict],
599
+ capture(:multiname)],
600
+ backref(:multiname),
601
+ ],
602
+ [
603
+ [:find_property_strict,
604
+ [:m, [:set, "*"], any]],
605
+ capture(:multiname)
606
+ ],
607
+ [
608
+ [:get_scope_object, 0],
609
+ capture(:multiname)
610
+ ],
611
+ ],
422
612
  capture_rest(:arguments)]
423
613
  end
424
614
 
@@ -438,9 +628,16 @@ module Furnace::AVM2
438
628
 
439
629
  def expr_get_super(opcode)
440
630
  subject, multiname, = opcode.children
441
- if This.match subject
442
- get_name(token(SuperToken), multiname)
631
+
632
+ stmt = get_name(token(SuperToken), multiname)
633
+
634
+ unless This.match subject
635
+ stmt = token(SupplementaryCommentToken,
636
+ "subject != this: #{subject.inspect}",
637
+ [ stmt ])
443
638
  end
639
+
640
+ stmt
444
641
  end
445
642
 
446
643
  def expr_set_property(opcode)
@@ -461,12 +658,19 @@ module Furnace::AVM2
461
658
 
462
659
  def expr_set_super(opcode)
463
660
  subject, multiname, value = opcode.children
464
- if This.match subject
465
- token(AssignmentToken, [
466
- get_name(token(SuperToken), multiname),
467
- expr(value)
468
- ])
661
+
662
+ stmt = token(AssignmentToken, [
663
+ get_name(token(SuperToken), multiname),
664
+ expr(value)
665
+ ])
666
+
667
+ unless This.match subject
668
+ stmt = token(SupplementaryCommentToken,
669
+ "subject != this: #{subject.inspect}",
670
+ [ stmt ])
469
671
  end
672
+
673
+ stmt
470
674
  end
471
675
 
472
676
  def expr_delete_property(opcode)
@@ -492,6 +696,7 @@ module Furnace::AVM2
492
696
  def expr_call_property(opcode)
493
697
  expr_do_property(opcode, CallToken)
494
698
  end
699
+ alias :expr_call_property_lex :expr_call_property
495
700
 
496
701
  def expr_call_super(opcode)
497
702
  subject, multiname, *args = opcode.children
@@ -556,7 +761,7 @@ module Furnace::AVM2
556
761
  token(CallToken, [
557
762
  subject_token,
558
763
  token(ArgumentsToken, [
559
- token(VariableNameToken, "this"),
764
+ token(VariableNameToken, local_name(0)),
560
765
  *exprs(args)
561
766
  ])
562
767
  ])
@@ -581,6 +786,10 @@ module Furnace::AVM2
581
786
  token(IsToken, parenthesize_each(exprs(opcode.children)))
582
787
  end
583
788
 
789
+ def expr_instance_of(opcode)
790
+ token(InstanceOfToken, parenthesize_each(exprs(opcode.children)))
791
+ end
792
+
584
793
  def expr_type_of(opcode)
585
794
  token(TypeOfToken, exprs(opcode.children))
586
795
  end
@@ -594,9 +803,8 @@ module Furnace::AVM2
594
803
  ])
595
804
  end
596
805
 
597
- def expr_coerce(opcode)
598
- typename, subject, = opcode.children
599
- expr(subject)
806
+ def expr_new_class(opcode)
807
+ throw :skip
600
808
  end
601
809
 
602
810
  def expr_convert(opcode)
@@ -609,6 +817,69 @@ module Furnace::AVM2
609
817
  ])
610
818
  end
611
819
 
820
+ ## Closures
821
+
822
+ def expr_new_function(opcode)
823
+ index, = opcode.children
824
+ body = @method.root.method_body_at(index)
825
+
826
+ token(ClosureToken,
827
+ body)
828
+ end
829
+
830
+ ## XML literals
831
+
832
+ # FFFUUUUUUUUUU~~~
833
+ XmlLiteralMatcher = Matcher.new do
834
+ [:coerce, [:q, "XML"],
835
+ [:construct,
836
+ either[
837
+ [:get_lex, [:q, "XML"]],
838
+ [:get_property,
839
+ [:find_property_strict, [:q, "XML"]], [:q, "XML"]]
840
+ ],
841
+ capture(:body),
842
+ ]
843
+ ]
844
+ end
845
+
846
+ def expr_coerce(opcode)
847
+ if captures = XmlLiteralMatcher.match(opcode)
848
+ # Oh, shit...
849
+ token(XmlLiteralToken,
850
+ xml_expr(captures[:body]))
851
+ else
852
+ typename, subject, = opcode.children
853
+ expr(subject)
854
+ end
855
+ end
856
+
857
+ def xml_expr(node)
858
+ if respond_to?(:"xml_#{node.type}")
859
+ send :"xml_#{node.type}", node
860
+ else
861
+ "{#{expr(node).to_text}}"
862
+ end
863
+ end
864
+
865
+ def xml_string(node)
866
+ node.children.first
867
+ end
868
+
869
+ def xml_add(node)
870
+ node.children.map do |child|
871
+ xml_expr child
872
+ end.join
873
+ end
874
+
875
+ def xml_esc_xattr(node)
876
+ xml_expr(node.children.first)
877
+ end
878
+
879
+ def xml_esc_xelem(node)
880
+ xml_expr(node.children.first)
881
+ end
882
+
612
883
  private
613
884
 
614
885
  def token(klass, *args)