# This file contains some of the code that drives the .NET EXE/DLL to PIR
# translation process.

.HLL '_dotnet'

# This sub is the way translation from .NET to PIR is started.
.sub dotnet_to_pir
    .param string filename
    .param int continue
    .param int standalone
    .param int trace
    .local string pir_output, src, summary, tmp, emsg
    .local pmc assembly, classes, class_order, type, e, entry_meth, entry_class, c
    .local int is_dll, i, max_class, class_id, total_types, done_types

    # Instantiate a new assembly class.
    loadlib $P0, "dotnet"
    assembly = new "DotNetAssembly"

    # Set filename and attempt to load.
    assembly = filename
    assembly.'load'()

    # Load the escaper library, which we will be using.
    load_bytecode "library/Data/Escape.pir"

    # Initialize PIR output string.
    pir_output = ""

    # Output HLL directive.
    pir_output = concat ".HLL 'dotnet'\n"

    # Put in ops loader code.
    pir_output = concat <<"PIR"
.loadlib "dotnet_ops"
.sub __LOAD_DOTNET_OPS :load
    loadlib $P0, "dotnet_runtime"
.end
PIR

    # If standalone flag is set, jump over adding import code.
    if standalone == 1 goto NO_IMPORTS
    src = assembly_imports_sub(assembly)
    pir_output = concat src
NO_IMPORTS:

    # Insert code to make call-boxing classes used to make MMD work on the
    # non-reference types Parrot doesn't recognize.
    pir_output = concat <<"PIR"
.sub __CREATE_DOTNET_MMDBOXES :load
    .local pmc class

    $P0 = get_class "Integer"
    class = get_class "@@DOTNET_MMDBOX_I1"
    unless null class goto EXISTS_I1
    subclass $P1, $P0, "@@DOTNET_MMDBOX_I1"
EXISTS_I1:
    class = get_class "@@DOTNET_MMDBOX_I2"
    unless null class goto EXISTS_I2
    subclass $P1, $P0, "@@DOTNET_MMDBOX_I2"
EXISTS_I2:
    class = get_class "@@DOTNET_MMDBOX_U1"
    unless null class goto EXISTS_U1
    subclass $P1, $P0, "@@DOTNET_MMDBOX_U1"
EXISTS_U1:
    class = get_class "@@DOTNET_MMDBOX_U2"
    unless null class goto EXISTS_U2
    subclass $P1, $P0, "@@DOTNET_MMDBOX_U2"
EXISTS_U2:
    class = get_class "@@DOTNET_MMDBOX_U4"
    unless null class goto EXISTS_U4
    subclass $P1, $P0, "@@DOTNET_MMDBOX_U4"
EXISTS_U4:
    $P0 = get_class "Float"
    class = get_class "@@DOTNET_MMDBOX_R4"
    unless null class goto EXISTS_R4
    subclass $P1, $P0, "@@DOTNET_MMDBOX_R4"
EXISTS_R4:
.end
PIR

    # We'll only put fake System.Object in when we're in standalone mode.
    if standalone == 0 goto NO_STANDALONE_CLASSES
    pir_output = concat <<"PIR"
.namespace [ "System"; "Object" ]
.sub __FAKE_SYSTEM_OBJECT :load
    $P0 = get_class [ "System" ; "Object" ]
    unless null $P0 goto EXISTS
    $P0 = newclass [ "System" ; "Object" ]
EXISTS:
.end
.sub ".ctor" :method
.end
PIR
NO_STANDALONE_CLASSES:

    # Put in fake temporary System.Exception and System.String.
    pir_output = concat <<"PIR"
.namespace [ "System" ; "Exception" ]
.sub __FAKE_SYSTEM_EXCEPTION :load
    $P0 = get_class [ "System" ; "Exception" ]
    unless null $P0 goto EXISTS
    $P0 = newclass [ "System" ; "Exception" ]
EXISTS:
.end
.sub ".ctor" :method
.end
.namespace [ "System" ; "String" ]
.sub __FAKE_SYSTEM_STRING :load
    $P0 = get_class [ "System" ; "String" ]
    unless null $P0 goto EXISTS
    $P0 = newclass [ "System" ; "String" ]
    addattribute $P0, "Chars"
EXISTS:
.end
.sub ".ctor" :method :multi("System.String", string)
    .param string s
    $P0 = new "String"
    $P0 = s
    setattribute self, "Chars", $P0
.end
.sub __get_string :method
    $P0 = getattribute self, "Chars"
    $S0 = $P0
    .return($S0)
.end
.namespace [ "System" ; "ValueType" ]
.sub __FAKE_SYSTEM_VALUETYPE :load
    $P0 = get_class [ "System" ; "ValueType" ]
    unless null $P0 goto EXISTS
    $P0 = newclass [ "System" ; "ValueType" ]
EXISTS:
.end
.namespace [ "System" ; "Enum" ]
.sub __FAKE_SYSTEM_ENUM :load
    $P0 = get_class [ "System" ; "Enum" ]
    unless null $P0 goto EXISTS
    $P0 = newclass [ "System" ; "Enum" ]
EXISTS:
.end
PIR

    # Translate global stuff.
    # XXX TODO: Translate globals.

    # Translate each class according to the ordering.
    classes = assembly.'get_classes'()
    class_order = assembly.'get_class_order'()
    max_class = elements classes
    i = 0
    total_types = 0
    done_types = 0
CLOOP:
    if i >= max_class goto CEND
    inc total_types
    class_id = class_order[i]
    type = classes[class_id]
    if continue == 0 goto NO_EH
    push_eh trans_failure_handler
NO_EH:
    src = trans_class(assembly, type, trace)
    pir_output = concat src
    inc done_types
    if continue == 0 goto RESUME
    pop_eh
RESUME:
    inc i
    goto CLOOP
trans_failure_handler:
    .get_results (e)
    emsg = e
    # Emit trace message.
    unless trace goto NOTRACE
    printerr "  **FAILED** ("
    printerr emsg
    emsg = ""
    printerr ")\n"
NOTRACE:
    goto RESUME
CEND:

    # If it's an EXE, do entry point stuff.
    is_dll = assembly."is_dll"()
    if is_dll > 0 goto ISEXE
    src = pir_output
    entry_meth = assembly.'get_entry_method'()
    entry_class = entry_meth.'get_class'()
    pir_output = ".sub __ENTRY_POINT\n__DO_IMPORTS()\n$P0 = get_hll_global \""
    tmp = entry_class.'get_fullname'()
    pir_output = concat tmp
    pir_output = concat "\", \""
    tmp = entry_meth
    pir_output = concat tmp
    pir_output = concat "\"\n$P0()\n.end\n"
    pir_output = concat src
ISEXE:

    # Generate summary.
    summary = "Translated "
    tmp = done_types
    summary = concat tmp
    summary = concat " types out of "
    tmp = total_types
    summary = concat tmp
    summary = concat " from "
    summary = concat filename
    summary = concat "\n"

    # Return output.
    .return (pir_output, summary)
.end


# This produces a sub that loads libraries that we need to import for this one
# to work.
.sub assembly_imports_sub
    .param pmc assembly
    .local pmc assrefs, assref
    .local int assref_count, i
    .local string name, pir_output

    # Emit start of load sub.
    pir_output = ".sub __DO_IMPORTS :load\n"

    # Loop over assembly refs.
    assrefs = assembly.'get_assemblyrefs'()
    assref_count = elements assrefs
    i = 0
AR_LOOP:
    if i == assref_count goto AR_LOOP_END
    assref = assrefs[i]
    inc i

    # Get name and emit load code.
    name = assref
    pir_output = concat "load_bytecode \""
    pir_output = concat name
    pir_output = concat ".pbc\"\n"
AR_LOOP_END:

    # Do end and return.
    pir_output = concat ".end\n"
    .return(pir_output)
.end


# This sub translates an individual class.
.sub trans_class
    .param pmc assembly
    .param pmc class
    .param int trace
    .local string pir_output, name, namespace, internal_name, tmp, p_name, name_key
    .local pmc fields, field, methods, meth, ex, int_types, int_ids
    .local int i, max_field, max_method, parent_id, parent_type
    .local int flags, is_interface, is_abstract, num_interfaces, done_init

    done_init = 0

    # Get class name and namespace and build combo of them.
    name = class
    namespace = class.'get_namespace'()
    internal_name = class.'get_fullname'()

    # Emit trace message.
    unless trace goto NOTRACE
    printerr "Type "
    printerr internal_name
    printerr "\n"
NOTRACE:

    # Emit a namespace directive.
    name_key = namespace_to_key(internal_name)
    pir_output = concat ".namespace "
    pir_output = concat name_key
    pir_output = concat "\n\n"

    # Emit start of on load type setup.
    pir_output = concat ".sub \"__onload\" :load\n"
    pir_output = concat "    .local pmc type, parent\n"
    pir_output = concat "     push_eh FAILED\n" # XXX Ignoring missing parents
    pir_output = concat "    type = newclass "
    pir_output = concat name_key
    pir_output = concat "\n"

    # Add any interfaces that this class implements.
    int_types = class.'get_interface_types'()
    int_ids = class.'get_interface_ids'()
    num_interfaces = elements int_types
    i = 0
INT_LOOP:
    if i == num_interfaces goto END_INT_LOOP
    parent_type = int_types[i]
    parent_id = int_ids[i]
    dec parent_id
    (tmp, p_name) = add_parent(assembly, parent_type, parent_id)
    pir_output = concat tmp
    inc i
END_INT_LOOP:

    # Inherit the parent class. Note System.Object has ID 0, and jump over this stuff.
    parent_id = class.'get_parent_id'()
    if parent_id == 0 goto NO_PARENT
    dec parent_id
    parent_type = class.'get_parent_type'()
    (tmp, p_name) = add_parent(assembly, parent_type, parent_id)
    pir_output = concat tmp
NO_PARENT:

    # Emit field list.
    fields = class.'get_fields'()
    max_field = elements fields
    i = 0
FLOOP:
    if i >= max_field goto FEND
    field = fields[i]
    tmp = trans_field(assembly, class, field)
    pir_output = concat tmp
    inc i
    goto FLOOP
FEND:

    # Add code to run constructor.
    pir_output = concat "push_eh FAILED\n"
    pir_output = concat "$P0 = get_hll_global "
    tmp = namespace_to_key(internal_name)
    pir_output = concat tmp
    pir_output = concat ", \".cctor\"\n$P0()\n"

    # This is the end of the on load type setup sub.
    pir_output = concat "pop_eh\n"
    pir_output = concat "pop_eh\n"
    pir_output = concat "FAILED:\n.end\n\n"

    # If it's an interface, emit code to prevent it being instantiated.
    flags = class.'get_flags'()
    is_interface = band flags, 0x20
    if is_interface == 0 goto NOT_INTERFACE
    pir_output = concat <<"PIR"
.sub 'init' :method :vtable
    $P0 = class self
    $S0 = classname $P0
PIR
    pir_output = concat "    if $S0 != \""
    pir_output = concat internal_name
    pir_output = concat "\" goto INIT_OK\n"
    pir_output = concat <<"PIR"
    $P1 = new 'Exception'
    $P1 = "You can not instantiate an interface"
    throw $P1
INIT_OK:
.end
PIR
    done_init = 1
    goto NOT_ABSTRACT
NOT_INTERFACE:

    # If it's an abstract class, emit code to prevent it being instantiated.
    is_abstract = band flags, 0x80
    if is_abstract == 0 goto NOT_ABSTRACT
    pir_output = concat <<"PIR"
.sub 'init' :method :vtable
    $P0 = class self
    $S0 = classname $P0
PIR
    pir_output = concat "    if $S0 != \""
    pir_output = concat internal_name
    pir_output = concat "\" goto INIT_OK\n"
    pir_output = concat <<"PIR"
    $P1 = new 'Exception'
    $P1 = "You can not instantiate an abstract class"
    throw $P1
INIT_OK:
.end
PIR
    done_init = 1
NOT_ABSTRACT:

    # If it is a value type, add the __init and __clone v-table methods.
    if p_name == "[ \"System\" ; \"ValueType\" ]" goto VAL_TYPE
    if p_name == "[ \"System\" ; \"Enum\" ]" goto VAL_TYPE
    goto NOT_VAL_TYPE
VAL_TYPE:
    tmp = value_type_methods(assembly, class, p_name)
    pir_output = concat tmp
    done_init = 1
NOT_VAL_TYPE:

    # Emit methods.
    methods = class.'get_methods'()
    max_method = elements methods
    i = 0
MLOOP:
    if i >= max_method goto MEND
    meth = methods[i]
    tmp = trans_method(assembly, class, meth, 1, trace)
    pir_output = concat tmp
    inc i
    goto MLOOP
MEND:

    # Return PIR that was generated.
    .return (pir_output)
.end


# This emits the code to add a parent to a class.
.sub add_parent
    .param pmc assembly
    .param int parent_type
    .param int parent_id
    .local pmc ex, classes, pclass
    .local string pclass_ns, pir_output, tmp

    # Find out what type of parent we have.
    pir_output = "    parent = get_class "
    if parent_type == 0 goto PARENT_DEF
    if parent_type == 1 goto PARENT_REF
    ex = new 'Exception'
    ex = "Can not subclass a TypeSpec parent."
    throw ex

    # Parent may be a type in this file.
PARENT_DEF:
    dec parent_id # Because row 2 = element 0 here, thanks to the global class
    classes = assembly.'get_classes'()
    pclass = classes[parent_id]
    pclass_ns = pclass.'get_fullname'()
    pclass_ns = namespace_to_key(pclass_ns)
    pir_output = concat pclass_ns
    goto PARENT_DONE

    # Parent may be a type in another file.
PARENT_REF:
    classes = assembly.'get_typerefs'()
    pclass = classes[parent_id]
    pclass_ns = pclass.'get_namespace'()
    pclass_ns = clone pclass_ns
    if pclass_ns == "" goto PARENT_NO_NS
    pclass_ns = concat "."
PARENT_NO_NS:
    tmp = pclass
    pclass_ns = concat tmp
    pclass_ns = namespace_to_key(pclass_ns)
    pir_output = concat pclass_ns

    # Finally, do code to add parent to the class and return.
PARENT_DONE:
    pir_output = concat "\n    addparent type, parent\n"
    .return (pir_output, pclass_ns)
.end


# This translates a field into an addattribute op.
.sub trans_field
    .param pmc assembly
    .param pmc class
    .param pmc field
    .local int flags, static
    .local string pir_output, name

    # Check it's an instance field.
    flags = field.'get_flags'()
    static = band flags, 0x10
    if static != 0 goto STATIC

    # Generate add attribute instruction provided it's an instance field.
    name = field
    pir_output = "    addattribute type, \""
    pir_output = concat name
    pir_output = concat "\"\n"

    # Return generated string.
STATIC:
    .return (pir_output)
.end


# Generate the special __init and __clone v-table methods for value types.
.sub value_type_methods
    .param pmc assembly
    .param pmc class
    .param string parent
    .local pmc fields, field, sig, sig_info
    .local int i, sig_id, type, flags, static
    .local string pir_output, init_body, clone_body, name, sig_data
    .const int ELEMENT_TYPE_I1 = 0x04
    .const int ELEMENT_TYPE_U1 = 0x05
    .const int ELEMENT_TYPE_I2 = 0x06
    .const int ELEMENT_TYPE_U2 = 0x07
    .const int ELEMENT_TYPE_I4 = 0x08
    .const int ELEMENT_TYPE_U4 = 0x09
    .const int ELEMENT_TYPE_R4 = 0x0C
    .const int ELEMENT_TYPE_R8 = 0x0D
    .const int ELEMENT_TYPE_I = 0x18
    .const int ELEMENT_TYPE_U = 0x19
    .const int ELEMENT_TYPE_VALUETYPE = 0x11

    # The __init method needs to zero or null out any attributes.
    # The __clone method needs to clone each attribute.
    fields = class.'get_fields'()
    i = elements fields
    init_body = ""
    clone_body = ""
ILOOP:
    if i == 0 goto ILOOP_END
    dec i
    field = fields[i]

    # Skip if field is static.
    flags = field.'get_flags'()
    static = band flags, 0x10
    if static != 0 goto ILOOP

    # For clone, emit code to just copy the attribute.
    name = field
    clone_body = concat "$P0 = getattribute self, \""
    clone_body = concat name
    clone_body = concat "\"\n$P0 = clone $P0\nsetattribute cpy, \""
    clone_body = concat name
    clone_body = concat "\", $P0\n"

    # Need to look at signature to initialize attributes by type.
    sig_id = field.'get_signature'()
    sig_data = assembly.'get_blob'(sig_id)
    sig = new "DotNetSignature"
    sig = sig_data
    sig_info = get_signature_Field(sig)
    type = sig_info["type"]
    if type == ELEMENT_TYPE_I4 goto INT_TYPE
    if type == ELEMENT_TYPE_U4 goto INT_TYPE
    if type == ELEMENT_TYPE_I2 goto INT_TYPE
    if type == ELEMENT_TYPE_U2 goto INT_TYPE
    if type == ELEMENT_TYPE_I1 goto INT_TYPE
    if type == ELEMENT_TYPE_U1 goto INT_TYPE
    if type == ELEMENT_TYPE_I goto INT_TYPE
    if type == ELEMENT_TYPE_U goto INT_TYPE
    if type == ELEMENT_TYPE_R4 goto FLOAT_TYPE
    if type == ELEMENT_TYPE_R8 goto FLOAT_TYPE
    if type == ELEMENT_TYPE_VALUETYPE goto VALUE_TYPE
    goto OBJ_TYPE

INT_TYPE:
    init_body = concat "$P0 = new 'Integer'\n$P0 = 0\nsetattribute self, \""
    init_body = concat name
    init_body = concat "\", $P0\n"
    goto DONE_INIT

FLOAT_TYPE:
    init_body = concat "$P0 = new 'Float'\n$P0 = 0.0\nsetattribute self, \""
    init_body = concat name
    init_body = concat "\", $P0\n"
    goto DONE_INIT

OBJ_TYPE:
    init_body = concat "$P0 = null\nsetattribute self, \""
    init_body = concat name
    init_body = concat "\", $P0\n"
    goto DONE_INIT

VALUE_TYPE:
    $P0 = new 'Exception'
    $P0 = "Not doing nested value types yet!"
    throw $P0
    goto DONE_INIT

    # Next.
DONE_INIT:
    goto ILOOP
ILOOP_END:

    # Build the code.
    pir_output = ".sub 'init' :method :vtable\n"
    pir_output = concat init_body
    pir_output = concat <<"PIR"
.end
.sub 'clone' :method :vtable
.local pmc cpy
$P0 = class self
$P1 = classname $P0
cpy = new $P1
PIR
    pir_output = concat clone_body
    pir_output = concat ".return(cpy)\n.end\n"

    # If we have an enum, provide get and set integer and float v-table
    # methods to provide or hand back first field. This is for supporting
    # enums.
    if parent != "[ \"System\" ; \"Enum\" ]" goto NOT_ENUM
    pir_output = concat <<"PIR"
.sub 'get_integer' :vtable
    .param pmc s
    $P0 = getattribute s, "value__"
    $I0 = $P0
    .return($I0)
.end
.sub 'set_integer_native' :vtable
    .param pmc s
    .param int i
    $P0 = new 'Integer'
    $P0 = i
    setattribute s, "value__", $P0
.end
.sub 'get_number' :vtable
    .param pmc s
    $P0 = getattribute s, "value__"
    $N0 = $P0
    .return($N0)
.end
.sub 'set_number_native' :vtable
    .param pmc s
    .param num i
    $P0 = new 'Float'
    $P0 = i
    setattribute s, "value__", $P0
.end
PIR
NOT_ENUM:

    # Return generated code.
    .return(pir_output)
.end


# Takes a .Net namespace separated by dots and makes a Parrot namespace key.
.sub namespace_to_key
    .param string in_ns
    .local string ns_key, tmp
    .local pmc keys
    .local int i, max

    # Initial bracket of key.
    ns_key = "[ "

    # Split up and make key sequence.
    keys = split ".", in_ns
    max = elements keys
    i = 0
LOOP:
    if i == max goto LOOP_END
    if i == 0 goto NO_SC
    ns_key = concat "; "
NO_SC:
    tmp = keys[i]
    ns_key = concat "\""
    ns_key = concat tmp
    ns_key = concat "\" "
    inc i
    goto LOOP
LOOP_END:

    # End and return key.
    ns_key = concat "]"
    .return(ns_key)
.end

# Local Variables:
#   mode: pir
#   fill-column: 100
# End:
# vim: expandtab shiftwidth=4 ft=pir:
