#
# z3 opcode functions
# a lot are valid for other version too
#
# Most functions here implement a particular opcode function.
#
# On the first pass translation pass (just finding labels), they
# usually just make sure they read all the args (labels & return values
# aren't included in the opcode counts stored in the first byte of an opcode)
# and then return the new pc, plus any label found in the opcode, and
# a boolean saying whether it's a return-like opcode.
#
# On the second pass, each sub creates the PIR code translation of an opcode
# The translation routines then compile that code to run it.
#

# Numbers are stored as unsigned ints
# Change them to signed ints when doing addition, comparisons, etc.

#
# return the packed address of a subroutine
# z3 specific
#
.sub packed_addr :method
  .param int adr
  # V3 - real adr * 2
  adr *= 2
  .return(adr)
.end

##########
# ARITHMETIC
##########

# 2OP:20 14 add a b -> (result)
.sub zop_add :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass
  # get variable for result storage
  (args, pc) = self."decode_args"(im, pc, args, "v")
  unless pass goto done
    self."emit_binary_op"(args, "+", 1)
done:
  .return (args, pc, 0, 0)
.end

# 2OP:21 15 sub a b -> (result)
.sub zop_sub :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass
  # get variable for result storage
  (args, pc) = self."decode_args"(im, pc, args, "v")
  unless pass goto done
    self."emit_binary_op"(args, "-", 1)
done:
  .return (args, pc, 0, 0)
.end

# 2OP:22 16 mul a b -> (result)
.sub zop_mul :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass
  # get variable for result storage
  (args, pc) = self."decode_args"(im, pc, args, "v")
  unless pass goto done
    self."emit_binary_op"(args, "*", 1)
done:
  .return (args, pc, 0, 0)
.end

# 2OP:23 17 div a b -> (result)
.sub zop_div :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass
  # get variable for result storage
  (args, pc) = self."decode_args"(im, pc, args, "v")
  unless pass goto done
    self."emit_binary_op"(args, "/", 1)
done:
  .return (args, pc, 0, 0)
.end

# 2OP:24 18 mod a b -> (result)
# Can't use emit_binary_mod because % does mod and we need cmod
.sub zop_mod :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass
  # get variable for result storage
  (args, pc) = self."decode_args"(im, pc, args, "v")
  unless pass goto done
    .local string left, l, right, r, var, val
    left = args[0]
    right = args[1]
    var = args[2]
    l = self."emit_get"(left, 1)
    r  = self."emit_get"(right, 1)
    val = self."temp"()
    $S0 = "\t"
    $S0 .= val
    $S0 .= " = cmod "
    $S0 .= l
    $S0 .= ", "
    $S0 .= r
    $S0 .= "\n"
    self."code"($S0)
    self."emit_store"(var, val)
done:
  .return (args, pc, 0, 0)
.end

.sub emit_binary_op :method
  .param pmc args
  .param string operator
  .param string is_signed
    .local string left, l, right, r, var, val
    left = args[0]
    right = args[1]
    var = args[2]
    l = self."emit_get"(left, is_signed)
    r  = self."emit_get"(right, is_signed)
    val = self."temp"()
    $S0 = "\t"
    $S0 .= val
    $S0 .= " = "
    $S0 .= l
    $S0 .= " "
    $S0 .= operator
    $S0 .= " "
    $S0 .= r
    $S0 .= "\n"
    self."code"($S0)
    self."emit_store"(var, val)
    .return (1)
.end

##########
# LOGICAL
##########

# and
# 2OP:9 9 and a b -> (result)
.sub zop_and :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass
  # get variable for result storage
  (args, pc) = self."decode_args"(im, pc, args, "v")
  unless pass goto done
    self."emit_binary_op"(args, "&", 0)
done:
  .return (args, pc, 0, 0)
.end

# or
# 2OP:8 8 or a b -> (result)
.sub zop_or :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass
  # get variable for result storage
  (args, pc) = self."decode_args"(im, pc, args, "v")
  unless pass goto done
    self."emit_binary_op"(args, "|", 0)
done:
  .return (args, pc, 0, 0)
.end

# not
# 1OP:143 F 1/4 not value -> (result)
# VAR:248 18 5/6 not value -> (result)
.sub zop_not :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass
  # get variable for result storage
  (args, pc) = self."decode_args"(im, pc, args, "v")
  unless pass goto done
  .local string operand, notted, store_var, val
  operand = args[0]
  store_var = args[1]
  notted = self."emit_get"(operand, 0)
    val = self."temp"()
    $S0 = "\t"
    $S0 .= val
    $S0 .= " = ~ "
    $S0 .= notted
    $S0 .= "\n"
    self."code"($S0)
    self."emit_store"(store_var, val)
done:
  .return (args, pc, 0, 0)
.end


##########
# JUMP
##########
.sub zop_jump :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass

  .local int label
  label = args[0]
  label += pc
  if label < 65536 goto ok
    label -= 65536
ok:
  label -= 2
  self."remember_label"(label)
  args[0] = label
  if pass goto emit
    goto done
emit:
    $S0 = "\tgoto L"
    $S1 = to_hex(label)
    $S0 .= $S1
    $S0 .= "\n"
    self."code"($S0)
done:
  .return (args, pc, 0, label)
.end

##########
.sub zop_jz :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass

  .local int label, on_true
  (label, on_true, pc) = self."decode_label"(im, pc)
  push args, label
  unless pass goto done
    .local string left, l
    left = args[0]
    l = self."emit_get"(left, 1)
    $S0 = l
    $S0 .= " == 0"
    self."emit_branch"(on_true, $S0, label)
done:
  .return (args, pc, 0, label)
.end

##########
# Jump on equals
# TODO if equal to ANY of its args!!!
.sub zop_je :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass

  .local int label, on_true
  (label, on_true, pc) = self."decode_label"(im, pc)
  push args, label
  unless pass goto done
    .local string left, l, right, r
    left = args[0]
    right = args[1]
    l = self."emit_get"(left, 1)
    r  = self."emit_get"(right, 1)
    $S0 = l
    $S0 .= " == "
    $S0 .= r
    self."emit_branch"(on_true, $S0, label)
done:
  .return (args, pc, 0, label)
.end

##########
# Jump if a > b (using a signed 16-bit comparison). 
.sub zop_jg :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass

  .local int label, on_true
  (label, on_true, pc) = self."decode_label"(im, pc)
  push args, label
  unless pass goto done
    .local string left, l, right, r
    left = args[0]
    right = args[1]
    l = self."emit_get"(left, 1)
    r  = self."emit_get"(right, 1)
    $S0 = l
    $S0 .= " > "
    $S0 .= r
    $S0 = self."emit_branch"(on_true, $S0, label)
done:
  .return (args, pc, 0, label)
.end


##########
# Jump if a < b (using a signed 16-bit comparison). 
.sub zop_jl :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass

  .local int label, on_true
  (label, on_true, pc) = self."decode_label"(im, pc)
  push args, label
  unless pass goto done
    .local string left, l, right, r
    left = args[0]
    right = args[1]
    l = self."emit_get"(left, 1)
    r  = self."emit_get"(right, 1)
    $S0 = l
    $S0 .= " < "
    $S0 .= r
    $S0 = self."emit_branch"(on_true, $S0, label)
done:
  .return (args, pc, 0, label)
.end

# Given a condition, output an if/unless statement
.sub emit_branch :method
  .param int on_true
  .param string condition
  .param int label
  .local string label_name

  unless label < 2 goto no_return
    on_true = ! on_true
  no_return:
  unless on_true goto emit_unless
    $S0 = "\tif "
    goto emit_cond
  emit_unless:
    $S0 = "\tunless "
  emit_cond:
  $S0 .= condition
  $S0 .= " goto L"
  if label < 2 goto if_return
    $S1 = to_hex(label)
    goto no_if_return
  if_return:
    $S1 = "_if_return"
    label_name = self."temp"() # get a non-repeatable number
    label_name = substr label_name, 2
    $S1 .= label_name
    $S1 .= "\n\t.return("
    $S2 = label
    $S1 .= $S2
    $S1 .= ")\n"
    $S1 .= "L_if_return"
    $S1 .= label_name
    $S1 .= ":\n"
  no_if_return:
  $S0 .= $S1
  $S0 .= "\n"
  self."code"($S0)
  .return (1)
.end

##########
# PRINT
##########
# 3.2
.sub decode_text :method
  .param pmc im
  .param int pc

  .local int word, cur_a, next_a, i, z, sh, fin, double_count, double_sum
  .local string s, A0, A1, A2, c, a
  .local pmc alphabets
  alphabets = new FixedStringArray
  alphabets = 3
  A0 = "abcdefghijklmnopqrstuvwxyz"
  A1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  A2 = " \n0123456789.,!?_#'\"/\\-:()"	#"
  alphabets[0] = A0
  alphabets[1] = A1
  alphabets[2] = A2
  next_a = 0
  s = "\""
  fin = 0
  double_count = 0
  # trace 1
text_loop:
  (word, pc) = self."get_word"(pc)
  fin = word & 0x8000
  sh = 10
  word_loop:
    z = word >> sh
    z &= 0x1f
    cur_a = next_a
    next_a = 0 # by default. 3.2.3: "There are no shift lock characters" (v3+)

    # Handle ZSCII char
    unless double_count goto not_double
      unless double_count == 2 goto not_end_double
        double_sum = double_sum << 5
    not_end_double:
      double_sum += z
      unless double_count == 2 goto not_end_double2
        if double_sum < 128 goto valid_ascii
	  printerr "Don't support ZSCII >= 128: "
	  printerr double_sum
	  printerr "\n"
	  goto next_z
	valid_ascii:
        c = chr double_sum
	s .= c
	double_count = 0
	goto next_z
    not_end_double2:
	inc double_count
	goto next_z

    not_double:
      if z goto not_nul
	s .= " "
	goto next_z
    not_nul:
      if z < 6 goto special
      if z != 6 goto not_6 # 6 in A2 ONLY is specia
      if cur_a == 2 goto special
    not_6:
      z -= 6
      a = alphabets[cur_a]
      c = a[z]
      s .= c
      goto next_z
    special:
      if z != 4 goto not_4
	next_a = 1
	goto next_z
    not_4:
      if z != 5 goto not_5
	next_a = 2
	goto next_z
    not_5:
	if z != 6 goto not_6b
	double_count = 1 # Read a two-zchar ZSCII char
	double_sum = 0
	goto next_z
    not_6b:
	# 3.3 abbrev z (1,2,3) abbrev = 32(z-1) + x
	printerr "unimpl abbrev in text\n"
	#exit 1
	goto next_z

    next_z:
      sh -= 5
      if sh >= 0 goto word_loop

  unless fin goto text_loop
  .local pmc escape
  escape = find_global "Data::Escape", "String"
  s = escape(s)
  s .= "\""
  .return (s, pc)
.end

##########
.sub zop_print :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass

  .local string txt

  (txt, pc) = self."decode_text"(im, pc)
  push args, txt
  unless pass goto done
    $S0 = "\tprint "
    $S0 .= txt
    $S0 .= "\n"
    self."code"($S0)
done:
  .return (args, pc, 0, 0)
.end

##########
# TODO print a newline before return
.sub zop_print_ret :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass

  .local string txt

  (txt, pc) = self."decode_text"(im, pc)
  push args, txt
  unless pass goto done
    $S0 = "\tprint "
    $S0 .= txt
    $S0 .= "\n\t.return(1)\n"
    self."code"($S0)
done:

  .return (args, pc, 1, 0)
.end

##########
.sub zop_print_num :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass
  unless pass goto done
    .local string var
    $S0 = "\tprint "
    $S1 = args[0]
    var = self."emit_get"($S1, 1)
    $S0 .= var
    $S0 .= "\n"
    self."code"($S0)
done:
  .return (args, pc, 0, 0)
.end

##########
# TODO '@print_paddr I5' breaks. Need to call decode_text at runtime
.sub zop_print_paddr :method
    #write_zchunk($ztext->decode_text($_[0]));
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass
  # TODO if regular text, send text back for debug
  unless pass goto done
  .local string txt, var, tempi1, tempi2, temps1
  .local int adr, dumb
  txt = args[0]
  adr = txt # see whether it's a number or not
  unless adr goto non_constant_addr
    adr = self."packed_addr"(adr)
    (txt, dumb) = self."decode_text"(im, adr)
    push args, txt
    $S0 = "\tprint "
    $S0 .= txt
    $S0 .= "\n"
    goto encode
  non_constant_addr:
    printerr "Unimplemented print_paddr("
    printerr txt
    printerr ")\n"
# Below doesn't work: no access to Zmachine object or decode_text method
# I need to subclass ZMachine with ZRuntime and create a Z object
# so I can have access to packed_addr and decode_text on the image
# In fact, pass the ZRun object into the main sub, then get its image
# object and make it global
#    var = self."emit_get"(txt, 0)
#    tempi1 = self."temp"()
#    tempi2 = self."temp"()
#    temps1 = tempi1
#    temps1[1] = "S" # string temp var instead of int
#    $S0 = "\t"
#    $S0 .= tempi1
#    $S0 .= " = "
#    $S0 .= var
#    $S0 .= "\n"
#    $S0 .= "\t"
#    $S0 .= tempi1
#    $S0 .= " = self.\"packed_addr\"("
#    $S0 .= tempi1
#    $S0 .= ")\n\t"
#    $S0 .= temps1
#    $S0 .= " = self.\"decode_text\"(image, "
#    $S0 .= tempi1
#    $S0 .= ")\n"
#    $S0 .= "\tprint "
#    $S0 .= temps1
#    $S0 .= "\n"

encode:
    self."code"($S0)
done:
  .return (args, pc, 0, 0)
.end

##########
# VARIABLES
# Warning: indirect variables live here!
##########

# pop (takes no args)
.sub zop_pop :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass
  unless pass goto done
    $S0 = "\trestore $I0\n" # restore to I0 and throw away
    self."code"($S0)
done:
  .return (args, pc, 0, 0)
.end

# pull (variable)
# variable is indirect
.sub zop_pull :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass
  .local string store_var, tvar
  $I0 = args[0]
  store_var = to_var($I0)
  args[0] = store_var
  unless pass goto done
    tvar = self."temp"()
    $S0 = "\trestore "
    $S0 .= tvar
    $S0 .= "\n"
    self."code"($S0)
    self."emit_store"(store_var, tvar)
done:
  .return (args, pc, 0, 0)
.end

# push value
.sub zop_push :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass
  .local string store_var, valget
  unless pass goto done
    store_var = args[0]
    valget = self."emit_get"(store_var, 0)
    $S0 = "\tsave "
    $S0 .= valget
    $S0 .= "\n"
    self."code"($S0)
done:
  .return (args, pc, 0, 0)
.end


# store (var) value
# var is indirect constant
.sub zop_store :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass
  .local string var, val, valget
  $I0 = args[0]
  var = to_var($I0)
  args[0] = var
  unless pass goto done
    var = args[0]
    val = args[1]
    valget = self."emit_get"(val, 0)
    self."emit_store"(var, valget)
done:
  .return (args, pc, 0, 0)
.end

.sub zop_dec :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass
  # args[0] is a small constant meaning var
  # parse again as variable, dec pc, clear args
  dec pc
  args = 0
  (args, pc) = self."decode_args"(im, pc, args, "v")
  unless pass goto done
    .local string var
    $S1 = args[0]
    var = self."emit_get"($S1, 0)
    $S0 = "\tdec "
    $S0 .= var
    $S0 .= "\n"
    self."code"($S0)
    # 15."dec": "This is signed, so 0 decrements to -1"
    # Does that only matter for dec_chk? Stack & vars are unsigned?
    # emit_store will conv_u2
    self."emit_store"($S1, var)
done:
  .return (args, pc, 0, 0)
.end

.sub zop_inc :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass
  # args[0] is a small constant meaning var
  # parse again as variable, dec pc, clear args
  dec pc
  args = 0
  (args, pc) = self."decode_args"(im, pc, args, "v")
  unless pass goto done
    .local string var
    $S1 = args[0]
    var = self."emit_get"($S1, 0)
    $S0 = "\tinc "
    $S0 .= var
    $S0 .= "\n"
    self."code"($S0)
    self."emit_store"($S1, var)
done:
  .return (args, pc, 0, 0)
.end

##########
.sub zop_quit :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass

  unless pass goto done
    self."code"("\tend\n")
  done:
  .return (args, pc, 1, 0)
.end

##########
# CALL / RETURN
##########
# call addr [args..] store

.sub zop_call :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass
  .local int locals

  .local int var

  ##unless pass goto no1
  ##trace 1
  ##no1:
  # get variable for result storage
  (args, pc) = self."decode_args"(im, pc, args, "v")
  # remember sub at args[0] for decoding
  .local int adr, start, n, i
  adr = args[0]
  adr = self."packed_addr"(adr)
  start = adr
  args[0] = adr
  locals = im[adr]
  adr += 1
  locals *= 2	# 1 word per local
  adr += locals
  self."remember_sub"(adr, start)
  unless pass goto done
    $I0 = 0
    .local string result
    result = self."temp"()
    $S0 = "\t"
    $S0 .= result
    $S0 .= " = R"
    $S1 = to_hex(adr)
    $S0 .= $S1
    $S0 .= "("
    n = elements args
    dec n
    i = 1
arg_loop:
    # TODO BUG somewhere in here: calling @call R54a G5, 1, 3 breaks
    # The G5 arg doesn't get switched to be a variable.
    # Do we need to do an emit_get on everything?
    if i >= n goto end_args
      $S1 = args[i]
      $S0 .= $S1
      $I1 = n - 1
      if i >= $I1 goto no_comm
      $S0 .= ", "
  no_comm:
      inc i
      goto arg_loop
end_args:
    $S0 .= ")\n"
    self."code"($S0)
    .local string var
    $I0 = n
    #dec $I0
    var = args[$I0]
    self."emit_store"(var, result)
done:
  .return (args, pc, 0, 0)
.end

##########
.sub zop_ret :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass

  unless pass goto done
    .local int val
    val = args[0]
    $S0 = "\t.return("
    $S1 = val
    $S0 .= $S1
    $S0 .= ")\n"
    self."code"($S0)
  done:
  .return (args, pc, 1, 0)
.end

##########
.sub zop_rtrue :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass

  unless pass goto done
    self."code"("\t.return(1)\n")
  done:
  .return (args, pc, 1, 0)
.end

##########
.sub zop_rfalse :method
  .param pmc im
  .param int pc
  .param pmc args
  .param int pass

  unless pass goto done
    self."code"("\t.return(0)\n")
  done:
  .return (args, pc, 1, 0)
.end

