#!/bin/bash

# cccl
# Wrapper around MS's cl.exe to make it act more like Unix cc
#
# Copyright (C) 2000-2025 by contributors listed in the AUTHORS file.
#
# 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 3 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, see <http://www.gnu.org/licenses/>.


usage()
{
    cat <<EOF
Usage: cccl [--cccl-link OPTION] [--cccl-muffle] [--cccl-slash] [--cccl-verbose] [--help] [--version]
       [OPTIONS]

cccl is a wrapper around Microsoft's cl.exe compiler.  It translates parameters
given by [OPTIONS] that Unix cc understands into parameters that cl understands.
EOF
}

operating_system=$(uname -o)
case $operating_system in
    Msys)
        slash="-"
        ;;
    *)
        slash="/"
        ;;
esac

# prog specifies the program that should be run cl.exe
prog=cl
# opts specifies the command line to pass to the MSVC program
clopt=("${slash}nologo")
linkopt=("${slash}link")
debug=
# gotparam is 0 if we didn't ever see a param, in which case we show usage()
gotparam=
muffle=
slash_slash="${slash}"
verbose=
shared_index=-1

### First process arguments that exit immediately
for arg in $@; do
    case "$arg" in
    -dumpmachine)
        # "dumpmachine" is a CC command-line argument to show the compiler's host triplet and exit immediately
        # "cccl" will follow "clang"'s MSVC "dumpmachine" output
        cl_output_parts=($($prog 2>&1))
        arch="$(echo ${cl_output_parts[8]} | tr "[:upper:]" "[:lower:]")"
        output="pc-windows-msvc"

        case $arch in
        x64)
            output="x86_64-$output"
            ;;

        # "file" command, with "libmagic", shows "Intel 80386" for gcc/clang/msvc, use i686 like on gcc/clang
        x86)
            output="i686-$output"
            ;;

        # Output triplet same as gcc and clang on ARMv8
        arm64)
            output="aarch64-$output"
            ;;

        # Output triplet same as gcc and clang on ARMv7
        arm)
            output="arm-$output"
            ;;

        *)
            output="unknown-$output"
            ;;
        esac

        echo $output
        exit 0
        ;;

    -dumpversion)
        cl_output_parts=($($prog 2>&1))
        cl_version="$(echo ${cl_output_parts[6]} | tr "[:upper:]" "[:lower:]")"
        echo $cl_version
        exit 0
        ;;
    esac
done

processargs()
{
### Run through every option and convert it to the proper MS one
while test $# -gt 0; do
    case "$1" in
    -D*) optarg= ;;
    -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;;
    *) optarg= ;;
    esac
    gotparam=1

    case "$1" in
    --help)
        usage
        exit 0
        ;;

    --cccl-link)
        # One single option for the linker
        shift
        case "$1" in
        /*)
            linkopt+=("${slash_slash}${1:1}")
            ;;

        *)
            linkopt+=("$1")
            ;;
        esac
        ;;

    --cccl-muffle)
        # Remove the unnecessary junk that the compiler outputs to stdout
        muffle=1
        ;;

    --cccl-slash)
        slash_slash="/"
        ;;

    --cccl-verbose)
        verbose=1
        ;;

    --version)
        cat <<EOF
cccl 1.4
EOF
        exit 0;
        ;;

    -ansi)
        clopt+=("${slash}Za")
        ;;

    -c)
        # -c (compile only) is actually the same, but for clarity...
        clopt+=("${slash}c")
        ;;

    -g[0-9] | -g)
        # cl only supports one debugging level
        clopt+=("${slash}Zi")
        debug=1
        ;;

    -O0)
        clopt+=("${slash}Ot")
        ;;

    -O2)
        clopt+=("${slash}O2")
        ;;

    -L)
        shift
        path=`echo "$1"`
        linkopt+=("${slash}LIBPATH:$path")
        ;;

    -L*)
        path=`echo "$1" | sed 's/-L//'`
        linkopt+=("${slash}LIBPATH:$path")
        ;;

    -link)
        # Libtool compatibility which is trying to pass linker options to cl
        # Same behaviour as cl - all options after -link are linker options
        shift
        while test $# -gt 0; do
            case "$1" in
            -*)
                linkopt+=("${slash}${1:1}")
                ;;

            /*)
                linkopt+=("${slash_slash}${1:1}")
                ;;

            *)
                linkopt+=("$1")
                ;;
            esac
            shift
        done
        ;;

    -l*)
        lib=`echo "$1" | sed 's/-l//'`
        lib="$lib.lib"
        clopt+=("$lib")
        ;;

    -m386)
        clopt+=("${slash}G3")
        ;;

    -m486)
        clopt+=("${slash}G4")
        ;;

    -mpentium)
        clopt+=("${slash}G5")
        ;;

    -mpentiumpro)
        clopt+=("${slash}G6")
        ;;

    -o)
        # specifying output file, is it an object or an executable
        shift
        case "$1" in
        *.o | *.obj)
            clopt+=("${slash}Fo$1")
            ;;

        *.exe | *.dll | *.pyd)
            clopt+=("${slash}Fe$1")
            ;;
        esac
        ;;

    -std=*)
        case "$1" in
        -std=c++98|-std=c++03|-std=c++11)
            # "Note that the MSVC compiler does not, and never will, support a
            # C++11, C++03, or C++98 standards version switch."
            # https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/
            #
            # We can at least turn off permissive mode.
            clopt+=("${slash}permissive-")
            ;;

        -std=gnu++98|-std=gnu++03|-std=gnu++11)
            # Similar to above, but leave permissive mode on.
            ;;

        -std=c89|-std=c90|-std=c99)
            # MSVC only documents accepting /std=c11 and upwards.
            #
            # We can at least turn off permissive mode.
            clopt+=("${slash}permissive-")
            ;;

        -std=gnu89|-std=gnu90|-std=gnu99)
            # Similar to above, but leave permissive mode on.
            ;;

        *)
            clopt+=("${slash}std:${1:5}")
            ;;
        esac

        case "$1" in
        -std=c++*|-std=gnu++*)
            # This is needed to get MSVC to define __cplusplus to the correct
            # value, but is only supported by Visual Studio 2017 version 15.7
            # and later.  Earlier versions will emit a warning but it's hard
            # to see how to avoid this:
            #
            # cl : Command line warning D9002 : ignoring unknown option '/Zc:__cplusplus'
            clopt+=("${slash}Zc:__cplusplus")
            ;;
        esac
        ;;

    -pedantic)
        #ignore pedantic
        ;;

    -Wl,*)
        IFS=',' read -ra linkopt2 <<< "$(echo $1 | sed 's/-Wl,//')"
        for linkarg in ${linkopt2[@]}; do
            linkopt+=("${linkarg}")
        done
        ;;

    -Werror)
        clopt+=("${slash}WX")
        ;;
    -W*)
        #ignore remaining warnings
        ;;

    -fno-strict-aliasing*)
        #ignore aliasing
        ;;

    -isystem)
        shift
        clopt+=("${slash}I$1")
        ;;

    -I)
        shift
        clopt+=("${slash}I$1")
        ;;

    -include)
        shift
        clopt+=("${slash}FI$1")
        ;;

    -rpath)
        #ignore this arg and the path
        shift
        ;;

    -MT)
        exit 0
        ;;

    -mno-cygwin)
        ;;

    -shared | -dll)
        shared_index=${#clopt[@]}
        clopt+=("${slash}LD")
        ;;

    -*)
        # Remaining '-' options are passed to the compiler
        if test x$optarg != x ; then
            clopt+=("${slash}${1:1}=$optarg")
        else
            clopt+=("${slash}${1:1}")
        fi
        ;;

    *.cc | *.cxx | *.C)
        # C++ source file with non .cpp extension, make sure cl understand 
        # that it is C++
        clopt+=("${slash}Tp$1")
        ;;

    /link)
        # Same behaviour as cl - all options after /link are linker options
        shift
        while test $# -gt 0; do
            case "$1" in
            -*)
                linkopt+=("${slash}${1:1}")
                ;;

            /*)
                linkopt+=("${slash_slash}${1:1}")
                ;;

            *)
                linkopt+=("$1")
                ;;
            esac
            shift
        done
        ;;

    /*)
        # All '/' options are assumed to be for cl and are passed through
        clopt+=("${slash_slash}${1:1}")
        ;;

    *)
        clopt+=("$1")
        ;;

    esac
    shift
done
}

# Whitespace in paths is dealt with by setting IFS and using bash arrays
# Except additional arguments in CCCL_OPTIONS need to be space separated
processargs $CCCL_OPTIONS
IFS=""
processargs $@

if test $shared_index -ge 0 -a -n "$debug"; then
    clopt[$shared_index]="${slash}LDd"
fi

if test x$gotparam = x ; then
    usage
    exit 1
fi

if test ${#linkopt[@]} -eq 1 ; then
    linkopt=()
fi

if test x$V = x1 ; then
    verbose=1
fi

if test -n "$verbose" ; then
    echo -n "$prog"
    for opt in "${clopt[@]}" ; do
        echo -n " \"$opt\""
    done
    for opt in "${linkopt[@]}" ; do
        echo -n " \"$opt\""
    done
    echo ""
fi

if test -z "$muffle" ; then
    exec $prog ${clopt[@]} ${linkopt[@]}
else
  # tr needed below for $ in regex to work (simple alternative to dos2unix)
    exec $prog ${clopt[@]} ${linkopt[@]} | tr -d '\r' | grep -a -v -e "\.cpp$" -e "\.cxx$" -e "\.cc$" -e "\.C$" -e "\.c$" -e "^   Creating library" -e "^Generating Code"
    exit ${PIPESTATUS[0]}
fi

