###############################################################################
#
# csharp.eagle --
#
# Extensible Adaptable Generalized Logic Engine (Eagle)
# Eagle CSharp Package File
#
# Copyright (c) 2007-2012 by Joe Mistachkin.  All rights reserved.
#
# See the file "license.terms" for information on usage and redistribution of
# this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
# RCS: @(#) $Id: $
#
###############################################################################

#
# NOTE: Use our own namespace here because even though we do not directly
#       support namespaces ourselves, we do not want to pollute the global
#       namespace if this script actually ends up being evaluated in Tcl.
#
namespace eval ::Eagle {
  #
  # NOTE: This procedure is used to dynamically compile some C# code from
  #       within a script.  While this procedure was originally designed
  #       to be used by the test suite, it can be quite useful in non-test
  #       scripts as well.
  #
  proc compileCSharp {
          string memory symbols strict resultsVarName errorsVarName args } {
    #
    # NOTE: The [object] command is required by this procedure.  If it
    #       is not available, bail out now.
    #
    if {[llength [info commands object]] == 0} then {
      #
      # NOTE: We cannot even attempt to compile anything, fail.
      #
      set code Error

      #
      # NOTE: Prepare to transfer error messages to the caller.
      #
      if {[string length $errorsVarName] > 0} then {
        upvar 1 $errorsVarName local_errors
      }

      #
      # NOTE: Append to the list of errors.
      #
      lappend local_errors "cannot compile, missing \"object\" command"

      #
      # NOTE: Return the overall result to the caller.
      #
      return $code
    }

    #
    # NOTE: Create the C# code provider object (i.e. the compiler).
    #
    set provider [object create -alias Microsoft.CSharp.CSharpCodeProvider]

    #
    # NOTE: Create the object that provides various parameters to the C#
    #       code provider (i.e. the compiler options).
    #
    set parameters [object create -alias \
        System.CodeDom.Compiler.CompilerParameters]

    #
    # NOTE: Do we not want to persist the generated assembly to disk?
    #
    if {$memory} then {
      $parameters GenerateInMemory true
    }

    #
    # NOTE: Do we want symbols to be generated for the generated assembly?
    #
    if {$symbols} then {
      $parameters IncludeDebugInformation true
    }

    #
    # NOTE: Make sure that the "standard" preprocessor defines match those
    #       for the platform (i.e. the ones used to compile the Eagle core
    #       library assembly).
    #
    set platformOptions [expr { \
        [info exists ::eagle_platform(compileOptions)] ? \
        $::eagle_platform(compileOptions) : [list]}]

    #
    # NOTE: Permit extra C# compiler options to be passed via the global
    #       array element "csharpOptions", if it exists.
    #
    set csharpOptions [expr { \
        [info exists ::eagle_platform(csharpOptions)] ? \
        $::eagle_platform(csharpOptions) : [list]}]

    if {[llength $platformOptions] > 0 || \
        [llength $csharpOptions] > 0} then {
      #
      # NOTE: Grab the existing compiler options, if any.
      #
      set compilerOptions [$parameters CompilerOptions]

      if {"DEBUG" in $platformOptions} then {
        if {[string length $compilerOptions] > 0} then {
          append compilerOptions " "
        }

        append compilerOptions /define:DEBUG
      }

      if {"TRACE" in $platformOptions} then {
        if {[string length $compilerOptions] > 0} then {
          append compilerOptions " "
        }

        append compilerOptions /define:TRACE
      }

      #
      # NOTE: Append the configured extra C# compiler options configured
      #       via the global array element "csharpOptions", if any.
      #
      foreach csharpOption $csharpOptions {
        if {[string length $compilerOptions] > 0} then {
          append compilerOptions " "
        }

        append compilerOptions $csharpOption
      }

      #
      # NOTE: Reset the compiler options to the pre-existing ones plus the
      #       extra defines we may have added (above).
      #
      $parameters CompilerOptions $compilerOptions
    }

    #
    # NOTE: Process any extra compiler settings the caller may have
    #       provided.
    #
    foreach {name value} $args {
      $parameters -nocase $name $value
    }

    #
    # NOTE: Prepare to transfer the object reference to the caller.  We
    #       must use upvar here because otherwise the object is lost when
    #       the procedure call frame is cleaned up.
    #
    if {[string length $resultsVarName] > 0} then {
      upvar 1 $resultsVarName results
    }

    #
    # NOTE: Attempt to compile the specified string as C# and capture the
    #       results into the variable provided by the caller.
    #
    set results [$provider -alias CompileAssemblyFromSource $parameters \
        $string]

    #
    # NOTE: We no longer need the C# code provider object (i.e. the
    #       compiler); therefore, dispose it now.
    #
    unset provider; # dispose

    #
    # NOTE: Fetch the collection of compiler errors (which may be empty).
    #
    set errors [$results -alias Errors]

    #
    # NOTE: It is assumed that no assembly was generated if there were
    #       any compiler errors.  Ignore all compiler warnings unless
    #       we are in strict mode.
    #
    if {[$errors HasErrors] || ($strict && [$errors HasWarnings])} then {
      #
      # NOTE: Compilation of the assembly failed.
      #
      set code Error

      #
      # NOTE: Prepare to transfer error messages to the caller.
      #
      if {[string length $errorsVarName] > 0} then {
        upvar 1 $errorsVarName local_errors
      }

      #
      # NOTE: How many compile errors?
      #
      set count [$errors Count]

      #
      # NOTE: Grab each error object and append the string itself to
      #       the overall list of errors.
      #
      for {set index 0} {$index < $count} {incr index} {
        #
        # NOTE: Get the compiler error object at this index.
        #
        set error [$errors -alias Item $index]

        #
        # NOTE: Convert it to a string and append it to the list of
        #       errors.
        #
        lappend local_errors [$error ToString]

        #
        # NOTE: Since the error itself is actually an object, we must
        #       dispose it.
        #
        unset error; # dispose
      }
    } else {
      #
      # NOTE: Compilation of the assembly succeeded.
      #
      set code Ok
    }

    #
    # NOTE: We no longer need the collection of compiler errors;
    #       therefore, dispose it now.
    #
    unset errors; # dispose

    #
    # NOTE: Return the overall result to the caller.
    #
    return $code
  }

  #
  # NOTE: Provide the Eagle "C#" package to the interpreter.
  #
  package provide Eagle.CSharp \
    [expr {[isEagle] ? [info engine PatchLevel] : "1.0"}]
}
