BytecodeHelpers < BasicDecorators {

##
# Bytecode Generation Helper Methods

var debug_mode: false
var overall_fail: new_label

g: self # For consistency

puts_string: |string|
  g.push_rubinius; g.push_literal(string); g.send(:puts, 1, true); g.pop

inspect_top:
  g.dup_top; g.push_rubinius; g.swap; g.send(:p, 1, true); g.pop

set_subject:   g.set_local  (0)
push_subject:  g.push_local (0)
set_idx:       g.set_local  (1)
push_idx:      g.push_local (1)
set_captures:  g.set_ivar   (:"@captures")
push_captures: g.push_ivar  (:"@captures")
set_memo_for:  |name| g.set_ivar  (:"@memo_for_"name"")
push_memo_for: |name| g.push_ivar (:"@memo_for_"name"")

pop_to_set_idx:
  g.set_idx; g.pop

push_temp_captures:
  g.push_captures; g.make_array(0); g.set_captures; g.pop

pop_to_reject_captures:
  g.set_captures; g.pop

pop_to_accept_captures:
  g.push_captures; g.send(:concat, 1); g.set_captures; g.pop

push_subject_at_idx:
  g.push_subject; g.push_idx; g.send(:at, 1)

push_subject_chr_at_idx:
  g.push_subject; g.push_idx; g.send(:chr_at, 1)

push_new_table:
  g.push_rubinius; g.find_const(:LookupTable); g.send(:new, 0)

# Set the memo ivar to a new hash if it doesn't already exist
memo_or_eq_new_hash: |name| {
  memo_exists_label = g.new_label

  g.push_memo_for(name)
  g.goto_if_true(memo_exists_label)
  g.push_new_table
  g.set_memo_for(name)
  g.pop # ivar
  memo_exists_label.set!
}

# Copy the top two values into the memo ivar for |name| at the current idx.
copy_result_to_memo: |name| {
  g.dup_top
  g.push_captures
  g.make_array(2) # [result_idx, result_captures]

  g.push_memo_for(name)
    g.swap # [result_idx, result_captures]
    g.push_idx # idx
    g.swap # TODO: use rotate
  g.send(:"[]=", 2)
  g.pop
}

# Goto the given |label| and push the result values if a memoized result
# exists for the given |name| at the current idx.
goto_if_memo_for_idx: |name, label| {
  skip_label = g.new_label

  g.push_memo_for(name)
    g.push_idx
  g.send(:"[]", 1)
  g.dup_top # dup the result to retain after goto test
  g.goto_if_nil(skip_label)

  # Shift result_idx onto the stack and swap with array
  g.shift_array; g.swap
  # Shift result_captures, set to @captures, and pop
  g.shift_array; g.set_captures; g.pop
  g.pop # pop the empty containing array
  g.goto(label)

  skip_label.set!
  g.pop
}

# Increment the idx local by the current number at the top of the stack
# Saves the new idx to @highest_idx ivar if it is highest
increment_idx: {
  g.push_idx
  g.send(:"+", 1)
  g.set_idx
  g.dup_top
  g.push_ivar(:"@highest_idx")
  g.send(:">", 1)
  not_highest_idx_label = g.new_label
  g.goto_if_false(not_highest_idx_label)
  g.set_ivar(:"@highest_idx")
  not_highest_idx_label.set!
  g.pop
}

# Begin a compiled parser method
rule_start: {
  # 2 argument: (subject, idx)
  g.required_args = 2
  g.total_args    = 2
  g.splat_index   = null

  g.local_count = 2
  g.local_names = [:subject, :idx]

  g.debug_mode && (
    g.push_idx; g.inspect_top; g.pop
    g.puts_string("trying.. "g.name"")
  )
}

# Wrap up a compiled parser method
rule_finish: {
  overall_done = g.new_label

  # end
  g.push_idx
  g.debug_mode && (
    g.push_idx; g.inspect_top; g.pop
    g.puts_string("SUCCESS! "g.name"")
  )
  g.goto(overall_done)

  # fail
  g.overall_fail.set!
  g.push_nil
  g.debug_mode && (
    g.push_idx; g.inspect_top; g.pop
    g.puts_string("failure: "g.name"")
  )

  overall_done.set!
  g.ret
  g.close
}

push_literal_array: |ary|
  ary.each |lit| { g.push_literal_or_array(lit) }; make_array(ary.size)

push_literal_or_array: |item|
  item.is_a?(Array)
    &? push_literal_array(item)
    ?? push_literal(item)

capture: |*metadata| {
  g.push_captures
    g.push_idx
    g.push_literal_array(metadata)
    g.make_array(2)
  g.send(:push, 1)
  g.pop
}

}