/*----------------------------------------------------------------------------
  ChucK Concurrent, On-the-fly Audio Programming Language
    Compiler and Virtual Machine

  Copyright (c) 2004 Ge Wang and Perry R. Cook.  All rights reserved.
    http://chuck.stanford.edu/
    http://chuck.cs.princeton.edu/

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  U.S.A.
-----------------------------------------------------------------------------*/

//-----------------------------------------------------------------------------
// file: chuck_compile.cpp
// desc: chuck compile system unifying parser, type checker, and emitter
//
// author: Ge Wang (ge@ccrma.stanford.edu | gewang@cs.princeton.edu)
// date: Autumn 2005 - original
//-----------------------------------------------------------------------------
#include "chuck_compile.h"
#include "chuck_lang.h"
#include "chuck_errmsg.h"
#include "chuck_io.h"
#ifndef  __DISABLE_OTF_SERVER__
#include "chuck_otf.h"
#endif

#include "ugen_osc.h"
#include "ugen_xxx.h"
#include "ugen_filter.h"
#include "ugen_stk.h"
#include "uana_xform.h"
#include "uana_extract.h"

#include "ulib_ai.h"
#include "ulib_doc.h"
#include "ulib_machine.h"
#include "ulib_math.h"
#include "ulib_std.h"

#ifndef __DISABLE_NETWORK__
#include "ulib_opsc.h"
#endif

#if defined(__PLATFORM_WIN32__)
  #include "dirent_win32.h"
#else
  #ifndef __DISABLE_WORDEXP__
  #include <wordexp.h> // 1.5.0.0 (ge) added
  #endif
#endif

//#if defined(__WINDOWS_PTHREAD__)
#include <sys/stat.h>
//#endif

using namespace std;




// function prototypes
t_CKBOOL load_internal_modules( Chuck_Compiler * compiler );
t_CKBOOL load_external_modules( Chuck_Compiler * compiler,
                                const char * extension,
                                std::list<std::string> & chugin_search_paths,
                                std::list<std::string> & named_dls);
t_CKBOOL load_module( Chuck_Compiler * compiler, Chuck_Env * env,
                      f_ck_query query, const char * name, const char * nspc );




//-----------------------------------------------------------------------------
// name: Chuck_Compiler()
// desc: constructor
//-----------------------------------------------------------------------------
Chuck_Compiler::Chuck_Compiler()
{
    emitter = NULL;
    code = NULL;

    // REFACTOR-2017: add carrier
    m_carrier = NULL;

    // origin hint | 1.5.0.0 (ge) added
    m_originHint = te_originUnknown;
}




//-----------------------------------------------------------------------------
// name: ~Chuck_Compiler()
// desc: destructor
//-----------------------------------------------------------------------------
Chuck_Compiler::~Chuck_Compiler()
{
    // call shutdown
    this->shutdown();
}




//-----------------------------------------------------------------------------
// name: initialize()
// desc: initialize the compiler
//-----------------------------------------------------------------------------
t_CKBOOL Chuck_Compiler::initialize()
{
    // set origin hint
    m_originHint = te_originBuiltin;

    // allocate the type checker
    Chuck_Env * env = type_engine_init( m_carrier );
    // add reference
    env->add_ref();

    // allocate the emitter
    emitter = emit_engine_init( env );
    // add reference
    emitter->add_ref();
    // set auto depend to 0
    m_auto_depend = FALSE;

    // load internal libs
    if( !load_internal_modules( this ) )
        goto error;

    // unset origin hint
    m_originHint = te_originUnknown;

    return TRUE;

error:
    // clean up | 1.4.1.0 (ge) removed -- should be cleaned up outside
    // this->shutdown();

    // pop indent
    EM_poplog();

    return FALSE;
}




//-----------------------------------------------------------------------------
// name: shutdown()
// desc: shutdown the compiler
//-----------------------------------------------------------------------------
void Chuck_Compiler::shutdown()
{
    // log
    EM_log( CK_LOG_SYSTEM, "shutting down compiler..." ) ;
    // push indent
    EM_pushlog();

    // TODO: free
    type_engine_shutdown( env() );
    // TODO: check if emitter gets cleaned up
    // emit_engine_shutdown( emitter );
    emitter = NULL;
    code = NULL;
    m_auto_depend = FALSE;
    m_recent.clear();

    for(std::list<Chuck_DLL *>::iterator i = m_dlls.begin();
        i != m_dlls.end(); i++)
    {
        delete (*i);
    }

    m_dlls.clear();

    // pop indent
    EM_poplog();
}





//-----------------------------------------------------------------------------
// name: bind()
// desc: bind a new type system module, via query function
//-----------------------------------------------------------------------------
t_CKBOOL Chuck_Compiler::bind( f_ck_query query_func, const std::string & name,
                               const std::string & nspc )
{
    // log
    EM_log( CK_LOG_SYSTEM, "on-demand binding compiler module '%s'...",
            name.c_str() );
    // push indent level
    EM_pushlog();

    // get env
    Chuck_Env * env = this->env();
    // make context
    Chuck_Context * context = type_engine_make_context(
        NULL, string("@[bind:") + name + string("]") );
    // reset env - not needed since we just created the env
    env->reset();
    // load it
    type_engine_load_context( env, context );

    // do it
    if( !load_module( this, env, query_func, name.c_str(), nspc.c_str() ) ) goto error;

    // clear context
    type_engine_unload_context( env );

    // commit what is in the type checker at this point
    env->global()->commit();

    // pop indent level
    EM_poplog();

    return TRUE;

error:

    // probably dangerous: rollback
    env->global()->rollback();

    // clear context
    type_engine_unload_context( env );

    // pop indent level
    EM_poplog();

    return FALSE;
}




//-----------------------------------------------------------------------------
// name: set_auto_depend()
// desc: auto dependency resolve for types
//-----------------------------------------------------------------------------
void Chuck_Compiler::set_auto_depend( t_CKBOOL v )
{
    // log
    EM_log( CK_LOG_SYSTEM, "type dependency resolution: %s",
            v ? "AUTO" : "MANUAL" );
    m_auto_depend = v;
}




//-----------------------------------------------------------------------------
// name: go()
// desc: parse, type-check, and emit a program
//-----------------------------------------------------------------------------
t_CKBOOL Chuck_Compiler::go( const string & filename, FILE * fd, const char * str_src, const string & full_path )
{
    t_CKBOOL ret = TRUE;
    // Chuck_Context * context = NULL;

    EM_reset_msg();

    // check to see if resolve dependencies automatically
    if( !m_auto_depend )
    {
        // normal (note: full_path added 1.3.0.0)
        ret = this->do_normal_depend( filename, fd, str_src, full_path );
    }
    else // auto depend
    {
        // call auto-depend compile (1.4.1.0)
        ret = this->do_auto_depend( filename, fd, str_src, full_path );
    }

    // 1.4.1.0 (ge) | added to unset the fileName reference, which determines
    // how messages print to console (e.g., [file.ck]: or [chuck]:)
    EM_change_file( NULL );

    return ret;
}




//-----------------------------------------------------------------------------
// name: resolve()
// desc: resolve type automatically - if auto_depend is off, return FALSE
//-----------------------------------------------------------------------------
t_CKBOOL Chuck_Compiler::resolve( const string & type )
{
    t_CKBOOL ret = TRUE;

    // check auto_depend
    if( !m_auto_depend )
        return FALSE;

    // look up if name is already parsed



    return ret;
}




//-----------------------------------------------------------------------------
// name: setReplaceDac()
// desc: tell the compiler whether dac should be replaced in scripts
//       with the name of an external UGen, and if so which one
//-----------------------------------------------------------------------------
void Chuck_Compiler::setReplaceDac( t_CKBOOL shouldReplaceDac,
    const std::string & replacement )
{
    emitter->should_replace_dac = shouldReplaceDac;
    emitter->dac_replacement = replacement;
}




//-----------------------------------------------------------------------------
// name: do_entire_file()
// desc: parse, type-check, and emit a program
//-----------------------------------------------------------------------------
t_CKBOOL Chuck_Compiler::do_entire_file( Chuck_Context * context )
{
    // 0th-scan (pass 0)
    if( !type_engine_scan0_prog( env(), g_program, te_do_all ) )
         return FALSE;

    // 1st-scan (pass 1)
    if( !type_engine_scan1_prog( env(), g_program, te_do_all ) )
        return FALSE;

    // 2nd-scan (pass 2)
    if( !type_engine_scan2_prog( env(), g_program, te_do_all ) )
        return FALSE;

    // check the program (pass 3)
    if( !type_engine_check_context( env(), context, te_do_all ) )
        return FALSE;

    // emit (pass 4)
    if( !emit_engine_emit_prog( emitter, g_program ) )
        return FALSE;

    // set the state of the context to done
    context->progress = Chuck_Context::P_ALL_DONE;

    return TRUE;
}




//-----------------------------------------------------------------------------
// name: do_only_classes()
// desc: compile only classes definitions
//-----------------------------------------------------------------------------
t_CKBOOL Chuck_Compiler::do_only_classes( Chuck_Context * context )
{
    // 0th-scan (pass 0)
    if( !type_engine_scan0_prog( env(), g_program, te_do_classes_only ) )
        return FALSE;

    // 1st-scan (pass 1)
    if( !type_engine_scan1_prog( env(), g_program, te_do_classes_only ) )
        return FALSE;

    // 2nd-scan (pass 2)
    if( !type_engine_scan2_prog( env(), g_program, te_do_classes_only ) )
        return FALSE;

    // check the program (pass 3)
    if( !type_engine_check_context( env(), context, te_do_classes_only ) )
        return FALSE;

    // emit (pass 4)
    code = emit_engine_emit_prog( emitter, g_program , te_do_classes_only );
    if( !code ) return FALSE;

    // set the state of the context to done
    context->progress = Chuck_Context::P_ALL_DONE;

    return TRUE;
}




//-----------------------------------------------------------------------------
// name: do_all_except_classes()
// desc: compile everything except classes
//-----------------------------------------------------------------------------
t_CKBOOL Chuck_Compiler::do_all_except_classes( Chuck_Context * context )
{
    // 0th scan only deals with classes, so is not needed

    // 1st-scan (pass 1)
    if( !type_engine_scan1_prog( env(), g_program, te_do_no_classes ) )
        return FALSE;

    // 2nd-scan (pass 2)
    if( !type_engine_scan2_prog( env(), g_program, te_do_no_classes ) )
        return FALSE;

    // check the program (pass 3)
    if( !type_engine_check_context( env(), context, te_do_no_classes ) )
        return FALSE;

    // emit (pass 4)
    code = emit_engine_emit_prog( emitter, g_program, te_do_no_classes );
    if( !code ) return FALSE;

    // set the state of the context to done
    context->progress = Chuck_Context::P_ALL_DONE;

    return TRUE;
}




//-----------------------------------------------------------------------------
// name: do_normal_depend()
// desc: compile normally without auto-depend
//-----------------------------------------------------------------------------
t_CKBOOL Chuck_Compiler::do_normal_depend( const string & filename, FILE * fd,
                                          const char * str_src, const string & full_path )
{
    t_CKBOOL ret = TRUE;
    Chuck_Context * context = NULL;

    // parse the code
    if( !chuck_parse( filename.c_str(), fd, str_src ) )
        return FALSE;

    // make the context
    context = type_engine_make_context( g_program, filename );
    if( !context ) return FALSE;

    // remember full path (added 1.3.0.0)
    context->full_path = full_path;

    // reset the env
    env()->reset();

    // load the context
    if( !type_engine_load_context( env(), context ) )
        return FALSE;

    // 0th-scan (pass 0)
    if( !type_engine_scan0_prog( env(), g_program, te_do_all ) )
    { ret = FALSE; goto cleanup; }

    // 1st-scan (pass 1)
    if( !type_engine_scan1_prog( env(), g_program, te_do_all ) )
    { ret = FALSE; goto cleanup; }

    // 2nd-scan (pass 2)
    if( !type_engine_scan2_prog( env(), g_program, te_do_all ) )
    { ret = FALSE; goto cleanup; }

    // check the program (pass 3)
    if( !type_engine_check_context( env(), context, te_do_all ) )
    { ret = FALSE; goto cleanup; }

    // emit (pass 4)
    code = emit_engine_emit_prog( emitter, g_program, te_do_all );
    if( !code ) { ret = FALSE; goto cleanup; }

cleanup:

    // commit
    if( ret ) env()->global()->commit();
    // or rollback
    else env()->global()->rollback();

    // unload the context from the type-checker
    if( !type_engine_unload_context( env() ) )
    {
        EM_error2( 0, "internal error unloading context...\n" );
        return FALSE;
    }

    return ret;
}




//-----------------------------------------------------------------------------
// name: do_auto_depend()
// desc: compile with auto-depend (TODO: this doesn't work yet, I don't think)
//-----------------------------------------------------------------------------
t_CKBOOL Chuck_Compiler::do_auto_depend( const string & filename, FILE * fd,
                                         const char * str_src, const string & full_path )
{
    t_CKBOOL ret = TRUE;
    Chuck_Context * context = NULL;

    // parse the code
    if( !chuck_parse( filename.c_str(), fd, str_src ) )
        return FALSE;

    // make the context
    context = type_engine_make_context( g_program, filename );
    if( !context ) return FALSE;

    // reset the env
    env()->reset();

    // load the context
    if( !type_engine_load_context( env(), context ) )
        return FALSE;

    // do entire file
    if( !do_entire_file( context ) )
    { ret = FALSE; goto cleanup; }

    // get the code
    code = context->code();
    if( !code )
    {
        ret = FALSE;
        EM_error2( 0, "internal error: context->code() NULL!" );
        goto cleanup;
    }

cleanup:

    // commit
    if( ret ) env()->global()->commit();
    // or rollback
    else env()->global()->rollback();

    // unload the context from the type-checker
    if( !type_engine_unload_context( env() ) )
    {
        EM_error2( 0, "internal error unloading context...\n" );
        return FALSE;
    }

    return ret;
}





//-----------------------------------------------------------------------------
// name: find_recent_path()
// desc: find recent context by path
//-----------------------------------------------------------------------------
Chuck_Context * Chuck_Compiler::find_recent_path( const string & path )
{
    return NULL;
}




//-----------------------------------------------------------------------------
// name: find_recent_type()
// desc: find recent context by type name
//-----------------------------------------------------------------------------
Chuck_Context * Chuck_Compiler::find_recent_type( const string & type )
{
    return NULL;
}




//-----------------------------------------------------------------------------
// name: add_recent_path()
// desc: add recent context by path
//-----------------------------------------------------------------------------
t_CKBOOL Chuck_Compiler::add_recent_path( const string & path,
                                          Chuck_Context * context )
{
    return TRUE;
}




//-----------------------------------------------------------------------------
// name: output()
// desc: get the code generated by the last do()
//-----------------------------------------------------------------------------
Chuck_VM_Code * Chuck_Compiler::output()
{
    return this->code;
}




//-----------------------------------------------------------------------------
// name: load_module()
// desc: load a dll and add it
//-----------------------------------------------------------------------------
t_CKBOOL load_module( Chuck_Compiler * compiler, Chuck_Env * env, f_ck_query query,
                      const char * name, const char * nspc )
{
    Chuck_DLL * dll = NULL;
    t_CKBOOL query_failed = FALSE;

    // load osc
    dll = new Chuck_DLL( compiler->carrier(), name );
    // (fixed: 1.3.0.0) query_failed now catches either failure of load or query
    query_failed = !(dll->load( query ) && dll->query());
    if( query_failed || !type_engine_add_dll( env, dll, nspc ) )
    {
        CK_FPRINTF_STDERR(
                 "[chuck]: internal error loading module '%s.%s'...\n",
                 nspc, name );
        if( query_failed )
            CK_FPRINTF_STDERR( "       %s", dll->last_error() );

        return FALSE;
    }

    return TRUE;
}




//-----------------------------------------------------------------------------
// name: load_internal_modules()
// desc: ...
//-----------------------------------------------------------------------------
t_CKBOOL load_internal_modules( Chuck_Compiler * compiler )
{
    // log
    EM_log( CK_LOG_SEVERE, "loading built-in modules..." );
    // push indent level
    EM_pushlog();

    // get env
    Chuck_Env * env = compiler->env();
    // make context
    Chuck_Context * context = type_engine_make_context( NULL, "@[internal]" );
    // reset env - not needed since we just created the env
    env->reset();
    // load it
    type_engine_load_context( env, context );

    // still compile MidiMsg even if __DISABLE_MIDI__
    if( !init_class_Midi( env ) ) goto error;
#ifndef __DISABLE_MIDI__
    if( !init_class_MidiRW( env ) ) goto error;
#endif // __DISABLE_MIDI__

    // load
    EM_log( CK_LOG_SEVERE, "module 'math'" );
    if( !load_module( compiler, env, libmath_query, "Math", "global" ) ) goto error;
    EM_log( CK_LOG_SEVERE, "module 'osc'" );
    load_module( compiler, env, osc_query, "osc", "global" );
    EM_log( CK_LOG_SEVERE, "module 'ai'" );
    if( !load_module( compiler, env, libai_query, "AI", "global" ) ) goto error;
    EM_log( CK_LOG_SEVERE, "module 'extract'" );
    load_module( compiler, env, extract_query, "extract", "global" );
    EM_log( CK_LOG_SEVERE, "module 'filter'" );
    load_module( compiler, env, filter_query, "filter", "global" );
    EM_log( CK_LOG_SEVERE, "module 'STK'" );
    load_module( compiler, env, stk_query, "stk", "global" );
    EM_log( CK_LOG_SEVERE, "module 'xform'" );
    load_module( compiler, env, xform_query, "xform", "global" );
    EM_log( CK_LOG_SEVERE, "module 'xxx'" );
    load_module( compiler, env, xxx_query, "xxx", "global" );
    EM_log( CK_LOG_SEVERE, "module 'std'" );
    if( !load_module( compiler, env, libstd_query, "Std", "global" ) ) goto error;

    // load
    EM_log( CK_LOG_SEVERE, "module 'machine'" );
    if( !load_module( compiler, env, machine_query, "Machine", "global" ) ) goto error;
    #ifndef __DISABLE_OTF_SERVER__
    machine_init( compiler, otf_process_msg );
    #endif

    #ifndef __DISABLE_NETWORK__
    EM_log( CK_LOG_SEVERE, "module 'opsc'" );
    if( !load_module( compiler, env, opensoundcontrol_query, "opsc", "global" ) ) goto error;
    #endif

    // deprecated:
    // if( !load_module( compiler, env, net_query, "net", "global" ) ) goto error;

    #ifndef __DISABLE_HID__
    EM_log( CK_LOG_SEVERE, "module 'HID'" );
    if( !init_class_HID( env ) ) goto error;
    #endif

    #ifndef __DISABLE_SERIAL__
    EM_log( CK_LOG_SEVERE, "module 'SerialIO'" );
    if( !init_class_serialio( env ) ) goto error;
    #endif

    EM_log( CK_LOG_SEVERE, "module 'CKDoc'" );
    if( !load_module( compiler, env, ckdoc_query, "CKDoc", "global" ) ) goto error;

    // clear context
    type_engine_unload_context( env );

    // commit what is in the type checker at this point
    env->global()->commit();

    // pop indent level
    EM_poplog();

    return TRUE;

error:

    // probably dangerous: rollback
    env->global()->rollback();

    // clear context
    type_engine_unload_context( env );

    // pop indent level
    EM_poplog();

    return FALSE;
}



//-----------------------------------------------------------------------------
// name: load_external_module_at_path()
// desc: ...
//-----------------------------------------------------------------------------
t_CKBOOL load_external_module_at_path( Chuck_Compiler * compiler,
                                       const char * name,
                                       const char * dl_path )
{
    Chuck_Env * env = compiler->env();

    EM_log(CK_LOG_SEVERE, "loading chugin '%s'", name);

    Chuck_DLL * dll = new Chuck_DLL( compiler->carrier(), name );
    t_CKBOOL query_failed = FALSE;

    // try to load and query
    query_failed = !(dll->load(dl_path) && dll->query());
    if( query_failed || !type_engine_add_dll2(env, dll, "global"))
    {
        EM_pushlog();
        EM_log(CK_LOG_SEVERE, "cannot load chugin '%s', skipping...", name);
        // EM_error2( 0, "error: cannot load chugin '%s', skipping...", name );
        if( query_failed )
        {
            // EM_error2( 0, "%s", dll->last_error() );
            EM_log( CK_LOG_SEVERE, "%s", dll->last_error() );
        }
        delete dll;
        EM_poplog();

        return FALSE;
    }
    else
    {
        compiler->m_dlls.push_back(dll);
        return TRUE;
    }
}




//-----------------------------------------------------------------------------
// name: extension_matches()
// desc: ...
//-----------------------------------------------------------------------------
static t_CKBOOL extension_matches( const char * filename, const char * extension )
{
    t_CKUINT extension_length = strlen(extension);
    t_CKUINT filename_length = strlen(filename);

    return strncmp( extension, filename+(filename_length-extension_length),
                    extension_length) == 0;
}




//-----------------------------------------------------------------------------
// name: load_external_modules_in_directory()
// desc: ...
//-----------------------------------------------------------------------------
t_CKBOOL load_external_modules_in_directory( Chuck_Compiler * compiler,
                                             const char * directory,
                                             const char * extension )
{
    static const t_CKBOOL RECURSIVE_SEARCH = false;

    // directory path
    string path = directory, exp = "";

#ifndef __PLATFORM_WIN32__
#ifndef __DISABLE_WORDEXP__
    // 1.5.0.0 (ge) added (for ~)
    wordexp_t exp_result;
    // "perform shell-style word expansions"
    wordexp( directory, &exp_result, 0 );
    // concatenate in case there are spaces in the path
    for( t_CKINT i = 0; i < exp_result.we_wordc; i++ )
        exp += string(i > 0 ? " " : "") + exp_result.we_wordv[i];
    // replace
    path = exp;
#endif
#endif

    // open the directory
    DIR * dir = opendir( path.c_str() );

    // if successful
    if( dir )
    {
        // log
        EM_log( CK_LOG_INFO, "examining directory '%s' for chugins", directory );

        // do first read | 1.5.0.0 (ge + eito) #chunreal
        struct dirent * de = readdir(dir);

        // while( (de = readdir(dir)) ) <- in ge's opinion clearer than what UE5 forces us to do
        while( de != NULL )
        {
            t_CKBOOL is_regular = false;
            t_CKBOOL is_directory = false;

#if defined(__PLATFORM_WIN32__)
            is_directory = de->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
            is_regular = ((de->data.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) ||
                          (de->data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ||
                          (de->data.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE));
#elif defined(__WINDOWS_PTHREAD__) // Cygwin -- doesn't have dirent d_type
            std::string absolute_path = std::string(directory) + "/" + de->d_name;
            struct stat st;
            if( stat(absolute_path.c_str(), &st) == 0 )
            {
                is_directory = st.st_mode & S_IFDIR;
                is_regular = st.st_mode & S_IFREG;
            }
            else
            {
                // uhh ...
                EM_log( CK_LOG_INFO,
                        "unable to open file '%s', ignoring for chugins",
                        absolute_path.c_str() );
                continue;
            }
#else
            is_directory = de->d_type == DT_DIR;
            is_regular = de->d_type == DT_REG;
#endif

            if( is_regular ) // TODO: follow links?
            {
                if( extension_matches(de->d_name, extension) )
                {
                    std::string absolute_path = std::string(directory) + "/" + de->d_name;

                    load_external_module_at_path(compiler, de->d_name,
                                                 absolute_path.c_str());
                }
                else if( extension_matches(de->d_name, ".ck") )
                {
                    std::string absolute_path = std::string(directory) + "/" + de->d_name;
                    compiler->m_cklibs_to_preload.push_back(absolute_path);
                }
            }
            else if( RECURSIVE_SEARCH && is_directory )
            {
                // recurse
                // TODO: max depth?
                if( strncmp(de->d_name, ".", sizeof(".")) != 0 &&
                    strncmp(de->d_name, "..", sizeof("..")) != 0 )
                {
                    std::string absolute_path = std::string(directory) + "/" + de->d_name;
                    load_external_modules_in_directory(compiler,
                                                       absolute_path.c_str(),
                                                       extension);
                }
            }
#ifdef __PLATFORM_MACOSX__
            // #if __APPLE__ probably cleaner but using the above for consistency for now (ge)
            // ---
            // On macOS, a chugin can either be a regular file (a .dylib simply renamed to .chug)
            // or it can be a MODULE like using CMake with add_library(FooChugin MODULE foo.cpp)
            // If it's a module bundle, then because .chug is a nonstandard extension,
            // on the filesystem it shows up as a directory ending in .chug.
            // If we see one of these directories, we can dive directly to the Contents/MacOS subfolder
            // and look for a regular file. | 1.5.0.1 (dbraun) added
            else if (is_directory && extension_matches(de->d_name, extension)) {
                std::string absolute_path = std::string(directory) + "/" + de->d_name + "/Contents/MacOS";
                const char* subdirectory = absolute_path.c_str();
                load_external_modules_in_directory(compiler, subdirectory, "");
            }
#endif // #ifdef __PLATFORM_MACOSX__

            // read next | 1.5.0.0 (ge) moved here due to #chunreal
            de = readdir( dir );
        }

        // close
        closedir( dir );
    }
    else
    {
        // log (1.3.1.2: changed to 2 lines to stay within 80 chars)
        EM_log( CK_LOG_INFO, "unable to open directory:" );
        EM_pushlog();
        EM_log( CK_LOG_INFO, "'%s'", directory );
        EM_log( CK_LOG_INFO, "ignoring for chugins..." );
        EM_poplog();
    }

#ifndef __PLATFORM_WIN32__
#ifndef __DISABLE_WORDEXP__
    // free | 1.5.0.0 (ge) added
    wordfree( &exp_result );
#endif
#endif

    return TRUE;
}




//-----------------------------------------------------------------------------
// name: load_external_modules()
// desc: ...
//-----------------------------------------------------------------------------
t_CKBOOL Chuck_Compiler:: load_external_modules( const char * extension,
    std::list<std::string> & chugin_search_paths,
    std::list<std::string> & named_dls )
{
    // get env
    Chuck_Env * env = this->env();
    // make context
    Chuck_Context * context = type_engine_make_context( NULL, "@[external]" );
    // reset env - not needed since we just created the env
    env->reset();
    // load it
    type_engine_load_context( env, context );

    /* first load dynamic libraries explicitly named on the command line */
    for(std::list<std::string>::iterator i_dl = named_dls.begin();
        i_dl != named_dls.end(); i_dl++)
    {
        std::string & dl_path = *i_dl;
        if(!extension_matches(dl_path.c_str(), extension))
            dl_path += extension;

        load_external_module_at_path(this, dl_path.c_str(),
                                     dl_path.c_str());
    }

    /* now recurse through search paths and load any DLs or .ck files found */
    for(std::list<std::string>::iterator i_sp = chugin_search_paths.begin();
        i_sp != chugin_search_paths.end(); i_sp++)
    {
        load_external_modules_in_directory(this, (*i_sp).c_str(), extension);
    }

    // clear context
    type_engine_unload_context( env );

    // commit what is in the type checker at this point
    env->global()->commit();

    return TRUE;
   /*
error:

    // probably dangerous: rollback
    env->global()->rollback();

    // clear context
    type_engine_unload_context( env );

    // pop indent level
    EM_poplog();

    return FALSE;
   */
}
