
/* samplecm.cpp
 * 
 * This file implements an CodeSonar compiler model for the
 * (fictional) sc2000 compiler.
 *
 * For more information, see the "Authoring Compiler Models" manual section.
 */

/* Defines class cscm::C_CompilerModel. */
#include "cscm_libcm.hpp"

/* Defines some utility functions for argument matching. */ 
#include "cscm_argument_matcher.hpp"

#include <map>
#include <fstream>


namespace cscm{


  /* All compiler models must subclass cscm::CompilerModel.
   * The name of the class is not important, except that it must be 
   * used as the argument to  register_cm_plugin() later.
   */
class SampleModel : public C_CompilerModel {

private:
    /* The list of options that will always be passed to the front
     * end. 
     */
    strings fecmds_always; 

    /* The list of options that will be passed to the front end for
     * all compilations in C++ mode.
     */
    strings fecmds_always_cxx;   

    /* The list of options that will be passed to the front end for
     * all compilations in C mode.
     */
    strings fecmds_always_c;     
    
    /* A list of flags that the compiler recognizes as enabling all warnings
     * (IE, -Wall in gcc). If this vector is empty, the compiler is assumed to
     * enable all warnings by default. */
    strings wall_flags = {"-Wall"};

    /* A list of flags that the compiler recognizes as treating warnings as errors
     * (IE, -Werror in gcc). */
    strings werror_flags = {"-Werror"};

    /* A list of all flags seen on the command line that disable reporting of warnings */
    strings seen_wall_disable_flags = {};

    /* A list of all flags seen on the command line that disable reporting of errors or
     * downgrade errors to warnings. */
    strings seen_werror_disable_flags {};

    boost::regex dotc;      /* Will match C source file names. */ 
    boost::regex dotcxx;    /* Will match C++ source file names. */ 
    
    //The name of the compiler model
    std::string m_name;

public:
    /* See the CompilerModel class for more information about requirements.
     */

    SampleModel(std::string name){
        
        /* Macros always predefined: ALPHA, BETA, GAMMA */
        fecmds_always.push_back("-DALPHA");
        fecmds_always.push_back("-DBETA");
        fecmds_always.push_back("-DGAMMA");
        
        /* Additional macro predefined in C++ mode: BARATHRUM */
        fecmds_always_cxx.push_back("-DBARATHRUM");

        /* Additional macro predefined in C mode: RUBICK */
        fecmds_always_c.push_back("-DRUBICK");

        /* The compiler recognizes file extensions as follows:
         * - .c or .C indicates a C source file.
         * - (case-insensitive).cpp or .cxx indicates a C++ source file.
         */
        dotc = boost::regex("\\.(c|C)$");
        dotcxx = boost::regex("\\.[cC]([pP][pP]|[xX][xX])$");
        
        //Store the name of the compiler model
        m_name = name;
    }

    /* All compiler model classes must implement name(). The returned
       name must be unique in the set of compiler model names.
     * The name of this model is "samplecm".
     */
    virtual std::string name() {
        return m_name;
    }

    /* All compiler model classes must implement verbose_name().
     */
    virtual std::string verbose_name() { 
        return "CodeSecure's Sample Compiler Model Plugin."; 
    }

    /* All compiler model classes must implement this overloaded form
     * of operator().
     *
     * Given information about a single invocation of the sc2000
     * compiler, it creates a CodeSonar front end invocation
     * (or sequence of invocations) that models the sc2000 compilation.
     *
     * arguments is the list of command arguments passed to sc2000 for a
     * particular invocation.
     *
     * config is a ParseConfiguration object that describes various
     * properties of the native sc2000 compiler. Most of these are not
     * directly interesting to the compiler model; exceptions are
     * config.compiler_name, config.compiler_path, and
     * config.verbose. 
     * The model will use this object in three locations:
     * - To determine the compiler path, and thus the system include
     *   directory.
     * - As an argument to add_version_predefined_macros(), to help
     *   determine the compiler version, and thus whether macro
     *   __INVISIBLE_TREE__ should be defined.
     * - As an argument to cs_and_fe_options,
     *   to determine basic CodeSonar-specific front end command 
     *   components.
     */
    virtual CompilerModelResult operator()(const strings &arguments, 
                                           const ParseConfiguration &config)
    {
        strings args = arguments;
        std::vector<source_file> source_files;
        strings commands;
        strings native_flags,
            native_cmd_line,
            native_all_warnings_flags,
            native_warning_suppression_flags,
            native_warnings_as_errors_flags,
            native_error_suppression_flags;

        /* Compilers may read options from the environment.
           Use cu::get_environment instead of getenv() for reading
           environment variables. Here is an example where sc2000
           compiler doesnt compile anything if DISABLE_SC2000 is set.
        */
        std::string disable_sc2000 = cu::get_environment("DISABLE_SC2000");
        if(disable_sc2000 == "1"){
            return CompilerModelResult();
        }

        /* A typical use case for the config argument: extract the
         * path to the native compiler and use it to construct one or
         * more system include directory names. 
         */
        cu::path sincdir = config.get_compiler_path();
        sincdir = sincdir.parent_path() / ".." / "include";

        /*Results from the argument matcher are returned as a pair.
        The first element is a match_result enum, which has one of these values:
            SUCCESS - argument was found
            SUCCESS_USED_ARG2 - argument was found and the next argument was consumed 
            MATCH_FAILED - argument was not found
        The second element is a std::string containing the value of that argument. */
        std::pair<match_result, std::string> res;
        
        /* Loop over all arguments for the sc2000 invocation and
         * translate them one by one. For convenience, we use function
         * short_mand() from argument_matcher.hpp to match arguments
         * with mandatory values.
         */
        for(size_t ai=0;ai < args.size();++ai){
            std::string value;
            bool valid_arg2 = args.size() > ai+1;
            bool used_arg2 = false;
            const std::string& arg1 = args[ai];
            const std::string& arg2 = valid_arg2 ? args[ai+1] : std::string("dummy");

            /* Skip the next argument if it was already consumed
             * (because it was the "value" component of a flag with a
             * mandatory value).
             */
            if(res.first == SUCCESS_USED_ARG2) {
                res.first = MATCH_FAILED;
                continue;
            }
            
            /* sc2000 -D, -I, and -U flags behave the same as the
             * front end flags of the same names, so "translation"
             * consists merely of pushing the same flag and value onto
             * the list of front end arguments.
             */
            res = short_mand("-D", arg1, arg2);
            if(res.first != MATCH_FAILED){
                commands.push_back("-D"+res.second);
                native_flags.push_back("-D"+res.second);
                continue;
            }
            res = short_mand("-I",arg1,arg2);
            if(res.first != MATCH_FAILED){
                commands.push_back("-I"+res.second);
                native_flags.push_back("-I"+res.second);
                continue;
            }
            res = short_mand("-U",arg1,arg2);
            if(res.first != MATCH_FAILED){
                commands.push_back("-U"+res.second);
                native_flags.push_back("-U"+res.second);
                continue;
            }

            /* --ENABLE-RTTI is directly equivalent to the front end
             * --rtti flag.
             */
            if(arg1 == "--ENABLE-RTTI"){
                fecmds_always_cxx.push_back("--rtti");
                native_flags.push_back(arg1);
                continue;
            }

            /*  -O causes macro __OPTIMIZE__ to be defined, so
             * translate it into a -D argument.
             */
            if(arg1 == "-O"){
                commands.push_back("-D__OPTIMIZE__");
                native_flags.push_back(arg1);
                continue;
            }

            /* -c and -help have no effect on the compilation.
             */
            if(arg1 == "-c" || arg1 == "-help"){
                //do nothing
                continue;
            }
            
            if(arg1 == "-Wno-warnings"){
                seen_wall_disable_flags.push_back(arg1);
                native_flags.push_back(arg1);
                continue;
            }
            
            if(arg1 == "-Wno-errors"){
                seen_werror_disable_flags.push_back(arg1);
                native_flags.push_back(arg1);
                continue;
            }

            /* If @fname is specified for nonempty fname, read
             * additional arguments out of file fname and append to
             * the argument list for processing.
             */
            if(arg1.size() > 1 && arg1[0] == '@'){
                strings options_from_file = cu::read_response_file(arg1.substr(1),
                                                            false);
                args.insert(args.end(),
                            options_from_file.begin(),
                            options_from_file.end());
                continue;
            }

            /* If an argument matches the "C source file name"
             * pattern, append it to the list of source files and note
             * that it's a C file.
             */
            if(boost::regex_search(arg1,dotc)){
                source_files.push_back(source_file(arg1,csl_edgcp_c));
                continue;
            }
            /* ... and correspondingly for C++ source file names. */
            if( boost::regex_search(arg1,dotcxx)){
                source_files.push_back(source_file(arg1,csl_edgcp_cxx));
                continue;
            }

        }

        /* Some front end arguments are OS-specific. */
        if(OS_WINDOWS){
            commands.push_back("--cs_cygwin_path_translation");
        }

        /* Make sure the standard EDG macros are defined.
           This is typically used if the real compiler is EDG based.
        */
        commands.push_back("--cs_define_edg_macros");
        
        /* Call add_version_predefined_macros() (defined below) to
         * update commands with any macro definitions that depend on
         * the native compiler version. 
         */
        add_version_predefined_macros(commands, config);

        /* The bit width is always 32 for this compiler. 
         * If you are modeling a compiler for which bit width can vary
         * depending on (for example) certain command arguments, you
         * will need to take that into account.
         */
        const csuint32 bit_width = 32;

        /* Command arguments and strings may be shared across multiple
         * FrontEndCommand objects, so avoid making redundant copies
         * by storing them as CmdArgumentVector objects. 
         */

        /* Basic front end arguments, which depend on source language (C or
         * C++), bit width, and ParseConfiguration.
         */
        CmdArgumentVector edg_fe_cmds_c(EDGFrontEnd::cs_and_fe_options(csl_edgcp_c,bit_width,config));
        CmdArgumentVector edg_fe_cmds_cxx(EDGFrontEnd::cs_and_fe_options(csl_edgcp_cxx,bit_width,config));

        /* The model's standard front end argument sets.
         */
        CmdArgumentVector vfecmds_always(fecmds_always);
        CmdArgumentVector vfecmds_always_cxx(fecmds_always_cxx);
        CmdArgumentVector vfecmds_always_c(fecmds_always_c);

        /* The front end arguments that have been translated from the
         * arguments to this particular compilation.
         */
        CmdArgumentVector vcommands(commands);

        /* Using the accumulated information, generate one
         * FrontEndCommand per source file.
         */
        std::vector<FrontEndCommand> fecs;
        std::vector<CmdArgument> cao;
        for(size_t i=0;i<source_files.size();++i){
            source_file& sf = source_files[i];
            CmdArgumentVector& extras = sf.second == csl_edgcp_cxx ? vfecmds_always_cxx : vfecmds_always_c;

            /* Some CodeSonar-specific front end arguments
             * (such as the path to the configuration files in
             * compiler_confs) are independent of the compiler
             * model. Use the EDGFrontEnd class to get those arguments
             * and pass them along. These arguments must be provided
             * before any other front end arguments.  See the EDGFrontEnd
             * class documentation (cscm_compmodels.hpp) for more
             * details.
             */

            /* The values returned by EDGFrontEnd::first_options() must
             * always be the first arguments to the front end.
             */
            const std::vector<CmdArgument>& edg_fe_ffa = EDGFrontEnd::first_options(sf.first,"object_file.o");
            cao.insert(cao.end(),edg_fe_ffa.begin(),edg_fe_ffa.end());

            const CmdArgumentNativeCmdLineFile cso_native_cmd_line_file(native_flags,
                                                                        args, wall_flags, seen_wall_disable_flags,
                                                                        werror_flags, seen_werror_disable_flags );
            std::vector<CmdArgument> _vnative_cmd_line_file;
            _vnative_cmd_line_file.push_back(cso_native_cmd_line_file);
            CmdArgumentVector vnative_cmd_line_file(_vnative_cmd_line_file);

            /* The values returned by edg_fe_cmds_c() or
             * edg_fe_cmds_cxx() (depending on the source file
             * language) must come after the values from
             * first_options() and before any other arguments.
             */
            CmdArgumentVector& edg_fe_commands = sf.second == csl_edgcp_cxx ? edg_fe_cmds_cxx : edg_fe_cmds_c;
            cao.push_back(edg_fe_commands);

           /* Now add in the arguments generated by this compiler model.*/
            cao.push_back(vfecmds_always);
            cao.push_back(extras);
            cao.push_back(vcommands);
            cao.push_back(vnative_cmd_line_file);

           /* Generate the front end command. */
            FrontEndCommand fec(cao,sf.first,sf.second,FET_EDGCP);
            fecs.push_back(fec);
            cao.clear();
        }

        /* Create and return a CompilerModelResult based on the
         * accumulated set of front end commands. */
        CompilerModelResult ret(fecs);

        return ret;
    }

    virtual ~SampleModel(){

    }

private:

    /* Like a number of the compiler models shipped with
     * CodeSonar (for example, the gcc model), this model
     * invokes the native compiler to obtain some of the information
     * it needs.
     *
     * In this case, the model is invoking the native compiler with
     * --version to obtain its version string, then using that version
     * string to decide whether or not to instruct the front end to
     * define version-dependent macro __INVISIBLE_TREE__.
    */
    void add_version_predefined_macros(strings& commands, 
                                       const ParseConfiguration& config){
        cu::SpawnProcess sp(config);
        sp.args.push_back(config.get_compiler_path().str());
        sp.args.push_back("--version");
        sp.redirect_output = true;
        sp.output_file_name = cu::unique_file_name(config.get_csurf_tmp_dir());
        
        sp.spawn_blocking();
        const strings& lines = sp.get_output_text();
        if(sp.get_return_code() == 0 && lines.size() > 0){
            if(lines[0].find("Codename Invisible Tree")){
                commands.push_back("-D__INVISIBLE_TREE__");
            }
        }
    }


}; /* SampleModel */

}  /* cscm namespace */


/* Invoke register_cm_plugin() on the class name. This serves as the
 * entry point from CodeSonar to this model, and must occur after the
 * class definition.
 */
register_cm_plugin(SampleModel)
