Class: Furnace::AVM2::Decompiler

Inherits:
Object
  • Object
show all
Includes:
Furnace::AST, ABC, Tokens
Defined in:
lib/furnace-avm2/source/decompiler.rb

Defined Under Namespace

Classes: ExpressionNotRecognized

Constant Summary collapse

StaticProperty =
Matcher.new do
  [ either[:set_property, :init_property],
    [:find_property, capture(:property)],
    backref(:property),
    capture(:value)
  ]
end
KnownPushScopeMatcher =
AST::Matcher.new do
  [:push_scope,
    either[
      [:get_local, capture(:get_local)],
      [:set_local, capture(:set_activation_local),
        [:new_activation]]
    ]
  ]
end
GetSlot =
Matcher.new do
  [:get_slot,
    capture(:index),
    either[
      [:get_scope_object, capture(:scope_pos)],
      [:get_global_scope]
    ]
  ]
end
IMMEDIATE_TYPE_MAP =
{
  :any       => '*',
  :integer   => 'int',
  :unsigned  => 'uint',
  :string    => 'String',
  :double    => 'Number',
  :object    => 'Object',
  :boolean   => 'Boolean',
  :true      => 'Boolean',
  :false     => 'Boolean',
}
XmlLiteralPreMatcher =
Matcher.new do
  [:coerce, [:q, "XML"], any]
end
SetSlot =
Matcher.new do
  [:set_slot,
    capture(:index),
    either[
      [:get_scope_object, capture(:scope_pos)],
      [:get_global_scope],
      [:push_scope,
        [:set_local, capture(:catch_local),
          [:new_catch, capture(:catch_id)]]]
    ],
    capture(:value)
  ]
end
ExceptionVariable =
Matcher.new do
  [:exception_variable, capture(:variable)]
end
INPLACE_OPERATOR_MAP =

Arithmetics

{
  :inc_local   => :"++",
  :dec_local   => :"--",
}
PSEUDO_OPERATOR_MAP =
{
  :increment   => [:"+", 1],
  :decrement   => [:"-", 1],
}
PrePostIncDecSlot =
Matcher.new do
  [any,
    capture(:index),
    either[
      [:get_global_scope],
      [:get_scope_object, capture(:scope_pos)]
    ]
  ]
end
OPERATOR_MAP =
{
  :and         => :"&&",
  :or          => :"||",

  :add         => :"+",
  :subtract    => :"-",
  :multiply    => :"*",
  :divide      => :"/",
  :modulo      => :"%",
  :multiply    => :"*",
  :negate      => :"-",

  :!           => :!,
  :>           => :>,
  :>=          => :>=,
  :<           => :<,
  :<=          => :<=,
  :==          => :==,
  :===         => :===,
  :!=          => :!=,
  :"!=="       => :"!==",

  :bit_and     => :"&",
  :bit_or      => :"|",
  :bit_xor     => :"^",
  :bit_not     => :"~",
  :lshift      => :"<<",
  :rshift      => :">>",
  :urshift     => :">>>",
}
This =

Properties and objects

Matcher.new do
  [:get_local, 0]
end
PropertyGlobal =
Matcher.new do
  [any,
    either_multi[
      [
        [ either[:find_property, :find_property_strict],
              capture(:multiname)],
        backref(:multiname),
      ],
      [
        [:find_property_strict,
          [:m, [:set, any], any]],
        capture(:multiname)
      ],
      [
        either[
          [:get_scope_object, 0],
          [:get_global_scope]
        ],
        capture(:multiname)
      ],
    ],
    capture_rest(:arguments)]
end
CallThisGlobal =

See /src/java/macromedia/asc/semantics/CodeGenerator.java If this looks stupid to you, that’s because it IS stupid.

Matcher.new do
  [:get_global_scope]
end
CallThisLocal =
Matcher.new do
  [ either[:get_scope_object, :get_local], 0 ]
end
XmlLiteralMatcher =

FFFUUUUUUUUUU~~~

Matcher.new do
  [:coerce, [:q, "XML"],
    [:construct,
      either[
        [:get_lex, [:q, "XML"]],
        [:get_property,
          [:find_property_strict, [:q, "XML"]], [:q, "XML"]]
      ],
      capture(:body),
    ]
  ]
end

Constants included from ABC

ABC::AST, ABC::CFG

Instance Method Summary collapse

Constructor Details

#initialize(body, options) ⇒ Decompiler

Returns a new instance of Decompiler.



15
16
17
18
# File 'lib/furnace-avm2/source/decompiler.rb', line 15

def initialize(body, options)
  @body, @method, @options = body, body.method, options.dup
  @closure = @options.delete(:closure)
end

Instance Method Details

#decompileObject



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/furnace-avm2/source/decompiler.rb', line 20

def decompile
  begin
    @locals = Set.new([0]) + (1..@method.param_count).to_a
    @scopes = []
    @metascopes = []

    @catch_scopes = {}

    @closure_locals = Set.new

    @closure_slots  = {}
    @body.slot_traits.each do |trait|
      @closure_slots[trait.idx] = trait
    end

    @global_slots = @options[:global_slots] || {}

    stmt_block @body.code_to_nf,
      function: !@options[:global_code],
      closure:  @closure

  rescue Exception => e
    @failed = true

    comment = "'Ouch!' cried I, then died.\n" +
      "#{e.class}: #{e.message}\n" +
      "#{e.backtrace[0..5].map { |l| "    #{l}\n"}.join}" +
      "      ... et cetera\n"

    token(ScopeToken, [
      token(CommentToken, comment)
    ], function: !@options[:global_code],
       closure:  @closure)

  ensure
    if stat = @options[:stat]
      stat[:total] += 1

      if @failed
        stat[:failed]  += 1
      elsif @partial
        stat[:partial] += 1
      else
        stat[:success] += 1
      end
    end
  end
end

#decompose_static_initializerObject



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/furnace-avm2/source/decompiler.rb', line 77

def decompose_static_initializer
  properties = {}

  StaticProperty.find_all(@body.code_to_nf.children) do |match, captures|
    begin
      token = handle_expression(captures[:value])
    rescue ExpressionNotRecognized => e
      token = token(CommentToken, "Unrecognized static initializer:\n#{e.opcode.inspect}")
    end

    properties[captures[:property]] = token
  end

  properties
end

#expr_apply_type(opcode) ⇒ Object



1014
1015
1016
1017
1018
1019
1020
1021
# File 'lib/furnace-avm2/source/decompiler.rb', line 1014

def expr_apply_type(opcode)
  base, *args = opcode.children
  token(GenericTypeToken, [
    expr(base),
    token(GenericSpecializersToken,
      exprs(args))
  ])
end

#expr_arithmetic(opcode) ⇒ Object Also known as: expr_and, expr_or, expr_add, expr_subtract, expr_multiply, expr_divide, expr_modulo, expr_negate, expr_!, expr_>, expr_>=, expr_<, expr_<=, expr_==, expr_===, expr_!=, expr_!==, expr_bit_and, expr_bit_or, expr_bit_xor, expr_bit_not, expr_lshift, expr_rshift, expr_urshift



757
758
759
760
761
762
763
764
765
766
767
768
769
# File 'lib/furnace-avm2/source/decompiler.rb', line 757

def expr_arithmetic(opcode)
  if OPERATOR_MAP.include?(opcode.type)
    insides = parenthesize_each(exprs(opcode.children))

    if insides.count == 1
      token(UnaryOperatorToken, insides.first, OPERATOR_MAP[opcode.type])
    elsif insides.count == 2
      token(BinaryOperatorToken, insides, OPERATOR_MAP[opcode.type])
    else
      token(CommentToken, "Unexpected #{insides.count}-ary operator")
    end
  end
end

#expr_as_type_late(opcode) ⇒ Object

Types



998
999
1000
# File 'lib/furnace-avm2/source/decompiler.rb', line 998

def expr_as_type_late(opcode)
  token(AsToken, parenthesize_each(exprs(opcode.children)))
end

#expr_call(opcode) ⇒ Object



968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
# File 'lib/furnace-avm2/source/decompiler.rb', line 968

def expr_call(opcode)
  subject, this, *args = opcode.children

  subject_token = token(AccessToken, [
    parenthesize(expr(subject)),
    token(PropertyNameToken, "call")
  ])

  if CallThisGlobal.match(this) || CallThisLocal.match(this)
    # FUCK YOU!
    token(CallToken, [
      subject_token,
      token(ArgumentsToken, [
        local_token(0),
        *exprs(args)
      ])
    ])
  else
    token(CallToken, [
      subject_token,
      token(ArgumentsToken, exprs([
        this,
        *args
      ]))
    ])
  end
end

#expr_call_property(opcode) ⇒ Object Also known as: expr_call_property_lex



913
914
915
# File 'lib/furnace-avm2/source/decompiler.rb', line 913

def expr_call_property(opcode)
  expr_do_property(opcode, CallToken, true)
end

#expr_call_super(opcode) ⇒ Object



918
919
920
921
922
923
924
925
926
# File 'lib/furnace-avm2/source/decompiler.rb', line 918

def expr_call_super(opcode)
  subject, multiname, *args = opcode.children
  if This.match subject
    token(CallToken, [
      get_name(token(SuperToken), multiname),
      token(ArgumentsToken, exprs(args))
    ])
  end
end

#expr_check_filter(node) ⇒ Object



1100
1101
1102
1103
# File 'lib/furnace-avm2/source/decompiler.rb', line 1100

def expr_check_filter(node)
  content, = node.children
  expr(content)
end

#expr_coerce(opcode) ⇒ Object



1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
# File 'lib/furnace-avm2/source/decompiler.rb', line 1063

def expr_coerce(opcode)
  if captures = XmlLiteralMatcher.match(opcode)
    # Oh, shit...
    token(XmlLiteralToken,
      xml_expr(captures[:body]))
  else
    typename, subject, = opcode.children
    expr(subject)
  end
end

#expr_construct(opcode) ⇒ Object



934
935
936
937
938
939
940
# File 'lib/furnace-avm2/source/decompiler.rb', line 934

def expr_construct(opcode)
  type, *args = opcode.children
  token(NewToken, [
    parenthesize(expr(type)),
    token(ArgumentsToken, exprs(args))
  ])
end

#expr_construct_property(opcode) ⇒ Object

Object creation



930
931
932
# File 'lib/furnace-avm2/source/decompiler.rb', line 930

def expr_construct_property(opcode)
  expr_do_property(opcode, NewToken, true)
end

#expr_construct_super(opcode) ⇒ Object



942
943
944
945
946
947
948
949
950
# File 'lib/furnace-avm2/source/decompiler.rb', line 942

def expr_construct_super(opcode)
  subject, *args = opcode.children
  if This.match subject
    token(CallToken, [
      token(SuperToken),
      token(ArgumentsToken, exprs(args))
    ])
  end
end

#expr_convert(opcode) ⇒ Object



1027
1028
1029
1030
1031
1032
1033
1034
1035
# File 'lib/furnace-avm2/source/decompiler.rb', line 1027

def expr_convert(opcode)
  type, subject = opcode.children
  token(CallToken, [
    token(ImmediateTypenameToken, IMMEDIATE_TYPE_MAP[type]),
    token(ArgumentsToken, [
      expr(subject)
    ])
  ])
end

#expr_delete_property(opcode) ⇒ Object



909
910
911
# File 'lib/furnace-avm2/source/decompiler.rb', line 909

def expr_delete_property(opcode)
  expr_do_property(opcode, DeleteToken, false)
end

#expr_do_property(opcode, klass, has_args) ⇒ Object



894
895
896
897
898
899
900
901
902
903
904
905
906
907
# File 'lib/furnace-avm2/source/decompiler.rb', line 894

def expr_do_property(opcode, klass, has_args)
  if captures = PropertyGlobal.match(opcode)
    token(klass, [
      get_name(nil, captures[:multiname]),
      (token(ArgumentsToken, exprs(captures[:arguments])) if has_args)
    ])
  else
    subject, multiname, *args = opcode.children
    token(klass, [
      get_name(expr(subject), multiname),
      (token(ArgumentsToken, exprs(args)) if has_args)
    ])
  end
end

#expr_get_lex(opcode) ⇒ Object



833
834
835
836
# File 'lib/furnace-avm2/source/decompiler.rb', line 833

def expr_get_lex(opcode)
  multiname, = opcode.children
  get_name(nil, multiname)
end

#expr_get_local(opcode) ⇒ Object



436
437
438
439
# File 'lib/furnace-avm2/source/decompiler.rb', line 436

def expr_get_local(opcode)
  index, = opcode.children
  local_token(index)
end

#expr_get_property(opcode) ⇒ Object



838
839
840
841
842
843
844
845
# File 'lib/furnace-avm2/source/decompiler.rb', line 838

def expr_get_property(opcode)
  if captures = PropertyGlobal.match(opcode)
    get_name(nil, captures[:multiname])
  else
    subject, multiname, = opcode.children
    get_name(expr(subject), multiname)
  end
end

#expr_get_slot(opcode) ⇒ Object



451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
# File 'lib/furnace-avm2/source/decompiler.rb', line 451

def expr_get_slot(opcode)
  if captures = GetSlot.match(opcode)
    scope = @scopes[captures[:scope_pos] || 0]

    if scope.is_a? Hash
      # treat as an inline scope, probably from an eh
      if scope[captures[:index]]
        var = scope[captures[:index]]
        token(VariableNameToken, var.[:origin].name)
      end
    elsif @closure_slots && scope == :activation
      # treat as a local variable
      slot = @closure_slots[captures[:index]]
      token(VariableNameToken, slot.name.name)
    elsif scope == :this
      # treat as a global property
      if slot = @global_slots[captures[:index]]
        get_name(nil, slot.name.to_astlet)
      else
        token(PropertyNameToken,
          "$__GLOBAL_#{captures[:index]}")
      end
    end
  end
end

#expr_get_super(opcode) ⇒ Object



847
848
849
850
851
852
853
854
855
856
857
858
859
# File 'lib/furnace-avm2/source/decompiler.rb', line 847

def expr_get_super(opcode)
  subject, multiname, = opcode.children

  stmt = get_name(token(SuperToken), multiname)

  unless This.match subject
    stmt = token(SupplementaryCommentToken,
      "subject != this: #{subject.inspect}",
      [ stmt ])
  end

  stmt
end

#expr_imm(opcode) ⇒ Object Also known as: expr_integer, expr_double, expr_null, expr_undefined, expr_true, expr_false

Immediates



376
377
378
379
380
381
382
383
# File 'lib/furnace-avm2/source/decompiler.rb', line 376

def expr_imm(opcode)
  case opcode.type
  when :integer, :double
    token(ImmediateToken, *opcode.children)
  when :null, :undefined, :true, :false
    token(ImmediateToken, opcode.type)
  end
end

#expr_in(opcode) ⇒ Object



799
800
801
# File 'lib/furnace-avm2/source/decompiler.rb', line 799

def expr_in(opcode)
  token(InToken, parenthesize_each(exprs(opcode.children)))
end

#expr_inplace_arithmetic(opcode) ⇒ Object Also known as: expr_inc_local, expr_dec_local



642
643
644
645
646
647
648
# File 'lib/furnace-avm2/source/decompiler.rb', line 642

def expr_inplace_arithmetic(opcode)
  index, = opcode.children

  token(UnaryPostOperatorToken,
    local_token(index),
    INPLACE_OPERATOR_MAP[opcode.type])
end

#expr_instance_of(opcode) ⇒ Object



1006
1007
1008
# File 'lib/furnace-avm2/source/decompiler.rb', line 1006

def expr_instance_of(opcode)
  token(InstanceOfToken, parenthesize_each(exprs(opcode.children)))
end

#expr_is_type_late(opcode) ⇒ Object



1002
1003
1004
# File 'lib/furnace-avm2/source/decompiler.rb', line 1002

def expr_is_type_late(opcode)
  token(IsToken, parenthesize_each(exprs(opcode.children)))
end

#expr_nan(opcode) ⇒ Object



396
397
398
# File 'lib/furnace-avm2/source/decompiler.rb', line 396

def expr_nan(opcode)
  token(ImmediateToken, "NaN")
end

#expr_new_array(opcode) ⇒ Object



400
401
402
# File 'lib/furnace-avm2/source/decompiler.rb', line 400

def expr_new_array(opcode)
  token(ArrayToken, exprs(opcode.children))
end

#expr_new_class(opcode) ⇒ Object



1023
1024
1025
# File 'lib/furnace-avm2/source/decompiler.rb', line 1023

def expr_new_class(opcode)
  throw :skip
end

#expr_new_function(opcode) ⇒ Object

Closures



1039
1040
1041
1042
1043
1044
1045
# File 'lib/furnace-avm2/source/decompiler.rb', line 1039

def expr_new_function(opcode)
  index, = opcode.children
  body = @method.root.method_body_at(index)

  token(ClosureToken,
    body)
end

#expr_new_object(opcode) ⇒ Object



404
405
406
407
408
409
410
411
412
# File 'lib/furnace-avm2/source/decompiler.rb', line 404

def expr_new_object(opcode)
  token(ObjectToken, opcode.children.
    each_slice(2).map do |key, value|
      token(ObjectPairToken, [
        expr(key),
        expr(value)
      ])
    end)
end

#expr_prepost_incdec_local(opcode) ⇒ Object Also known as: expr_post_increment_local, expr_post_decrement_local, expr_pre_increment_local, expr_pre_decrement_local



672
673
674
675
676
677
678
679
680
681
682
683
684
685
# File 'lib/furnace-avm2/source/decompiler.rb', line 672

def expr_prepost_incdec_local(opcode)
  index, = opcode.children
  lvar = local_token(index)

  if opcode.type == :post_increment_local
    token(UnaryPostOperatorToken, lvar, "++")
  elsif opcode.type == :post_decrement_local
    token(UnaryPostOperatorToken, lvar, "--")
  elsif opcode.type == :pre_increment_local
    token(UnaryOperatorToken, lvar, "++")
  elsif opcode.type == :pre_decrement_local
    token(UnaryOperatorToken, lvar, "--")
  end
end

#expr_prepost_incdec_slot(opcode) ⇒ Object Also known as: expr_post_increment_slot, expr_post_decrement_slot, expr_pre_increment_slot, expr_pre_decrement_slot



701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
# File 'lib/furnace-avm2/source/decompiler.rb', line 701

def expr_prepost_incdec_slot(opcode)
  if captures = PrePostIncDecSlot.match(opcode)
    scope = @scopes[captures[:scope_pos] || 0]

    if @closure_slots && scope == :activation
      slot_trait = @closure_slots[captures[:index]]
      slot = token(VariableNameToken, slot_trait.name.name)

      if opcode.type == :post_increment_slot
        token(UnaryPostOperatorToken, slot, "++")
      elsif opcode.type == :post_decrement_slot
        token(UnaryPostOperatorToken, slot, "--")
      elsif opcode.type == :pre_increment_slot
        token(UnaryOperatorToken, slot, "++")
      elsif opcode.type == :pre_decrement_slot
        token(UnaryOperatorToken, slot, "--")
      end
    end
  end
end

#expr_pseudo_arithmetic(opcode) ⇒ Object Also known as: expr_increment, expr_decrement



658
659
660
661
662
663
664
665
666
667
# File 'lib/furnace-avm2/source/decompiler.rb', line 658

def expr_pseudo_arithmetic(opcode)
  inside = expr(*opcode.children)
  inside = token(ParenthesesToken, [inside]) if inside.complex?

  operator, other_value = PSEUDO_OPERATOR_MAP[opcode.type]
  token(BinaryOperatorToken, [
    inside,
    token(ImmediateToken, other_value)
  ], operator)
end

#expr_set_local(opcode, toplevel = false) ⇒ Object



536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
# File 'lib/furnace-avm2/source/decompiler.rb', line 536

def expr_set_local(opcode, toplevel=false)
  index, value = opcode.children
  if IMMEDIATE_TYPE_MAP.include?(value.type)
    type = token(TypeToken, [
      token(ImmediateTypenameToken, IMMEDIATE_TYPE_MAP[value.type])
    ])
  elsif XmlLiteralPreMatcher.match value
    # XML literals work through expr_coerce
    type  = type_token(value.children.first)
    value = value
  elsif value.type == :coerce || value.type == :convert
    # Don't emit spurious coercion; typed variables already
    # imply it
    type  = type_token(value.children.first)
    value = value.children.last
  end

  expr_set_var(local_token(index), value, type, !@locals.include?(index), toplevel)
ensure
  @locals.add index if index
end

#expr_set_local_toplevel(opcode) ⇒ Object



558
559
560
# File 'lib/furnace-avm2/source/decompiler.rb', line 558

def expr_set_local_toplevel(opcode)
  expr_set_local(opcode, true)
end

#expr_set_property(opcode) ⇒ Object Also known as: expr_init_property



861
862
863
864
865
866
867
868
869
870
871
872
873
874
# File 'lib/furnace-avm2/source/decompiler.rb', line 861

def expr_set_property(opcode)
  if captures = PropertyGlobal.match(opcode)
    token(AssignmentToken, [
      get_name(nil, captures[:multiname]),
      parenthesize(expr(*captures[:arguments]))
    ])
  else
    subject, multiname, value, = opcode.children
    token(AssignmentToken, [
      get_name(expr(subject), multiname),
      parenthesize(expr(value))
    ])
  end
end

#expr_set_slot(opcode, toplevel = false) ⇒ Object



580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
# File 'lib/furnace-avm2/source/decompiler.rb', line 580

def expr_set_slot(opcode, toplevel=false)
  if captures = SetSlot.match(opcode)
    scope = @scopes[captures[:scope_pos] || 0]

    if captures[:catch_id]
      stmt = nil

      unless ExceptionVariable.match captures[:value], captures
        stmt = token(SupplementaryCommentToken,
          "Non-matching catch_id and catch id", [])
      end

      scope = {
        captures[:index] => captures[:variable]
      }

      @catch_scopes[captures[:catch_local]] = scope
      @scopes << scope

      throw :skip unless stmt
      stmt
    elsif @closure_slots && scope == :activation
      # treat as a local variable
      index, value = captures.values_at(:index, :value)
      slot = @closure_slots[index]

      type = type_token(slot.type.to_astlet) if slot.type
      expr = expr_set_var(token(VariableNameToken, slot.name.name),
            value, type,
            !@closure_locals.include?(index), toplevel)
      @closure_locals.add index

      expr
    elsif scope == :this
      # treat as a global property
      index, value = captures.values_at(:index, :value)

      if slot = @global_slots[index]
        name = get_name(nil, slot.name.to_astlet)
      else
        name = token(PropertyNameToken, "$__GLOBAL_#{index}")
      end

      token(AssignmentToken, [
        name,
        parenthesize(expr(value))
      ])
    end
  end
end

#expr_set_slot_toplevel(opcode) ⇒ Object



631
632
633
# File 'lib/furnace-avm2/source/decompiler.rb', line 631

def expr_set_slot_toplevel(opcode)
  expr_set_slot(opcode, true)
end

#expr_set_super(opcode) ⇒ Object



877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
# File 'lib/furnace-avm2/source/decompiler.rb', line 877

def expr_set_super(opcode)
  subject, multiname, value = opcode.children

  stmt = token(AssignmentToken, [
    get_name(token(SuperToken), multiname),
    parenthesize(expr(value))
  ])

  unless This.match subject
    stmt = token(SupplementaryCommentToken,
      "subject != this: #{subject.inspect}",
      [ stmt ])
  end

  stmt
end

#expr_set_var(var, value, type, declare, toplevel) ⇒ Object



505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
# File 'lib/furnace-avm2/source/decompiler.rb', line 505

def expr_set_var(var, value, type, declare, toplevel)
  if declare
    declaration =
      token(LocalVariableToken, [
        var,
        type
      ])
  end

  if declare && toplevel
    declaration.children <<
      token(InitializationToken, [
        expr(value)
      ])

    declaration
  else
    if declare
      @collected_vars <<
        token(StatementToken, [
          declaration
        ])
    end

    token(AssignmentToken, [
      var,
      parenthesize(expr(value))
    ])
  end
end

#expr_string(opcode) ⇒ Object



391
392
393
394
# File 'lib/furnace-avm2/source/decompiler.rb', line 391

def expr_string(opcode)
  string, = opcode.children
  token(ImmediateToken, string.inspect)
end

#expr_ternary(opcode) ⇒ Object

Control flow



954
955
956
# File 'lib/furnace-avm2/source/decompiler.rb', line 954

def expr_ternary(opcode)
  token(TernaryOperatorToken, parenthesize_each(exprs(opcode.children)))
end

#expr_type_of(opcode) ⇒ Object



1010
1011
1012
# File 'lib/furnace-avm2/source/decompiler.rb', line 1010

def expr_type_of(opcode)
  token(TypeOfToken, exprs(opcode.children))
end

#expression(opcode, toplevel = false) ⇒ Object Also known as: expr



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/furnace-avm2/source/decompiler.rb', line 350

def expression(opcode, toplevel=false)
  if toplevel
    handler = :"expr_#{opcode.type}_toplevel"
    if respond_to?(handler) && node = send(handler, opcode)
      return node
    end
  end

  handler = :"expr_#{opcode.type}"
  if respond_to?(handler) && node = send(handler, opcode)
    node
  else
    raise ExpressionNotRecognized.new(nil, opcode)
  end
end

#expressions(opcodes) ⇒ Object Also known as: exprs



367
368
369
370
371
# File 'lib/furnace-avm2/source/decompiler.rb', line 367

def expressions(opcodes)
  opcodes.map do |opcode|
    expression opcode
  end
end

#handle_expression(opcode) ⇒ Object

Expressions



344
345
346
347
348
# File 'lib/furnace-avm2/source/decompiler.rb', line 344

def handle_expression(opcode)
  expression(opcode, true)
rescue ExpressionNotRecognized => e
  raise ExpressionNotRecognized.new(opcode, e.opcode)
end

#local_token(index) ⇒ Object

Locals



416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'lib/furnace-avm2/source/decompiler.rb', line 416

def local_token(index)
  if index < 0
    token(VariableNameToken, "sp#{-index}")
  elsif index == 0
    if @options[:static]
      token(VariableNameToken, @options[:instance].name.name)
    else
      token(ThisToken)
    end
  elsif index <= @method.param_count
    if @method.has_param_names?
      token(VariableNameToken, @method.param_names[index - 1])
    else
      token(VariableNameToken, "param#{index - 1}")
    end
  else
    token(VariableNameToken, "local#{index - @method.param_count - 1}")
  end
end

#stmt_begin(opcode, nodes) ⇒ Object



137
138
139
# File 'lib/furnace-avm2/source/decompiler.rb', line 137

def stmt_begin(opcode, nodes)
  nodes << stmt_block(opcode)
end

#stmt_block(block, options = {}) ⇒ Object

Statements



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/furnace-avm2/source/decompiler.rb', line 95

def stmt_block(block, options={})
  nodes = []
  last_index = 0

  block.children.each_with_index do |opcode, index|
    last_index = index

    if respond_to?(:"stmt_#{opcode.type}")
      send :"stmt_#{opcode.type}", opcode, nodes
    else
      catch(:skip) do
        @collected_vars = []
        stmt = token(StatementToken, [
          handle_expression(opcode)
        ])

        nodes.concat @collected_vars
        nodes.push stmt
      end
    end
  end

rescue ExpressionNotRecognized => e
  @partial = true

  comment = "Well, this is embarassing.\n\n" +
    "Expression recognizer failed at:\n" +
    "#{e.opcode.inspect}\n"

  comment << "\nRest of the code in this block:\n"
  block.children[last_index..-1].each do |opcode|
    comment << "#{opcode.inspect}\n"
  end

  nodes << CommentToken.new(@body, comment, @options)

ensure
  if $!.nil? || $!.is_a?(ExpressionNotRecognized)
    return token(ScopeToken, nodes, options)
  end
end

#stmt_break(opcode, nodes) ⇒ Object



213
214
215
216
217
218
# File 'lib/furnace-avm2/source/decompiler.rb', line 213

def stmt_break(opcode, nodes)
  label, = opcode.children
  nodes << token(BreakToken, [
    (token(LabelNameToken, label) if label)
  ])
end

#stmt_case(opcode, nodes) ⇒ Object



282
283
284
285
# File 'lib/furnace-avm2/source/decompiler.rb', line 282

def stmt_case(opcode, nodes)
  value, = opcode.children
  nodes << token(CaseToken, handle_expression(value))
end

#stmt_continue(opcode, nodes) ⇒ Object



220
221
222
223
224
225
# File 'lib/furnace-avm2/source/decompiler.rb', line 220

def stmt_continue(opcode, nodes)
  label, = opcode.children
  nodes << token(ContinueToken, [
    (token(LabelNameToken, label) if label)
  ])
end

#stmt_default(opcode, nodes) ⇒ Object



278
279
280
# File 'lib/furnace-avm2/source/decompiler.rb', line 278

def stmt_default(opcode, nodes)
  nodes << token(CaseToken, nil)
end

#stmt_do_while(opcode, nodes) ⇒ Object



175
176
177
178
179
180
181
# File 'lib/furnace-avm2/source/decompiler.rb', line 175

def stmt_do_while(opcode, nodes)
  condition, body = opcode.children

  nodes << token(DoWhileToken,
    stmt_block(body, continuation: true),
    handle_expression(condition))
end

#stmt_for(opcode, nodes) ⇒ Object Also known as: stmt_for_in, stmt_for_each_in



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/furnace-avm2/source/decompiler.rb', line 183

def stmt_for(opcode, nodes)
  value_reg, value_type, object_reg, body = opcode.children

  @locals.add(value_reg)

  if opcode.type == :for_in
    klass = ForToken
  elsif opcode.type == :for_each_in
    klass = ForEachToken
  end

  if @activation_local
    name = token(VariableNameToken, @closure_slots[value_reg].name.name)
  else
    name = local_token(value_reg)
  end

  nodes << token(klass,
    token(InToken, [
      token(LocalVariableToken, [
        name,
        type_token(value_type)
      ]),
      local_token(object_reg),
    ]),
    stmt_block(body))
end

#stmt_if(opcode, nodes) ⇒ Object



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/furnace-avm2/source/decompiler.rb', line 141

def stmt_if(opcode, nodes)
  condition, if_true, if_false = opcode.children

  nodes << token(IfToken, handle_expression(condition),
    stmt_block(if_true, continuation: !if_false.nil?))

  if if_false
    first_child = if_false.children.first
    if if_false.children.count == 1 &&
          first_child.type == :if
      nodes << token(ElseToken,
        nil)
      stmt_if(first_child, nodes)
    else
      nodes << token(ElseToken,
        stmt_block(if_false))
    end
  end
end

#stmt_label(opcode, nodes) ⇒ Object



161
162
163
164
165
# File 'lib/furnace-avm2/source/decompiler.rb', line 161

def stmt_label(opcode, nodes)
  name, = opcode.children

  nodes << token(LabelDeclarationToken, name)
end

#stmt_pop_scope(opcode, nodes) ⇒ Object



332
333
334
335
336
337
338
339
340
# File 'lib/furnace-avm2/source/decompiler.rb', line 332

def stmt_pop_scope(opcode, nodes)
  if @options[:global_code]
    @scopes.pop
  elsif @scopes.any?
    @scopes.pop
  else
    raise "popscope with empty stack"
  end
end

#stmt_push_scope(opcode, nodes) ⇒ Object



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/furnace-avm2/source/decompiler.rb', line 306

def stmt_push_scope(opcode, nodes)
  if @options[:global_code]
    @scopes.push opcode.children.first
  elsif captures = KnownPushScopeMatcher.match(opcode)
    if captures[:get_local] == 0
      @scopes << :this
    elsif !@activation_local.nil? &&
        captures[:get_local] == @activation_local
      @scopes << :activation
    elsif @catch_scopes.include? captures[:get_local]
      @scopes << @catch_scopes[captures[:get_local]]
    elsif captures[:set_activation_local]
      if @activation_local
        raise "more than one activation per function is not supported"
      end

      @scopes << :activation
      @activation_local = captures[:set_activation_local]
    else
      raise "abnormal matched pushscope in nonglobal code: #{captures.inspect}"
    end
  else
    raise "abnormal pushscope in nonglobal code"
  end
end

#stmt_return(opcode, nodes) ⇒ Object Also known as: stmt_return_value, stmt_return_void



231
232
233
# File 'lib/furnace-avm2/source/decompiler.rb', line 231

def stmt_return(opcode, nodes)
  nodes << token(ReturnToken, exprs(opcode.children))
end

#stmt_switch(opcode, nodes) ⇒ Object



270
271
272
273
274
275
276
# File 'lib/furnace-avm2/source/decompiler.rb', line 270

def stmt_switch(opcode, nodes)
  condition, body = opcode.children

  nodes << token(SwitchToken,
    handle_expression(condition),
    stmt_block(body))
end

#stmt_throw(opcode, nodes) ⇒ Object



227
228
229
# File 'lib/furnace-avm2/source/decompiler.rb', line 227

def stmt_throw(opcode, nodes)
  nodes << token(ThrowToken, exprs(opcode.children))
end

#stmt_try(opcode, nodes) ⇒ Object



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/furnace-avm2/source/decompiler.rb', line 237

def stmt_try(opcode, nodes)
  body, *handlers = opcode.children

  nodes << token(TryToken, [
    stmt_block(body, continuation: true),
  ])

  handlers.each_with_index do |handler, index|
    block = within_meta_scope do
      stmt_block(handler.children.last, continuation: index < handlers.size - 1)
    end

    if handler.type == :catch
      type, variable, = handler.children

      if type
        filter_node = token(CatchFilterToken, [
          token(MultinameToken, variable.[:origin]),
          token(MultinameToken, type.[:origin])
        ])
      else
        filter_node = token(MultinameToken, variable.[:origin])
      end

      nodes << token(CatchToken, filter_node, block)
    elsif handler.type == :finally
      nodes << token(FinallyToken, block)
    else
      raise "unknown handler type #{handler.type}"
    end
  end
end

#stmt_while(opcode, nodes) ⇒ Object



167
168
169
170
171
172
173
# File 'lib/furnace-avm2/source/decompiler.rb', line 167

def stmt_while(opcode, nodes)
  condition, body = opcode.children

  nodes << token(WhileToken,
    handle_expression(condition),
    stmt_block(body))
end

#type_token(type) ⇒ Object



489
490
491
492
493
494
495
496
497
498
499
# File 'lib/furnace-avm2/source/decompiler.rb', line 489

def type_token(type)
  if IMMEDIATE_TYPE_MAP.include?(type)
    token(TypeToken, [
      token(ImmediateTypenameToken, IMMEDIATE_TYPE_MAP[type])
    ])
  else
    token(TypeToken, [
      token(MultinameToken, type.[:origin])
    ])
  end
end

#within_meta_scopeObject



287
288
289
290
291
292
293
294
# File 'lib/furnace-avm2/source/decompiler.rb', line 287

def within_meta_scope
  @metascopes.push @scopes
  @scopes = []

  yield
ensure
  @scopes = @metascopes.pop
end

#xml_add(node) ⇒ Object



1086
1087
1088
1089
1090
# File 'lib/furnace-avm2/source/decompiler.rb', line 1086

def xml_add(node)
  node.children.map do |child|
    xml_expr child
  end.join
end

#xml_esc_xattr(node) ⇒ Object



1092
1093
1094
# File 'lib/furnace-avm2/source/decompiler.rb', line 1092

def xml_esc_xattr(node)
  xml_expr(node.children.first)
end

#xml_esc_xelem(node) ⇒ Object



1096
1097
1098
# File 'lib/furnace-avm2/source/decompiler.rb', line 1096

def xml_esc_xelem(node)
  xml_expr(node.children.first)
end

#xml_expr(node) ⇒ Object



1074
1075
1076
1077
1078
1079
1080
# File 'lib/furnace-avm2/source/decompiler.rb', line 1074

def xml_expr(node)
  if respond_to?(:"xml_#{node.type}")
    send :"xml_#{node.type}", node
  else
    "{#{expr(node).to_text}}"
  end
end

#xml_string(node) ⇒ Object



1082
1083
1084
# File 'lib/furnace-avm2/source/decompiler.rb', line 1082

def xml_string(node)
  node.children.first
end