furnace-avm2 0.9.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/.gitignore +1 -0
  2. data/Gemfile +3 -1
  3. data/bin/furnace-avm2 +27 -13
  4. data/bin/furnace-avm2-decompiler +14 -4
  5. data/bin/furnace-avm2-shell +4 -0
  6. data/furnace-avm2.gemspec +1 -1
  7. data/lib/furnace-avm2.rb +1 -1
  8. data/lib/furnace-avm2/abc.rb +3 -0
  9. data/lib/furnace-avm2/abc/metadata/exception_info.rb +7 -2
  10. data/lib/furnace-avm2/abc/metadata/method_body_info.rb +4 -2
  11. data/lib/furnace-avm2/abc/metadata/script_info.rb +8 -3
  12. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_add.rb +1 -1
  13. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_add_i.rb +2 -1
  14. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_declocal.rb +1 -1
  15. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_declocal_i.rb +2 -1
  16. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_decrement.rb +1 -1
  17. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_decrement_i.rb +2 -1
  18. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_divide.rb +1 -1
  19. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_equals.rb +1 -1
  20. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_greaterequals.rb +1 -1
  21. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_greaterthan.rb +1 -1
  22. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_inclocal.rb +1 -1
  23. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_inclocal_i.rb +2 -1
  24. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_increment.rb +1 -1
  25. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_increment_i.rb +2 -1
  26. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_lessequals.rb +1 -1
  27. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_lessthan.rb +1 -1
  28. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_modulo.rb +1 -1
  29. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_multiply.rb +1 -1
  30. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_multiply_i.rb +2 -1
  31. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_negate.rb +1 -1
  32. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_negate_i.rb +2 -1
  33. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_not.rb +1 -1
  34. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_strictequals.rb +1 -1
  35. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_subtract.rb +1 -1
  36. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_subtract_i.rb +2 -1
  37. data/lib/furnace-avm2/abc/opcodes/arithmetic_opcode.rb +5 -0
  38. data/lib/furnace-avm2/abc/opcodes/function_return/as3_returnvalue.rb +1 -1
  39. data/lib/furnace-avm2/abc/opcodes/function_return/as3_returnvoid.rb +1 -1
  40. data/lib/furnace-avm2/abc/opcodes/function_return_opcode.rb +4 -0
  41. data/lib/furnace-avm2/abc/opcodes/opcode.rb +1 -1
  42. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_coerce.rb +1 -1
  43. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_coerce_a.rb +4 -1
  44. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_coerce_b.rb +3 -2
  45. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_coerce_s.rb +3 -2
  46. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_d.rb +3 -2
  47. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_i.rb +3 -2
  48. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_o.rb +3 -2
  49. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_s.rb +3 -2
  50. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_u.rb +3 -2
  51. data/lib/furnace-avm2/abc/opcodes/type_conversion_opcode.rb +10 -0
  52. data/lib/furnace-avm2/abc/primitives/opcode_sequence.rb +4 -0
  53. data/lib/furnace-avm2/abc/primitives/record.rb +21 -1
  54. data/lib/furnace-avm2/source/declaration_tokens/package_token.rb +16 -8
  55. data/lib/furnace-avm2/source/declaration_tokens/script_token.rb +7 -0
  56. data/lib/furnace-avm2/source/declaration_tokens/slot_token.rb +12 -4
  57. data/lib/furnace-avm2/source/declaration_tokens/token_with_traits.rb +10 -9
  58. data/lib/furnace-avm2/source/decompiler.rb +363 -166
  59. data/lib/furnace-avm2/source/implementation_tokens/case_token.rb +20 -0
  60. data/lib/furnace-avm2/source/implementation_tokens/catch_filter_token.rb +7 -0
  61. data/lib/furnace-avm2/source/implementation_tokens/catch_token.rb +9 -0
  62. data/lib/furnace-avm2/source/implementation_tokens/closure_token.rb +2 -0
  63. data/lib/furnace-avm2/source/implementation_tokens/control_flow_token.rb +0 -1
  64. data/lib/furnace-avm2/source/implementation_tokens/do_while_token.rb +16 -0
  65. data/lib/furnace-avm2/source/implementation_tokens/else_token.rb +3 -1
  66. data/lib/furnace-avm2/source/implementation_tokens/finally_token.rb +11 -0
  67. data/lib/furnace-avm2/source/implementation_tokens/label_name_token.rb +12 -0
  68. data/lib/furnace-avm2/source/implementation_tokens/this_token.rb +9 -0
  69. data/lib/furnace-avm2/source/implementation_tokens/try_token.rb +11 -0
  70. data/lib/furnace-avm2/source/implementation_tokens/unary_operator_token.rb +2 -2
  71. data/lib/furnace-avm2/source/implementation_tokens/unary_post_operator_token.rb +2 -2
  72. data/lib/furnace-avm2/transform.rb +2 -0
  73. data/lib/furnace-avm2/transform/ast_build.rb +234 -92
  74. data/lib/furnace-avm2/transform/ast_normalize.rb +4 -13
  75. data/lib/furnace-avm2/transform/cfg_build.rb +74 -33
  76. data/lib/furnace-avm2/transform/cfg_reduce.rb +457 -75
  77. data/lib/furnace-avm2/transform/nf_normalize.rb +69 -40
  78. data/lib/furnace-avm2/transform/propagate_constants.rb +49 -0
  79. data/lib/furnace-avm2/transform/propagate_labels.rb +31 -0
  80. data/lib/furnace-avm2/version.rb +1 -1
  81. data/test/basic.as +111 -3
  82. data/test/switch.as +27 -0
  83. metadata +907 -313
  84. data/Gemfile.lock +0 -19
@@ -1,5 +1,5 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3Multiply < Opcode
2
+ class AS3Multiply < ArithmeticOpcode
3
3
  instruction 0xa2
4
4
 
5
5
  consume 2
@@ -1,10 +1,11 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3MultiplyI < Opcode
2
+ class AS3MultiplyI < ArithmeticOpcode
3
3
  instruction 0xc7
4
4
 
5
5
  consume 2
6
6
  produce 1
7
7
 
8
8
  type :integer
9
+ ast_type :multiply
9
10
  end
10
11
  end
@@ -1,5 +1,5 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3Negate < Opcode
2
+ class AS3Negate < ArithmeticOpcode
3
3
  instruction 0x90
4
4
 
5
5
  consume 1
@@ -1,10 +1,11 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3NegateI < Opcode
2
+ class AS3NegateI < ArithmeticOpcode
3
3
  instruction 0xc4
4
4
 
5
5
  consume 1
6
6
  produce 1
7
7
 
8
8
  type :integer
9
+ ast_type :negate
9
10
  end
10
11
  end
@@ -1,5 +1,5 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3Not < Opcode
2
+ class AS3Not < ArithmeticOpcode
3
3
  instruction 0x96
4
4
  ast_type :!
5
5
 
@@ -1,5 +1,5 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3StrictEquals < Opcode
2
+ class AS3StrictEquals < ArithmeticOpcode
3
3
  instruction 0xac
4
4
  ast_type :===
5
5
 
@@ -1,5 +1,5 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3Subtract < Opcode
2
+ class AS3Subtract < ArithmeticOpcode
3
3
  instruction 0xa1
4
4
 
5
5
  consume 2
@@ -1,10 +1,11 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3SubtractI < Opcode
2
+ class AS3SubtractI < ArithmeticOpcode
3
3
  instruction 0xc6
4
4
 
5
5
  consume 2
6
6
  produce 1
7
7
 
8
8
  type :integer
9
+ ast_type :subtract
9
10
  end
10
11
  end
@@ -0,0 +1,5 @@
1
+ module Furnace::AVM2::ABC
2
+ class ArithmeticOpcode < Opcode
3
+ define_property :ast_type
4
+ end
5
+ end
@@ -1,5 +1,5 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3ReturnValue < Opcode
2
+ class AS3ReturnValue < FunctionReturnOpcode
3
3
  instruction 0x48
4
4
 
5
5
  consume 1
@@ -1,5 +1,5 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3ReturnVoid < Opcode
2
+ class AS3ReturnVoid < FunctionReturnOpcode
3
3
  instruction 0x47
4
4
 
5
5
  consume 0
@@ -0,0 +1,4 @@
1
+ module Furnace::AVM2::ABC
2
+ class FunctionReturnOpcode < Opcode
3
+ end
4
+ end
@@ -122,7 +122,7 @@ module Furnace::AVM2::ABC
122
122
  end
123
123
 
124
124
  def disassemble
125
- " #{offset.to_s.rjust(4, "0")} #{self.class.mnemonic.rjust(20, " ")} #{disassemble_parameters} # params: #{parameters.inspect}"
125
+ " #{offset.to_s.rjust(4, "0")} #{self.class.mnemonic.rjust(20, " ")} #{disassemble_parameters}"
126
126
  end
127
127
  alias :inspect :disassemble
128
128
  end
@@ -1,5 +1,5 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3Coerce < Opcode
2
+ class AS3Coerce < TypeConversionOpcode
3
3
  instruction 0x80
4
4
 
5
5
  body do
@@ -1,8 +1,11 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3CoerceA < Opcode
2
+ class AS3CoerceA < TypeConversionOpcode
3
3
  instruction 0x82
4
4
 
5
5
  consume 1
6
6
  produce 1
7
+
8
+ ast_type :coerce
9
+ type :any
7
10
  end
8
11
  end
@@ -1,10 +1,11 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3CoerceB < Opcode
2
+ class AS3CoerceB < TypeConversionOpcode
3
3
  instruction 0x76
4
4
 
5
5
  consume 1
6
6
  produce 1
7
7
 
8
- type :boolean
8
+ ast_type :coerce
9
+ type :boolean
9
10
  end
10
11
  end
@@ -1,10 +1,11 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3CoerceS < Opcode
2
+ class AS3CoerceS < TypeConversionOpcode
3
3
  instruction 0x85
4
4
 
5
5
  consume 1
6
6
  produce 1
7
7
 
8
- type :string
8
+ ast_type :coerce
9
+ type :string
9
10
  end
10
11
  end
@@ -1,10 +1,11 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3ConvertD < Opcode
2
+ class AS3ConvertD < TypeConversionOpcode
3
3
  instruction 0x75
4
4
 
5
5
  consume 1
6
6
  produce 1
7
7
 
8
- type :double
8
+ ast_type :convert
9
+ type :double
9
10
  end
10
11
  end
@@ -1,10 +1,11 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3ConvertI < Opcode
2
+ class AS3ConvertI < TypeConversionOpcode
3
3
  instruction 0x73
4
4
 
5
5
  consume 1
6
6
  produce 1
7
7
 
8
- type :integer
8
+ ast_type :convert
9
+ type :integer
9
10
  end
10
11
  end
@@ -1,10 +1,11 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3ConvertO < Opcode
2
+ class AS3ConvertO < TypeConversionOpcode
3
3
  instruction 0x77
4
4
 
5
5
  consume 1
6
6
  produce 1
7
7
 
8
- type :object
8
+ ast_type :convert
9
+ type :object
9
10
  end
10
11
  end
@@ -1,10 +1,11 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3ConvertS < Opcode
2
+ class AS3ConvertS < TypeConversionOpcode
3
3
  instruction 0x70
4
4
 
5
5
  consume 1
6
6
  produce 1
7
7
 
8
- type :string
8
+ ast_type :convert
9
+ type :string
9
10
  end
10
11
  end
@@ -1,10 +1,11 @@
1
1
  module Furnace::AVM2::ABC
2
- class AS3ConvertU < Opcode
2
+ class AS3ConvertU < TypeConversionOpcode
3
3
  instruction 0x74
4
4
 
5
5
  consume 1
6
6
  produce 1
7
7
 
8
- type :integer
8
+ ast_type :convert
9
+ type :unsigned
9
10
  end
10
11
  end
@@ -0,0 +1,10 @@
1
+ module Furnace::AVM2::ABC
2
+ class TypeConversionOpcode < Opcode
3
+ define_property :ast_type
4
+ define_property :type
5
+
6
+ def parameters
7
+ [ type ]
8
+ end
9
+ end
10
+ end
@@ -62,10 +62,14 @@ module Furnace::AVM2::ABC
62
62
  end
63
63
 
64
64
  def opcode_at(position)
65
+ parse if @raw_code
66
+
65
67
  @pos_cache[position]
66
68
  end
67
69
 
68
70
  def offset_of(opcode)
71
+ parse if @raw_code
72
+
69
73
  @opcode_cache[opcode]
70
74
  end
71
75
 
@@ -37,7 +37,27 @@ module Furnace::AVM2::ABC
37
37
  elsif index = pool.index(value)
38
38
  send(:"#{field}=", index + 1)
39
39
  else
40
- raise "cpool setter: no such object in cpool"
40
+ format_entry = ConstPoolInfo.format.find { |f| f[1] == array }
41
+ if format_entry.nil?
42
+ raise "cpool setter: [BUG] no such cpool field"
43
+ end
44
+
45
+ klass = nil
46
+ case format_entry[2][:type]
47
+ when :vstring; klass = String
48
+ when :nested; klass = format_entry[2][:class]
49
+ end
50
+
51
+ if klass.nil?
52
+ raise "cpool setter: [BUG] no checker for type #{format_entry[2][:type]}"
53
+ end
54
+
55
+ if value.class != klass
56
+ raise "cpool setter: trying to set wrong kind of value (#{value.class} instead of #{klass})"
57
+ end
58
+
59
+ pool.push value
60
+ send(:"#{field}=", pool.index(value) + 1)
41
61
  end
42
62
  end
43
63
  end
@@ -11,19 +11,27 @@ module Furnace::AVM2::Tokens
11
11
  ns.name !~ /^[A-Za-z0-9_$.]+$/
12
12
  }
13
13
 
14
- super(origin, [
15
- (PackageNameToken.new(origin, options[:package_name].ns.name, options) if options[:package_name]),
16
- ScopeToken.new(origin, [
14
+ case options[:package_type]
15
+ when :class, :interface
16
+ content = ClassToken.new(origin, options)
17
+ when :script
18
+ content = ScriptToken.new(origin, options)
19
+ end
20
+
21
+ scope = nil
22
+ if content.children.any?
23
+ scope = ScopeToken.new(origin, [
17
24
  *import_ns.map { |ns|
18
25
  ImportToken.new(origin, ns.name, options)
19
26
  },
20
27
  (Furnace::Code::NewlineToken.new(origin, options) if import_ns.any?),
21
- (case options[:package_type]
22
- when :class; ClassToken.new(origin, options)
23
- when :interface; ClassToken.new(origin, options)
24
- when :script; ScriptToken.new(origin, options)
25
- end)
28
+ content
26
29
  ], options)
30
+ end
31
+
32
+ super(origin, [
33
+ (PackageNameToken.new(origin, options[:package_name].ns.name, options) if options[:package_name]),
34
+ scope,
27
35
  ], options)
28
36
  end
29
37
  end
@@ -12,6 +12,13 @@ module Furnace::AVM2::Tokens
12
12
  *transform_traits(origin, options.merge(static: false)),
13
13
  (global_code if global_code.children.any?)
14
14
  ], options)
15
+
16
+ if options[:debug_funids] && global_code.children.any?
17
+ @children.unshift \
18
+ CommentToken.new(origin,
19
+ "Function ##{origin.initializer_idx}",
20
+ options)
21
+ end
15
22
  end
16
23
  end
17
24
  end
@@ -10,10 +10,18 @@ module Furnace::AVM2::Tokens
10
10
  ], options) if origin.type)
11
11
  ], options)
12
12
 
13
- if origin.printable_value
14
- @children << InitializationToken.new(origin, [
15
- ImmediateToken.new(origin, origin.printable_value, options)
16
- ], @options)
13
+ value = nil
14
+
15
+ if options[:property_values]
16
+ *, value = options[:property_values].find { |k,v| k == origin.name.to_astlet }
17
+ end
18
+
19
+ if value.nil? && origin.printable_value
20
+ value = ImmediateToken.new(origin, origin.printable_value, options)
21
+ end
22
+
23
+ if value
24
+ @children << InitializationToken.new(origin, [ value ], @options)
17
25
  end
18
26
  end
19
27
 
@@ -7,21 +7,22 @@ module Furnace::AVM2::Tokens
7
7
  [:Class, :Slot, :Const].include? trait.kind
8
8
  end
9
9
 
10
+ if options[:environment] == :class && options[:static]
11
+ if origin.initializer_body
12
+ properties = Furnace::AVM2::Decompiler.new(
13
+ origin.initializer_body, options).decompose_static_initializer
14
+ options = options.merge(property_values: properties)
15
+ end
16
+ end
17
+
10
18
  tokens += vars.map { |trait| transform_trait trait, options }
11
19
 
12
20
  if tokens.any?
13
21
  tokens << Furnace::Code::NewlineToken.new(origin, options)
14
22
  end
15
23
 
16
- if options[:environment] == :class
17
- if options[:static]
18
- tokens << CommentToken.new(origin, "Static initializer", options)
19
- tokens << CommentToken.new(origin,
20
- ConstructorToken.new(origin, options.merge(commented: true)),
21
- options)
22
- else
23
- tokens << ConstructorToken.new(origin, options)
24
- end
24
+ if options[:environment] == :class && !options[:static]
25
+ tokens << ConstructorToken.new(origin, options)
25
26
  end
26
27
 
27
28
  tokens += methods.map { |trait| transform_trait trait, options }
@@ -17,55 +17,24 @@ module Furnace::AVM2
17
17
  @closure = @options.delete(:closure)
18
18
  end
19
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
- ]
40
- end
41
-
42
20
  def decompile
43
21
  begin
44
22
  @locals = Set.new([0]) + (1..@method.param_count).to_a
45
23
  @scopes = []
24
+ @metascopes = []
46
25
 
47
- @nf = @body.code_to_nf
48
-
49
- if captures = ActivationPrologue.match(@nf)
50
- @closure_slots = {}
51
- @body.slot_traits.each do |trait|
52
- @closure_slots[trait.idx] = trait
53
- end
26
+ @catch_scopes = {}
54
27
 
55
- @closure_locals = Set.new
28
+ @closure_locals = Set.new
56
29
 
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
30
+ @closure_slots = {}
31
+ @body.slot_traits.each do |trait|
32
+ @closure_slots[trait.idx] = trait
64
33
  end
65
34
 
66
- @global_slots = @options[:global_slots]
35
+ @global_slots = @options[:global_slots] || {}
67
36
 
68
- stmt_block @nf,
37
+ stmt_block @body.code_to_nf,
69
38
  function: !@options[:global_code],
70
39
  closure: @closure
71
40
 
@@ -97,19 +66,50 @@ module Furnace::AVM2
97
66
  end
98
67
  end
99
68
 
69
+ StaticProperty = Matcher.new do
70
+ [ either[:set_property, :init_property],
71
+ [:find_property, capture(:property)],
72
+ backref(:property),
73
+ capture(:value)
74
+ ]
75
+ end
76
+
77
+ def decompose_static_initializer
78
+ properties = {}
79
+
80
+ StaticProperty.find_all(@body.code_to_nf.children) do |match, captures|
81
+ begin
82
+ token = handle_expression(captures[:value])
83
+ rescue ExpressionNotRecognized => e
84
+ token = token(CommentToken, "Unrecognized static initializer:\n#{e.opcode.inspect}")
85
+ end
86
+
87
+ properties[captures[:property]] = token
88
+ end
89
+
90
+ properties
91
+ end
92
+
100
93
  # Statements
101
94
 
102
95
  def stmt_block(block, options={})
103
96
  nodes = []
97
+ last_index = 0
98
+
99
+ block.children.each_with_index do |opcode, index|
100
+ last_index = index
104
101
 
105
- block.children.each do |opcode|
106
102
  if respond_to?(:"stmt_#{opcode.type}")
107
103
  send :"stmt_#{opcode.type}", opcode, nodes
108
104
  else
109
105
  catch(:skip) do
110
- nodes << token(StatementToken, [
106
+ @collected_vars = []
107
+ stmt = token(StatementToken, [
111
108
  handle_expression(opcode)
112
109
  ])
110
+
111
+ nodes.concat @collected_vars
112
+ nodes.push stmt
113
113
  end
114
114
  end
115
115
  end
@@ -121,9 +121,9 @@ module Furnace::AVM2
121
121
  "Expression recognizer failed at:\n" +
122
122
  "#{e.opcode.inspect}\n"
123
123
 
124
- if e.context != e.opcode
125
- comment << "\nOpcode at the top of stack:\n" +
126
- "#{e.context.inspect}\n"
124
+ comment << "\nRest of the code in this block:\n"
125
+ block.children[last_index..-1].each do |opcode|
126
+ comment << "#{opcode.inspect}\n"
127
127
  end
128
128
 
129
129
  nodes << CommentToken.new(@body, comment, @options)
@@ -134,13 +134,28 @@ module Furnace::AVM2
134
134
  end
135
135
  end
136
136
 
137
+ def stmt_begin(opcode, nodes)
138
+ nodes << stmt_block(opcode)
139
+ end
140
+
137
141
  def stmt_if(opcode, nodes)
138
142
  condition, if_true, if_false = opcode.children
139
143
 
140
144
  nodes << token(IfToken, handle_expression(condition),
141
145
  stmt_block(if_true, continuation: !if_false.nil?))
142
- nodes << token(ElseToken,
143
- stmt_block(if_false)) if if_false
146
+
147
+ if if_false
148
+ first_child = if_false.children.first
149
+ if if_false.children.count == 1 &&
150
+ first_child.type == :if
151
+ nodes << token(ElseToken,
152
+ nil)
153
+ stmt_if(first_child, nodes)
154
+ else
155
+ nodes << token(ElseToken,
156
+ stmt_block(if_false))
157
+ end
158
+ end
144
159
  end
145
160
 
146
161
  def stmt_label(opcode, nodes)
@@ -152,10 +167,19 @@ module Furnace::AVM2
152
167
  def stmt_while(opcode, nodes)
153
168
  condition, body = opcode.children
154
169
 
155
- nodes << token(WhileToken, handle_expression(condition),
170
+ nodes << token(WhileToken,
171
+ handle_expression(condition),
156
172
  stmt_block(body))
157
173
  end
158
174
 
175
+ def stmt_do_while(opcode, nodes)
176
+ condition, body = opcode.children
177
+
178
+ nodes << token(DoWhileToken,
179
+ stmt_block(body, continuation: true),
180
+ handle_expression(condition))
181
+ end
182
+
159
183
  def stmt_for(opcode, nodes)
160
184
  value_reg, value_type, object_reg, body = opcode.children
161
185
 
@@ -167,10 +191,10 @@ module Furnace::AVM2
167
191
  klass = ForEachToken
168
192
  end
169
193
 
170
- if @closure_slots
194
+ if @activation_local
171
195
  name = token(VariableNameToken, @closure_slots[value_reg].name.name)
172
196
  else
173
- name = token(VariableNameToken, local_name(value_reg))
197
+ name = local_token(value_reg)
174
198
  end
175
199
 
176
200
  nodes << token(klass,
@@ -179,7 +203,7 @@ module Furnace::AVM2
179
203
  name,
180
204
  type_token(value_type)
181
205
  ]),
182
- token(VariableNameToken, local_name(object_reg)),
206
+ local_token(object_reg),
183
207
  ]),
184
208
  stmt_block(body))
185
209
  end
@@ -187,11 +211,17 @@ module Furnace::AVM2
187
211
  alias :stmt_for_each_in :stmt_for
188
212
 
189
213
  def stmt_break(opcode, nodes)
190
- nodes << token(ReturnToken, exprs(opcode.children))
214
+ label, = opcode.children
215
+ nodes << token(BreakToken, [
216
+ (token(LabelNameToken, label) if label)
217
+ ])
191
218
  end
192
219
 
193
220
  def stmt_continue(opcode, nodes)
194
- nodes << token(ContinueToken, exprs(opcode.children))
221
+ label, = opcode.children
222
+ nodes << token(ContinueToken, [
223
+ (token(LabelNameToken, label) if label)
224
+ ])
195
225
  end
196
226
 
197
227
  def stmt_throw(opcode, nodes)
@@ -204,31 +234,127 @@ module Furnace::AVM2
204
234
  alias :stmt_return_value :stmt_return
205
235
  alias :stmt_return_void :stmt_return
206
236
 
237
+ def stmt_try(opcode, nodes)
238
+ body, *handlers = opcode.children
239
+
240
+ nodes << token(TryToken, [
241
+ stmt_block(body, continuation: true),
242
+ ])
243
+
244
+ handlers.each_with_index do |handler, index|
245
+ block = within_meta_scope do
246
+ stmt_block(handler.children.last, continuation: index < handlers.size - 1)
247
+ end
248
+
249
+ if handler.type == :catch
250
+ type, variable, = handler.children
251
+
252
+ if type
253
+ filter_node = token(CatchFilterToken, [
254
+ token(MultinameToken, variable.metadata[:origin]),
255
+ token(MultinameToken, type.metadata[:origin])
256
+ ])
257
+ else
258
+ filter_node = token(MultinameToken, variable.metadata[:origin])
259
+ end
260
+
261
+ nodes << token(CatchToken, filter_node, block)
262
+ elsif handler.type == :finally
263
+ nodes << token(FinallyToken, block)
264
+ else
265
+ raise "unknown handler type #{handler.type}"
266
+ end
267
+ end
268
+ end
269
+
270
+ def stmt_switch(opcode, nodes)
271
+ condition, body = opcode.children
272
+
273
+ nodes << token(SwitchToken,
274
+ handle_expression(condition),
275
+ stmt_block(body))
276
+ end
277
+
278
+ def stmt_default(opcode, nodes)
279
+ nodes << token(CaseToken, nil)
280
+ end
281
+
282
+ def stmt_case(opcode, nodes)
283
+ value, = opcode.children
284
+ nodes << token(CaseToken, handle_expression(value))
285
+ end
286
+
287
+ def within_meta_scope
288
+ @metascopes.push @scopes
289
+ @scopes = []
290
+
291
+ yield
292
+ ensure
293
+ @scopes = @metascopes.pop
294
+ end
295
+
296
+ KnownPushScopeMatcher = AST::Matcher.new do
297
+ [:push_scope,
298
+ either[
299
+ [:get_local, capture(:get_local)],
300
+ [:set_local, capture(:set_activation_local),
301
+ [:new_activation]]
302
+ ]
303
+ ]
304
+ end
305
+
207
306
  def stmt_push_scope(opcode, nodes)
208
307
  if @options[:global_code]
209
308
  @scopes.push opcode.children.first
309
+ elsif captures = KnownPushScopeMatcher.match(opcode)
310
+ if captures[:get_local] == 0
311
+ @scopes << :this
312
+ elsif !@activation_local.nil? &&
313
+ captures[:get_local] == @activation_local
314
+ @scopes << :activation
315
+ elsif @catch_scopes.include? captures[:get_local]
316
+ @scopes << @catch_scopes[captures[:get_local]]
317
+ elsif captures[:set_activation_local]
318
+ if @activation_local
319
+ raise "more than one activation per function is not supported"
320
+ end
321
+
322
+ @scopes << :activation
323
+ @activation_local = captures[:set_activation_local]
324
+ else
325
+ raise "abnormal matched pushscope in nonglobal code: #{captures.inspect}"
326
+ end
210
327
  else
211
- raise "pushscope in nonglobal code"
328
+ raise "abnormal pushscope in nonglobal code"
212
329
  end
213
330
  end
214
331
 
215
332
  def stmt_pop_scope(opcode, nodes)
216
333
  if @options[:global_code]
217
334
  @scopes.pop
335
+ elsif @scopes.any?
336
+ @scopes.pop
218
337
  else
219
- raise "popscope in nonglobal code"
338
+ raise "popscope with empty stack"
220
339
  end
221
340
  end
222
341
 
223
342
  # Expressions
224
343
 
225
344
  def handle_expression(opcode)
226
- expression(opcode)
345
+ expression(opcode, true)
227
346
  rescue ExpressionNotRecognized => e
228
347
  raise ExpressionNotRecognized.new(opcode, e.opcode)
229
348
  end
230
349
 
231
- def expression(opcode)
350
+ def expression(opcode, toplevel=false)
351
+ if toplevel
352
+ handler = :"expr_#{opcode.type}_toplevel"
353
+ if respond_to?(handler) && node = send(handler, opcode)
354
+ return node
355
+ end
356
+ end
357
+
232
358
  handler = :"expr_#{opcode.type}"
233
359
  if respond_to?(handler) && node = send(handler, opcode)
234
360
  node
@@ -287,59 +413,63 @@ module Furnace::AVM2
287
413
 
288
414
  ## Locals
289
415
 
290
- def local_name(index)
416
+ def local_token(index)
291
417
  if index < 0
292
- "sp#{-index}"
418
+ token(VariableNameToken, "sp#{-index}")
293
419
  elsif index == 0
294
420
  if @options[:static]
295
- @options[:instance].name.name
421
+ token(VariableNameToken, @options[:instance].name.name)
296
422
  else
297
- "this"
423
+ token(ThisToken)
298
424
  end
299
425
  elsif index <= @method.param_count
300
426
  if @method.has_param_names?
301
- @method.param_names[index - 1]
427
+ token(VariableNameToken, @method.param_names[index - 1])
302
428
  else
303
- "param#{index - 1}"
429
+ token(VariableNameToken, "param#{index - 1}")
304
430
  end
305
431
  else
306
- "local#{index - @method.param_count - 1}"
432
+ token(VariableNameToken, "local#{index - @method.param_count - 1}")
307
433
  end
308
434
  end
309
435
 
310
436
  def expr_get_local(opcode)
311
437
  index, = opcode.children
312
- token(VariableNameToken, local_name(index))
438
+ local_token(index)
313
439
  end
314
440
 
315
- ActivationGetSlot = Matcher.new do
316
- [:get_slot,
317
- capture(:index),
318
- [:get_scope_object, 1]]
319
- end
320
-
321
- GlobalGetSlot = Matcher.new do
441
+ GetSlot = Matcher.new do
322
442
  [:get_slot,
323
443
  capture(:index),
324
444
  either[
325
- [:get_global_scope], # normalized form
326
- [:get_scope_object, 0] # not emitted by ASC
327
- ]]
445
+ [:get_scope_object, capture(:scope_pos)],
446
+ [:get_global_scope]
447
+ ]
448
+ ]
328
449
  end
329
450
 
330
451
  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]}")
452
+ if captures = GetSlot.match(opcode)
453
+ scope = @scopes[captures[:scope_pos] || 0]
454
+
455
+ if scope.is_a? Hash
456
+ # treat as an inline scope, probably from an eh
457
+ if scope[captures[:index]]
458
+ var = scope[captures[:index]]
459
+ token(VariableNameToken, var.metadata[:origin].name)
460
+ end
461
+ elsif @closure_slots && scope == :activation
462
+ # treat as a local variable
463
+ slot = @closure_slots[captures[:index]]
464
+ token(VariableNameToken, slot.name.name)
465
+ elsif scope == :this
466
+ # treat as a global property
467
+ if slot = @global_slots[captures[:index]]
468
+ get_name(nil, slot.name.to_astlet)
469
+ else
470
+ token(PropertyNameToken,
471
+ "$__GLOBAL_#{captures[:index]}")
472
+ end
343
473
  end
344
474
  end
345
475
  end
@@ -351,7 +481,7 @@ module Furnace::AVM2
351
481
  :string => 'String',
352
482
  :double => 'Number',
353
483
  :object => 'Object',
354
- :bool => 'Boolean',
484
+ :boolean => 'Boolean',
355
485
  :true => 'Boolean',
356
486
  :false => 'Boolean',
357
487
  }
@@ -372,24 +502,38 @@ module Furnace::AVM2
372
502
  [:coerce, [:q, "XML"], any]
373
503
  end
374
504
 
375
- def expr_set_var(name, value, type, declare)
505
+ def expr_set_var(var, value, type, declare, toplevel)
376
506
  if declare
377
- token(LocalVariableToken, [
378
- token(VariableNameToken, name),
379
- type,
507
+ declaration =
508
+ token(LocalVariableToken, [
509
+ var,
510
+ type
511
+ ])
512
+ end
513
+
514
+ if declare && toplevel
515
+ declaration.children <<
380
516
  token(InitializationToken, [
381
517
  expr(value)
382
518
  ])
383
- ])
519
+
520
+ declaration
384
521
  else
522
+ if declare
523
+ @collected_vars <<
524
+ token(StatementToken, [
525
+ declaration
526
+ ])
527
+ end
528
+
385
529
  token(AssignmentToken, [
386
- token(VariableNameToken, name),
387
- expr(value)
530
+ var,
531
+ parenthesize(expr(value))
388
532
  ])
389
533
  end
390
534
  end
391
535
 
392
- def expr_set_local(opcode)
536
+ def expr_set_local(opcode, toplevel=false)
393
537
  index, value = opcode.children
394
538
  if IMMEDIATE_TYPE_MAP.include?(value.type)
395
539
  type = token(TypeToken, [
@@ -406,83 +550,109 @@ module Furnace::AVM2
406
550
  value = value.children.last
407
551
  end
408
552
 
409
- expr_set_var(local_name(index), value, type, !@locals.include?(index))
553
+ expr_set_var(local_token(index), value, type, !@locals.include?(index), toplevel)
410
554
  ensure
411
555
  @locals.add index if index
412
556
  end
413
557
 
414
- ActivationSetSlot = Matcher.new do
415
- [:set_slot,
416
- capture(:index),
417
- [:get_scope_object, 1],
418
- capture(:value)]
558
+ def expr_set_local_toplevel(opcode)
559
+ expr_set_local(opcode, true)
419
560
  end
420
561
 
421
- GlobalSetSlot = Matcher.new do
562
+ SetSlot = Matcher.new do
422
563
  [:set_slot,
423
564
  capture(:index),
424
565
  either[
425
- [:get_global_scope], # normalized form
426
- [:get_scope_object, 0] # not emitted by ASC
566
+ [:get_scope_object, capture(:scope_pos)],
567
+ [:get_global_scope],
568
+ [:push_scope,
569
+ [:set_local, capture(:catch_local),
570
+ [:new_catch, capture(:catch_id)]]]
427
571
  ],
428
- capture(:value)]
572
+ capture(:value)
573
+ ]
429
574
  end
430
575
 
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]
576
+ ExceptionVariable = Matcher.new do
577
+ [:exception_variable, capture(:variable)]
578
+ end
436
579
 
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
580
+ def expr_set_slot(opcode, toplevel=false)
581
+ if captures = SetSlot.match(opcode)
582
+ scope = @scopes[captures[:scope_pos] || 0]
441
583
 
442
- expr
443
- elsif captures = GlobalSetSlot.match(opcode)
444
- # treat as a global property
445
- index, value = captures.values_at(:index, :value)
584
+ if captures[:catch_id]
585
+ stmt = nil
446
586
 
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
587
+ unless ExceptionVariable.match captures[:value], captures
588
+ stmt = token(SupplementaryCommentToken,
589
+ "Non-matching catch_id and catch id", [])
590
+ end
453
591
 
454
- token(AssignmentToken, [
455
- name,
456
- expr(value)
457
- ])
592
+ scope = {
593
+ captures[:index] => captures[:variable]
594
+ }
595
+
596
+ @catch_scopes[captures[:catch_local]] = scope
597
+ @scopes << scope
598
+
599
+ throw :skip unless stmt
600
+ stmt
601
+ elsif @closure_slots && scope == :activation
602
+ # treat as a local variable
603
+ index, value = captures.values_at(:index, :value)
604
+ slot = @closure_slots[index]
605
+
606
+ type = type_token(slot.type.to_astlet) if slot.type
607
+ expr = expr_set_var(token(VariableNameToken, slot.name.name),
608
+ value, type,
609
+ !@closure_locals.include?(index), toplevel)
610
+ @closure_locals.add index
611
+
612
+ expr
613
+ elsif scope == :this
614
+ # treat as a global property
615
+ index, value = captures.values_at(:index, :value)
616
+
617
+ if slot = @global_slots[index]
618
+ name = get_name(nil, slot.name.to_astlet)
619
+ else
620
+ name = token(PropertyNameToken, "$__GLOBAL_#{index}")
621
+ end
622
+
623
+ token(AssignmentToken, [
624
+ name,
625
+ parenthesize(expr(value))
626
+ ])
627
+ end
458
628
  end
459
629
  end
460
630
 
631
+ def expr_set_slot_toplevel(opcode)
632
+ expr_set_slot(opcode, true)
633
+ end
634
+
461
635
  ## Arithmetics
462
636
 
463
637
  INPLACE_OPERATOR_MAP = {
464
638
  :inc_local => :"++",
465
- :inc_local_i => :"++",
466
639
  :dec_local => :"--",
467
- :dec_local_i => :"--",
468
640
  }
469
641
 
470
642
  def expr_inplace_arithmetic(opcode)
471
- token(UnaryPostOperatorToken, [
472
- token(VariableNameToken, local_name(*opcode.children)),
473
- ], INPLACE_OPERATOR_MAP[opcode.type])
643
+ index, = opcode.children
644
+
645
+ token(UnaryPostOperatorToken,
646
+ local_token(index),
647
+ INPLACE_OPERATOR_MAP[opcode.type])
474
648
  end
475
649
 
476
- alias :expr_inc_local :expr_inplace_arithmetic
477
- alias :expr_inc_local_i :expr_inplace_arithmetic
478
- alias :expr_dec_local :expr_inplace_arithmetic
479
- alias :expr_dec_local_i :expr_inplace_arithmetic
650
+ alias :expr_inc_local :expr_inplace_arithmetic
651
+ alias :expr_dec_local :expr_inplace_arithmetic
480
652
 
481
653
  PSEUDO_OPERATOR_MAP = {
482
654
  :increment => [:"+", 1],
483
- :increment_i => [:"+", 1],
484
655
  :decrement => [:"-", 1],
485
- :decrement_i => [:"-", 1],
486
656
  }
487
657
 
488
658
  def expr_pseudo_arithmetic(opcode)
@@ -497,25 +667,38 @@ module Furnace::AVM2
497
667
  end
498
668
 
499
669
  alias :expr_increment :expr_pseudo_arithmetic
500
- alias :expr_increment_i :expr_pseudo_arithmetic
501
670
  alias :expr_decrement :expr_pseudo_arithmetic
502
- alias :expr_decrement_i :expr_pseudo_arithmetic
671
+
672
+ def expr_prepost_incdec_local(opcode)
673
+ index, = opcode.children
674
+ lvar = local_token(index)
675
+
676
+ if opcode.type == :post_increment_local
677
+ token(UnaryPostOperatorToken, lvar, "++")
678
+ elsif opcode.type == :post_decrement_local
679
+ token(UnaryPostOperatorToken, lvar, "--")
680
+ elsif opcode.type == :pre_increment_local
681
+ token(UnaryOperatorToken, lvar, "++")
682
+ elsif opcode.type == :pre_decrement_local
683
+ token(UnaryOperatorToken, lvar, "--")
684
+ end
685
+ end
686
+ alias :expr_post_increment_local :expr_prepost_incdec_local
687
+ alias :expr_post_decrement_local :expr_prepost_incdec_local
688
+ alias :expr_pre_increment_local :expr_prepost_incdec_local
689
+ alias :expr_pre_decrement_local :expr_prepost_incdec_local
503
690
 
504
691
  OPERATOR_MAP = {
505
692
  :and => :"&&",
506
693
  :or => :"||",
507
694
 
508
695
  :add => :"+",
509
- :add_i => :"+",
510
696
  :subtract => :"-",
511
- :subtract_i => :"-",
512
697
  :multiply => :"*",
513
- :multiply_i => :"*",
514
698
  :divide => :"/",
515
699
  :modulo => :"%",
516
700
  :multiply => :"*",
517
701
  :negate => :"-",
518
- :negate_i => :"-",
519
702
 
520
703
  :! => :!,
521
704
  :> => :>,
@@ -541,7 +724,7 @@ module Furnace::AVM2
541
724
  insides = parenthesize_each(exprs(opcode.children))
542
725
 
543
726
  if insides.count == 1
544
- token(UnaryOperatorToken, insides, OPERATOR_MAP[opcode.type])
727
+ token(UnaryOperatorToken, insides.first, OPERATOR_MAP[opcode.type])
545
728
  elsif insides.count == 2
546
729
  token(BinaryOperatorToken, insides, OPERATOR_MAP[opcode.type])
547
730
  else
@@ -554,11 +737,8 @@ module Furnace::AVM2
554
737
  alias :expr_or :expr_arithmetic
555
738
 
556
739
  alias :expr_add :expr_arithmetic
557
- alias :expr_add_i :expr_arithmetic
558
740
  alias :expr_subtract :expr_arithmetic
559
- alias :expr_subtract_i :expr_arithmetic
560
741
  alias :expr_multiply :expr_arithmetic
561
- alias :expr_multiply_i :expr_arithmetic
562
742
  alias :expr_divide :expr_arithmetic
563
743
  alias :expr_modulo :expr_arithmetic
564
744
  alias :expr_negate :expr_arithmetic
@@ -601,11 +781,14 @@ module Furnace::AVM2
601
781
  ],
602
782
  [
603
783
  [:find_property_strict,
604
- [:m, [:set, "*"], any]],
784
+ [:m, [:set, any], any]],
605
785
  capture(:multiname)
606
786
  ],
607
787
  [
608
- [:get_scope_object, 0],
788
+ either[
789
+ [:get_scope_object, 0],
790
+ [:get_global_scope]
791
+ ],
609
792
  capture(:multiname)
610
793
  ],
611
794
  ],
@@ -644,13 +827,13 @@ module Furnace::AVM2
644
827
  if captures = PropertyGlobal.match(opcode)
645
828
  token(AssignmentToken, [
646
829
  get_name(nil, captures[:multiname]),
647
- expr(*captures[:arguments])
830
+ parenthesize(expr(*captures[:arguments]))
648
831
  ])
649
832
  else
650
833
  subject, multiname, value, = opcode.children
651
834
  token(AssignmentToken, [
652
835
  get_name(expr(subject), multiname),
653
- expr(value)
836
+ parenthesize(expr(value))
654
837
  ])
655
838
  end
656
839
  end
@@ -661,7 +844,7 @@ module Furnace::AVM2
661
844
 
662
845
  stmt = token(AssignmentToken, [
663
846
  get_name(token(SuperToken), multiname),
664
- expr(value)
847
+ parenthesize(expr(value))
665
848
  ])
666
849
 
667
850
  unless This.match subject
@@ -673,28 +856,27 @@ module Furnace::AVM2
673
856
  stmt
674
857
  end
675
858
 
676
- def expr_delete_property(opcode)
677
- subject, multiname, = opcode.children
678
- token(DeleteToken, [get_name(expr(subject), multiname)])
679
- end
680
-
681
- def expr_do_property(opcode, klass)
859
+ def expr_do_property(opcode, klass, has_args)
682
860
  if captures = PropertyGlobal.match(opcode)
683
861
  token(klass, [
684
862
  get_name(nil, captures[:multiname]),
685
- token(ArgumentsToken, exprs(captures[:arguments]))
863
+ (token(ArgumentsToken, exprs(captures[:arguments])) if has_args)
686
864
  ])
687
865
  else
688
866
  subject, multiname, *args = opcode.children
689
867
  token(klass, [
690
868
  get_name(expr(subject), multiname),
691
- token(ArgumentsToken, exprs(args))
869
+ (token(ArgumentsToken, exprs(args)) if has_args)
692
870
  ])
693
871
  end
694
872
  end
695
873
 
874
+ def expr_delete_property(opcode)
875
+ expr_do_property(opcode, DeleteToken, false)
876
+ end
877
+
696
878
  def expr_call_property(opcode)
697
- expr_do_property(opcode, CallToken)
879
+ expr_do_property(opcode, CallToken, true)
698
880
  end
699
881
  alias :expr_call_property_lex :expr_call_property
700
882
 
@@ -711,7 +893,7 @@ module Furnace::AVM2
711
893
  ## Object creation
712
894
 
713
895
  def expr_construct_property(opcode)
714
- expr_do_property(opcode, NewToken)
896
+ expr_do_property(opcode, NewToken, true)
715
897
  end
716
898
 
717
899
  def expr_construct(opcode)
@@ -761,7 +943,7 @@ module Furnace::AVM2
761
943
  token(CallToken, [
762
944
  subject_token,
763
945
  token(ArgumentsToken, [
764
- token(VariableNameToken, local_name(0)),
946
+ local_token(0),
765
947
  *exprs(args)
766
948
  ])
767
949
  ])
@@ -880,6 +1062,11 @@ module Furnace::AVM2
880
1062
  xml_expr(node.children.first)
881
1063
  end
882
1064
 
1065
+ def expr_check_filter(node)
1066
+ content, = node.children
1067
+ expr(content)
1068
+ end
1069
+
883
1070
  private
884
1071
 
885
1072
  def token(klass, *args)
@@ -910,14 +1097,24 @@ module Furnace::AVM2
910
1097
  parenthesize(subject),
911
1098
  expr(multiname.children.last)
912
1099
  ])
1100
+ elsif @scopes[0] == :this
1101
+ token(IndexToken, [
1102
+ local_token(0),
1103
+ expr(multiname.children.last)
1104
+ ])
913
1105
  else
914
- token(CommentToken, "%%type #{origin} with no subject")
1106
+ token(CommentToken, "%%type #{origin} with no subject and non-this global-scope")
915
1107
  end
916
1108
  when :RTQName, :RTQNameA
917
1109
  token(RTNameToken, [
918
1110
  parenthesize(expr(multiname.children.first)),
919
1111
  token(PropertyNameToken, origin.name)
920
1112
  ])
1113
+ when :RTQNameL, :RTQNameLA
1114
+ token(RTNameToken, [
1115
+ parenthesize(expr(multiname.children.first)),
1116
+ parenthesize(expr(multiname.children.last))
1117
+ ])
921
1118
  else
922
1119
  token(CommentToken, "%%type #{origin}")
923
1120
  end